This commit is contained in:
zhangjianjun 2026-08-20 15:05:11 +08:00
parent 7ecb4ef976
commit 8e1f3fbcc8
12 changed files with 554 additions and 59 deletions

View File

@ -62,6 +62,7 @@
"@tarojs/shared": "4.2.0", "@tarojs/shared": "4.2.0",
"@tarojs/taro": "4.2.0", "@tarojs/taro": "4.2.0",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"qrcode": "^1.5.4",
"react": "^18.0.0", "react": "^18.0.0",
"react-dom": "^18.0.0" "react-dom": "^18.0.0"
}, },
@ -76,6 +77,7 @@
"@tarojs/vite-runner": "4.2.0", "@tarojs/vite-runner": "4.2.0",
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "^4.2.2",
"@types/minimatch": "^5", "@types/minimatch": "^5",
"@types/qrcode": "^1.5.6",
"@types/react": "^18.0.0", "@types/react": "^18.0.0",
"@vitejs/plugin-react": "^4.3.0", "@vitejs/plugin-react": "^4.3.0",
"babel-preset-taro": "4.2.0", "babel-preset-taro": "4.2.0",

View File

@ -59,6 +59,9 @@ importers:
crypto-js: crypto-js:
specifier: ^4.2.0 specifier: ^4.2.0
version: 4.2.0 version: 4.2.0
qrcode:
specifier: ^1.5.4
version: 1.5.4
react: react:
specifier: ^18.0.0 specifier: ^18.0.0
version: 18.3.1 version: 18.3.1
@ -96,6 +99,9 @@ importers:
'@types/minimatch': '@types/minimatch':
specifier: ^5 specifier: ^5
version: 5.1.2 version: 5.1.2
'@types/qrcode':
specifier: ^1.5.6
version: 1.5.6
'@types/react': '@types/react':
specifier: ^18.0.0 specifier: ^18.0.0
version: 18.3.31 version: 18.3.31
@ -2126,6 +2132,9 @@ packages:
'@types/prop-types@15.7.15': '@types/prop-types@15.7.15':
resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
'@types/qrcode@1.5.6':
resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==}
'@types/react@18.3.31': '@types/react@18.3.31':
resolution: {integrity: sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==} resolution: {integrity: sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==}
@ -2552,6 +2561,10 @@ packages:
camel-case@4.1.2: camel-case@4.1.2:
resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==}
camelcase@5.3.1:
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
engines: {node: '>=6'}
caniuse-lite@1.0.30001797: caniuse-lite@1.0.30001797:
resolution: {integrity: sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==} resolution: {integrity: sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==}
@ -2628,6 +2641,9 @@ packages:
resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cliui@6.0.0:
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
cliui@7.0.4: cliui@7.0.4:
resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
@ -2802,6 +2818,10 @@ packages:
supports-color: supports-color:
optional: true optional: true
decamelize@1.2.0:
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
engines: {node: '>=0.10.0'}
decode-uri-component@0.2.2: decode-uri-component@0.2.2:
resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==}
engines: {node: '>=0.10'} engines: {node: '>=0.10'}
@ -2875,6 +2895,9 @@ packages:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
dijkstrajs@1.0.3:
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
dingtalk-jsapi@2.15.6: dingtalk-jsapi@2.15.6:
resolution: {integrity: sha512-804mFz2AFV/H9ysmo7dLqMjSGOQgREsgQIuep+Xg+yNQeQtnUOYntElEzlB798Sj/691e4mMKz9mtQ7v9qdjuA==} resolution: {integrity: sha512-804mFz2AFV/H9ysmo7dLqMjSGOQgREsgQIuep+Xg+yNQeQtnUOYntElEzlB798Sj/691e4mMKz9mtQ7v9qdjuA==}
@ -3221,6 +3244,10 @@ packages:
resolution: {integrity: sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==} resolution: {integrity: sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==}
engines: {node: '>=14.16'} engines: {node: '>=14.16'}
find-up@4.1.0:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
engines: {node: '>=8'}
find-up@5.0.0: find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -3899,6 +3926,10 @@ packages:
resolution: {integrity: sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==} resolution: {integrity: sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==}
engines: {node: '>= 12.13.0'} engines: {node: '>= 12.13.0'}
locate-path@5.0.0:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'}
locate-path@6.0.0: locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -4206,6 +4237,10 @@ packages:
resolution: {integrity: sha512-zL7VE4JVS2IFSkR2GQKDSPEVxkoH43/p7oEnwpdCndKYJO0HVeRB7fA8TJwuLOTBREtK0ea8eHaxdwcpob5dmg==} resolution: {integrity: sha512-zL7VE4JVS2IFSkR2GQKDSPEVxkoH43/p7oEnwpdCndKYJO0HVeRB7fA8TJwuLOTBREtK0ea8eHaxdwcpob5dmg==}
engines: {node: '>=4'} engines: {node: '>=4'}
p-limit@2.3.0:
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
engines: {node: '>=6'}
p-limit@3.1.0: p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -4214,6 +4249,10 @@ packages:
resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
p-locate@4.1.0:
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
engines: {node: '>=8'}
p-locate@5.0.0: p-locate@5.0.0:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -4226,6 +4265,10 @@ packages:
resolution: {integrity: sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==} resolution: {integrity: sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==}
engines: {node: '>=4'} engines: {node: '>=4'}
p-try@2.2.0:
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
engines: {node: '>=6'}
package-json@6.5.0: package-json@6.5.0:
resolution: {integrity: sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==} resolution: {integrity: sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -4336,6 +4379,10 @@ packages:
platform@1.3.6: platform@1.3.6:
resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==} resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==}
pngjs@5.0.0:
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
engines: {node: '>=10.13.0'}
possible-typed-array-names@1.1.0: possible-typed-array-names@1.1.0:
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -4475,6 +4522,11 @@ packages:
resolution: {integrity: sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==} resolution: {integrity: sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==}
engines: {node: '>=20'} engines: {node: '>=20'}
qrcode@1.5.4:
resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
engines: {node: '>=10.13.0'}
hasBin: true
query-string@5.1.1: query-string@5.1.1:
resolution: {integrity: sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==} resolution: {integrity: sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -4589,6 +4641,9 @@ packages:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
require-main-filename@2.0.0:
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
resolve-from@4.0.0: resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -4721,6 +4776,9 @@ packages:
resolution: {integrity: sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw==} resolution: {integrity: sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw==}
engines: {node: '>=10'} engines: {node: '>=10'}
set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
set-function-length@1.2.2: set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -5303,6 +5361,9 @@ packages:
resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
which-module@2.0.1:
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
which-typed-array@1.1.22: which-typed-array@1.1.22:
resolution: {integrity: sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==} resolution: {integrity: sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -5355,6 +5416,9 @@ packages:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'} engines: {node: '>=0.4'}
y18n@4.0.3:
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
y18n@5.0.8: y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -5367,6 +5431,10 @@ packages:
engines: {node: '>= 14.6'} engines: {node: '>= 14.6'}
hasBin: true hasBin: true
yargs-parser@18.1.3:
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
engines: {node: '>=6'}
yargs-parser@20.2.9: yargs-parser@20.2.9:
resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -5375,6 +5443,10 @@ packages:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'} engines: {node: '>=12'}
yargs@15.4.1:
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
engines: {node: '>=8'}
yargs@16.2.0: yargs@16.2.0:
resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -7480,6 +7552,10 @@ snapshots:
'@types/prop-types@15.7.15': {} '@types/prop-types@15.7.15': {}
'@types/qrcode@1.5.6':
dependencies:
'@types/node': 25.9.2
'@types/react@18.3.31': '@types/react@18.3.31':
dependencies: dependencies:
'@types/prop-types': 15.7.15 '@types/prop-types': 15.7.15
@ -8038,6 +8114,8 @@ snapshots:
pascal-case: 3.1.2 pascal-case: 3.1.2
tslib: 2.8.1 tslib: 2.8.1
camelcase@5.3.1: {}
caniuse-lite@1.0.30001797: {} caniuse-lite@1.0.30001797: {}
capital-case@1.0.4: capital-case@1.0.4:
@ -8140,6 +8218,12 @@ snapshots:
cli-width@3.0.0: {} cli-width@3.0.0: {}
cliui@6.0.0:
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 6.2.0
cliui@7.0.4: cliui@7.0.4:
dependencies: dependencies:
string-width: 4.2.3 string-width: 4.2.3
@ -8304,6 +8388,8 @@ snapshots:
dependencies: dependencies:
ms: 2.1.3 ms: 2.1.3
decamelize@1.2.0: {}
decode-uri-component@0.2.2: {} decode-uri-component@0.2.2: {}
decode-uri-component@0.4.1: {} decode-uri-component@0.4.1: {}
@ -8381,6 +8467,8 @@ snapshots:
detect-libc@2.1.2: detect-libc@2.1.2:
optional: true optional: true
dijkstrajs@1.0.3: {}
dingtalk-jsapi@2.15.6: dingtalk-jsapi@2.15.6:
dependencies: dependencies:
promise-polyfill: 7.1.2 promise-polyfill: 7.1.2
@ -8919,6 +9007,11 @@ snapshots:
filter-obj@5.1.0: {} filter-obj@5.1.0: {}
find-up@4.1.0:
dependencies:
locate-path: 5.0.0
path-exists: 4.0.0
find-up@5.0.0: find-up@5.0.0:
dependencies: dependencies:
locate-path: 6.0.0 locate-path: 6.0.0
@ -9633,6 +9726,10 @@ snapshots:
loader-utils@3.3.1: {} loader-utils@3.3.1: {}
locate-path@5.0.0:
dependencies:
p-locate: 4.1.0
locate-path@6.0.0: locate-path@6.0.0:
dependencies: dependencies:
p-locate: 5.0.0 p-locate: 5.0.0
@ -9916,6 +10013,10 @@ snapshots:
p-is-promise@1.1.0: {} p-is-promise@1.1.0: {}
p-limit@2.3.0:
dependencies:
p-try: 2.2.0
p-limit@3.1.0: p-limit@3.1.0:
dependencies: dependencies:
yocto-queue: 0.1.0 yocto-queue: 0.1.0
@ -9924,6 +10025,10 @@ snapshots:
dependencies: dependencies:
yocto-queue: 1.2.2 yocto-queue: 1.2.2
p-locate@4.1.0:
dependencies:
p-limit: 2.3.0
p-locate@5.0.0: p-locate@5.0.0:
dependencies: dependencies:
p-limit: 3.1.0 p-limit: 3.1.0
@ -9936,6 +10041,8 @@ snapshots:
dependencies: dependencies:
p-finally: 1.0.0 p-finally: 1.0.0
p-try@2.2.0: {}
package-json@6.5.0: package-json@6.5.0:
dependencies: dependencies:
got: 9.6.0 got: 9.6.0
@ -10028,6 +10135,8 @@ snapshots:
platform@1.3.6: {} platform@1.3.6: {}
pngjs@5.0.0: {}
possible-typed-array-names@1.1.0: {} possible-typed-array-names@1.1.0: {}
postcss-css-variables@0.19.0(postcss@8.5.15): postcss-css-variables@0.19.0(postcss@8.5.15):
@ -10149,6 +10258,12 @@ snapshots:
dependencies: dependencies:
hookified: 2.2.0 hookified: 2.2.0
qrcode@1.5.4:
dependencies:
dijkstrajs: 1.0.3
pngjs: 5.0.0
yargs: 15.4.1
query-string@5.1.1: query-string@5.1.1:
dependencies: dependencies:
decode-uri-component: 0.2.2 decode-uri-component: 0.2.2
@ -10281,6 +10396,8 @@ snapshots:
require-from-string@2.0.2: {} require-from-string@2.0.2: {}
require-main-filename@2.0.0: {}
resolve-from@4.0.0: {} resolve-from@4.0.0: {}
resolve-from@5.0.0: {} resolve-from@5.0.0: {}
@ -10457,6 +10574,8 @@ snapshots:
seroval@1.5.4: {} seroval@1.5.4: {}
set-blocking@2.0.0: {}
set-function-length@1.2.2: set-function-length@1.2.2:
dependencies: dependencies:
define-data-property: 1.1.4 define-data-property: 1.1.4
@ -11106,6 +11225,8 @@ snapshots:
is-weakmap: 2.0.2 is-weakmap: 2.0.2
is-weakset: 2.0.4 is-weakset: 2.0.4
which-module@2.0.1: {}
which-typed-array@1.1.22: which-typed-array@1.1.22:
dependencies: dependencies:
available-typed-arrays: 1.0.7 available-typed-arrays: 1.0.7
@ -11166,16 +11287,37 @@ snapshots:
xtend@4.0.2: {} xtend@4.0.2: {}
y18n@4.0.3: {}
y18n@5.0.8: {} y18n@5.0.8: {}
yallist@3.1.1: {} yallist@3.1.1: {}
yaml@2.9.0: {} yaml@2.9.0: {}
yargs-parser@18.1.3:
dependencies:
camelcase: 5.3.1
decamelize: 1.2.0
yargs-parser@20.2.9: {} yargs-parser@20.2.9: {}
yargs-parser@21.1.1: {} yargs-parser@21.1.1: {}
yargs@15.4.1:
dependencies:
cliui: 6.0.0
decamelize: 1.2.0
find-up: 4.1.0
get-caller-file: 2.0.5
require-directory: 2.1.1
require-main-filename: 2.0.0
set-blocking: 2.0.0
string-width: 4.2.3
which-module: 2.0.1
y18n: 4.0.3
yargs-parser: 18.1.3
yargs@16.2.0: yargs@16.2.0:
dependencies: dependencies:
cliui: 7.0.4 cliui: 7.0.4

View File

@ -1,5 +1,28 @@
import { describe, expect, it } from 'vitest' import { describe, expect, it } from 'vitest'
import { hasServiceContact, pickServiceContact } from './contact' import { getWeixinContactAction, hasServiceContact, pickServiceContact, selectServiceWeixinLink } from './contact'
describe('selectServiceWeixinLink', () => {
it('returns a single configured link unchanged', () => {
expect(selectServiceWeixinLink('https://u.wechat.com/example')).toBe('https://u.wechat.com/example')
})
it('trims candidates and ignores empty entries', () => {
expect(selectServiceWeixinLink(
' https://work.weixin.qq.com/example-a, , https://u.wechat.com/example-b, ',
() => 0.5,
)).toBe('https://u.wechat.com/example-b')
})
})
describe('getWeixinContactAction', () => {
it('shows a QR code for personal wechat u.wechat.com links', () => {
expect(getWeixinContactAction('https://u.wechat.com/ELkCkT_hVVAz8wNghBA3hMc?s=2')).toBe('qrcode')
})
it('copies the link for work weixin customer-service urls', () => {
expect(getWeixinContactAction('https://work.weixin.qq.com/ca/cawcde9d9bc540aebe')).toBe('copy')
})
})
describe('pickServiceContact', () => { describe('pickServiceContact', () => {
it('reads mobile online phone and weixin from client config', () => { it('reads mobile online phone and weixin from client config', () => {

View File

@ -16,6 +16,32 @@ export function pickServiceContact (config?: Record<string, any> | null): Servic
} }
} }
export function selectServiceWeixinLink (value: unknown, random: () => number = Math.random) {
if (typeof value !== 'string') return ''
const links = value
.split(',')
.map((link) => link.trim())
.filter(Boolean)
if (links.length === 0) return ''
return links[Math.floor(random() * links.length)] || ''
}
export function getWeixinContactAction (url?: string): 'qrcode' | 'copy' {
const value = String(url || '').trim()
if (!value) return 'copy'
try {
const host = new URL(value).hostname.toLowerCase()
if (host === 'u.wechat.com' || host.endsWith('.u.wechat.com')) return 'qrcode'
} catch {
if (/u\.wechat\.com/i.test(value)) return 'qrcode'
}
return 'copy'
}
export function hasServiceContact (config?: Record<string, any> | null) { export function hasServiceContact (config?: Record<string, any> | null) {
const contact = pickServiceContact(config) const contact = pickServiceContact(config)
return Boolean(contact.online || contact.phone || contact.weixin) return Boolean(contact.online || contact.phone || contact.weixin)

15
src/lib/qrImage.test.ts Normal file
View File

@ -0,0 +1,15 @@
import { describe, expect, it } from 'vitest'
import { buildPublicQrImageUrl } from './qrImage'
describe('buildPublicQrImageUrl', () => {
it('builds a domestic public qr image url with the wechat link encoded', () => {
expect(buildPublicQrImageUrl('https://u.wechat.com/ELkCkT_hVVAz8wNghBA3hMc?s=2')).toBe(
'https://api.qrtool.cn/?text=https%3A%2F%2Fu.wechat.com%2FELkCkT_hVVAz8wNghBA3hMc%3Fs%3D2&size=400&margin=16&level=H',
)
})
it('returns empty string for blank text', () => {
expect(buildPublicQrImageUrl('')).toBe('')
expect(buildPublicQrImageUrl(' ')).toBe('')
})
})

5
src/lib/qrImage.ts Normal file
View File

@ -0,0 +1,5 @@
export function buildPublicQrImageUrl (text: string) {
const value = String(text || '').trim()
if (!value) return ''
return `https://api.qrtool.cn/?text=${encodeURIComponent(value)}&size=400&margin=16&level=H`
}

10
src/lib/qrPng.test.ts Normal file
View File

@ -0,0 +1,10 @@
import { describe, expect, it } from 'vitest'
import { bytesToBase64, encodeQrPng } from './qrPng'
describe('encodeQrPng', () => {
it('encodes a wechat personal link into a png buffer', () => {
const png = encodeQrPng('https://u.wechat.com/ELkCkT_hVVAz8wNghBA3hMc?s=2')
expect(Array.from(png.slice(0, 8))).toEqual([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])
expect(bytesToBase64(png).startsWith('iVBOR')).toBe(true)
})
})

129
src/lib/qrPng.ts Normal file
View File

@ -0,0 +1,129 @@
import QRCode from 'qrcode'
const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]
const CRC_TABLE = makeCrcTable()
const BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
export function encodeQrPng (text: string, scale = 6, margin = 4) {
const qr = QRCode.create(text, { errorCorrectionLevel: 'M' })
const moduleCount = qr.modules.size
const dim = (moduleCount + margin * 2) * scale
const raw = new Uint8Array((dim * 3 + 1) * dim)
let offset = 0
for (let y = 0; y < dim; y += 1) {
raw[offset] = 0
offset += 1
const moduleY = Math.floor(y / scale) - margin
for (let x = 0; x < dim; x += 1) {
const moduleX = Math.floor(x / scale) - margin
const dark = moduleX >= 0 && moduleX < moduleCount && moduleY >= 0 && moduleY < moduleCount
? Boolean(qr.modules.get(moduleX, moduleY))
: false
const value = dark ? 0 : 255
raw[offset] = value
raw[offset + 1] = value
raw[offset + 2] = value
offset += 3
}
}
const ihdr = [
...u32(dim),
...u32(dim),
8,
2,
0,
0,
0,
]
const idat = zlibStore(raw)
const bytes = new Uint8Array([
...PNG_SIGNATURE,
...chunk('IHDR', ihdr),
...chunk('IDAT', idat),
...chunk('IEND', []),
])
return bytes
}
export function bytesToBase64 (bytes: Uint8Array) {
let result = ''
for (let i = 0; i < bytes.length; i += 3) {
const a = bytes[i]
const b = i + 1 < bytes.length ? bytes[i + 1] : 0
const c = i + 2 < bytes.length ? bytes[i + 2] : 0
const triple = (a << 16) | (b << 8) | c
result += BASE64_CHARS[(triple >> 18) & 63]
result += BASE64_CHARS[(triple >> 12) & 63]
result += i + 1 < bytes.length ? BASE64_CHARS[(triple >> 6) & 63] : '='
result += i + 2 < bytes.length ? BASE64_CHARS[triple & 63] : '='
}
return result
}
function zlibStore (raw: Uint8Array) {
const blocks: number[] = [0x78, 0x01]
const max = 65535
for (let i = 0; i < raw.length; i += max) {
const end = Math.min(i + max, raw.length)
const chunkData = raw.subarray(i, end)
const last = end === raw.length ? 1 : 0
const len = chunkData.length
blocks.push(last, len & 0xff, (len >> 8) & 0xff, (~len) & 0xff, ((~len) >> 8) & 0xff)
for (let j = 0; j < chunkData.length; j += 1) {
blocks.push(chunkData[j])
}
}
blocks.push(...u32(adler32(raw)))
return blocks
}
function chunk (type: string, data: number[]) {
const typeBytes = [type.charCodeAt(0), type.charCodeAt(1), type.charCodeAt(2), type.charCodeAt(3)]
return [
...u32(data.length),
...typeBytes,
...data,
...u32(crc32([...typeBytes, ...data])),
]
}
function u32 (value: number) {
return [
(value >>> 24) & 0xff,
(value >>> 16) & 0xff,
(value >>> 8) & 0xff,
value & 0xff,
]
}
function crc32 (bytes: number[]) {
let crc = 0xffffffff
for (let i = 0; i < bytes.length; i += 1) {
crc = CRC_TABLE[(crc ^ bytes[i]) & 0xff] ^ (crc >>> 8)
}
return (crc ^ 0xffffffff) >>> 0
}
function adler32 (bytes: Uint8Array) {
let a = 1
let b = 0
for (let i = 0; i < bytes.length; i += 1) {
a = (a + bytes[i]) % 65521
b = (b + a) % 65521
}
return ((b << 16) | a) >>> 0
}
function makeCrcTable () {
const table = new Uint32Array(256)
for (let n = 0; n < 256; n += 1) {
let c = n
for (let k = 0; k < 8; k += 1) {
c = (c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1)
}
table[n] = c >>> 0
}
return table
}

View File

@ -1,7 +1,9 @@
import { Image, Text, View } from '@tarojs/components' import { Image, Text, View } from '@tarojs/components'
import Taro from '@tarojs/taro' import Taro from '@tarojs/taro'
import { CORP_ASSET_BASE } from '@/lib/cdn' import { useEffect, useState } from 'react'
import type { ServiceContact } from '@/lib/contact' import { CORP_ASSET_BASE, MAP_ASSET_BASE } from '@/lib/cdn'
import { getWeixinContactAction, selectServiceWeixinLink, type ServiceContact } from '@/lib/contact'
import WeixinQrImage from './weixin-qr'
type Props = { type Props = {
open: boolean open: boolean
@ -32,11 +34,31 @@ function ContactItem ({
} }
export default function ContactModal ({ open, contact, onCancel }: Props) { export default function ContactModal ({ open, contact, onCancel }: Props) {
const [weixin, setWeixin] = useState('')
const [showQr, setShowQr] = useState(false)
useEffect(() => {
if (!open) {
setShowQr(false)
return
}
setWeixin(selectServiceWeixinLink(contact.weixin))
}, [open, contact.weixin])
if (!open) return null if (!open) return null
const copyLink = async (url: string, title: string) => { const copyLink = async (url: string) => {
await Taro.setClipboardData({ data: url }) await Taro.setClipboardData({ data: url })
Taro.showToast({ title: `${title}链接已复制`, icon: 'none' }) Taro.showToast({ title: '微信客服链接已复制', icon: 'none' })
}
const handleWeixin = () => {
if (!weixin) return
if (getWeixinContactAction(weixin) === 'qrcode') {
setShowQr((current) => !current)
return
}
void copyLink(weixin)
} }
const handlePhone = () => { const handlePhone = () => {
@ -44,48 +66,52 @@ export default function ContactModal ({ open, contact, onCancel }: Props) {
Taro.makePhoneCall({ phoneNumber: contact.phone.replace(/-/g, '') }) Taro.makePhoneCall({ phoneNumber: contact.phone.replace(/-/g, '') })
} }
const hasAny = Boolean(contact.online || contact.phone || contact.weixin) const hasAny = Boolean(weixin || contact.phone)
return ( return (
<View className='contact-mask' onClick={onCancel}> <View className='contact-mask' onClick={onCancel}>
<View className='contact-dialog' onClick={(event) => event.stopPropagation()}> <View className='contact-shell' onClick={(event) => event.stopPropagation()}>
<Text className='contact-dialog__title'></Text> <View className='contact-dialog'>
<Text className='contact-dialog__hours'> 9:00-18:00</Text> <View className='contact-dialog__header'>
<Text className='contact-dialog__title'></Text>
</View>
<View className='contact-dialog__body'>
<Text className='contact-dialog__hours'> 9:00-18:00</Text>
{contact.online ? ( {weixin ? (
<ContactItem <>
icon={`${CORP_ASSET_BASE}/images-pc/icon-contactus-service.png`} <ContactItem
title='在线客服' icon={`${CORP_ASSET_BASE}/images-pc/icon-service-weixin.png`}
desc='竭诚为您服务~' title='微信客服'
onClick={() => void copyLink(contact.online, '在线客服')} desc='一对一为您服务~'
/> onClick={handleWeixin}
) : null} />
{showQr && getWeixinContactAction(weixin) === 'qrcode' ? (
<WeixinQrImage url={weixin} />
) : null}
</>
) : null}
{contact.weixin ? ( {contact.phone ? (
<ContactItem <ContactItem
icon={`${CORP_ASSET_BASE}/images-pc/icon-service-weixin.png`} icon={`${CORP_ASSET_BASE}/images-pc/icon-contactus-phone.png`}
title='微信客服' title='电话客服'
desc='一对一为您服务~' desc={contact.phone}
onClick={() => void copyLink(contact.weixin, '微信客服')} onClick={handlePhone}
/> />
) : null} ) : null}
{contact.phone ? ( {!hasAny ? (
<ContactItem <Text className='contact-dialog__empty'></Text>
icon={`${CORP_ASSET_BASE}/images-pc/icon-contactus-phone.png`} ) : null}
title='电话客服' </View>
desc={contact.phone}
onClick={handlePhone}
/>
) : null}
{!hasAny ? (
<Text className='contact-dialog__empty'></Text>
) : null}
<View className='contact-dialog__close' onClick={onCancel}>
<Text></Text>
</View> </View>
<Image
className='contact-close'
src={`${MAP_ASSET_BASE}/service-modal-close.png`}
onClick={onCancel}
/>
</View> </View>
</View> </View>
) )

View File

@ -60,6 +60,13 @@
color: #c9cdd4; color: #c9cdd4;
} }
.home-search__clear {
width: 36px;
height: 36px;
margin: 0 12px;
flex-shrink: 0;
}
.home-search__button { .home-search__button {
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
@ -498,29 +505,53 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: rgba(0, 0, 0, 0.65); background: rgba(0, 0, 0, 0.8);
}
.contact-shell {
display: flex;
flex-direction: column;
align-items: center;
} }
.contact-dialog { .contact-dialog {
box-sizing: border-box; box-sizing: border-box;
width: 576px; width: 576px;
padding: 32px 28px 28px; overflow: hidden;
border-radius: 32px; border-radius: 40px;
background: #fff; background: #f9fbff;
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.18);
}
.contact-dialog__header {
display: flex;
align-items: center;
justify-content: center;
height: 120px;
padding: 0 28px;
background:
linear-gradient(180deg, rgba(249, 251, 255, 0) 0%, #f9fbff 100%),
linear-gradient(90deg, #c1ebff 0%, #bdf3ff 100%);
} }
.contact-dialog__title { .contact-dialog__title {
display: block;
color: #222; color: #222;
font-size: 34px; font-size: 34px;
font-weight: 600; font-weight: 700;
line-height: 48px; line-height: 44px;
text-align: center; text-align: center;
} }
.contact-dialog__body {
display: flex;
flex-direction: column;
gap: 28px;
padding: 0 28px 28px;
}
.contact-dialog__hours { .contact-dialog__hours {
display: block; display: block;
margin: 16px 0 28px; margin-bottom: 12px;
color: #999; color: #999;
font-size: 24px; font-size: 24px;
line-height: 32px; line-height: 32px;
@ -538,12 +569,13 @@
.contact-item { .contact-item {
display: flex; display: flex;
align-items: center; align-items: center;
height: 124px; box-sizing: border-box;
margin-bottom: 20px; width: 520px;
min-height: 124px;
padding: 0 24px; padding: 0 24px;
border-radius: 16px; border-radius: 16px;
background: #fff; background: #fff;
box-shadow: 0 0 20px rgba(147, 153, 161, 0.12); box-shadow: 0 0 20px rgba(147, 153, 161, 0.1);
} }
.contact-item__icon { .contact-item__icon {
@ -566,19 +598,40 @@
} }
.contact-item__desc { .contact-item__desc {
margin-top: 4px;
color: #999; color: #999;
font-size: 24px; font-size: 24px;
font-weight: 400;
line-height: 32px; line-height: 32px;
} }
.contact-dialog__close { .contact-close {
margin-top: 12px; width: 60px;
height: 80px; height: 60px;
margin-top: 40px;
}
.contact-qrcode {
display: flex;
box-sizing: border-box;
flex-direction: column;
align-items: center;
width: 520px;
padding: 24px 24px 28px;
border-radius: 16px; border-radius: 16px;
background: #f5f6f8; background: #fff;
color: #666; box-shadow: 0 0 20px rgba(147, 153, 161, 0.1);
font-size: 30px; }
line-height: 80px;
.contact-qrcode__image {
width: 320px;
height: 320px;
background: #fff;
}
.contact-qrcode__hint {
margin-top: 16px;
color: #999;
font-size: 20px;
line-height: 28px;
text-align: center; text-align: center;
} }

View File

@ -111,6 +111,13 @@ export default function Index () {
value={inputPhone} value={inputPhone}
onInput={(event) => setInputPhone(event.detail.value.replace(/\D/g, ''))} onInput={(event) => setInputPhone(event.detail.value.replace(/\D/g, ''))}
/> />
{inputPhone ? (
<Image
className='home-search__clear'
src={LOCAL_IMAGES.clear}
onClick={() => setInputPhone('')}
/>
) : null}
</View> </View>
<Button <Button
className='home-search__button' className='home-search__button'

View File

@ -0,0 +1,57 @@
import { Image, Text, View } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { useEffect, useState } from 'react'
import { buildPublicQrImageUrl } from '@/lib/qrImage'
export default function WeixinQrImage ({ url }: { url: string }) {
const [src, setSrc] = useState('')
useEffect(() => {
let cancelled = false
setSrc('')
const remote = buildPublicQrImageUrl(url)
if (!remote) return
if (Taro.getEnv() !== Taro.ENV_TYPE.WEAPP) {
setSrc(remote)
return
}
Taro.downloadFile({
url: remote,
success (res) {
if (cancelled) return
if (res.statusCode === 200 && res.tempFilePath) {
setSrc(res.tempFilePath)
return
}
Taro.showToast({ title: '二维码加载失败', icon: 'none' })
},
fail () {
if (!cancelled) {
Taro.showToast({ title: '二维码加载失败', icon: 'none' })
}
},
})
return () => {
cancelled = true
}
}, [url])
return (
<View className='contact-qrcode'>
{src ? (
<Image
className='contact-qrcode__image'
src={src}
showMenuByLongpress
onLongPress={() => Taro.previewImage({ current: src, urls: [src] })}
/>
) : (
<View className='contact-qrcode__image' />
)}
<Text className='contact-qrcode__hint'>使</Text>
</View>
)
}