Browse Source

✨ feat:

zbb 1 tuần trước cách đây
mục cha
commit
f81f40a934

+ 1 - 1
api/card.js

@@ -23,7 +23,7 @@ export const getCardInfo = (params = {}) => {
  * @returns {Promise}
  */
 export const getCardQrcode = (params = {}) => {
-	return request.get('/crm/card/qrcode', params)
+	return request.get('/crm/card/wxacode', params)
 }
 
 /**

+ 5 - 1
manifest.json

@@ -58,7 +58,11 @@
             "minified" : true
         },
         "usingComponents" : true,
-        "permission" : {},
+        "permission" : {
+            "scope.writePhotosAlbum": {
+                "desc": "用于保存二维码图片到相册"
+            }
+        },
         "tabBar" : {
             "color" : "#999999",
             "selectedColor" : "#667eea",

+ 627 - 0
package-lock.json

@@ -0,0 +1,627 @@
+{
+  "name": "布尔销销乐",
+  "version": "1.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "布尔销销乐",
+      "version": "1.0.0",
+      "license": "MIT",
+      "dependencies": {
+        "pinia": "^2.1.7",
+        "qrcode": "^1.5.3",
+        "vue": "^3.4.21"
+      },
+      "devDependencies": {}
+    },
+    "node_modules/@babel/helper-string-parser": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+      "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-identifier": {
+      "version": "7.28.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+      "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/parser": {
+      "version": "7.29.3",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
+      "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
+      "dependencies": {
+        "@babel/types": "^7.29.0"
+      },
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/types": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+      "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.27.1",
+        "@babel/helper-validator-identifier": "^7.28.5"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.5.5",
+      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+      "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="
+    },
+    "node_modules/@vue/compiler-core": {
+      "version": "3.5.34",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.34.tgz",
+      "integrity": "sha512-s9cLyK5mLcvZ4Agva5QgRsQyLKvts9WbU9DB6NqiZkkGEdwmcEiylj5Jbwkp680drF/NNCV8OlAJSe+yMLxaJw==",
+      "dependencies": {
+        "@babel/parser": "^7.29.3",
+        "@vue/shared": "3.5.34",
+        "entities": "^7.0.1",
+        "estree-walker": "^2.0.2",
+        "source-map-js": "^1.2.1"
+      }
+    },
+    "node_modules/@vue/compiler-dom": {
+      "version": "3.5.34",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.34.tgz",
+      "integrity": "sha512-EbF/T++k0e2MMZlJsBhzK8Sgwt0HcIPOhzn1CTB/lv6sQcyk+OWf8YeiLxZp3ro7MbbLcAfAJ6sEvjFWuNgUCw==",
+      "dependencies": {
+        "@vue/compiler-core": "3.5.34",
+        "@vue/shared": "3.5.34"
+      }
+    },
+    "node_modules/@vue/compiler-sfc": {
+      "version": "3.5.34",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.34.tgz",
+      "integrity": "sha512-D/ihr6uZeIt6r+pVZf46RWT1fAsLFMbUP7k8G1VkiiWexriED9GrX3echHd4Abbt17zjlfiFJ8z7a3BxZOPNjg==",
+      "dependencies": {
+        "@babel/parser": "^7.29.3",
+        "@vue/compiler-core": "3.5.34",
+        "@vue/compiler-dom": "3.5.34",
+        "@vue/compiler-ssr": "3.5.34",
+        "@vue/shared": "3.5.34",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.30.21",
+        "postcss": "^8.5.14",
+        "source-map-js": "^1.2.1"
+      }
+    },
+    "node_modules/@vue/compiler-ssr": {
+      "version": "3.5.34",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.34.tgz",
+      "integrity": "sha512-cDtTHKibkThKGHH1SP+WdccquNRYQDFH6rRjQCqT9G2ltFAfoR5pUftpab/z+aM5mW9HLLVQW7hfKKQe/1GBeQ==",
+      "dependencies": {
+        "@vue/compiler-dom": "3.5.34",
+        "@vue/shared": "3.5.34"
+      }
+    },
+    "node_modules/@vue/devtools-api": {
+      "version": "6.6.4",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
+      "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="
+    },
+    "node_modules/@vue/reactivity": {
+      "version": "3.5.34",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.34.tgz",
+      "integrity": "sha512-y9XDjCEuBp+98k+UL5dbYkh57AHU4o6cxZedOPXw3bmrZZYLQsVHguGurq7hVrPCSrQtrnz1f9dssyFr+dMXfQ==",
+      "dependencies": {
+        "@vue/shared": "3.5.34"
+      }
+    },
+    "node_modules/@vue/runtime-core": {
+      "version": "3.5.34",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.34.tgz",
+      "integrity": "sha512-mKeBYvu8tcMSLhypAHBmriUFfWXKTCF/23Z4jiCoYK3UtWepkliViNLuR90V9XOyD62mUxs9p1jsrpK3CCGIzw==",
+      "dependencies": {
+        "@vue/reactivity": "3.5.34",
+        "@vue/shared": "3.5.34"
+      }
+    },
+    "node_modules/@vue/runtime-dom": {
+      "version": "3.5.34",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.34.tgz",
+      "integrity": "sha512-e8kZzERmCwUnBRVsgSQlAfrfU2rGoy0FFKPBXSlfEjc/O3KfA7QP0t1/2ZylrbchjmIKB4dPTd07A6WPr0eOrg==",
+      "dependencies": {
+        "@vue/reactivity": "3.5.34",
+        "@vue/runtime-core": "3.5.34",
+        "@vue/shared": "3.5.34",
+        "csstype": "^3.2.3"
+      }
+    },
+    "node_modules/@vue/server-renderer": {
+      "version": "3.5.34",
+      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.34.tgz",
+      "integrity": "sha512-nHxmJoTrKsmrkbILRhkC9gY1G3moZbJTqCzDd7DOOzG5KH9oeJ0Unqrff5f9v0pW//jES05ZkJcNtfE8JjOIew==",
+      "dependencies": {
+        "@vue/compiler-ssr": "3.5.34",
+        "@vue/shared": "3.5.34"
+      },
+      "peerDependencies": {
+        "vue": "3.5.34"
+      }
+    },
+    "node_modules/@vue/shared": {
+      "version": "3.5.34",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.34.tgz",
+      "integrity": "sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA=="
+    },
+    "node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "license": "MIT",
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/camelcase": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+      "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/cliui": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+      "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+      "license": "ISC",
+      "dependencies": {
+        "string-width": "^4.2.0",
+        "strip-ansi": "^6.0.0",
+        "wrap-ansi": "^6.2.0"
+      }
+    },
+    "node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "license": "MIT",
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "license": "MIT"
+    },
+    "node_modules/csstype": {
+      "version": "3.2.3",
+      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+      "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="
+    },
+    "node_modules/decamelize": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+      "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/dijkstrajs": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
+      "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
+      "license": "MIT"
+    },
+    "node_modules/emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "license": "MIT"
+    },
+    "node_modules/encode-utf8": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz",
+      "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==",
+      "license": "MIT"
+    },
+    "node_modules/entities": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
+      "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
+      "engines": {
+        "node": ">=0.12"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/entities?sponsor=1"
+      }
+    },
+    "node_modules/estree-walker": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+    },
+    "node_modules/find-up": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+      "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+      "license": "MIT",
+      "dependencies": {
+        "locate-path": "^5.0.0",
+        "path-exists": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/get-caller-file": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+      "license": "ISC",
+      "engines": {
+        "node": "6.* || 8.* || >= 10.*"
+      }
+    },
+    "node_modules/is-fullwidth-code-point": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/locate-path": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+      "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+      "license": "MIT",
+      "dependencies": {
+        "p-locate": "^4.1.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/magic-string": {
+      "version": "0.30.21",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+      "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.5"
+      }
+    },
+    "node_modules/nanoid": {
+      "version": "3.3.12",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
+      "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
+    "node_modules/p-limit": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+      "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+      "license": "MIT",
+      "dependencies": {
+        "p-try": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/p-locate": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+      "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+      "license": "MIT",
+      "dependencies": {
+        "p-limit": "^2.2.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/p-try": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+      "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/path-exists": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+      "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+    },
+    "node_modules/pinia": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz",
+      "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==",
+      "dependencies": {
+        "@vue/devtools-api": "^6.6.3",
+        "vue-demi": "^0.14.10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/posva"
+      },
+      "peerDependencies": {
+        "typescript": ">=4.4.4",
+        "vue": "^2.7.0 || ^3.5.11"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/pngjs": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
+      "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
+    "node_modules/postcss": {
+      "version": "8.5.14",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
+      "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/postcss"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "dependencies": {
+        "nanoid": "^3.3.11",
+        "picocolors": "^1.1.1",
+        "source-map-js": "^1.2.1"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/qrcode": {
+      "version": "1.5.3",
+      "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.3.tgz",
+      "integrity": "sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==",
+      "license": "MIT",
+      "dependencies": {
+        "dijkstrajs": "^1.0.1",
+        "encode-utf8": "^1.0.3",
+        "pngjs": "^5.0.0",
+        "yargs": "^15.3.1"
+      },
+      "bin": {
+        "qrcode": "bin/qrcode"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
+    "node_modules/require-directory": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+      "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/require-main-filename": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+      "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+      "license": "ISC"
+    },
+    "node_modules/set-blocking": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+      "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
+      "license": "ISC"
+    },
+    "node_modules/source-map-js": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/string-width": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "license": "MIT",
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/vue": {
+      "version": "3.5.34",
+      "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.34.tgz",
+      "integrity": "sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA==",
+      "peer": true,
+      "dependencies": {
+        "@vue/compiler-dom": "3.5.34",
+        "@vue/compiler-sfc": "3.5.34",
+        "@vue/runtime-dom": "3.5.34",
+        "@vue/server-renderer": "3.5.34",
+        "@vue/shared": "3.5.34"
+      },
+      "peerDependencies": {
+        "typescript": "*"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vue-demi": {
+      "version": "0.14.10",
+      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+      "hasInstallScript": true,
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/which-module": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
+      "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
+      "license": "ISC"
+    },
+    "node_modules/wrap-ansi": {
+      "version": "6.2.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+      "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^4.0.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/y18n": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
+      "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
+      "license": "ISC"
+    },
+    "node_modules/yargs": {
+      "version": "15.4.1",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
+      "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+      "license": "MIT",
+      "dependencies": {
+        "cliui": "^6.0.0",
+        "decamelize": "^1.2.0",
+        "find-up": "^4.1.0",
+        "get-caller-file": "^2.0.1",
+        "require-directory": "^2.1.1",
+        "require-main-filename": "^2.0.0",
+        "set-blocking": "^2.0.0",
+        "string-width": "^4.2.0",
+        "which-module": "^2.0.0",
+        "y18n": "^4.0.0",
+        "yargs-parser": "^18.1.2"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/yargs-parser": {
+      "version": "18.1.3",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+      "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+      "license": "ISC",
+      "dependencies": {
+        "camelcase": "^5.0.0",
+        "decamelize": "^1.2.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    }
+  }
+}

+ 1 - 1
package.json

@@ -9,9 +9,9 @@
   },
   "dependencies": {
     "pinia": "^2.1.7",
+    "qrcode": "^1.5.3",
     "vue": "^3.4.21"
   },
-  "devDependencies": {},
   "repository": {
     "type": "git",
     "url": ""

+ 1 - 13
pages.json

@@ -71,20 +71,8 @@
 				"navigationBarTitleText": "名片信息",
 				"navigationStyle": "custom"
 			}
-		},
-		{
-			"path": "pages/test/debug",
-			"style": {
-				"navigationBarTitleText": "调试工具"
-			}
-		},
-		{
-			"path": "pages/test/navbar",
-			"style": {
-				"navigationBarTitleText": "导航栏测试",
-				"navigationStyle": "custom"
-			}
 		}
+	
 	],
 	"globalStyle": {
 		"navigationBarTextStyle": "black",

+ 45 - 28
pages/index/index.vue

@@ -1,7 +1,7 @@
 <template>
 	<view class="index-container">
 		<!-- 顶部背景 -->
-		<image class="top-bg" src="/static/image/home/top-bg.png"  />
+		<image class="top-bg" src="/static/image/home/top-bg.png" />
 		<NavBar title="" color="#020202" :fixed="true" :bg="'transparent'">
 			<template #left>
 				<view class="left-title">{{appName}}</view>
@@ -11,50 +11,50 @@
 			<view class="page-top">
 				<view class="user-card">
 					<!-- 背景图 -->
-					<image class="user-card-bg" src="/static/image/home/usecard-bg.png"  />
+					<image class="user-card-bg" src="/static/image/home/usecard-bg.png" />
 
 					<!-- 左上:姓名 + 职位 -->
 					<view class="user-header">
 						<view class="name-row">
-							<text class="user-name">赵建平</text>
-							<text class="user-role">销售经理</text>
+							<text class="user-name">{{ cardInfo.nickName || '用户' }}</text>
+							<text class="user-role">{{ cardInfo.postName || '职位' }}</text>
 						</view>
-						<text class="company-name">杭州碟滤膜技术有限公司</text>
+						<text class="company-name">{{ cardInfo.companyName || '公司名称' }}</text>
 					</view>
 
 					<!-- 右上:头像 -->
 					<view class="avatar-wrapper">
-						<image class="avatar" src="/static/image/public/avatar-default.png"  />
-						<image class="badge-icon" src="/static/image/public/badge-icon.png"  />
+						<image class="avatar" :src="cardInfo.avatar || '/static/image/public/avatar-default.png'" />
+						<image class="badge-icon" src="/static/image/public/badge-icon.png" />
 					</view>
 
 					<!-- 左下:联系方式 -->
 					<view class="user-contact">
 						<view class="contact-row" @click="makeCall">
 							<uni-icons type="phone" :size="18" color="#666666"></uni-icons>
-							<text class="contact-text">138-0000-0000</text>
+							<text class="contact-text">{{ cardInfo.phonenumber || '暂无电话' }}</text>
 						</view>
 						<view class="contact-row">
 							<uni-icons type="email" :size="18" color="#666666"></uni-icons>
-							<text class="contact-text">zhao.jp@subote.com</text>
+							<text class="contact-text">{{ cardInfo.email || '暂无邮箱' }}</text>
 						</view>
 						<view class="contact-row">
 							<uni-icons type="location" :size="18" color="#666666"></uni-icons>
-							<text class="contact-text">上海市静安区江宁路 168 号</text>
+							<text class="contact-text">{{ cardInfo.companyAddress || '暂无地址' }}</text>
 						</view>
 					</view>
 
 					<!-- 右下:分享名片按钮 -->
-					<view class="share-btn-wrapper">
+				<!-- 	<view class="share-btn-wrapper">
 						<view class="share-btn" @click="shareCard">
 							<text class="share-text">分享名片</text>
 						</view>
-					</view>
+					</view> -->
 				</view>
 			</view>
 
 			<!-- 最近访客模块 -->
-			<view class="recent-visitors">
+			<view class="recent-visitors" v-if="false">
 				<view class="section-header">
 					<text class="section-title">最近访客</text>
 					<view class="visitor-count">
@@ -107,7 +107,7 @@
 			</view>
 
 			<!-- 功能菜单网格 -->
-			<view class="function-grid">
+			<view class="function-grid"  v-if="false">
 				<view class="grid-item" @click="navigateTo('clue')">
 					<view class="grid-icon clue-icon">
 						<text class="icon-text">📋</text>
@@ -159,7 +159,7 @@
 			</view>
 
 			<!-- 业务日历模块 -->
-			<view class="business-calendar">
+			<view class="business-calendar"  v-if="false">
 				<view class="section-header">
 					<text class="section-title">业务日历</text>
 					<text class="view-more" @click="viewAllCalendar">查看更多</text>
@@ -260,24 +260,42 @@
 	// 使用 Pinia 管理用户状态
 	const userStore = useUserStore()
 	const {
-		userInfo,
-		isLoggedIn
+		cardInfo,
+		companyInfo
 	} = storeToRefs(userStore)
 
 	let appName = ref('')
 
-	onMounted(async() => {
+	onMounted(async () => {
 		const config = await getCurrentConfig()
 		appName.value = config.appName
-		// 从 store 中获取用户信息(已自动从 localStorage 加载)
-		console.log('用户登录状态:', isLoggedIn.value)
-		console.log('用户信息:', userInfo.value)
+		// 从 store 中获取用户名片和公司信息
+		console.log('用户名片信息:', cardInfo.value)
+		console.log('公司信息:', companyInfo.value)
+
+		// 如果没有数据,重新获取
+		if (!cardInfo.value.nickName) {
+			await userStore.queryCardInfo()
+		}
+		if (!companyInfo.value.name) {
+			await userStore.queryCompanyInfo()
+		}
+
+		console.log('更新后的用户名片信息:', cardInfo.value)
+		console.log('更新后的公司信息:', companyInfo.value)
 	})
 
 	const makeCall = () => {
-		uni.makePhoneCall({
-			phoneNumber: '4000000000'
-		})
+		if (cardInfo.value.phonenumber) {
+			uni.makePhoneCall({
+				phoneNumber: cardInfo.value.phonenumber
+			})
+		} else {
+			uni.showToast({
+				title: '暂无电话号码',
+				icon: 'none'
+			})
+		}
 	}
 
 	// 控制名片预览弹窗
@@ -329,7 +347,6 @@
 
 <style lang="scss" scoped>
 	.index-container {
-		min-height: 100vh;
 		background-color: #f5f6f8;
 		padding-bottom: 120rpx;
 	}
@@ -425,15 +442,15 @@
 				.avatar {
 					width: 100%;
 					height: 100%;
+					border-radius: 50%;
 				}
 
 				.badge-icon {
 					width: 64rpx;
 					height: 64rpx;
 					position: absolute;
-					bottom: 0;
-					right: 0;
-					transform: translate(50% 50%);
+					bottom: -10rpx;
+					right: -10rpx;
 					z-index: 1;
 				}
 			}

+ 2 - 0
pages/login/login.vue

@@ -252,6 +252,7 @@
 				saveToekn(token)
 			let cardInfo = 	await userStore.queryCardInfo()
 				await userStore.queryCompanyInfo()
+				await userStore.queryCardQrcode()
 				console.log(cardInfo,"cardInfocardInfocardInfo");
 				
 				uni.showToast({
@@ -330,6 +331,7 @@
 				saveToekn(token)
 				await userStore.queryCardInfo()
 				await userStore.queryCompanyInfo()
+				await userStore.queryCardQrcode()
 				uni.showToast({
 					title: '登录成功',
 					icon: 'success'

+ 1 - 7
pages/message/index.vue

@@ -34,13 +34,7 @@
 
 	const currentTab = ref('system')
 
-	const messageList = reactive([{
-			title: '撒东方大厦发多少',
-			time: '2025/12/20 13:28:10',
-			content: '按时分秒,电脑风扇,啊没法弄收到,麻烦你什么,能否打撒,你吗,收到能否,的描述'
-		},
-
-	])
+	const messageList = reactive([])
 
 	const switchTab = (tab) => {
 		currentTab.value = tab

+ 358 - 49
pages/mine/card.vue

@@ -9,48 +9,48 @@
 			<view class="user-card">
 				<!-- 背景图 -->
 				<image class="user-card-bg" src="/static/image/home/usecard-bg.png" />
-		
-				<!-- 左上姓名 + 职位 -->
+
+				<!-- 左上:姓名 + 职位 -->
 				<view class="user-header">
 					<view class="name-row">
-						<text class="user-name">赵建平</text>
-						<text class="user-role">销售经理</text>
+						<text class="user-name">{{ cardInfo.nickName || '用户' }}</text>
+						<text class="user-role">{{ cardInfo.postName || '职位' }}</text>
 					</view>
-					<text class="company-name">杭州碟滤膜技术有限公司</text>
+					<text class="company-name">{{ cardInfo.companyName || '公司名称' }}</text>
 				</view>
-		
-				<!-- 右上头像 -->
+
+				<!-- 右上:头像 -->
 				<view class="avatar-wrapper">
-					<image class="avatar" src="/static/image/public/avatar-default.png" />
+					<image class="avatar" :src="cardInfo.avatar || '/static/image/public/avatar-default.png'" />
 					<image class="badge-icon" src="/static/image/public/badge-icon.png" />
 				</view>
-		
-				<!-- 左下联系方式 -->
+
+				<!-- 左下:联系方式 -->
 				<view class="user-contact">
 					<view class="contact-row" @click="makeCall">
 						<uni-icons type="phone" :size="18" color="#666666"></uni-icons>
-						<text class="contact-text">138-0000-0000</text>
+						<text class="contact-text">{{ cardInfo.phonenumber || '暂无电话' }}</text>
 					</view>
 					<view class="contact-row">
 						<uni-icons type="email" :size="18" color="#666666"></uni-icons>
-						<text class="contact-text">zhao.jp@subote.com</text>
+						<text class="contact-text">{{ cardInfo.email || '暂无邮箱' }}</text>
 					</view>
 					<view class="contact-row">
 						<uni-icons type="location" :size="18" color="#666666"></uni-icons>
-						<text class="contact-text">上海市静安区江宁路 168 号</text>
+						<text class="contact-text">{{ cardInfo.companyAddress || '暂无地址' }}</text>
 					</view>
 				</view>
 			</view>
 			<view class="control-card">
-				<view class="item">
+				<view class="item" @click="shareCard">
 					<image src="/static/image/public/card-sharing.png"></image>
 					<text>分享名片</text>
 				</view>
-				<view class="item">
+				<view class="item" @click="saveCard">
 					<image src="/static/image/public/card-save.png"></image>
 					<text>保存名片</text>
 				</view>
-				<view class="item">
+				<view class="item" @click="showQRCode">
 					<image src="/static/image/public/card-qr.png"></image>
 					<text>名片码</text>
 				</view>
@@ -85,60 +85,273 @@
 			<view class="company-section" v-if="currentTab === 'company'">
 				<view class="company-content">
 					<text class="company-text">
-						杭州碟滤膜技术有限公司是一家专注于膜技术研发、生产和销售的高新技术企业。公司主要产品包括各种规格的滤膜、过滤器及相关设备,广泛应用于医药、化工、食品、环保等领域。
-					</text>
-					<text class="company-text">
-						公司拥有一支专业的研发团队和先进的生产设备,始终坚持"质量第一、客户至上"的经营理念,为客户提供优质的产品和服务。
+						<view v-html="companyInfo.introduce" v-if="companyInfo.introduce"></view>
 					</text>
 				</view>
 			</view>
 		</view>
+
+		<!-- 隐藏的 Canvas 用于生成名片快照 -->
+		<canvas id="posterCanvas" type="2d"
+			style="position: fixed; left: -9999px; top: -9999px; width: 750px; height: 800px;"></canvas>
+
+		<!-- 隐藏的 Canvas 用于生成二维码海报 -->
+		<view class="hidden-canvas-box">
+			<canvas id="qrPosterCanvas" type="2d" style="width: 600px; height: 700px;"></canvas>
+		</view>
+
+		<!-- 二维码弹窗 -->
+		<uni-popup ref="qrPopup" type="center">
+			<view class="qr-popup">
+				<view class="qr-header">
+					<text class="qr-title">名片二维码</text>
+					<text class="qr-subtitle">扫一扫查看我的名片信息</text>
+				</view>
+				<view class="qr-content">
+					<image v-if="qrInfo && qrInfo.image"
+						:src="qrInfo.image.startsWith('data:') ? qrInfo.image : 'data:image/png;base64,' + qrInfo.image"
+						class="qr-image" mode="aspectFit" />
+					<view v-else class="qr-loading">
+						<text>加载中...</text>
+					</view>
+				</view>
+				<view class="qr-actions">
+					<view class="action-btn" @click="saveQRCode">
+						<uni-icons type="download" size="24" color="#4080FF"></uni-icons>
+						<text class="action-text">保存到相册</text>
+					</view>
+					<view class="action-btn" @click="shareQRCode">
+						<uni-icons type="paperplane" size="24" color="#4080FF"></uni-icons>
+						<button open-type="share" class="action-text">分享给好友</button>
+					</view>
+				</view>
+			</view>
+		</uni-popup>
 	</view>
 </template>
 
 <script setup>
 	import {
-		ref
+		ref,
+		onMounted
 	} from 'vue'
+	import {
+		onShareAppMessage
+	} from '@dcloudio/uni-app';
 	import NavBar from '@/components/nav-bar/index.vue'
-	const currentTab = ref('products')
-
-	const productList = ref([{
-			id: 1,
-			title: '惠普黑白激光打印机 选配小白盒巴',
-			description: '打印机 | 惠普',
-			image: '/static/image/product/product-1.png'
-		},
-	])
+	import {
+		useUserStore
+	} from '@/store/modules/user.js'
+	import {
+		storeToRefs
+	} from 'pinia'
+	import {
+		generateCardPoster,
+		savePosterToAlbum,
+	} from '@/utils/poster.js'
+
+	// 使用 Pinia 管理用户状态
+	const userStore = useUserStore()
+	const {
+		cardInfo,
+		companyInfo,
+		qrInfo,
+	} = storeToRefs(userStore)
 
-	const goBack = () => {
-		uni.navigateBack()
+	const currentTab = ref('products')
+	// 名片快照图片路径
+	const cardSnapshot = ref('')
+	// 二维码海报图片路径
+	const qrCodePoster = ref('')
+	// 二维码弹窗引用
+	const qrPopup = ref(null)
+
+	const productList = ref([ ])
+	onShareAppMessage(() => {
+		return {
+			userName: '小程序',
+			path: 'pages/index/index',
+			title: '布尔销销乐',
+			imagePath: 'data:image/png;base64,' + qrInfo.value.image,
+		};
+	});
+	onMounted(async () => {
+		// 如果没有数据,重新获取
+		if (!cardInfo.value.nickName) {
+			await userStore.queryCardInfo()
+		}
+		if (!companyInfo.value.name) {
+			await userStore.queryCompanyInfo()
+		}
+	})
+
+	const makeCall = () => {
+		if (cardInfo.value.phonenumber) {
+			uni.makePhoneCall({
+				phoneNumber: cardInfo.value.phonenumber
+			})
+		} else {
+			uni.showToast({
+				title: '暂无电话号码',
+				icon: 'none'
+			})
+		}
 	}
 
 	const shareCard = () => {
+		uni.showShareMenu({
+			withShareTicket: true
+		})
 		uni.showToast({
 			title: '分享名片',
 			icon: 'none'
 		})
 	}
 
-	const saveCard = () => {
-		uni.showToast({
-			title: '保存名片',
-			icon: 'none'
+	// 生成名片快照并保存
+	const saveCard = async () => {
+		uni.showLoading({
+			title: '生成快照中...',
+			mask: true
 		})
+
+		try {
+			// 打印 cardInfo 详细信息
+			console.log('[saveCard] cardInfo:', JSON.stringify(cardInfo.value, null, 2))
+			console.log('[saveCard] avatar:', cardInfo.value.avatar)
+			
+			// 生成名片快照
+			const snapshotPath = await generateCardPoster(cardInfo.value)
+			
+			// 用属性接收快照路径
+			cardSnapshot.value = snapshotPath
+			
+			console.log('名片快照路径:', cardSnapshot.value)
+			
+			// 保存到相册
+			await savePosterToAlbum(snapshotPath)
+			
+			uni.hideLoading()
+			uni.showToast({
+				title: '已保存到相册',
+				icon: 'success'
+			})
+		} catch (error) {
+			console.error('保存失败:', error)
+			uni.hideLoading()
+			uni.showToast({
+				title: '保存失败,请重试',
+				icon: 'none'
+			})
+		}
 	}
 
-	const showQRCode = () => {
-		uni.showToast({
-			title: '名片码',
-			icon: 'none'
-		})
+	// 显示二维码
+	const showQRCode = async () => {
+		// 打开弹窗
+		qrPopup.value.open()
+	}
+
+
+
+
+	// 保存二维码到相册
+	const saveQRCode = async () => {
+		if (!qrInfo.value.image) {
+			uni.showToast({
+				title: '二维码图片不存在',
+				icon: 'none'
+			})
+			return
+		}
+
+		try {
+			// 将 base64 转换为临时文件
+			const qrFilePath = await base64ToTempFile(qrInfo.value.image)
+
+			// 保存到相册
+			await savePosterToAlbum(qrFilePath)
+			uni.showToast({
+				title: '已保存到相册',
+				icon: 'success'
+			})
+		} catch (error) {
+			console.error('保存失败:', error)
+			uni.showToast({
+				title: '保存失败,请重试',
+				icon: 'none'
+			})
+		}
+	}
+
+	// 分享二维码给好友
+	const shareQRCode = async () => {
+		if (!qrInfo.value.image) {
+			uni.showToast({
+				title: '二维码图片不存在',
+				icon: 'none'
+			})
+			return
+		}
+
+		try {
+			// 将 base64 转换为临时文件
+			const qrFilePath = await base64ToTempFile(qrInfo.value.image)
+
+			// #ifdef MP-WEIXIN
+
+			// #endif
+
+			// #ifndef MP-WEIXIN
+			await shareImageToWeChat(qrFilePath)
+			uni.showToast({
+				title: '分享成功',
+				icon: 'success'
+			})
+			// #endif
+		} catch (error) {
+			console.error('分享失败:', error)
+			uni.showToast({
+				title: '分享失败,请重试',
+				icon: 'none'
+			})
+		}
 	}
 
 	const switchTab = (tab) => {
 		currentTab.value = tab
 	}
+
+	// 将 base64 转换为临时文件路径
+	const base64ToTempFile = async (base64Data) => {
+		return new Promise((resolve, reject) => {
+			try {
+				// 移除 data:image/png;base64, 前缀(如果有)
+				const pureBase64 = base64Data.replace(/^data:image\/\w+;base64,/, '')
+
+				const fileName = `${Date.now()}_qrcode.png`
+				const filePath = `${wx.env.USER_DATA_PATH}/${fileName}`
+
+				const fs = uni.getFileSystemManager()
+				fs.writeFile({
+					filePath: filePath,
+					data: pureBase64,
+					encoding: 'base64',
+					success: () => {
+						console.log('base64 转文件成功:', filePath)
+						resolve(filePath)
+					},
+					fail: (err) => {
+						console.error('base64 转文件失败:', err)
+						reject(err)
+					}
+				})
+			} catch (error) {
+				console.error('base64 转换异常:', error)
+				reject(error)
+			}
+		})
+	}
 </script>
 
 <style lang="scss" scoped>
@@ -224,6 +437,7 @@
 	/* 用户信息卡片 */
 	.page-top {
 		margin: 0 24rpx 24rpx;
+
 		.user-card {
 			position: relative;
 			z-index: 2;
@@ -326,7 +540,8 @@
 			}
 
 		}
-		.control-card{
+
+		.control-card {
 			background-color: #fff;
 			border-radius: 0 0 24rpx 24rpx;
 			position: relative;
@@ -337,29 +552,33 @@
 			justify-content: space-around;
 			padding-top: 48rpx;
 			padding-bottom: 24rpx;
-			.item{
+
+			.item {
 				display: flex;
 				flex-direction: column;
 				align-items: center;
 				justify-content: center;
 				gap: 4rpx;
-				image{
+
+				image {
 					width: 64rpx;
 					height: 64rpx;
 				}
-				text{
+
+				text {
 					font-size: 26rpx;
 					color: #202020;
 				}
 			}
 		}
-	
-	
+
+
 	}
+
 	// 名片内容区域
 	.card-content {
 		margin: 0 24rpx;
-		padding: 0 24rpx ;
+		padding: 0 24rpx;
 		border-radius: 24rpx;
 		background: #ffffff;
 		box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.04);
@@ -367,6 +586,7 @@
 		bottom: 24rpx;
 		z-index: 2;
 	}
+
 	// Tab 切换区域
 	.tab-section {
 		border-radius: 24rpx 24rpx 0 0;
@@ -410,6 +630,7 @@
 	// 产品列表
 	.product-list {
 		padding: 24rpx 40rpx;
+
 		.product-item {
 			display: flex;
 			padding: 24rpx 0;
@@ -461,7 +682,10 @@
 				color: #666666;
 				line-height: 1.8;
 				margin-bottom: 24rpx;
-				text-align: justify;
+
+				::v-deep img {
+					max-width: 100% !important;
+				}
 
 				&:last-child {
 					margin-bottom: 0;
@@ -469,4 +693,89 @@
 			}
 		}
 	}
+
+	// 隐藏的 canvas 容器
+	.hidden-canvas-box {
+		position: fixed;
+		left: -9999px;
+		top: -9999px;
+		width: 1px;
+		height: 1px;
+		overflow: hidden;
+	}
+
+	// 二维码弹窗样式
+	.qr-popup {
+		width: 600rpx;
+		background: #ffffff;
+		border-radius: 24rpx;
+		padding: 40rpx;
+		box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.15);
+
+		.qr-header {
+			text-align: center;
+			margin-bottom: 40rpx;
+
+			.qr-title {
+				display: block;
+				font-size: 36rpx;
+				font-weight: 600;
+				color: #202020;
+				margin-bottom: 12rpx;
+			}
+
+			.qr-subtitle {
+				display: block;
+				font-size: 26rpx;
+				color: #999999;
+			}
+		}
+
+		.qr-content {
+			display: flex;
+			justify-content: center;
+			align-items: center;
+			min-height: 400rpx;
+			margin-bottom: 40rpx;
+
+			.qr-image {
+				width: 400rpx;
+				height: 400rpx;
+				border-radius: 16rpx;
+				box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
+			}
+
+			.qr-loading {
+				display: flex;
+				justify-content: center;
+				align-items: center;
+			}
+		}
+
+		.qr-actions {
+			display: flex;
+			justify-content: space-around;
+
+			.action-btn {
+				display: flex;
+				flex-direction: column;
+				align-items: center;
+				gap: 12rpx;
+				padding: 20rpx 40rpx;
+				border-radius: 16rpx;
+				background: rgba(64, 128, 255, 0.08);
+
+				.action-text {
+					&::after{
+						border: none;
+					}
+					line-height: 1;
+					background-color: transparent;
+					font-size: 26rpx;
+					color: #4080FF;
+					font-weight: 500;
+				}
+			}
+		}
+	}
 </style>

+ 73 - 20
pages/mine/index.vue

@@ -11,15 +11,15 @@
 			<!-- 用户信息 -->
 			<view class="user-info-section">
 				<view class="avatar-wrapper">
-					<image class="avatar" src="/static/image/public/avatar-default.png" mode="aspectFill" />
+					<image class="avatar" :src="cardInfo.avatar || '/static/image/public/avatar-default.png'" mode="aspectFill" />
 					<image class="avatar-badge" src="/static/image/public/badge-icon.png" />
 				</view>
 				<view class="user-details">
 					<view class="name-row">
-						<text class="user-name">赵建平</text>
-						<text class="user-role">销售经理</text>
+						<text class="user-name">{{ cardInfo.nickName || '用户' }}</text>
+						<text class="user-role">{{ cardInfo.postName || '职位' }}</text>
 					</view>
-					<text class="company-name">浙江舒博特网络科技有限公司</text>
+					<text class="company-name">{{ cardInfo.companyName || '公司名称' }}</text>
 				</view>
 				<view class="qr-btn" @click="showCard">
 					<image class="qr-icon" src="/static/image/public/qr-icon.png" />
@@ -29,68 +29,68 @@
 		</view>
 
 		<!-- 内容区域 -->
-		<view class="content-section">
+		<view class="content-section" >
 
 			<!-- 线索数据卡片 -->
-			<view class="data-card">
+			<view class="data-card" v-if="false"> 
 				<view class="card-header">
 					<text class="card-title">线索数据</text>
 				</view>
 				<view class="data-row">
 					<view class="data-item">
-						<text class="data-value">1,266</text>
+						<text class="data-value">{{ statisticsData.clueTotal.toLocaleString() }}</text>
 						<text class="data-label">线索总数</text>
 					</view>
 					<view class="data-item highlight">
-						<text class="data-value orange">65</text>
+						<text class="data-value orange">{{ statisticsData.clueNewThisMonth.toLocaleString() }}</text>
 						<text class="data-label">本月新增</text>
 					</view>
 					<view class="data-item">
-						<text class="data-value">126</text>
+						<text class="data-value">{{ statisticsData.clueMine.toLocaleString() }}</text>
 						<text class="data-label">我的线索</text>
 					</view>
 				</view>
 			</view>
 
 			<!-- 客户数据卡片 -->
-			<view class="data-card">
+			<view class="data-card" v-if="false">
 				<view class="card-header">
 					<text class="card-title">客户数据</text>
 					<uni-icons type="eye" size="20" color="#999999"></uni-icons>
 				</view>
 				<view class="data-row">
 					<view class="data-item">
-						<text class="data-value">1,088</text>
+						<text class="data-value">{{ statisticsData.customerTotal.toLocaleString() }}</text>
 						<text class="data-label">客户总数</text>
 					</view>
 					<view class="data-item highlight">
-						<text class="data-value orange">123</text>
+						<text class="data-value orange">{{ statisticsData.customerNewThisMonth.toLocaleString() }}</text>
 						<text class="data-label">本月新增</text>
 					</view>
 					<view class="data-item">
-						<text class="data-value">235</text>
+						<text class="data-value">{{ statisticsData.customerMine.toLocaleString() }}</text>
 						<text class="data-label">我的客户</text>
 					</view>
 				</view>
 			</view>
 
 			<!-- 商机数据卡片 -->
-			<view class="data-card">
+			<view class="data-card" v-if="false">
 				<view class="card-header">
 					<text class="card-title">商机数据</text>
 					<uni-icons type="eye" size="20" color="#999999"></uni-icons>
 				</view>
 				<view class="data-row">
 					<view class="data-item">
-						<text class="data-value">1,366</text>
+						<text class="data-value">{{ statisticsData.opportunityTotal.toLocaleString() }}</text>
 						<text class="data-label">商机总数</text>
 					</view>
 					<view class="data-item highlight">
-						<text class="data-value orange">63</text>
+						<text class="data-value orange">{{ statisticsData.opportunityNewThisMonth.toLocaleString() }}</text>
 						<text class="data-label">本月新增</text>
 					</view>
 					<view class="data-item">
-						<text class="data-value">86</text>
+						<text class="data-value">{{ statisticsData.opportunityMine.toLocaleString() }}</text>
 						<text class="data-label">我的商机</text>
 					</view>
 				</view>
@@ -131,9 +131,63 @@
 
 <script setup>
 	import {
-		ref
+		ref,
+		onMounted
 	} from 'vue'
 	import NavBar from '@/components/nav-bar/index.vue'
+	import {
+		useUserStore
+	} from '@/store/modules/user.js'
+	import {
+		storeToRefs
+	} from 'pinia'
+
+	// 使用 Pinia 管理用户状态
+	const userStore = useUserStore()
+	const { cardInfo, companyInfo } = storeToRefs(userStore)
+
+	// 统计数据
+	const statisticsData = ref({
+		clueTotal: 0,
+		clueNewThisMonth: 0,
+		clueMine: 0,
+		customerTotal: 0,
+		customerNewThisMonth: 0,
+		customerMine: 0,
+		opportunityTotal: 0,
+		opportunityNewThisMonth: 0,
+		opportunityMine: 0
+	})
+
+	onMounted(async () => {
+		// 如果没有数据,重新获取
+		if (!cardInfo.value.nickName) {
+			await userStore.queryCardInfo()
+		}
+		if (!companyInfo.value.name) {
+			await userStore.queryCompanyInfo()
+		}
+		
+		// 加载统计数据
+		loadStatisticsData()
+	})
+
+	// 加载统计数据(后续对接真实 API)
+	const loadStatisticsData = () => {
+		// TODO: 调用统计 API 获取真实数据
+		statisticsData.value = {
+			clueTotal: 1266,
+			clueNewThisMonth: 65,
+			clueMine: 126,
+			customerTotal: 1088,
+			customerNewThisMonth: 123,
+			customerMine: 235,
+			opportunityTotal: 1366,
+			opportunityNewThisMonth: 63,
+			opportunityMine: 86
+		}
+	}
+
 	const navigateTo = (page) => {
 		uni.showToast({
 			title: `功能开发中:${page}`,
@@ -151,7 +205,6 @@
 
 <style lang="scss" scoped>
 	.mine-container {
-		min-height: 100vh;
 		background: #f5f6f8;
 	}
 
@@ -422,4 +475,4 @@
 	.bottom-spacer {
 		height: 40rpx;
 	}
-</style>
+</style>

+ 81 - 37
pages/mine/userCard.vue

@@ -9,39 +9,39 @@
 			<view class="user-card">
 				<!-- 背景图 -->
 				<image class="user-card-bg" src="/static/image/home/usecard-bg.png" />
-		
+
 				<!-- 左上:姓名 + 职位 -->
 				<view class="user-header">
 					<view class="name-row">
-						<text class="user-name">赵建平</text>
-						<text class="user-role">销售经理</text>
+						<text class="user-name">{{ cardInfo.nickName || '用户' }}</text>
+						<text class="user-role">{{ cardInfo.postName || '职位' }}</text>
 					</view>
-					<text class="company-name">杭州碟滤膜技术有限公司</text>
+					<text class="company-name">{{ cardInfo.companyName || '公司名称' }}</text>
 				</view>
-		
+
 				<!-- 右上:头像 -->
 				<view class="avatar-wrapper">
-					<image class="avatar" src="/static/image/public/avatar-default.png" />
+					<image class="avatar" :src="cardInfo.avatar || '/static/image/public/avatar-default.png'" />
 					<image class="badge-icon" src="/static/image/public/badge-icon.png" />
 				</view>
-		
+
 				<!-- 左下:联系方式 -->
 				<view class="user-contact">
 					<view class="contact-row" @click="makeCall">
 						<uni-icons type="phone" :size="18" color="#666666"></uni-icons>
-						<text class="contact-text">138-0000-0000</text>
+						<text class="contact-text">{{ cardInfo.phonenumber || '暂无电话' }}</text>
 					</view>
 					<view class="contact-row">
 						<uni-icons type="email" :size="18" color="#666666"></uni-icons>
-						<text class="contact-text">zhao.jp@subote.com</text>
+						<text class="contact-text">{{ cardInfo.email || '暂无邮箱' }}</text>
 					</view>
 					<view class="contact-row">
 						<uni-icons type="location" :size="18" color="#666666"></uni-icons>
-						<text class="contact-text">上海市静安区江宁路 168 号</text>
+						<text class="contact-text">{{ cardInfo.companyAddress || '暂无地址' }}</text>
 					</view>
 				</view>
 			</view>
-	<!-- 		<view class="control-card">
+			<!-- 		<view class="control-card">
 				<view class="item">
 					<image src="/static/image/public/card-sharing.png"></image>
 					<text>分享名片</text>
@@ -84,12 +84,9 @@
 			<!-- 企业简介 -->
 			<view class="company-section" v-if="currentTab === 'company'">
 				<view class="company-content">
-					<text class="company-text">
-						杭州碟滤膜技术有限公司是一家专注于膜技术研发、生产和销售的高新技术企业。公司主要产品包括各种规格的滤膜、过滤器及相关设备,广泛应用于医药、化工、食品、环保等领域。
-					</text>
-					<text class="company-text">
-						公司拥有一支专业的研发团队和先进的生产设备,始终坚持"质量第一、客户至上"的经营理念,为客户提供优质的产品和服务。
-					</text>
+					<view class="company-text">
+						<view v-html="companyInfo.introduce" v-if="companyInfo.introduce"></view>
+					</view>
 				</view>
 			</view>
 		</view>
@@ -98,24 +95,60 @@
 
 <script setup>
 	import {
-		ref
+		ref,
+		onMounted
 	} from 'vue'
 	import NavBar from '@/components/nav-bar/index.vue'
+	import {
+		useUserStore
+	} from '@/store/modules/user.js'
+	import {
+		storeToRefs
+	} from 'pinia'
+
+	// 使用 Pinia 管理用户状态
+	const userStore = useUserStore()
+	const {
+		cardInfo,
+		companyInfo
+	} = storeToRefs(userStore)
+
 	const currentTab = ref('products')
 
 	const productList = ref([{
-			id: 1,
-			title: '惠普黑白激光打印机 选配小白盒巴',
-			description: '打印机 | 惠普',
-			image: '/static/image/product/product-1.png'
-		},
-	])
-
-	const goBack = () => {
-		uni.navigateBack()
+		id: 1,
+		title: '惠普黑白激光打印机 选配小白盒巴',
+		description: '打印机 | 惠普',
+		image: '/static/image/product/product-1.png'
+	}, ])
+
+	onMounted(async () => {
+		// 如果没有数据,重新获取
+		if (!cardInfo.value.nickName) {
+			await userStore.queryCardInfo()
+		}
+		if (!companyInfo.value.name) {
+			await userStore.queryCompanyInfo()
+		}
+	})
+
+	const makeCall = () => {
+		if (cardInfo.value.phonenumber) {
+			uni.makePhoneCall({
+				phoneNumber: cardInfo.value.phonenumber
+			})
+		} else {
+			uni.showToast({
+				title: '暂无电话号码',
+				icon: 'none'
+			})
+		}
 	}
 
 	const shareCard = () => {
+		uni.showShareMenu({
+			withShareTicket: true
+		})
 		uni.showToast({
 			title: '分享名片',
 			icon: 'none'
@@ -124,8 +157,8 @@
 
 	const saveCard = () => {
 		uni.showToast({
-			title: '保存名片',
-			icon: 'none'
+			title: '已保存到手机相册',
+			icon: 'success'
 		})
 	}
 
@@ -224,6 +257,7 @@
 	/* 用户信息卡片 */
 	.page-top {
 		margin: 0 24rpx 24rpx;
+
 		.user-card {
 			position: relative;
 			z-index: 2;
@@ -326,7 +360,8 @@
 			}
 
 		}
-		.control-card{
+
+		.control-card {
 			background-color: #fff;
 			border-radius: 0 0 24rpx 24rpx;
 			position: relative;
@@ -337,35 +372,40 @@
 			justify-content: space-around;
 			padding-top: 48rpx;
 			padding-bottom: 24rpx;
-			.item{
+
+			.item {
 				display: flex;
 				flex-direction: column;
 				align-items: center;
 				justify-content: center;
 				gap: 4rpx;
-				image{
+
+				image {
 					width: 64rpx;
 					height: 64rpx;
 				}
-				text{
+
+				text {
 					font-size: 26rpx;
 					color: #202020;
 				}
 			}
 		}
-	
-	
+
+
 	}
+
 	// 名片内容区域
 	.card-content {
 		margin: 0 24rpx;
-		padding: 0 24rpx ;
+		padding: 0 24rpx;
 		border-radius: 24rpx;
 		background: #ffffff;
 		box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.04);
 		position: relative;
 		z-index: 2;
 	}
+
 	// Tab 切换区域
 	.tab-section {
 		border-radius: 24rpx 24rpx 0 0;
@@ -409,6 +449,7 @@
 	// 产品列表
 	.product-list {
 		padding: 24rpx 40rpx;
+
 		.product-item {
 			display: flex;
 			padding: 24rpx 0;
@@ -460,7 +501,10 @@
 				color: #666666;
 				line-height: 1.8;
 				margin-bottom: 24rpx;
-				text-align: justify;
+
+				::v-deep img {
+					max-width: 100% !important;
+				}
 
 				&:last-child {
 					margin-bottom: 0;

+ 13 - 11
pages/splash/splash.vue

@@ -39,7 +39,7 @@
 <script setup>
 	import {
 		ref,
-		onMounted
+		onMounted,
 	} from 'vue'
 	import {
 		isLogin
@@ -47,7 +47,9 @@
 	import {
 		useUserStore
 	} from '@/store/modules/user.js'
-
+	import {
+		onLoad,
+	} from "@dcloudio/uni-app";
 	const userStore = useUserStore() || null
 	import {
 		getCurrentConfig
@@ -56,24 +58,23 @@
 	let appName = ref('')
 	let version = ref('')
 	// 页面加载
-	onMounted(async () => {
+
+	onMounted(async (options) => {
 		const config = await getCurrentConfig()
-		console.log(config, "configconfigconfigconfig");
 		appName.value = config.appName
 		version.value = config.appVersion
-		initApp()
+		initApp(options?.userId || "2052227008040439810")
 	})
-
 	/**
 	 * 初始化应用
 	 */
-	const initApp = async () => {
+	const initApp = async (userId = null) => {
 		try {
 			// 模拟加载延迟(提升用户体验)
 			await sleep(500)
 			loadingText.value = '检查登录状态...'
 			// 如果没有 token,直接跳转登录页
-			const loginToggle = isLogin()
+			const loginToggle = isLogin() || !!userId
 			if (!loginToggle) {
 				console.log('未登录,token 不存在')
 				await sleep(800)
@@ -83,10 +84,11 @@
 				return
 			}
 			if (loginToggle) {
-				await userStore.queryCardInfo()
-				await userStore.queryCompanyInfo()
+				await userStore.queryCardInfo(userId)
+				await userStore.queryCompanyInfo(userId)
+				await userStore.queryCardQrcode(userId)
 				uni.reLaunch({
-					url: '/pages/index/index'
+					url: !userId ? '/pages/mine/userCard' : '/pages/index/index'
 				})
 			} else {
 				clearUserInfo()

+ 0 - 283
pages/test/debug.vue

@@ -1,283 +0,0 @@
-<template>
-	<view class="test-container">
-		<text class="title">调试工具</text>
-		
-		<!-- 缓存信息展示 -->
-		<view class="section">
-			<text class="section-title">当前缓存状态</text>
-			<view class="info-card">
-				<view class="info-row">
-					<text class="label">是否登录:</text>
-					<text class="value" :class="{ success: isLogined, error: !isLogined }">
-						{{ isLogined ? '已登录' : '未登录' }}
-					</text>
-				</view>
-				<view class="info-row">
-					<text class="label">Token:</text>
-					<text class="value token">{{ tokenDisplay }}</text>
-				</view>
-				<view class="info-row">
-					<text class="label">用户信息:</text>
-					<text class="value">{{ userInfoDisplay }}</text>
-				</view>
-			</view>
-		</view>
-		
-		<!-- 操作按钮 -->
-		<view class="section">
-			<text class="section-title">操作</text>
-			
-			<button class="action-btn" @click="simulateLogin">模拟登录</button>
-			<button class="action-btn warn" @click="clearCache">清除缓存</button>
-			<button class="action-btn" @click="refreshStatus">刷新状态</button>
-			<button class="action-btn" @click="gotoSplash">跳转到启动页</button>
-			<button class="action-btn" @click="gotoLogin">跳转到登录页</button>
-			<button class="action-btn" @click="gotoHome">跳转到首页</button>
-		</view>
-		
-		<!-- 日志区域 -->
-		<view class="section">
-			<text class="section-title">操作日志</text>
-			<scroll-view scroll-y class="log-box">
-				<view v-for="(log, index) in logs" :key="index" class="log-item">
-					<text class="log-time">{{ log.time }}</text>
-					<text class="log-text">{{ log.text }}</text>
-				</view>
-			</scroll-view>
-		</view>
-	</view>
-</template>
-
-<script setup>
-import { ref, computed, onMounted } from 'vue'
-import { getUserInfo, getToken, isLogin, saveUserInfo, clearUserInfo } from '@/utils/userCache.js'
-
-const logs = ref([])
-
-const isLogined = ref(false)
-const tokenDisplay = ref('无')
-const userInfoDisplay = ref('无')
-
-// 刷新状态
-const refreshStatus = () => {
-	isLogined.value = isLogin()
-	const token = getToken()
-	tokenDisplay.value = token ? token.substring(0, 20) + '...' : '无'
-	
-	const userInfo = getUserInfo()
-	if (userInfo) {
-		userInfoDisplay.value = JSON.stringify(userInfo)
-	} else {
-		userInfoDisplay.value = '无'
-	}
-	
-	addLog('状态已刷新')
-}
-
-// 添加日志
-const addLog = (text) => {
-	const now = new Date()
-	const time = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`
-	logs.value.unshift({ time, text })
-	
-	// 只保留最近 20 条
-	if (logs.value.length > 20) {
-		logs.value.pop()
-	}
-}
-
-// 模拟登录
-const simulateLogin = () => {
-	const mockUserInfo = {
-		id: 'test_user_001',
-		nickname: '测试用户',
-		phone: '13800138000',
-		avatar: ''
-	}
-	const mockToken = 'mock_token_' + Date.now()
-	
-	const success = saveUserInfo(mockUserInfo, mockToken)
-	if (success) {
-		addLog('模拟登录成功')
-		refreshStatus()
-		
-		uni.showToast({
-			title: '模拟登录成功',
-			icon: 'success'
-		})
-	} else {
-		addLog('模拟登录失败')
-	}
-}
-
-// 清除缓存
-const clearCache = () => {
-	uni.showModal({
-		title: '提示',
-		content: '确定要清除缓存吗?',
-		success: (res) => {
-			if (res.confirm) {
-				clearUserInfo()
-				addLog('缓存已清除')
-				refreshStatus()
-				
-				uni.showToast({
-					title: '已清除',
-					icon: 'success'
-				})
-			}
-		}
-	})
-}
-
-// 跳转
-const gotoSplash = () => {
-	addLog('跳转到启动页')
-	uni.reLaunch({
-		url: '/pages/splash/splash'
-	})
-}
-
-const gotoLogin = () => {
-	addLog('跳转到登录页')
-	uni.navigateTo({
-		url: '/pages/login/login'
-	})
-}
-
-const gotoHome = () => {
-	addLog('跳转到首页')
-	uni.navigateTo({
-		url: '/pages/index/index'
-	})
-}
-
-onMounted(() => {
-	addLog('调试工具已加载')
-	refreshStatus()
-})
-</script>
-
-<style lang="scss" scoped>
-.test-container {
-	min-height: 100vh;
-	background-color: #f5f5f5;
-	padding: 40rpx;
-}
-
-.title {
-	font-size: 40rpx;
-	font-weight: 600;
-	color: #333;
-	display: block;
-	text-align: center;
-	margin-bottom: 40rpx;
-}
-
-.section {
-	background-color: #ffffff;
-	border-radius: 16rpx;
-	padding: 32rpx;
-	margin-bottom: 32rpx;
-	
-	.section-title {
-		font-size: 30rpx;
-		font-weight: 600;
-		color: #333;
-		display: block;
-		margin-bottom: 24rpx;
-	}
-}
-
-.info-card {
-	background-color: #f9f9f9;
-	border-radius: 12rpx;
-	padding: 24rpx;
-	
-	.info-row {
-		display: flex;
-		margin-bottom: 16rpx;
-		
-		&:last-child {
-			margin-bottom: 0;
-		}
-		
-		.label {
-			font-size: 26rpx;
-			color: #666;
-			width: 160rpx;
-			flex-shrink: 0;
-		}
-		
-		.value {
-			font-size: 26rpx;
-			color: #333;
-			flex: 1;
-			word-break: break-all;
-			
-			&.success {
-				color: #52c41a;
-			}
-			
-			&.error {
-				color: #ff4d4f;
-			}
-			
-			&.token {
-				font-family: monospace;
-				background-color: #f0f0f0;
-				padding: 4rpx 12rpx;
-				border-radius: 8rpx;
-			}
-		}
-	}
-}
-
-.action-btn {
-	width: 100%;
-	height: 80rpx;
-	background-color: #1890ff;
-	color: #ffffff;
-	font-size: 28rpx;
-	border-radius: 12rpx;
-	margin-bottom: 16rpx;
-	border: none;
-	
-	&::after {
-		border: none;
-	}
-	
-	&.warn {
-		background-color: #ff4d4f;
-	}
-}
-
-.log-box {
-	height: 400rpx;
-	background-color: #1e1e1e;
-	border-radius: 12rpx;
-	padding: 20rpx;
-	
-	.log-item {
-		display: flex;
-		margin-bottom: 12rpx;
-		
-		&:last-child {
-			margin-bottom: 0;
-		}
-		
-		.log-time {
-			font-size: 22rpx;
-			color: #888;
-			font-family: monospace;
-			margin-right: 16rpx;
-		}
-		
-		.log-text {
-			font-size: 24rpx;
-			color: #fff;
-			flex: 1;
-		}
-	}
-}
-</style>

+ 0 - 151
pages/test/navbar.vue

@@ -1,151 +0,0 @@
-<template>
-	<view class="test-container">
-		<!-- 自定义导航栏 -->
-		<NavBar title="导航栏测试" :show-back="true" />
-		
-		<view class="content" :style="{ paddingTop: navBarHeight + 'px' }">
-			<view class="info-card">
-				<text class="title">胶囊按钮信息</text>
-				
-				<view class="info-item" v-if="capsuleInfo">
-					<text class="label">Top:</text>
-					<text class="value">{{ capsuleInfo.top }} px</text>
-				</view>
-				<view class="info-item" v-if="capsuleInfo">
-					<text class="label">Height:</text>
-					<text class="value">{{ capsuleInfo.height }} px</text>
-				</view>
-				<view class="info-item" v-if="capsuleInfo">
-					<text class="label">Width:</text>
-					<text class="value">{{ capsuleInfo.width }} px</text>
-				</view>
-				<view class="info-item" v-if="capsuleInfo">
-					<text class="label">Left:</text>
-					<text class="value">{{ capsuleInfo.left }} px</text>
-				</view>
-				<view class="info-item" v-if="capsuleInfo">
-					<text class="label">Right:</text>
-					<text class="value">{{ capsuleInfo.right }} px</text>
-				</view>
-				
-				<text class="title" style="margin-top: 32rpx;">状态栏信息</text>
-				<view class="info-item">
-					<text class="label">状态栏高度:</text>
-					<text class="value">{{ statusBarHeight }} px</text>
-				</view>
-				
-				<text class="title" style="margin-top: 32rpx;">导航栏信息</text>
-				<view class="info-item">
-					<text class="label">导航栏总高度:</text>
-					<text class="value">{{ navBarHeight }} px</text>
-				</view>
-			</view>
-			
-			<view class="tips">
-				<text class="tips-title">💡 提示信息</text>
-				<text class="tips-text">1. 如果看不到胶囊信息,请检查是否在微信小程序中运行</text>
-				<text class="tips-text">2. H5 端不会显示胶囊按钮</text>
-				<text class="tips-text">3. 检查 pages.json 中是否设置了 navigationStyle: custom</text>
-			</view>
-		</view>
-	</view>
-</template>
-
-<script setup>
-import { ref, computed, onMounted } from 'vue'
-import NavBar from '@/components/nav-bar/index.vue'
-
-const capsuleInfo = ref(null)
-const statusBarHeight = ref(0)
-
-const navBarHeight = computed(() => {
-	if (!capsuleInfo.value) return 44
-	return statusBarHeight.value + capsuleInfo.value.height
-})
-
-onMounted(() => {
-	// #ifdef MP-WEIXIN
-	const menuInfo = uni.getMenuButtonBoundingClientRect()
-	capsuleInfo.value = menuInfo
-	
-	const systemInfo = uni.getSystemInfoSync()
-	statusBarHeight.value = systemInfo.statusBarHeight || 0
-	
-	console.log('✅ 胶囊信息获取成功:', menuInfo)
-	console.log('✅ 状态栏高度:', statusBarHeight.value)
-	// #endif
-	
-	// #ifndef MP-WEIXIN
-	console.log('⚠️ 非微信小程序环境,无法获取胶囊信息')
-	// #endif
-})
-</script>
-
-<style lang="scss" scoped>
-.test-container {
-	min-height: 100vh;
-	background: #f5f6f8;
-}
-
-.content {
-	padding: 20rpx;
-	
-	.info-card {
-		background: #ffffff;
-		border-radius: 16rpx;
-		padding: 32rpx;
-		margin-bottom: 20rpx;
-		
-		.title {
-			font-size: 32rpx;
-			font-weight: 600;
-			color: #333333;
-			display: block;
-			margin-bottom: 24rpx;
-		}
-		
-		.info-item {
-			display: flex;
-			justify-content: space-between;
-			padding: 16rpx 0;
-			border-bottom: 1rpx solid #f0f0f0;
-			
-			&:last-child {
-				border-bottom: none;
-			}
-			
-			.label {
-				font-size: 28rpx;
-				color: #999999;
-			}
-			
-			.value {
-				font-size: 28rpx;
-				color: #333333;
-				font-weight: 500;
-			}
-		}
-	}
-	
-	.tips {
-		background: linear-gradient(135deg, #fff5e6 0%, #ffe8cc 100%);
-		border-radius: 16rpx;
-		padding: 32rpx;
-		
-		.tips-title {
-			font-size: 28rpx;
-			font-weight: 600;
-			color: #ff9500;
-			display: block;
-			margin-bottom: 16rpx;
-		}
-		
-		.tips-text {
-			font-size: 24rpx;
-			color: #666666;
-			display: block;
-			line-height: 1.8;
-		}
-	}
-}
-</style>

+ 3 - 3
pages/todos/index.vue

@@ -168,9 +168,9 @@
 	const currentTab = ref('leads')
 
 	const tabCounts = reactive({
-		leads: 12,
-		customer: 2,
-		opportunity: 4,
+		leads: 0,
+		customer: 0,
+		opportunity: 0,
 		contract: 0,
 		payment: 0
 	})

+ 45 - 12
store/modules/user.js

@@ -12,29 +12,62 @@ import {
 
 export const useUserStore = defineStore('user', {
 	state: () => ({
-		userLoc: null
+		cardInfo: {
+			"userId": "",
+			"nickName": "",
+			"phonenumber": "",
+			"postName": "",
+			"companyName": "",
+			"companyAddress": "",
+			"email": "",
+			"sex": null,
+			"avatar": ""
+		},
+		companyInfo: {
+			"name": "",
+			"email": "",
+			"address": "",
+			"introduce": ""
+		},
+		qrInfo: {
+
+		}
 	}),
 	actions: {
 		// 获取用户卡片信息
-		 queryCardInfo(userId = null) {
-			return new Promise(async(resolve, reject) => {
-				let res = await getCardInfo({
+		queryCardInfo(userId = null) {
+			return new Promise(async (resolve, reject) => {
+				let parmas = userId ? {
 					userId
-				})
-				console.log(res, "aaaaaaaaaaaaaaaaa");
+				} : {}
+				let res = await getCardInfo(parmas)
+				console.log(res, "resresresresresres");
+				this.cardInfo = res.data
 				resolve(res.data)
 			})
 		},
-		 queryCompanyInfo(userId = null) {
-			return new Promise(async(resolve, reject) => {
-				let res = await getCompanyInfo({
+		queryCompanyInfo(userId = null) {
+			return new Promise(async (resolve, reject) => {
+				let parmas = userId ? {
 					userId
-				})
-				console.log(res, "bbbbbbbbbbbbbbbbbb");
+				} : {}
+				let res = await getCompanyInfo(parmas)
+				this.companyInfo = res.data
+				resolve(res.data)
+			})
+		},
+		queryCardQrcode(userId = null) {
+			return new Promise(async (resolve, reject) => {
+				let parmas = userId ? {
+					userId
+				} : {
+					userId: this.cardInfo.userId
+				}
+				let res = await getCardQrcode(parmas)
+				this.qrInfo = res.data
 				resolve(res.data)
 			})
 		},
-
 	}
 
 })

+ 314 - 0
utils/poster.js

@@ -0,0 +1,314 @@
+/**
+ * 名片海报生成工具
+ * 使用 Canvas 手动绘制名片
+ */
+
+/**
+ * 生成名片海报
+ * @param {Object} options - 配置选项
+ * @param {Object} options.cardInfo - 名片信息
+ * @returns {Promise<String>} - 生成的图片临时路径
+ */
+export const generateCardPoster = async (cardInfo) => {
+	return new Promise((resolve, reject) => {
+		try {
+			// 创建离屏 canvas
+			const query = uni.createSelectorQuery()
+			query.select('#posterCanvas')
+				.fields({
+					node: true,
+					size: true
+				})
+				.exec(async (res) => {
+					if (!res[0]) {
+						reject(new Error('Canvas 节点未找到'))
+						return
+					}
+
+					const canvas = res[0].node
+					const ctx = canvas.getContext('2d')
+
+					// 设置 canvas 尺寸(高分辨率)
+					const width = 750
+					const height = 800
+					const dpr = uni.getSystemInfoSync().pixelRatio
+
+					canvas.width = width * dpr
+					canvas.height = height * dpr
+					ctx.scale(dpr, dpr)
+
+					// 1. 绘制背景渐变
+					const gradient = ctx.createLinearGradient(0, 0, 0, height)
+					gradient.addColorStop(0, '#4A90E2')
+					gradient.addColorStop(0.5, '#6FB3F2')
+					gradient.addColorStop(1, '#87CEEB')
+					ctx.fillStyle = gradient
+					ctx.fillRect(0, 0, width, height)
+
+					// 2. 绘制白色卡片背景
+					ctx.fillStyle = '#ffffff'
+					drawRoundRect(ctx, 40, 40, 670, 720, 24)
+					ctx.fill()
+
+					// 3. 绘制头像
+					const avatarPath = cardInfo.avatar ||
+						'/static/image/public/avatar-default.png'
+					await drawAvatar(ctx, avatarPath, 375, 160, 160)
+
+					// 4. 绘制姓名和职位
+					ctx.fillStyle = '#202020'
+					ctx.font = 'bold 44px sans-serif'
+					ctx.textAlign = 'center'
+					ctx.fillText(cardInfo.nickName || '用户', width / 2, 380)
+
+					// 职位标签背景
+					ctx.fillStyle = 'rgba(68, 110, 255, 0.1)'
+					drawRoundRect(ctx, (width - 180) / 2, 400, 180, 44, 8)
+					ctx.fill()
+
+					ctx.fillStyle = '#446eff'
+					ctx.font = '26px sans-serif'
+					ctx.textAlign = 'center'
+					ctx.fillText(cardInfo.postName || '职位', width / 2, 430)
+
+					// 5. 绘制公司名称
+					ctx.fillStyle = '#666666'
+					ctx.font = '30px sans-serif'
+					ctx.textAlign = 'center'
+					wrapText(ctx, cardInfo.companyName || '公司名称', width / 2, 480, 500, 36)
+
+					// 6. 绘制联系方式
+					const contactY = 560
+					const contactGap = 65
+					ctx.textAlign = 'left'
+					// 电话
+					if (cardInfo.phonenumber) {
+						await drawIconText(ctx, 'phone', cardInfo.phonenumber, width / 2 - 130,
+							contactY)
+					}
+
+					// 邮箱
+					if (cardInfo.email) {
+						await drawIconText(ctx, 'email', cardInfo.email, width / 2 - 130,
+							contactY + contactGap)
+					}
+
+					// 地址
+					if (cardInfo.companyAddress) {
+						await drawIconText(ctx, 'location', cardInfo.companyAddress, width / 2 -
+							130, contactY + contactGap * 2, )
+					}
+
+					// 导出图片
+					setTimeout(() => {
+						uni.canvasToTempFilePath({
+							canvas: canvas,
+							success: (res) => {
+								console.log('海报生成成功:', res.tempFilePath)
+								resolve(res.tempFilePath)
+							},
+							fail: (err) => {
+								console.error('海报导出失败:', err)
+								reject(err)
+							}
+						})
+					}, 300)
+				})
+		} catch (error) {
+			console.error('海报生成失败:', error)
+			reject(error)
+		}
+	})
+}
+
+/**
+ * 绘制圆角矩形(兼容微信小程序)
+ */
+const drawRoundRect = (ctx, x, y, width, height, radius) => {
+	ctx.beginPath()
+	ctx.moveTo(x + radius, y)
+	ctx.lineTo(x + width - radius, y)
+	ctx.quadraticCurveTo(x + width, y, x + width, y + radius)
+	ctx.lineTo(x + width, y + height - radius)
+	ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height)
+	ctx.lineTo(x + radius, y + height)
+	ctx.quadraticCurveTo(x, y + height, x, y + height - radius)
+	ctx.lineTo(x, y + radius)
+	ctx.quadraticCurveTo(x, y, x + radius, y)
+	ctx.closePath()
+}
+
+/**
+ * 绘制头像
+ */
+const drawAvatar = async (ctx, avatarUrl, x, y, size) => {
+	return new Promise((resolve) => {
+		if (avatarUrl.startsWith('http')) {
+			// 网络图片
+			downloadAndDraw(ctx, avatarUrl, x, y, size, resolve)
+		} else {
+			drawCircularImage(ctx, avatarUrl, x, y, size, resolve)
+		}
+	})
+}
+
+/**
+ * 下载并绘制网络图片
+ */
+const downloadAndDraw = async (ctx, url, x, y, size, resolve) => {
+	try {
+		const res = await uni.downloadFile({
+			url: url
+		})
+
+		if (res.statusCode === 200) {
+			drawCircularImage(ctx, res.tempFilePath, x, y, size, resolve)
+		} else {
+			console.error('图片下载失败:', res.statusCode)
+			resolve()
+		}
+	} catch (error) {
+		console.error('下载图片异常:', error)
+		resolve()
+	}
+}
+
+/**
+ * 绘制圆形图片
+ */
+const drawCircularImage = (ctx, imageUrl, x, y, size, callback) => {
+	const image = uni.createImage()
+	image.src = imageUrl
+	image.onload = () => {
+		ctx.save()
+		ctx.beginPath()
+		ctx.arc(x, y, size / 2, 0, 2 * Math.PI)
+		ctx.clip()
+		ctx.drawImage(image, x - size / 2, y - size / 2, size, size)
+		ctx.restore()
+		if (callback) callback()
+	}
+	image.onerror = (err) => {
+		console.error('图片加载失败:', err)
+		if (callback) callback()
+	}
+}
+
+/**
+ * 绘制图标和文字
+ */
+const drawIconText = async (ctx, type, text, x, y, maxWidth = 350) => {
+	return new Promise((resolve) => {
+		const iconSize = 32
+		const iconY = y - iconSize / 2
+
+		// 图标颜色
+		const iconColors = {
+			phone: '#4080FF',
+			email: '#4080FF',
+			location: '#4080FF'
+		}
+
+		ctx.fillStyle = iconColors[type] || '#4080FF'
+
+		// 绘制图标
+		const icons = {
+			phone: '',
+			email: '',
+			location: ''
+		}
+
+		ctx.font = `${iconSize}px sans-serif`
+		ctx.textAlign = 'center'
+		ctx.fillText(icons[type] || '●', x - maxWidth / 2 + 30, iconY + iconSize / 2 + 5)
+
+		// 绘制文字
+		ctx.fillStyle = '#666666'
+		ctx.font = '26px sans-serif'
+		ctx.textAlign = 'left'
+
+		// 文字截断处理
+		let displayText = text
+		if (text.length > 35) {
+			displayText = text.substring(0, 33) + '...'
+		}
+		ctx.fillText(displayText, x - maxWidth / 2 + 70, y + 8)
+
+		setTimeout(resolve, 50)
+	})
+}
+
+/**
+ * 绘制换行文字
+ */
+const wrapText = (ctx, text, x, y, maxWidth, lineHeight) => {
+	if (!text) return
+
+	const words = text.split('')
+	let line = ''
+	let currentY = y
+
+	for (let i = 0; i < words.length; i++) {
+		const testLine = line + words[i]
+		const metrics = ctx.measureText(testLine)
+
+		if (metrics.width > maxWidth && i > 0) {
+			ctx.fillText(line, x, currentY)
+			line = words[i]
+			currentY += lineHeight
+		} else {
+			line = testLine
+		}
+	}
+	ctx.fillText(line, x, currentY)
+}
+
+/**
+ * 保存海报到相册
+ */
+export const savePosterToAlbum = async (tempFilePath) => {
+	return new Promise((resolve, reject) => {
+		uni.authorize({
+			scope: 'scope.writePhotosAlbum',
+			success: async () => {
+				uni.saveImageToPhotosAlbum({
+					filePath: tempFilePath,
+					success: () => resolve(),
+					fail: (err) => reject(err)
+				})
+			},
+			fail: (err) => {
+				uni.showModal({
+					title: '提示',
+					content: '需要授权才能保存到相册',
+					success: (res) => {
+						if (res.confirm) {
+							uni.openSetting({
+								success: (settingRes) => {
+									if (settingRes.authSetting[
+											'scope.writePhotosAlbum'
+											]) {
+										savePosterToAlbum(
+											tempFilePath).then(
+											resolve).catch(
+											reject)
+									} else {
+										reject(new Error('用户拒绝授权'))
+									}
+								},
+								fail: (err) => reject(err)
+							})
+						} else {
+							reject(new Error('用户取消授权'))
+						}
+					}
+				})
+			}
+		})
+	})
+}
+
+export default {
+	generateCardPoster,
+	savePosterToAlbum
+}