@@ -16,6 +16,7 @@
|
||||
"dev:test:mp-weixin": "uni -p mp-weixin --mode test",
|
||||
"build:test:mp-weixin": "uni build -p mp-weixin --mode test",
|
||||
"build:mp-weixin": "uni build -p mp-weixin --mode production",
|
||||
"test:unit": "vitest run",
|
||||
"type-check": "vue-tsc --noEmit",
|
||||
"lint": "eslint \"src/**/*.{ts,vue}\""
|
||||
},
|
||||
@@ -39,9 +40,11 @@
|
||||
"@vue/runtime-core": "3.4.21",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-plugin-vue": "9.30.0",
|
||||
"sass": "1.99.0",
|
||||
"sass-embedded": "1.99.0",
|
||||
"typescript": "5.3.3",
|
||||
"vite": "5.2.8",
|
||||
"vitest": "1.6.1",
|
||||
"vue-eslint-parser": "9.4.3",
|
||||
"vue-tsc": "1.8.27"
|
||||
}
|
||||
|
||||
402
pnpm-lock.yaml
generated
402
pnpm-lock.yaml
generated
@@ -60,6 +60,9 @@ importers:
|
||||
eslint-plugin-vue:
|
||||
specifier: 9.30.0
|
||||
version: 9.30.0(eslint@8.57.1)
|
||||
sass:
|
||||
specifier: 1.99.0
|
||||
version: 1.99.0
|
||||
sass-embedded:
|
||||
specifier: 1.99.0
|
||||
version: 1.99.0
|
||||
@@ -69,6 +72,9 @@ importers:
|
||||
vite:
|
||||
specifier: 5.2.8
|
||||
version: 5.2.8(@types/node@25.9.1)(sass@1.99.0)(terser@5.47.1)
|
||||
vitest:
|
||||
specifier: 1.6.1
|
||||
version: 1.6.1(@types/node@25.9.1)(jsdom@16.7.0)(sass@1.99.0)(terser@5.47.1)
|
||||
vue-eslint-parser:
|
||||
specifier: 9.4.3
|
||||
version: 9.4.3(eslint@8.57.1)
|
||||
@@ -1046,6 +1052,10 @@ packages:
|
||||
node-notifier:
|
||||
optional: true
|
||||
|
||||
'@jest/schemas@29.6.3':
|
||||
resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
|
||||
'@jest/source-map@27.5.1':
|
||||
resolution: {integrity: sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==}
|
||||
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
|
||||
@@ -1497,6 +1507,9 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@sinclair/typebox@0.27.10':
|
||||
resolution: {integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==}
|
||||
|
||||
'@sinonjs/commons@1.8.6':
|
||||
resolution: {integrity: sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==}
|
||||
|
||||
@@ -1652,6 +1665,21 @@ packages:
|
||||
vite: ^5.0.0 || ^6.0.0
|
||||
vue: ^3.2.25
|
||||
|
||||
'@vitest/expect@1.6.1':
|
||||
resolution: {integrity: sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==}
|
||||
|
||||
'@vitest/runner@1.6.1':
|
||||
resolution: {integrity: sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==}
|
||||
|
||||
'@vitest/snapshot@1.6.1':
|
||||
resolution: {integrity: sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==}
|
||||
|
||||
'@vitest/spy@1.6.1':
|
||||
resolution: {integrity: sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==}
|
||||
|
||||
'@vitest/utils@1.6.1':
|
||||
resolution: {integrity: sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==}
|
||||
|
||||
'@volar/language-core@1.11.1':
|
||||
resolution: {integrity: sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==}
|
||||
|
||||
@@ -1756,6 +1784,10 @@ packages:
|
||||
resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
acorn-walk@8.3.5:
|
||||
resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
acorn@7.4.1:
|
||||
resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
@@ -1821,6 +1853,9 @@ packages:
|
||||
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
assertion-error@1.1.0:
|
||||
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
|
||||
|
||||
asynckit@0.4.0:
|
||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
|
||||
@@ -1942,6 +1977,10 @@ packages:
|
||||
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
cac@6.7.14:
|
||||
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
cac@6.7.9:
|
||||
resolution: {integrity: sha512-XN5qEpfNQCJ8jRaZgitSkkukjMRCGio+X3Ks5KUbGGlPbV+pSem1l9VuzooCBXOiMFshUZgyYqg6rgN8rjkb/w==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1972,6 +2011,10 @@ packages:
|
||||
centra@2.7.0:
|
||||
resolution: {integrity: sha512-PbFMgMSrmgx6uxCdm57RUos9Tc3fclMvhLSATYN39XsDV29B89zZ3KA89jmY0vwSGazyU+uerqwa6t+KaodPcg==}
|
||||
|
||||
chai@4.3.10:
|
||||
resolution: {integrity: sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
chalk@2.4.2:
|
||||
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -1984,6 +2027,9 @@ packages:
|
||||
resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
check-error@1.0.3:
|
||||
resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==}
|
||||
|
||||
chokidar@3.6.0:
|
||||
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
|
||||
engines: {node: '>= 8.10.0'}
|
||||
@@ -2161,6 +2207,10 @@ packages:
|
||||
dedent@0.7.0:
|
||||
resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==}
|
||||
|
||||
deep-eql@4.1.4:
|
||||
resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
deep-is@0.1.4:
|
||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||
|
||||
@@ -2196,6 +2246,10 @@ packages:
|
||||
resolution: {integrity: sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==}
|
||||
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
|
||||
|
||||
diff-sequences@29.6.3:
|
||||
resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
|
||||
dir-glob@3.0.1:
|
||||
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -2259,8 +2313,8 @@ packages:
|
||||
es-module-lexer@1.5.4:
|
||||
resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==}
|
||||
|
||||
es-object-atoms@1.1.1:
|
||||
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
||||
es-object-atoms@1.1.2:
|
||||
resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-set-tostringtag@2.1.0:
|
||||
@@ -2359,6 +2413,10 @@ packages:
|
||||
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
execa@8.0.1:
|
||||
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
|
||||
engines: {node: '>=16.17'}
|
||||
|
||||
exif-parser@0.1.12:
|
||||
resolution: {integrity: sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==}
|
||||
|
||||
@@ -2480,6 +2538,9 @@ packages:
|
||||
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
||||
engines: {node: 6.* || 8.* || >= 10.*}
|
||||
|
||||
get-func-name@2.0.2:
|
||||
resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
|
||||
|
||||
get-intrinsic@1.3.0:
|
||||
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2496,6 +2557,10 @@ packages:
|
||||
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
get-stream@8.0.1:
|
||||
resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
glob-parent@5.1.2:
|
||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -2583,6 +2648,10 @@ packages:
|
||||
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
|
||||
engines: {node: '>=10.17.0'}
|
||||
|
||||
human-signals@5.0.0:
|
||||
resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
|
||||
engines: {node: '>=16.17.0'}
|
||||
|
||||
iconv-lite@0.4.24:
|
||||
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -2679,6 +2748,10 @@ packages:
|
||||
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
is-stream@3.0.0:
|
||||
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
is-typedarray@1.0.0:
|
||||
resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==}
|
||||
|
||||
@@ -2949,6 +3022,10 @@ packages:
|
||||
resolution: {integrity: sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==}
|
||||
engines: {node: '>= 12.13.0'}
|
||||
|
||||
local-pkg@0.5.0:
|
||||
resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
local-pkg@1.2.1:
|
||||
resolution: {integrity: sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -2977,6 +3054,9 @@ packages:
|
||||
lodash@4.18.1:
|
||||
resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==}
|
||||
|
||||
loupe@2.3.7:
|
||||
resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
|
||||
|
||||
lru-cache@5.1.1:
|
||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||
|
||||
@@ -3051,6 +3131,10 @@ packages:
|
||||
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
mimic-fn@4.0.0:
|
||||
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
min-document@2.19.2:
|
||||
resolution: {integrity: sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==}
|
||||
|
||||
@@ -3117,6 +3201,10 @@ packages:
|
||||
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
npm-run-path@5.3.0:
|
||||
resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
nth-check@2.1.1:
|
||||
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
|
||||
|
||||
@@ -3141,6 +3229,10 @@ packages:
|
||||
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
onetime@6.0.0:
|
||||
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
optionator@0.9.4:
|
||||
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -3157,6 +3249,10 @@ packages:
|
||||
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
p-limit@5.0.0:
|
||||
resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
p-locate@4.1.0:
|
||||
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -3217,6 +3313,10 @@ packages:
|
||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
path-key@4.0.0:
|
||||
resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
path-parse@1.0.7:
|
||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||
|
||||
@@ -3227,9 +3327,15 @@ packages:
|
||||
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
pathe@1.1.2:
|
||||
resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
|
||||
|
||||
pathe@2.0.3:
|
||||
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
||||
|
||||
pathval@1.1.1:
|
||||
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
|
||||
|
||||
phin@2.9.3:
|
||||
resolution: {integrity: sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
@@ -3353,6 +3459,10 @@ packages:
|
||||
resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
|
||||
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
|
||||
|
||||
pretty-format@29.7.0:
|
||||
resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
|
||||
process@0.11.10:
|
||||
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
|
||||
engines: {node: '>= 0.6.0'}
|
||||
@@ -3407,6 +3517,9 @@ packages:
|
||||
react-is@17.0.2:
|
||||
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
|
||||
|
||||
react-is@18.0.0:
|
||||
resolution: {integrity: sha512-yUcBYdBBbo3QiPsgYDcfQcIkGZHfxOaoE6HLSnr1sPzMhdyxusbfKOSUbSd/ocGi32dxcj366PsTj+5oggeKKw==}
|
||||
|
||||
read-cache@1.0.0:
|
||||
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
|
||||
|
||||
@@ -3684,9 +3797,16 @@ packages:
|
||||
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
siginfo@2.0.0:
|
||||
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
||||
|
||||
signal-exit@3.0.7:
|
||||
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
||||
|
||||
signal-exit@4.1.0:
|
||||
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
sisteransi@1.0.5:
|
||||
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
||||
|
||||
@@ -3716,10 +3836,16 @@ packages:
|
||||
resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
stackback@0.0.2:
|
||||
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||
|
||||
statuses@2.0.1:
|
||||
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
std-env@3.5.0:
|
||||
resolution: {integrity: sha512-JGUEaALvL0Mf6JCfYnJOTcobY+Nc7sG/TemDRBqCA0wEr4DER7zDchaaixTlmOxAjG1uRJmX82EQcxwTQTkqVA==}
|
||||
|
||||
string-hash@1.1.3:
|
||||
resolution: {integrity: sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==}
|
||||
|
||||
@@ -3743,10 +3869,17 @@ packages:
|
||||
resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
strip-final-newline@3.0.0:
|
||||
resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
strip-json-comments@3.1.1:
|
||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
strip-literal@2.1.1:
|
||||
resolution: {integrity: sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==}
|
||||
|
||||
strip-literal@3.1.0:
|
||||
resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==}
|
||||
|
||||
@@ -3813,9 +3946,20 @@ packages:
|
||||
timm@1.7.1:
|
||||
resolution: {integrity: sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==}
|
||||
|
||||
tinybench@2.5.1:
|
||||
resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==}
|
||||
|
||||
tinycolor2@1.6.0:
|
||||
resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
|
||||
|
||||
tinypool@0.8.3:
|
||||
resolution: {integrity: sha512-Ud7uepAklqRH1bvwy22ynrliC7Dljz7Tm8M/0RBUW+YRa4YHhZ6e4PpgE+fu1zr/WqB1kbeuVrdfeuyIBpy4tw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
tinyspy@2.2.1:
|
||||
resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
tmpl@1.0.5:
|
||||
resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}
|
||||
|
||||
@@ -3970,6 +4114,11 @@ packages:
|
||||
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
vite-node@1.6.1:
|
||||
resolution: {integrity: sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
|
||||
vite@5.2.8:
|
||||
resolution: {integrity: sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
@@ -3998,6 +4147,31 @@ packages:
|
||||
terser:
|
||||
optional: true
|
||||
|
||||
vitest@1.6.1:
|
||||
resolution: {integrity: sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@edge-runtime/vm': '*'
|
||||
'@types/node': ^18.0.0 || >=20.0.0
|
||||
'@vitest/browser': 1.6.1
|
||||
'@vitest/ui': 1.6.1
|
||||
happy-dom: '*'
|
||||
jsdom: '*'
|
||||
peerDependenciesMeta:
|
||||
'@edge-runtime/vm':
|
||||
optional: true
|
||||
'@types/node':
|
||||
optional: true
|
||||
'@vitest/browser':
|
||||
optional: true
|
||||
'@vitest/ui':
|
||||
optional: true
|
||||
happy-dom:
|
||||
optional: true
|
||||
jsdom:
|
||||
optional: true
|
||||
|
||||
vue-eslint-parser@9.4.3:
|
||||
resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==}
|
||||
engines: {node: ^14.17.0 || >=16.0.0}
|
||||
@@ -4063,6 +4237,11 @@ packages:
|
||||
engines: {node: '>= 8'}
|
||||
hasBin: true
|
||||
|
||||
why-is-node-running@2.3.0:
|
||||
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
|
||||
engines: {node: '>=8'}
|
||||
hasBin: true
|
||||
|
||||
word-wrap@1.2.5:
|
||||
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -4159,6 +4338,10 @@ packages:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
yocto-queue@1.2.2:
|
||||
resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==}
|
||||
engines: {node: '>=12.20'}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@ampproject/remapping@2.3.0':
|
||||
@@ -4374,7 +4557,7 @@ snapshots:
|
||||
'@babel/helper-validator-identifier': 7.28.5
|
||||
chalk: 2.4.2
|
||||
js-tokens: 4.0.0
|
||||
picocolors: 1.1.0
|
||||
picocolors: 1.1.1
|
||||
|
||||
'@babel/parser@7.25.6':
|
||||
dependencies:
|
||||
@@ -5693,6 +5876,10 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@jest/schemas@29.6.3':
|
||||
dependencies:
|
||||
'@sinclair/typebox': 0.27.10
|
||||
|
||||
'@jest/source-map@27.5.1':
|
||||
dependencies:
|
||||
callsites: 3.1.0
|
||||
@@ -6202,6 +6389,8 @@ snapshots:
|
||||
'@rollup/rollup-win32-x64-msvc@4.60.4':
|
||||
optional: true
|
||||
|
||||
'@sinclair/typebox@0.27.10': {}
|
||||
|
||||
'@sinonjs/commons@1.8.6':
|
||||
dependencies:
|
||||
type-detect: 4.0.8
|
||||
@@ -6396,6 +6585,35 @@ snapshots:
|
||||
vite: 5.2.8(@types/node@25.9.1)(sass@1.99.0)(terser@5.47.1)
|
||||
vue: 3.4.21(typescript@5.3.3)
|
||||
|
||||
'@vitest/expect@1.6.1':
|
||||
dependencies:
|
||||
'@vitest/spy': 1.6.1
|
||||
'@vitest/utils': 1.6.1
|
||||
chai: 4.3.10
|
||||
|
||||
'@vitest/runner@1.6.1':
|
||||
dependencies:
|
||||
'@vitest/utils': 1.6.1
|
||||
p-limit: 5.0.0
|
||||
pathe: 1.1.2
|
||||
|
||||
'@vitest/snapshot@1.6.1':
|
||||
dependencies:
|
||||
magic-string: 0.30.21
|
||||
pathe: 1.1.2
|
||||
pretty-format: 29.7.0
|
||||
|
||||
'@vitest/spy@1.6.1':
|
||||
dependencies:
|
||||
tinyspy: 2.2.1
|
||||
|
||||
'@vitest/utils@1.6.1':
|
||||
dependencies:
|
||||
diff-sequences: 29.6.3
|
||||
estree-walker: 3.0.3
|
||||
loupe: 2.3.7
|
||||
pretty-format: 29.7.0
|
||||
|
||||
'@volar/language-core@1.11.1':
|
||||
dependencies:
|
||||
'@volar/source-map': 1.11.1
|
||||
@@ -6559,6 +6777,10 @@ snapshots:
|
||||
|
||||
acorn-walk@7.2.0: {}
|
||||
|
||||
acorn-walk@8.3.5:
|
||||
dependencies:
|
||||
acorn: 8.16.0
|
||||
|
||||
acorn@7.4.1: {}
|
||||
|
||||
acorn@8.16.0: {}
|
||||
@@ -6613,6 +6835,8 @@ snapshots:
|
||||
|
||||
array-union@2.1.0: {}
|
||||
|
||||
assertion-error@1.1.0: {}
|
||||
|
||||
asynckit@0.4.0: {}
|
||||
|
||||
autoprefixer@10.4.20(postcss@8.5.15):
|
||||
@@ -6779,6 +7003,8 @@ snapshots:
|
||||
|
||||
bytes@3.1.2: {}
|
||||
|
||||
cac@6.7.14: {}
|
||||
|
||||
cac@6.7.9: {}
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
@@ -6805,6 +7031,16 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
chai@4.3.10:
|
||||
dependencies:
|
||||
assertion-error: 1.1.0
|
||||
check-error: 1.0.3
|
||||
deep-eql: 4.1.4
|
||||
get-func-name: 2.0.2
|
||||
loupe: 2.3.7
|
||||
pathval: 1.1.1
|
||||
type-detect: 4.0.8
|
||||
|
||||
chalk@2.4.2:
|
||||
dependencies:
|
||||
ansi-styles: 3.2.1
|
||||
@@ -6818,6 +7054,10 @@ snapshots:
|
||||
|
||||
char-regex@1.0.2: {}
|
||||
|
||||
check-error@1.0.3:
|
||||
dependencies:
|
||||
get-func-name: 2.0.2
|
||||
|
||||
chokidar@3.6.0:
|
||||
dependencies:
|
||||
anymatch: 3.1.3
|
||||
@@ -6833,7 +7073,6 @@ snapshots:
|
||||
chokidar@4.0.3:
|
||||
dependencies:
|
||||
readdirp: 4.1.2
|
||||
optional: true
|
||||
|
||||
ci-info@3.9.0: {}
|
||||
|
||||
@@ -6959,6 +7198,10 @@ snapshots:
|
||||
|
||||
dedent@0.7.0: {}
|
||||
|
||||
deep-eql@4.1.4:
|
||||
dependencies:
|
||||
type-detect: 4.0.8
|
||||
|
||||
deep-is@0.1.4: {}
|
||||
|
||||
deepmerge@4.3.1: {}
|
||||
@@ -6980,6 +7223,8 @@ snapshots:
|
||||
|
||||
diff-sequences@27.5.1: {}
|
||||
|
||||
diff-sequences@29.6.3: {}
|
||||
|
||||
dir-glob@3.0.1:
|
||||
dependencies:
|
||||
path-type: 4.0.0
|
||||
@@ -7026,7 +7271,7 @@ snapshots:
|
||||
|
||||
es-module-lexer@1.5.4: {}
|
||||
|
||||
es-object-atoms@1.1.1:
|
||||
es-object-atoms@1.1.2:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
|
||||
@@ -7035,7 +7280,7 @@ snapshots:
|
||||
es-errors: 1.3.0
|
||||
get-intrinsic: 1.3.0
|
||||
has-tostringtag: 1.0.2
|
||||
hasown: 2.0.3
|
||||
hasown: 2.0.4
|
||||
|
||||
esbuild@0.20.2:
|
||||
optionalDependencies:
|
||||
@@ -7187,6 +7432,18 @@ snapshots:
|
||||
signal-exit: 3.0.7
|
||||
strip-final-newline: 2.0.0
|
||||
|
||||
execa@8.0.1:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
get-stream: 8.0.1
|
||||
human-signals: 5.0.0
|
||||
is-stream: 3.0.0
|
||||
merge-stream: 2.0.0
|
||||
npm-run-path: 5.3.0
|
||||
onetime: 6.0.0
|
||||
signal-exit: 4.1.0
|
||||
strip-final-newline: 3.0.0
|
||||
|
||||
exif-parser@0.1.12: {}
|
||||
|
||||
exit@0.1.2: {}
|
||||
@@ -7337,17 +7594,19 @@ snapshots:
|
||||
|
||||
get-caller-file@2.0.5: {}
|
||||
|
||||
get-func-name@2.0.2: {}
|
||||
|
||||
get-intrinsic@1.3.0:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
es-define-property: 1.0.1
|
||||
es-errors: 1.3.0
|
||||
es-object-atoms: 1.1.1
|
||||
es-object-atoms: 1.1.2
|
||||
function-bind: 1.1.2
|
||||
get-proto: 1.0.1
|
||||
gopd: 1.2.0
|
||||
has-symbols: 1.1.0
|
||||
hasown: 2.0.3
|
||||
hasown: 2.0.4
|
||||
math-intrinsics: 1.1.0
|
||||
|
||||
get-package-type@0.1.0: {}
|
||||
@@ -7355,10 +7614,12 @@ snapshots:
|
||||
get-proto@1.0.1:
|
||||
dependencies:
|
||||
dunder-proto: 1.0.1
|
||||
es-object-atoms: 1.1.1
|
||||
es-object-atoms: 1.1.2
|
||||
|
||||
get-stream@6.0.1: {}
|
||||
|
||||
get-stream@8.0.1: {}
|
||||
|
||||
glob-parent@5.1.2:
|
||||
dependencies:
|
||||
is-glob: 4.0.3
|
||||
@@ -7453,6 +7714,8 @@ snapshots:
|
||||
|
||||
human-signals@2.1.0: {}
|
||||
|
||||
human-signals@5.0.0: {}
|
||||
|
||||
iconv-lite@0.4.24:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
@@ -7522,6 +7785,8 @@ snapshots:
|
||||
|
||||
is-stream@2.0.1: {}
|
||||
|
||||
is-stream@3.0.0: {}
|
||||
|
||||
is-typedarray@1.0.0: {}
|
||||
|
||||
isbinaryfile@5.0.2: {}
|
||||
@@ -8038,6 +8303,11 @@ snapshots:
|
||||
|
||||
loader-utils@3.3.1: {}
|
||||
|
||||
local-pkg@0.5.0:
|
||||
dependencies:
|
||||
mlly: 1.8.2
|
||||
pkg-types: 1.3.1
|
||||
|
||||
local-pkg@1.2.1:
|
||||
dependencies:
|
||||
mlly: 1.8.2
|
||||
@@ -8062,6 +8332,10 @@ snapshots:
|
||||
|
||||
lodash@4.18.1: {}
|
||||
|
||||
loupe@2.3.7:
|
||||
dependencies:
|
||||
get-func-name: 2.0.2
|
||||
|
||||
lru-cache@5.1.1:
|
||||
dependencies:
|
||||
yallist: 3.1.1
|
||||
@@ -8117,6 +8391,8 @@ snapshots:
|
||||
|
||||
mimic-fn@2.1.0: {}
|
||||
|
||||
mimic-fn@4.0.0: {}
|
||||
|
||||
min-document@2.19.2:
|
||||
dependencies:
|
||||
dom-walk: 0.1.2
|
||||
@@ -8171,6 +8447,10 @@ snapshots:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
|
||||
npm-run-path@5.3.0:
|
||||
dependencies:
|
||||
path-key: 4.0.0
|
||||
|
||||
nth-check@2.1.1:
|
||||
dependencies:
|
||||
boolbase: 1.0.0
|
||||
@@ -8193,6 +8473,10 @@ snapshots:
|
||||
dependencies:
|
||||
mimic-fn: 2.1.0
|
||||
|
||||
onetime@6.0.0:
|
||||
dependencies:
|
||||
mimic-fn: 4.0.0
|
||||
|
||||
optionator@0.9.4:
|
||||
dependencies:
|
||||
deep-is: 0.1.4
|
||||
@@ -8214,6 +8498,10 @@ snapshots:
|
||||
dependencies:
|
||||
yocto-queue: 0.1.0
|
||||
|
||||
p-limit@5.0.0:
|
||||
dependencies:
|
||||
yocto-queue: 1.2.2
|
||||
|
||||
p-locate@4.1.0:
|
||||
dependencies:
|
||||
p-limit: 2.3.0
|
||||
@@ -8270,14 +8558,20 @@ snapshots:
|
||||
|
||||
path-key@3.1.1: {}
|
||||
|
||||
path-key@4.0.0: {}
|
||||
|
||||
path-parse@1.0.7: {}
|
||||
|
||||
path-to-regexp@0.1.10: {}
|
||||
|
||||
path-type@4.0.0: {}
|
||||
|
||||
pathe@1.1.2: {}
|
||||
|
||||
pathe@2.0.3: {}
|
||||
|
||||
pathval@1.1.1: {}
|
||||
|
||||
phin@2.9.3: {}
|
||||
|
||||
phin@3.7.1:
|
||||
@@ -8399,6 +8693,12 @@ snapshots:
|
||||
ansi-styles: 5.2.0
|
||||
react-is: 17.0.2
|
||||
|
||||
pretty-format@29.7.0:
|
||||
dependencies:
|
||||
'@jest/schemas': 29.6.3
|
||||
ansi-styles: 5.2.0
|
||||
react-is: 18.0.0
|
||||
|
||||
process@0.11.10: {}
|
||||
|
||||
prompts@2.4.2:
|
||||
@@ -8446,6 +8746,8 @@ snapshots:
|
||||
|
||||
react-is@17.0.2: {}
|
||||
|
||||
react-is@18.0.0: {}
|
||||
|
||||
read-cache@1.0.0:
|
||||
dependencies:
|
||||
pify: 2.3.0
|
||||
@@ -8454,8 +8756,7 @@ snapshots:
|
||||
dependencies:
|
||||
picomatch: 2.3.2
|
||||
|
||||
readdirp@4.1.2:
|
||||
optional: true
|
||||
readdirp@4.1.2: {}
|
||||
|
||||
regenerate-unicode-properties@10.2.2:
|
||||
dependencies:
|
||||
@@ -8654,7 +8955,6 @@ snapshots:
|
||||
source-map-js: 1.2.1
|
||||
optionalDependencies:
|
||||
'@parcel/watcher': 2.5.6
|
||||
optional: true
|
||||
|
||||
sax@1.6.0: {}
|
||||
|
||||
@@ -8749,8 +9049,12 @@ snapshots:
|
||||
side-channel-map: 1.0.1
|
||||
side-channel-weakmap: 1.0.2
|
||||
|
||||
siginfo@2.0.0: {}
|
||||
|
||||
signal-exit@3.0.7: {}
|
||||
|
||||
signal-exit@4.1.0: {}
|
||||
|
||||
sisteransi@1.0.5: {}
|
||||
|
||||
slash@3.0.0: {}
|
||||
@@ -8772,8 +9076,12 @@ snapshots:
|
||||
dependencies:
|
||||
escape-string-regexp: 2.0.0
|
||||
|
||||
stackback@0.0.2: {}
|
||||
|
||||
statuses@2.0.1: {}
|
||||
|
||||
std-env@3.5.0: {}
|
||||
|
||||
string-hash@1.1.3: {}
|
||||
|
||||
string-length@4.0.2:
|
||||
@@ -8795,8 +9103,14 @@ snapshots:
|
||||
|
||||
strip-final-newline@2.0.0: {}
|
||||
|
||||
strip-final-newline@3.0.0: {}
|
||||
|
||||
strip-json-comments@3.1.1: {}
|
||||
|
||||
strip-literal@2.1.1:
|
||||
dependencies:
|
||||
js-tokens: 9.0.1
|
||||
|
||||
strip-literal@3.1.0:
|
||||
dependencies:
|
||||
js-tokens: 9.0.1
|
||||
@@ -8858,8 +9172,14 @@ snapshots:
|
||||
|
||||
timm@1.7.1: {}
|
||||
|
||||
tinybench@2.5.1: {}
|
||||
|
||||
tinycolor2@1.6.0: {}
|
||||
|
||||
tinypool@0.8.3: {}
|
||||
|
||||
tinyspy@2.2.1: {}
|
||||
|
||||
tmpl@1.0.5: {}
|
||||
|
||||
to-fast-properties@2.0.0: {}
|
||||
@@ -9002,6 +9322,23 @@ snapshots:
|
||||
|
||||
vary@1.1.2: {}
|
||||
|
||||
vite-node@1.6.1(@types/node@25.9.1)(sass@1.99.0)(terser@5.47.1):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
debug: 4.4.3
|
||||
pathe: 1.1.2
|
||||
picocolors: 1.1.1
|
||||
vite: 5.2.8(@types/node@25.9.1)(sass@1.99.0)(terser@5.47.1)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- less
|
||||
- lightningcss
|
||||
- sass
|
||||
- stylus
|
||||
- sugarss
|
||||
- supports-color
|
||||
- terser
|
||||
|
||||
vite@5.2.8(@types/node@25.9.1)(sass@1.99.0)(terser@5.47.1):
|
||||
dependencies:
|
||||
esbuild: 0.20.2
|
||||
@@ -9013,6 +9350,40 @@ snapshots:
|
||||
sass: 1.99.0
|
||||
terser: 5.47.1
|
||||
|
||||
vitest@1.6.1(@types/node@25.9.1)(jsdom@16.7.0)(sass@1.99.0)(terser@5.47.1):
|
||||
dependencies:
|
||||
'@vitest/expect': 1.6.1
|
||||
'@vitest/runner': 1.6.1
|
||||
'@vitest/snapshot': 1.6.1
|
||||
'@vitest/spy': 1.6.1
|
||||
'@vitest/utils': 1.6.1
|
||||
acorn-walk: 8.3.5
|
||||
chai: 4.3.10
|
||||
debug: 4.4.3
|
||||
execa: 8.0.1
|
||||
local-pkg: 0.5.0
|
||||
magic-string: 0.30.21
|
||||
pathe: 1.1.2
|
||||
picocolors: 1.1.1
|
||||
std-env: 3.5.0
|
||||
strip-literal: 2.1.1
|
||||
tinybench: 2.5.1
|
||||
tinypool: 0.8.3
|
||||
vite: 5.2.8(@types/node@25.9.1)(sass@1.99.0)(terser@5.47.1)
|
||||
vite-node: 1.6.1(@types/node@25.9.1)(sass@1.99.0)(terser@5.47.1)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/node': 25.9.1
|
||||
jsdom: 16.7.0
|
||||
transitivePeerDependencies:
|
||||
- less
|
||||
- lightningcss
|
||||
- sass
|
||||
- stylus
|
||||
- sugarss
|
||||
- supports-color
|
||||
- terser
|
||||
|
||||
vue-eslint-parser@9.4.3(eslint@8.57.1):
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
@@ -9087,6 +9458,11 @@ snapshots:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
||||
why-is-node-running@2.3.0:
|
||||
dependencies:
|
||||
siginfo: 2.0.0
|
||||
stackback: 0.0.2
|
||||
|
||||
word-wrap@1.2.5: {}
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
@@ -9157,3 +9533,5 @@ snapshots:
|
||||
yargs-parser: 20.2.9
|
||||
|
||||
yocto-queue@0.1.0: {}
|
||||
|
||||
yocto-queue@1.2.2: {}
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
|
||||
<view
|
||||
class="arrival-primary"
|
||||
:class="{ disabled: !selectedTarget }"
|
||||
:class="{ disabled: !selectedTarget || isOpeningNavigation }"
|
||||
@tap="handleNavigateTap"
|
||||
>
|
||||
<text class="arrival-primary-text">{{ primaryActionText }}</text>
|
||||
@@ -124,10 +124,6 @@ import {
|
||||
THIRD_PARTY_MAP_PROVIDERS,
|
||||
type ThirdPartyMapProviderOption
|
||||
} from '@/services/ThirdPartyMapSearchService'
|
||||
import {
|
||||
isNativeWechatMiniProgram,
|
||||
isWechatMiniProgramRuntime
|
||||
} from '@/utils/hostEnvironment'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
visible?: boolean
|
||||
@@ -151,13 +147,13 @@ const emit = defineEmits<{
|
||||
|
||||
const typeOptions = computed(() => ARRIVAL_TARGET_TYPES)
|
||||
const mapProviders = computed(() => THIRD_PARTY_MAP_PROVIDERS)
|
||||
const shouldUseMiniProgramLocation = computed(() => isWechatMiniProgramRuntime())
|
||||
const primaryActionText = computed(() => (
|
||||
shouldUseMiniProgramLocation.value ? '打开地图导航' : '第三方导航'
|
||||
isOpeningNavigation.value ? '正在打开...' : '打开地图导航'
|
||||
))
|
||||
const arrivalPanelRef = ref<unknown>(null)
|
||||
const isCollapsed = ref(false)
|
||||
const providerSheetVisible = ref(false)
|
||||
const isOpeningNavigation = ref(false)
|
||||
let resizeObserver: ResizeObserver | null = null
|
||||
|
||||
const activeTypeLabel = computed(() => (
|
||||
@@ -243,6 +239,7 @@ const startLayoutObserver = () => {
|
||||
}
|
||||
|
||||
const handleNavigateTap = async () => {
|
||||
if (isOpeningNavigation.value) return
|
||||
if (!props.selectedTarget) {
|
||||
uni.showToast({
|
||||
title: '请先选择点位',
|
||||
@@ -257,14 +254,15 @@ const handleNavigateTap = async () => {
|
||||
name: props.selectedTarget.title,
|
||||
address: props.selectedTarget.subtitle
|
||||
}
|
||||
const isNativeMiniProgram = isNativeWechatMiniProgram()
|
||||
const openedInMiniProgram = await openWechatMiniProgramLocation(target)
|
||||
|
||||
if (openedInMiniProgram || isNativeMiniProgram || isWechatMiniProgramRuntime()) {
|
||||
return
|
||||
isOpeningNavigation.value = true
|
||||
try {
|
||||
const result = await openWechatMiniProgramLocation(target)
|
||||
if (result.status === 'not-embedded') {
|
||||
providerSheetVisible.value = true
|
||||
}
|
||||
} finally {
|
||||
isOpeningNavigation.value = false
|
||||
}
|
||||
|
||||
providerSheetVisible.value = true
|
||||
}
|
||||
|
||||
const handleProviderSelect = (provider: ThirdPartyMapProviderOption) => {
|
||||
|
||||
@@ -16,15 +16,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { onLoad, onUnload } from '@dcloudio/uni-app'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
interface OpenLocationOptions {
|
||||
latitude?: string
|
||||
longitude?: string
|
||||
name?: string
|
||||
address?: string
|
||||
}
|
||||
import { openWechatHostLocation } from '@/services/WechatOpenLocationService'
|
||||
import {
|
||||
parseWechatOpenLocationPageOptions,
|
||||
type WechatOpenLocationPageOptions
|
||||
} from '@/utils/wechatOpenLocationProtocol'
|
||||
|
||||
const latitude = ref<number | null>(null)
|
||||
const longitude = ref<number | null>(null)
|
||||
@@ -32,6 +30,10 @@ const locationName = ref('')
|
||||
const locationAddress = ref('')
|
||||
const hasOpened = ref(false)
|
||||
const hasError = ref(false)
|
||||
const isOpening = ref(false)
|
||||
const errorMessage = ref('')
|
||||
let isPageActive = true
|
||||
let autoBackTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const title = computed(() => {
|
||||
if (hasError.value) return '地图打开失败'
|
||||
@@ -40,22 +42,18 @@ const title = computed(() => {
|
||||
})
|
||||
|
||||
const description = computed(() => {
|
||||
if (hasError.value) return '请返回导览后重试,或检查小程序位置权限。'
|
||||
if (hasError.value) return errorMessage.value || '请返回导览后重试,或检查小程序位置权限。'
|
||||
if (locationName.value) return locationName.value
|
||||
return '正在为你唤起地图位置页。'
|
||||
})
|
||||
|
||||
const safeDecode = (value?: string) => {
|
||||
if (!value) return ''
|
||||
|
||||
try {
|
||||
return decodeURIComponent(value)
|
||||
} catch {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
if (!isPageActive) return
|
||||
isPageActive = false
|
||||
if (autoBackTimer) {
|
||||
clearTimeout(autoBackTimer)
|
||||
autoBackTimer = null
|
||||
}
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
fail: () => {
|
||||
@@ -66,9 +64,11 @@ const goBack = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const openLocation = () => {
|
||||
const openLocation = async () => {
|
||||
if (isOpening.value) return
|
||||
if (latitude.value === null || longitude.value === null) {
|
||||
hasError.value = true
|
||||
errorMessage.value = '地图参数无效,请返回导览后重试。'
|
||||
uni.showToast({
|
||||
title: '缺少地图坐标',
|
||||
icon: 'none'
|
||||
@@ -76,42 +76,57 @@ const openLocation = () => {
|
||||
return
|
||||
}
|
||||
|
||||
uni.openLocation({
|
||||
isOpening.value = true
|
||||
const result = await openWechatHostLocation({
|
||||
latitude: latitude.value,
|
||||
longitude: longitude.value,
|
||||
name: locationName.value,
|
||||
address: locationAddress.value,
|
||||
scale: 18,
|
||||
success: () => {
|
||||
hasOpened.value = true
|
||||
hasError.value = false
|
||||
setTimeout(goBack, 300)
|
||||
},
|
||||
fail: (error) => {
|
||||
console.error('打开地图位置失败:', error)
|
||||
hasError.value = true
|
||||
uni.showToast({
|
||||
title: '无法打开地图',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
address: locationAddress.value
|
||||
})
|
||||
}
|
||||
if (!isPageActive) return
|
||||
isOpening.value = false
|
||||
|
||||
onLoad((options?: OpenLocationOptions) => {
|
||||
latitude.value = Number(options?.latitude)
|
||||
longitude.value = Number(options?.longitude)
|
||||
locationName.value = safeDecode(options?.name) || '深圳自然博物馆'
|
||||
locationAddress.value = safeDecode(options?.address)
|
||||
|
||||
if (!Number.isFinite(latitude.value) || !Number.isFinite(longitude.value)) {
|
||||
latitude.value = null
|
||||
longitude.value = null
|
||||
hasError.value = true
|
||||
if (result.status === 'opened') {
|
||||
hasOpened.value = true
|
||||
hasError.value = false
|
||||
autoBackTimer = setTimeout(goBack, 300)
|
||||
return
|
||||
}
|
||||
|
||||
openLocation()
|
||||
hasError.value = true
|
||||
errorMessage.value = result.reason === 'timeout'
|
||||
? '微信地图响应超时,请重试或返回导览。'
|
||||
: '微信地图能力调用失败,请检查权限后重试。'
|
||||
uni.showToast({
|
||||
title: result.reason === 'timeout' ? '微信地图响应超时' : '无法打开地图',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
onLoad((options?: WechatOpenLocationPageOptions) => {
|
||||
isPageActive = true
|
||||
const result = parseWechatOpenLocationPageOptions(options)
|
||||
if (!result.ok) {
|
||||
hasError.value = true
|
||||
errorMessage.value = result.message
|
||||
uni.showToast({
|
||||
title: result.message,
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
latitude.value = result.value.latitude
|
||||
longitude.value = result.value.longitude
|
||||
locationName.value = result.value.name
|
||||
locationAddress.value = result.value.address || ''
|
||||
void openLocation()
|
||||
})
|
||||
|
||||
onUnload(() => {
|
||||
isPageActive = false
|
||||
if (autoBackTimer) clearTimeout(autoBackTimer)
|
||||
autoBackTimer = null
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -3,6 +3,11 @@ import {
|
||||
isEmbeddedInWechatMiniProgram,
|
||||
isNativeWechatMiniProgram
|
||||
} from '@/utils/hostEnvironment'
|
||||
import {
|
||||
buildWechatOpenLocationPageUrl,
|
||||
validateWechatOpenLocationTarget,
|
||||
type WechatOpenLocationTarget
|
||||
} from '@/utils/wechatOpenLocationProtocol'
|
||||
|
||||
export type ThirdPartyMapProvider = 'amap' | 'baidu' | 'tencent'
|
||||
|
||||
@@ -11,12 +16,7 @@ export interface ThirdPartyMapSearchTarget {
|
||||
region: string
|
||||
}
|
||||
|
||||
export interface WechatMiniProgramLocationTarget {
|
||||
latitude: number
|
||||
longitude: number
|
||||
name: string
|
||||
address?: string
|
||||
}
|
||||
export type WechatMiniProgramLocationTarget = WechatOpenLocationTarget
|
||||
|
||||
interface WechatMiniProgramBridge {
|
||||
getEnv?: (callback: (result: { miniprogram?: boolean }) => void) => void
|
||||
@@ -27,7 +27,17 @@ interface WechatMiniProgramBridge {
|
||||
}) => void
|
||||
}
|
||||
|
||||
let wechatJsSdkLoadPromise: Promise<boolean> | null = null
|
||||
export type WechatMiniProgramLocationOpenResult =
|
||||
| { status: 'opened' }
|
||||
| { status: 'not-embedded' }
|
||||
| { status: 'failed'; reason: 'invalid-target' | 'bridge-unavailable' | 'not-in-mini-program' | 'navigation-failed' }
|
||||
|
||||
const BRIDGE_WAIT_TIMEOUT = 2500
|
||||
const BRIDGE_POLL_INTERVAL = 50
|
||||
const SDK_SCRIPT_LOAD_TIMEOUT = 10000
|
||||
const GET_ENV_TIMEOUT = 1500
|
||||
const NAVIGATION_TIMEOUT = 3000
|
||||
let miniProgramNavigationPromise: Promise<WechatMiniProgramLocationOpenResult> | null = null
|
||||
|
||||
export interface ThirdPartyMapProviderOption {
|
||||
provider: ThirdPartyMapProvider
|
||||
@@ -82,18 +92,7 @@ export const openThirdPartyMapSearch = (
|
||||
|
||||
export const buildWechatMiniProgramOpenLocationUrl = (
|
||||
target: WechatMiniProgramLocationTarget
|
||||
) => {
|
||||
const params = [
|
||||
['latitude', String(target.latitude)],
|
||||
['longitude', String(target.longitude)],
|
||||
['name', target.name],
|
||||
['address', target.address || '']
|
||||
]
|
||||
.map(([key, value]) => `${key}=${encode(value)}`)
|
||||
.join('&')
|
||||
|
||||
return `/pages/open-location/index?${params}`
|
||||
}
|
||||
) => buildWechatOpenLocationPageUrl(target)
|
||||
|
||||
const getWechatMiniProgramBridge = (): WechatMiniProgramBridge | null => {
|
||||
if (typeof window === 'undefined') return null
|
||||
@@ -105,29 +104,35 @@ const getWechatMiniProgramBridge = (): WechatMiniProgramBridge | null => {
|
||||
}).wx?.miniProgram || null
|
||||
}
|
||||
|
||||
const loadWechatJsSdk = () => {
|
||||
if (typeof document === 'undefined') return Promise.resolve(false)
|
||||
if (getWechatMiniProgramBridge()) return Promise.resolve(true)
|
||||
if (wechatJsSdkLoadPromise) return wechatJsSdkLoadPromise
|
||||
const ensureWechatJsSdkScript = () => {
|
||||
if (typeof document === 'undefined' || getWechatMiniProgramBridge()) return
|
||||
const existingScript = document.querySelector<HTMLScriptElement>('script[data-wechat-js-sdk="true"]')
|
||||
if (existingScript) {
|
||||
const startedAt = Number(existingScript.dataset.wechatJsSdkStartedAt || 0)
|
||||
const isExpiredLoading = existingScript.dataset.wechatJsSdkState === 'loading'
|
||||
&& Date.now() - startedAt > SDK_SCRIPT_LOAD_TIMEOUT
|
||||
if (existingScript.dataset.wechatJsSdkState !== 'error' && !isExpiredLoading) return
|
||||
existingScript.remove()
|
||||
}
|
||||
|
||||
wechatJsSdkLoadPromise = new Promise<boolean>((resolve) => {
|
||||
const existingScript = document.querySelector<HTMLScriptElement>('script[data-wechat-js-sdk="true"]')
|
||||
if (existingScript) {
|
||||
existingScript.addEventListener('load', () => resolve(Boolean(getWechatMiniProgramBridge())), { once: true })
|
||||
existingScript.addEventListener('error', () => resolve(false), { once: true })
|
||||
return
|
||||
}
|
||||
|
||||
const script = document.createElement('script')
|
||||
script.src = 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js'
|
||||
script.async = true
|
||||
script.dataset.wechatJsSdk = 'true'
|
||||
script.onload = () => resolve(Boolean(getWechatMiniProgramBridge()))
|
||||
script.onerror = () => resolve(false)
|
||||
const script = document.createElement('script')
|
||||
script.src = 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js'
|
||||
script.async = true
|
||||
script.dataset.wechatJsSdk = 'true'
|
||||
script.dataset.wechatJsSdkState = 'loading'
|
||||
script.dataset.wechatJsSdkStartedAt = String(Date.now())
|
||||
script.onload = () => {
|
||||
script.dataset.wechatJsSdkState = 'loaded'
|
||||
}
|
||||
script.onerror = () => {
|
||||
script.dataset.wechatJsSdkState = 'error'
|
||||
script.remove()
|
||||
}
|
||||
try {
|
||||
document.head.appendChild(script)
|
||||
})
|
||||
|
||||
return wechatJsSdkLoadPromise
|
||||
} catch (error) {
|
||||
console.error('加载微信 JS SDK 失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const isWechatBrowser = () => (
|
||||
@@ -135,6 +140,25 @@ const isWechatBrowser = () => (
|
||||
&& /MicroMessenger/i.test(window.navigator?.userAgent || '')
|
||||
)
|
||||
|
||||
const wait = (duration: number) => new Promise<void>((resolve) => {
|
||||
setTimeout(resolve, duration)
|
||||
})
|
||||
|
||||
const waitForWechatMiniProgramBridge = async () => {
|
||||
const immediateBridge = getWechatMiniProgramBridge()
|
||||
if (immediateBridge) return immediateBridge
|
||||
|
||||
ensureWechatJsSdkScript()
|
||||
const startedAt = Date.now()
|
||||
while (Date.now() - startedAt < BRIDGE_WAIT_TIMEOUT) {
|
||||
await wait(BRIDGE_POLL_INTERVAL)
|
||||
const bridge = getWechatMiniProgramBridge()
|
||||
if (bridge) return bridge
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const resolveMiniProgramWebViewEnvironment = (
|
||||
miniProgram: WechatMiniProgramBridge
|
||||
) => {
|
||||
@@ -150,13 +174,13 @@ const resolveMiniProgramWebViewEnvironment = (
|
||||
clearTimeout(timeout)
|
||||
resolve(isMiniProgram)
|
||||
}
|
||||
const timeout = setTimeout(() => finish(false), 1500)
|
||||
const timeout = setTimeout(() => finish(isEmbeddedInWechatMiniProgram()), GET_ENV_TIMEOUT)
|
||||
|
||||
try {
|
||||
getEnv((result) => finish(Boolean(result.miniprogram)))
|
||||
getEnv((result) => finish(Boolean(result?.miniprogram) || isEmbeddedInWechatMiniProgram()))
|
||||
} catch (error) {
|
||||
console.error('识别小程序 web-view 环境失败:', error)
|
||||
finish(false)
|
||||
// 保留超时重检,让延迟注入的宿主环境标识仍有机会生效。
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -164,50 +188,127 @@ const resolveMiniProgramWebViewEnvironment = (
|
||||
const navigateToMiniProgramLocationPage = (
|
||||
miniProgram: WechatMiniProgramBridge,
|
||||
target: WechatMiniProgramLocationTarget
|
||||
) => new Promise<boolean>((resolve) => {
|
||||
) => new Promise<WechatMiniProgramLocationOpenResult>((resolve) => {
|
||||
let settled = false
|
||||
const finish = (result: WechatMiniProgramLocationOpenResult) => {
|
||||
if (settled) return
|
||||
settled = true
|
||||
clearTimeout(timeout)
|
||||
resolve(result)
|
||||
}
|
||||
const timeout = setTimeout(() => {
|
||||
uni.showToast({
|
||||
title: '宿主地图页响应超时',
|
||||
icon: 'none'
|
||||
})
|
||||
finish({ status: 'failed', reason: 'navigation-failed' })
|
||||
}, NAVIGATION_TIMEOUT)
|
||||
|
||||
if (!miniProgram.navigateTo) {
|
||||
resolve(false)
|
||||
uni.showToast({
|
||||
title: '宿主地图桥接不可用',
|
||||
icon: 'none'
|
||||
})
|
||||
finish({ status: 'failed', reason: 'bridge-unavailable' })
|
||||
return
|
||||
}
|
||||
|
||||
miniProgram.navigateTo({
|
||||
url: buildWechatMiniProgramOpenLocationUrl(target),
|
||||
success: () => resolve(true),
|
||||
fail: (error) => {
|
||||
console.error('跳转小程序地图页失败:', error)
|
||||
uni.showToast({
|
||||
title: '无法打开地图导航',
|
||||
icon: 'none'
|
||||
})
|
||||
resolve(false)
|
||||
}
|
||||
})
|
||||
try {
|
||||
miniProgram.navigateTo({
|
||||
url: buildWechatMiniProgramOpenLocationUrl(target),
|
||||
success: () => finish({ status: 'opened' }),
|
||||
fail: (error) => {
|
||||
if (settled) return
|
||||
console.error('跳转小程序地图页失败:', error)
|
||||
const errorMessage = typeof error === 'object' && error && 'errMsg' in error
|
||||
? String(error.errMsg)
|
||||
: ''
|
||||
uni.showToast({
|
||||
title: /page.*not found|not found.*page/i.test(errorMessage)
|
||||
? '宿主小程序未配置地图页'
|
||||
: '宿主地图页跳转失败',
|
||||
icon: 'none'
|
||||
})
|
||||
finish({ status: 'failed', reason: 'navigation-failed' })
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('调用小程序地图桥接失败:', error)
|
||||
uni.showToast({
|
||||
title: '宿主地图桥接调用失败',
|
||||
icon: 'none'
|
||||
})
|
||||
finish({ status: 'failed', reason: 'navigation-failed' })
|
||||
}
|
||||
})
|
||||
|
||||
export const openWechatMiniProgramLocation = async (
|
||||
const openWechatMiniProgramLocationOnce = async (
|
||||
target: WechatMiniProgramLocationTarget
|
||||
) => {
|
||||
if (isNativeWechatMiniProgram()) {
|
||||
return openExternalNavigation(target)
|
||||
): Promise<WechatMiniProgramLocationOpenResult> => {
|
||||
const validation = validateWechatOpenLocationTarget(target)
|
||||
if (!validation.ok) {
|
||||
uni.showToast({
|
||||
title: validation.message,
|
||||
icon: 'none'
|
||||
})
|
||||
return { status: 'failed', reason: 'invalid-target' }
|
||||
}
|
||||
|
||||
const synchronouslyEmbedded = isEmbeddedInWechatMiniProgram()
|
||||
if (!synchronouslyEmbedded && !isWechatBrowser()) return false
|
||||
|
||||
await loadWechatJsSdk()
|
||||
const miniProgram = getWechatMiniProgramBridge()
|
||||
if (!miniProgram?.navigateTo) {
|
||||
if (synchronouslyEmbedded) {
|
||||
if (isNativeWechatMiniProgram()) {
|
||||
try {
|
||||
const opened = await openExternalNavigation(validation.value)
|
||||
return opened
|
||||
? { status: 'opened' }
|
||||
: { status: 'failed', reason: 'navigation-failed' }
|
||||
} catch (error) {
|
||||
console.error('原生小程序地图能力调用失败:', error)
|
||||
uni.showToast({
|
||||
title: '请在小程序内打开地图',
|
||||
title: '无法打开微信地图',
|
||||
icon: 'none'
|
||||
})
|
||||
return { status: 'failed', reason: 'navigation-failed' }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if (!isEmbeddedInWechatMiniProgram() && !isWechatBrowser()) {
|
||||
return { status: 'not-embedded' }
|
||||
}
|
||||
|
||||
const miniProgram = await waitForWechatMiniProgramBridge()
|
||||
if (!miniProgram?.navigateTo) {
|
||||
if (isEmbeddedInWechatMiniProgram()) {
|
||||
uni.showToast({
|
||||
title: '小程序地图桥接加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
return { status: 'failed', reason: 'bridge-unavailable' }
|
||||
}
|
||||
return { status: 'not-embedded' }
|
||||
}
|
||||
|
||||
const isMiniProgramWebView = await resolveMiniProgramWebViewEnvironment(miniProgram)
|
||||
if (!isMiniProgramWebView) return false
|
||||
if (!isMiniProgramWebView) {
|
||||
if (isEmbeddedInWechatMiniProgram()) {
|
||||
uni.showToast({
|
||||
title: '当前页面未连接宿主小程序',
|
||||
icon: 'none'
|
||||
})
|
||||
return { status: 'failed', reason: 'not-in-mini-program' }
|
||||
}
|
||||
return { status: 'not-embedded' }
|
||||
}
|
||||
|
||||
return navigateToMiniProgramLocationPage(miniProgram, target)
|
||||
return navigateToMiniProgramLocationPage(miniProgram, validation.value)
|
||||
}
|
||||
|
||||
export const openWechatMiniProgramLocation = (
|
||||
target: WechatMiniProgramLocationTarget
|
||||
) => {
|
||||
if (miniProgramNavigationPromise) return miniProgramNavigationPromise
|
||||
|
||||
miniProgramNavigationPromise = openWechatMiniProgramLocationOnce(target)
|
||||
.finally(() => {
|
||||
miniProgramNavigationPromise = null
|
||||
})
|
||||
return miniProgramNavigationPromise
|
||||
}
|
||||
|
||||
40
src/services/WechatOpenLocationService.ts
Normal file
40
src/services/WechatOpenLocationService.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { WechatOpenLocationTarget } from '@/utils/wechatOpenLocationProtocol'
|
||||
|
||||
export type WechatOpenLocationResult =
|
||||
| { status: 'opened' }
|
||||
| { status: 'failed'; reason: 'api-failed' | 'timeout' }
|
||||
|
||||
const OPEN_LOCATION_TIMEOUT = 5000
|
||||
|
||||
export const openWechatHostLocation = (
|
||||
target: WechatOpenLocationTarget
|
||||
) => new Promise<WechatOpenLocationResult>((resolve) => {
|
||||
let settled = false
|
||||
const finish = (result: WechatOpenLocationResult) => {
|
||||
if (settled) return
|
||||
settled = true
|
||||
clearTimeout(timeout)
|
||||
resolve(result)
|
||||
}
|
||||
const timeout = setTimeout(() => {
|
||||
finish({ status: 'failed', reason: 'timeout' })
|
||||
}, OPEN_LOCATION_TIMEOUT)
|
||||
|
||||
try {
|
||||
uni.openLocation({
|
||||
latitude: target.latitude,
|
||||
longitude: target.longitude,
|
||||
name: target.name,
|
||||
address: target.address || '',
|
||||
scale: 18,
|
||||
success: () => finish({ status: 'opened' }),
|
||||
fail: (error) => {
|
||||
console.error('打开地图位置失败:', error)
|
||||
finish({ status: 'failed', reason: 'api-failed' })
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('调用微信地图能力失败:', error)
|
||||
finish({ status: 'failed', reason: 'api-failed' })
|
||||
}
|
||||
})
|
||||
93
src/utils/wechatOpenLocationProtocol.ts
Normal file
93
src/utils/wechatOpenLocationProtocol.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
export const WECHAT_OPEN_LOCATION_PAGE_PATH = '/pages/open-location/index'
|
||||
|
||||
export interface WechatOpenLocationTarget {
|
||||
latitude: number
|
||||
longitude: number
|
||||
name: string
|
||||
address?: string
|
||||
}
|
||||
|
||||
export interface WechatOpenLocationPageOptions {
|
||||
latitude?: unknown
|
||||
longitude?: unknown
|
||||
name?: unknown
|
||||
address?: unknown
|
||||
}
|
||||
|
||||
export type WechatOpenLocationValidationResult =
|
||||
| { ok: true; value: WechatOpenLocationTarget }
|
||||
| { ok: false; message: string }
|
||||
|
||||
const MAX_NAME_LENGTH = 100
|
||||
const MAX_ADDRESS_LENGTH = 300
|
||||
|
||||
const firstValue = (value: unknown) => (
|
||||
Array.isArray(value) ? value[0] : value
|
||||
)
|
||||
|
||||
const normalizeText = (value: unknown) => {
|
||||
const first = firstValue(value)
|
||||
return typeof first === 'string' ? first.trim() : ''
|
||||
}
|
||||
|
||||
const normalizeCoordinate = (value: unknown) => {
|
||||
const first = firstValue(value)
|
||||
if (typeof first === 'string' && !first.trim()) return null
|
||||
if (typeof first !== 'string' && typeof first !== 'number') return null
|
||||
|
||||
const coordinate = Number(first)
|
||||
return Number.isFinite(coordinate) ? coordinate : null
|
||||
}
|
||||
|
||||
export const validateWechatOpenLocationTarget = (
|
||||
target: WechatOpenLocationPageOptions
|
||||
): WechatOpenLocationValidationResult => {
|
||||
const latitude = normalizeCoordinate(target.latitude)
|
||||
const longitude = normalizeCoordinate(target.longitude)
|
||||
const name = normalizeText(target.name)
|
||||
const address = normalizeText(target.address)
|
||||
|
||||
if (latitude === null || longitude === null) {
|
||||
return { ok: false, message: '地图坐标缺失或格式错误' }
|
||||
}
|
||||
if (latitude < -90 || latitude > 90 || longitude < -180 || longitude > 180) {
|
||||
return { ok: false, message: '地图坐标超出有效范围' }
|
||||
}
|
||||
if (!name) {
|
||||
return { ok: false, message: '目的地名称不能为空' }
|
||||
}
|
||||
if (name.length > MAX_NAME_LENGTH || address.length > MAX_ADDRESS_LENGTH) {
|
||||
return { ok: false, message: '目的地名称或地址过长' }
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
value: {
|
||||
latitude,
|
||||
longitude,
|
||||
name,
|
||||
address
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const buildWechatOpenLocationPageUrl = (
|
||||
target: WechatOpenLocationPageOptions
|
||||
) => {
|
||||
const result = validateWechatOpenLocationTarget(target)
|
||||
if (!result.ok) throw new TypeError(result.message)
|
||||
|
||||
const params = new URLSearchParams()
|
||||
params.set('latitude', String(result.value.latitude))
|
||||
params.set('longitude', String(result.value.longitude))
|
||||
params.set('name', result.value.name)
|
||||
params.set('address', result.value.address || '')
|
||||
|
||||
// 微信页面路由对加号的解码行为不统一,空格固定使用百分号编码。
|
||||
const query = params.toString().replace(/\+/g, '%20')
|
||||
return `${WECHAT_OPEN_LOCATION_PAGE_PATH}?${query}`
|
||||
}
|
||||
|
||||
export const parseWechatOpenLocationPageOptions = (
|
||||
options: WechatOpenLocationPageOptions = {}
|
||||
) => validateWechatOpenLocationTarget(options)
|
||||
231
tests/unit/ThirdPartyMapSearchService.spec.ts
Normal file
231
tests/unit/ThirdPartyMapSearchService.spec.ts
Normal file
@@ -0,0 +1,231 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('@/utils/hostEnvironment', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/utils/hostEnvironment')>()
|
||||
return {
|
||||
...actual,
|
||||
isNativeWechatMiniProgram: () => false
|
||||
}
|
||||
})
|
||||
|
||||
import {
|
||||
buildThirdPartyMapSearchUri,
|
||||
openWechatMiniProgramLocation
|
||||
} from '@/services/ThirdPartyMapSearchService'
|
||||
|
||||
const target = {
|
||||
latitude: 22.604321,
|
||||
longitude: 114.058765,
|
||||
name: '深圳自然博物馆',
|
||||
address: '深圳市龙华区民治街道'
|
||||
}
|
||||
|
||||
const createWindow = (overrides: Record<string, unknown> = {}) => ({
|
||||
location: { search: '', hash: '' },
|
||||
navigator: { userAgent: 'Mozilla/5.0 MicroMessenger/8.0.50' },
|
||||
...overrides
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
vi.stubGlobal('document', undefined)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
describe('微信小程序 web-view 地图桥接', () => {
|
||||
it('桥接延迟注入时快速调用两次也只跳转一次', async () => {
|
||||
const navigateTo = vi.fn((options: { success?: () => void }) => options.success?.())
|
||||
const testWindow = createWindow()
|
||||
vi.stubGlobal('window', testWindow)
|
||||
vi.stubGlobal('uni', { showToast: vi.fn() })
|
||||
|
||||
setTimeout(() => {
|
||||
Object.assign(testWindow, {
|
||||
__wxjs_environment: 'miniprogram',
|
||||
wx: { miniProgram: { navigateTo } }
|
||||
})
|
||||
}, 100)
|
||||
|
||||
const first = openWechatMiniProgramLocation(target)
|
||||
const second = openWechatMiniProgramLocation(target)
|
||||
await vi.advanceTimersByTimeAsync(150)
|
||||
|
||||
await expect(first).resolves.toEqual({ status: 'opened' })
|
||||
await expect(second).resolves.toEqual({ status: 'opened' })
|
||||
expect(navigateTo).toHaveBeenCalledTimes(1)
|
||||
|
||||
const url = navigateTo.mock.calls[0]?.[0].url
|
||||
const params = new URLSearchParams(url.split('?')[1])
|
||||
expect(Object.fromEntries(params)).toEqual({
|
||||
latitude: String(target.latitude),
|
||||
longitude: String(target.longitude),
|
||||
name: target.name,
|
||||
address: target.address
|
||||
})
|
||||
})
|
||||
|
||||
it('普通浏览器返回 H5 分支并保留第三方地图 URI', async () => {
|
||||
const showToast = vi.fn()
|
||||
vi.stubGlobal('window', createWindow({
|
||||
navigator: { userAgent: 'Mozilla/5.0 Chrome/126.0' }
|
||||
}))
|
||||
vi.stubGlobal('uni', { showToast })
|
||||
|
||||
await expect(openWechatMiniProgramLocation(target)).resolves.toEqual({ status: 'not-embedded' })
|
||||
expect(showToast).not.toHaveBeenCalled()
|
||||
expect(buildThirdPartyMapSearchUri('amap', {
|
||||
keyword: '深圳自然博物馆',
|
||||
region: '深圳市'
|
||||
})).toContain('https://uri.amap.com/search?')
|
||||
})
|
||||
|
||||
it('显式宿主环境缺少桥接时显示错误提示', async () => {
|
||||
const showToast = vi.fn()
|
||||
vi.stubGlobal('window', createWindow({
|
||||
location: { search: '?embedded=weapp', hash: '' }
|
||||
}))
|
||||
vi.stubGlobal('uni', { showToast })
|
||||
|
||||
const result = openWechatMiniProgramLocation(target)
|
||||
await vi.advanceTimersByTimeAsync(2600)
|
||||
|
||||
await expect(result).resolves.toEqual({ status: 'failed', reason: 'bridge-unavailable' })
|
||||
expect(showToast).toHaveBeenCalledWith({
|
||||
title: '小程序地图桥接加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
})
|
||||
|
||||
it('微信 SDK 首次加载失败后再次点击会重新创建脚本', async () => {
|
||||
const showToast = vi.fn()
|
||||
let currentScript: Record<string, unknown> | null = null
|
||||
const appendChild = vi.fn((script: Record<string, unknown>) => {
|
||||
currentScript = script
|
||||
setTimeout(() => (script.onerror as (() => void) | undefined)?.(), 0)
|
||||
})
|
||||
vi.stubGlobal('window', createWindow({
|
||||
location: { search: '?embedded=weapp', hash: '' }
|
||||
}))
|
||||
vi.stubGlobal('document', {
|
||||
querySelector: () => currentScript,
|
||||
createElement: () => {
|
||||
const script: Record<string, unknown> = { dataset: {} }
|
||||
script.remove = () => {
|
||||
if (currentScript === script) currentScript = null
|
||||
}
|
||||
return script
|
||||
},
|
||||
head: { appendChild }
|
||||
})
|
||||
vi.stubGlobal('uni', { showToast })
|
||||
|
||||
const first = openWechatMiniProgramLocation(target)
|
||||
await vi.advanceTimersByTimeAsync(2600)
|
||||
await expect(first).resolves.toEqual({ status: 'failed', reason: 'bridge-unavailable' })
|
||||
|
||||
const second = openWechatMiniProgramLocation(target)
|
||||
await vi.advanceTimersByTimeAsync(2600)
|
||||
await expect(second).resolves.toEqual({ status: 'failed', reason: 'bridge-unavailable' })
|
||||
expect(appendChild).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('宿主页跳转失败时给出缺页提示', async () => {
|
||||
const showToast = vi.fn()
|
||||
const navigateTo = vi.fn((options: { fail?: (error: unknown) => void }) => {
|
||||
options.fail?.({ errMsg: 'navigateTo:fail page not found' })
|
||||
})
|
||||
vi.stubGlobal('window', createWindow({
|
||||
location: { search: '?embedded=weapp', hash: '' },
|
||||
wx: { miniProgram: { navigateTo } }
|
||||
}))
|
||||
vi.stubGlobal('uni', { showToast })
|
||||
|
||||
await expect(openWechatMiniProgramLocation(target)).resolves.toEqual({
|
||||
status: 'failed',
|
||||
reason: 'navigation-failed'
|
||||
})
|
||||
expect(showToast).toHaveBeenCalledWith({
|
||||
title: '宿主小程序未配置地图页',
|
||||
icon: 'none'
|
||||
})
|
||||
})
|
||||
|
||||
it('宿主页没有回调时超时返回并显示提示', async () => {
|
||||
const showToast = vi.fn()
|
||||
let navigateOptions: { fail?: (error: unknown) => void } | undefined
|
||||
vi.stubGlobal('window', createWindow({
|
||||
location: { search: '?embedded=weapp', hash: '' },
|
||||
wx: {
|
||||
miniProgram: {
|
||||
navigateTo: vi.fn((options: { fail?: (error: unknown) => void }) => {
|
||||
navigateOptions = options
|
||||
})
|
||||
}
|
||||
}
|
||||
}))
|
||||
vi.stubGlobal('uni', { showToast })
|
||||
|
||||
const result = openWechatMiniProgramLocation(target)
|
||||
await vi.advanceTimersByTimeAsync(3100)
|
||||
|
||||
await expect(result).resolves.toEqual({ status: 'failed', reason: 'navigation-failed' })
|
||||
expect(showToast).toHaveBeenCalledWith({
|
||||
title: '宿主地图页响应超时',
|
||||
icon: 'none'
|
||||
})
|
||||
navigateOptions?.fail?.({ errMsg: 'navigateTo:fail page not found' })
|
||||
expect(showToast).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('非缺页类宿主跳转失败使用通用提示', async () => {
|
||||
const showToast = vi.fn()
|
||||
vi.stubGlobal('window', createWindow({
|
||||
location: { search: '?embedded=weapp', hash: '' },
|
||||
wx: {
|
||||
miniProgram: {
|
||||
navigateTo: vi.fn((options: { fail?: (error: unknown) => void }) => {
|
||||
options.fail?.({ errMsg: 'navigateTo:fail page stack full' })
|
||||
})
|
||||
}
|
||||
}
|
||||
}))
|
||||
vi.stubGlobal('uni', { showToast })
|
||||
|
||||
await expect(openWechatMiniProgramLocation(target)).resolves.toEqual({
|
||||
status: 'failed',
|
||||
reason: 'navigation-failed'
|
||||
})
|
||||
expect(showToast).toHaveBeenCalledWith({
|
||||
title: '宿主地图页跳转失败',
|
||||
icon: 'none'
|
||||
})
|
||||
})
|
||||
|
||||
it('宿主桥接同步抛错时显示调用失败提示', async () => {
|
||||
const showToast = vi.fn()
|
||||
vi.stubGlobal('window', createWindow({
|
||||
location: { search: '?embedded=weapp', hash: '' },
|
||||
wx: {
|
||||
miniProgram: {
|
||||
navigateTo: vi.fn(() => {
|
||||
throw new Error('bridge unavailable')
|
||||
})
|
||||
}
|
||||
}
|
||||
}))
|
||||
vi.stubGlobal('uni', { showToast })
|
||||
|
||||
await expect(openWechatMiniProgramLocation(target)).resolves.toEqual({
|
||||
status: 'failed',
|
||||
reason: 'navigation-failed'
|
||||
})
|
||||
expect(showToast).toHaveBeenCalledWith({
|
||||
title: '宿主地图桥接调用失败',
|
||||
icon: 'none'
|
||||
})
|
||||
})
|
||||
})
|
||||
53
tests/unit/WechatOpenLocationService.spec.ts
Normal file
53
tests/unit/WechatOpenLocationService.spec.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { openWechatHostLocation } from '@/services/WechatOpenLocationService'
|
||||
|
||||
const target = {
|
||||
latitude: 22.604321,
|
||||
longitude: 114.058765,
|
||||
name: '深圳自然博物馆',
|
||||
address: '深圳市龙华区民治街道'
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
describe('宿主微信地图能力', () => {
|
||||
it('向 uni.openLocation 透传完整位置参数', async () => {
|
||||
const openLocation = vi.fn((options: { success?: () => void }) => options.success?.())
|
||||
vi.stubGlobal('uni', { openLocation })
|
||||
|
||||
await expect(openWechatHostLocation(target)).resolves.toEqual({ status: 'opened' })
|
||||
expect(openLocation).toHaveBeenCalledWith(expect.objectContaining({
|
||||
...target,
|
||||
scale: 18
|
||||
}))
|
||||
})
|
||||
|
||||
it('宿主能力无回调时按超时失败并释放调用', async () => {
|
||||
vi.stubGlobal('uni', { openLocation: vi.fn() })
|
||||
|
||||
const result = openWechatHostLocation(target)
|
||||
await vi.advanceTimersByTimeAsync(5100)
|
||||
|
||||
await expect(result).resolves.toEqual({ status: 'failed', reason: 'timeout' })
|
||||
})
|
||||
|
||||
it('宿主能力同步抛错时返回可处理的失败结果', async () => {
|
||||
vi.stubGlobal('uni', {
|
||||
openLocation: vi.fn(() => {
|
||||
throw new Error('openLocation unavailable')
|
||||
})
|
||||
})
|
||||
|
||||
await expect(openWechatHostLocation(target)).resolves.toEqual({
|
||||
status: 'failed',
|
||||
reason: 'api-failed'
|
||||
})
|
||||
})
|
||||
})
|
||||
49
tests/unit/hostEnvironment.spec.ts
Normal file
49
tests/unit/hostEnvironment.spec.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { isEmbeddedInWechatMiniProgram } from '@/utils/hostEnvironment'
|
||||
|
||||
const createWindow = (overrides: Record<string, unknown> = {}) => ({
|
||||
location: { search: '', hash: '' },
|
||||
navigator: { userAgent: '' },
|
||||
...overrides
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
describe('微信小程序宿主环境识别', () => {
|
||||
it('同一模块可以识别延迟注入的微信环境标识', () => {
|
||||
const testWindow = createWindow()
|
||||
vi.stubGlobal('window', testWindow)
|
||||
|
||||
expect(isEmbeddedInWechatMiniProgram()).toBe(false)
|
||||
Object.assign(testWindow, { __wxjs_environment: 'miniprogram' })
|
||||
expect(isEmbeddedInWechatMiniProgram()).toBe(true)
|
||||
})
|
||||
|
||||
it('支持宿主在普通查询或 hash 查询中显式声明环境', () => {
|
||||
vi.stubGlobal('window', createWindow({
|
||||
location: {
|
||||
search: '?embedded=weapp',
|
||||
hash: '#/pages/index/index?tab=guide'
|
||||
}
|
||||
}))
|
||||
expect(isEmbeddedInWechatMiniProgram()).toBe(true)
|
||||
|
||||
vi.stubGlobal('window', createWindow({
|
||||
location: {
|
||||
search: '',
|
||||
hash: '#/pages/index/index?tab=guide&embedded=wechat-mini-program'
|
||||
}
|
||||
}))
|
||||
expect(isEmbeddedInWechatMiniProgram()).toBe(true)
|
||||
})
|
||||
|
||||
it('普通微信 H5 不会被误判为小程序 web-view', () => {
|
||||
vi.stubGlobal('window', createWindow({
|
||||
navigator: { userAgent: 'Mozilla/5.0 MicroMessenger/8.0.50' }
|
||||
}))
|
||||
|
||||
expect(isEmbeddedInWechatMiniProgram()).toBe(false)
|
||||
})
|
||||
})
|
||||
38
tests/unit/wechatOpenLocationProtocol.spec.ts
Normal file
38
tests/unit/wechatOpenLocationProtocol.spec.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
buildWechatOpenLocationPageUrl,
|
||||
parseWechatOpenLocationPageOptions,
|
||||
validateWechatOpenLocationTarget,
|
||||
WECHAT_OPEN_LOCATION_PAGE_PATH
|
||||
} from '@/utils/wechatOpenLocationProtocol'
|
||||
|
||||
describe('微信宿主地图参数协议', () => {
|
||||
it('中文及特殊字符编码后可以完整还原', () => {
|
||||
const source = {
|
||||
latitude: 22.604321,
|
||||
longitude: 114.058765,
|
||||
name: '深圳自然博物馆 A&B #1',
|
||||
address: '龙华区 100% 路口?入口=东&层=1'
|
||||
}
|
||||
const url = buildWechatOpenLocationPageUrl(source)
|
||||
const [path, query = ''] = url.split('?')
|
||||
const params = Object.fromEntries(new URLSearchParams(query))
|
||||
|
||||
expect(path).toBe(WECHAT_OPEN_LOCATION_PAGE_PATH)
|
||||
expect(parseWechatOpenLocationPageOptions(params)).toEqual({
|
||||
ok: true,
|
||||
value: source
|
||||
})
|
||||
})
|
||||
|
||||
it.each([
|
||||
[{ latitude: '', longitude: 114, name: '博物馆' }, '地图坐标缺失或格式错误'],
|
||||
[{ latitude: Number.NaN, longitude: 114, name: '博物馆' }, '地图坐标缺失或格式错误'],
|
||||
[{ latitude: 91, longitude: 114, name: '博物馆' }, '地图坐标超出有效范围'],
|
||||
[{ latitude: 22, longitude: 181, name: '博物馆' }, '地图坐标超出有效范围'],
|
||||
[{ latitude: 22, longitude: 114, name: ' ' }, '目的地名称不能为空']
|
||||
])('拒绝非法参数 %#', (target, message) => {
|
||||
expect(validateWechatOpenLocationTarget(target)).toEqual({ ok: false, message })
|
||||
expect(() => buildWechatOpenLocationPageUrl(target)).toThrow(message)
|
||||
})
|
||||
})
|
||||
16
vitest.config.ts
Normal file
16
vitest.config.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
}
|
||||
},
|
||||
test: {
|
||||
environment: 'node',
|
||||
include: ['tests/unit/**/*.spec.ts'],
|
||||
clearMocks: true,
|
||||
restoreMocks: true
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user