修复小程序内来馆地图导航
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
lyf
2026-07-11 17:24:45 +08:00
parent 12c0b5ede2
commit f9fefa11ef
12 changed files with 1161 additions and 146 deletions

View File

@@ -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
View File

@@ -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: {}

View File

@@ -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) => {

View File

@@ -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>

View File

@@ -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
}

View 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' })
}
})

View 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)

View 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'
})
})
})

View 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'
})
})
})

View 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)
})
})

View 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
View 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
}
})