diff --git a/package.json b/package.json
index 06580c6..27f0b07 100644
--- a/package.json
+++ b/package.json
@@ -18,17 +18,18 @@
"dependencies": {
"@dcloudio/uni-app": "3.0.0-alpha-5010120260519001",
"@dcloudio/uni-app-plus": "3.0.0-alpha-5010120260519001",
+ "@dcloudio/uni-components": "3.0.0-alpha-5010120260519001",
"@dcloudio/uni-h5": "3.0.0-alpha-5010120260519001",
"@dcloudio/uni-mp-weixin": "3.0.0-alpha-5010120260519001",
"three": "0.163.0",
"vue": "3.4.21"
},
"devDependencies": {
- "@types/three": "0.163.0",
"@dcloudio/types": "3.4.31",
"@dcloudio/uni-automator": "3.0.0-alpha-5010120260519001",
"@dcloudio/uni-cli-shared": "3.0.0-alpha-5010120260519001",
"@dcloudio/vite-plugin-uni": "3.0.0-alpha-5010120260519001",
+ "@types/three": "0.163.0",
"@typescript-eslint/eslint-plugin": "7.18.0",
"@typescript-eslint/parser": "7.18.0",
"@vue/runtime-core": "3.4.21",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index eff68ec..554fbfe 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -14,6 +14,9 @@ importers:
'@dcloudio/uni-app-plus':
specifier: 3.0.0-alpha-5010120260519001
version: 3.0.0-alpha-5010120260519001(postcss@8.5.15)(rollup@4.60.4)(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))
+ '@dcloudio/uni-components':
+ specifier: 3.0.0-alpha-5010120260519001
+ version: 3.0.0-alpha-5010120260519001(postcss@8.5.15)(rollup@4.60.4)(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))
'@dcloudio/uni-h5':
specifier: 3.0.0-alpha-5010120260519001
version: 3.0.0-alpha-5010120260519001(postcss@8.5.15)(rollup@4.60.4)(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))
@@ -136,6 +139,10 @@ packages:
resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-globals@7.29.7':
+ resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-member-expression-to-functions@7.28.5':
resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==}
engines: {node: '>=6.9.0'}
@@ -178,14 +185,26 @@ packages:
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-string-parser@7.29.7':
+ resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-validator-identifier@7.28.5':
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-validator-identifier@7.29.7':
+ resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-validator-option@7.27.1':
resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-validator-option@7.29.7':
+ resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-wrap-function@7.28.6':
resolution: {integrity: sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==}
engines: {node: '>=6.9.0'}
@@ -2420,8 +2439,8 @@ packages:
debug:
optional: true
- form-data@3.0.4:
- resolution: {integrity: sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==}
+ form-data@3.0.5:
+ resolution: {integrity: sha512-j23EibVLnp4zNXGW7LjryXYa2X6U/M96yoOX+ybZxwkYajdxRNEqYY3zhh7y0i6kfISKS2jr+EJq1YTUDEv5+w==}
engines: {node: '>= 6'}
forwarded@0.2.0:
@@ -2485,9 +2504,9 @@ packages:
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
engines: {node: '>=10.13.0'}
- glob@7.2.3:
- resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
- deprecated: Glob versions prior to v9 are no longer supported
+ glob@7.2.0:
+ resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==}
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
global@4.4.0:
resolution: {integrity: sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==}
@@ -2533,6 +2552,10 @@ packages:
resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==}
engines: {node: '>= 0.4'}
+ hasown@2.0.4:
+ resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
+ engines: {node: '>= 0.4'}
+
he@1.2.0:
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
hasBin: true
@@ -2836,8 +2859,8 @@ packages:
js-tokens@9.0.1:
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
- js-yaml@3.14.2:
- resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==}
+ js-yaml@3.15.0:
+ resolution: {integrity: sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==}
hasBin: true
js-yaml@4.2.0:
@@ -3097,8 +3120,8 @@ packages:
nth-check@2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
- nwsapi@2.2.23:
- resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==}
+ nwsapi@2.2.24:
+ resolution: {integrity: sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==}
object-inspect@1.13.4:
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
@@ -4054,8 +4077,8 @@ packages:
write-file-atomic@3.0.3:
resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==}
- ws@7.5.10:
- resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==}
+ ws@7.5.11:
+ resolution: {integrity: sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==}
engines: {node: '>=8.3.0'}
peerDependencies:
bufferutil: ^4.0.1
@@ -4128,8 +4151,8 @@ packages:
resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
engines: {node: '>=10'}
- yargs@16.2.0:
- resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
+ yargs@16.2.2:
+ resolution: {integrity: sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==}
engines: {node: '>=10'}
yocto-queue@0.1.0:
@@ -4150,7 +4173,7 @@ snapshots:
'@babel/code-frame@7.29.0':
dependencies:
- '@babel/helper-validator-identifier': 7.28.5
+ '@babel/helper-validator-identifier': 7.29.7
js-tokens: 4.0.0
picocolors: 1.1.1
@@ -4218,7 +4241,7 @@ snapshots:
'@babel/helper-compilation-targets@7.28.6':
dependencies:
'@babel/compat-data': 7.29.3
- '@babel/helper-validator-option': 7.27.1
+ '@babel/helper-validator-option': 7.29.7
browserslist: 4.28.2
lru-cache: 5.1.1
semver: 6.3.1
@@ -4256,6 +4279,8 @@ snapshots:
'@babel/helper-globals@7.28.0': {}
+ '@babel/helper-globals@7.29.7': {}
+
'@babel/helper-member-expression-to-functions@7.28.5':
dependencies:
'@babel/traverse': 7.29.0
@@ -4274,7 +4299,7 @@ snapshots:
dependencies:
'@babel/core': 7.25.2
'@babel/helper-module-imports': 7.28.6
- '@babel/helper-validator-identifier': 7.28.5
+ '@babel/helper-validator-identifier': 7.29.7
'@babel/traverse': 7.29.0
transitivePeerDependencies:
- supports-color
@@ -4283,7 +4308,7 @@ snapshots:
dependencies:
'@babel/core': 7.29.0
'@babel/helper-module-imports': 7.28.6
- '@babel/helper-validator-identifier': 7.28.5
+ '@babel/helper-validator-identifier': 7.29.7
'@babel/traverse': 7.29.0
transitivePeerDependencies:
- supports-color
@@ -4321,10 +4346,16 @@ snapshots:
'@babel/helper-string-parser@7.27.1': {}
+ '@babel/helper-string-parser@7.29.7': {}
+
'@babel/helper-validator-identifier@7.28.5': {}
+ '@babel/helper-validator-identifier@7.29.7': {}
+
'@babel/helper-validator-option@7.27.1': {}
+ '@babel/helper-validator-option@7.29.7': {}
+
'@babel/helper-wrap-function@7.28.6':
dependencies:
'@babel/template': 7.28.6
@@ -4955,7 +4986,7 @@ snapshots:
dependencies:
'@babel/code-frame': 7.29.0
'@babel/generator': 7.29.1
- '@babel/helper-globals': 7.28.0
+ '@babel/helper-globals': 7.29.7
'@babel/parser': 7.29.3
'@babel/template': 7.28.6
'@babel/types': 7.29.0
@@ -4971,8 +5002,8 @@ snapshots:
'@babel/types@7.29.0':
dependencies:
- '@babel/helper-string-parser': 7.27.1
- '@babel/helper-validator-identifier': 7.28.5
+ '@babel/helper-string-parser': 7.29.7
+ '@babel/helper-validator-identifier': 7.29.7
'@bcoe/v8-coverage@0.2.3': {}
@@ -5559,7 +5590,7 @@ snapshots:
camelcase: 5.3.1
find-up: 4.1.0
get-package-type: 0.1.0
- js-yaml: 3.14.2
+ js-yaml: 3.15.0
resolve-from: 5.0.0
'@istanbuljs/schema@0.1.6': {}
@@ -5643,7 +5674,7 @@ snapshots:
chalk: 4.1.2
collect-v8-coverage: 1.0.3
exit: 0.1.2
- glob: 7.2.3
+ glob: 7.2.0
graceful-fs: 4.2.11
istanbul-lib-coverage: 3.2.2
istanbul-lib-instrument: 5.2.1
@@ -7271,12 +7302,12 @@ snapshots:
follow-redirects@1.16.0: {}
- form-data@3.0.4:
+ form-data@3.0.5:
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
es-set-tostringtag: 2.1.0
- hasown: 2.0.3
+ hasown: 2.0.4
mime-types: 2.1.35
forwarded@0.2.0: {}
@@ -7336,7 +7367,7 @@ snapshots:
dependencies:
is-glob: 4.0.3
- glob@7.2.3:
+ glob@7.2.0:
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
@@ -7385,6 +7416,10 @@ snapshots:
dependencies:
function-bind: 1.1.2
+ hasown@2.0.4:
+ dependencies:
+ function-bind: 1.1.2
+
he@1.2.0: {}
html-encoding-sniffer@2.0.1:
@@ -7567,7 +7602,7 @@ snapshots:
jest-util: 27.5.1
jest-validate: 27.5.1
prompts: 2.4.2
- yargs: 16.2.0
+ yargs: 16.2.2
transitivePeerDependencies:
- bufferutil
- canvas
@@ -7584,7 +7619,7 @@ snapshots:
chalk: 4.1.2
ci-info: 3.9.0
deepmerge: 4.3.1
- glob: 7.2.3
+ glob: 7.2.0
graceful-fs: 4.2.11
jest-circus: 27.5.1
jest-environment-jsdom: 27.5.1
@@ -7789,7 +7824,7 @@ snapshots:
cjs-module-lexer: 1.4.3
collect-v8-coverage: 1.0.3
execa: 5.1.1
- glob: 7.2.3
+ glob: 7.2.0
graceful-fs: 4.2.11
jest-haste-map: 27.5.1
jest-message-util: 27.5.1
@@ -7898,7 +7933,7 @@ snapshots:
js-tokens@9.0.1: {}
- js-yaml@3.14.2:
+ js-yaml@3.15.0:
dependencies:
argparse: 1.0.10
esprima: 4.0.1
@@ -7918,12 +7953,12 @@ snapshots:
decimal.js: 10.6.0
domexception: 2.0.1
escodegen: 2.1.0
- form-data: 3.0.4
+ form-data: 3.0.5
html-encoding-sniffer: 2.0.1
http-proxy-agent: 4.0.1
https-proxy-agent: 5.0.1
is-potential-custom-element-name: 1.0.1
- nwsapi: 2.2.23
+ nwsapi: 2.2.24
parse5: 6.0.1
saxes: 5.0.1
symbol-tree: 3.2.4
@@ -7934,7 +7969,7 @@ snapshots:
whatwg-encoding: 1.0.5
whatwg-mimetype: 2.3.0
whatwg-url: 8.7.0
- ws: 7.5.10
+ ws: 7.5.11
xml-name-validator: 3.0.0
transitivePeerDependencies:
- bufferutil
@@ -8140,7 +8175,7 @@ snapshots:
dependencies:
boolbase: 1.0.0
- nwsapi@2.2.23: {}
+ nwsapi@2.2.24: {}
object-inspect@1.13.4: {}
@@ -8478,7 +8513,7 @@ snapshots:
rimraf@3.0.2:
dependencies:
- glob: 7.2.3
+ glob: 7.2.0
rollup@4.60.4:
dependencies:
@@ -8812,7 +8847,7 @@ snapshots:
test-exclude@6.0.0:
dependencies:
'@istanbuljs/schema': 0.1.6
- glob: 7.2.3
+ glob: 7.2.0
minimatch: 3.1.5
text-table@0.2.0: {}
@@ -9069,7 +9104,7 @@ snapshots:
signal-exit: 3.0.7
typedarray-to-buffer: 3.1.5
- ws@7.5.10: {}
+ ws@7.5.11: {}
ws@8.18.0: {}
@@ -9111,7 +9146,7 @@ snapshots:
yargs-parser@20.2.9: {}
- yargs@16.2.0:
+ yargs@16.2.2:
dependencies:
cliui: 7.0.4
escalade: 3.2.0
diff --git a/src/assets/data/README.md b/src/assets/data/README.md
index dc79e80..439fe5c 100644
--- a/src/assets/data/README.md
+++ b/src/assets/data/README.md
@@ -2,7 +2,7 @@
这些 JSON 是早期演示数据,当前导览模块不再以这里作为数据源。
-当前室内导览、设施搜索、位置预览统一通过 Provider / Adapter / Repository / UseCase 数据边界读取 `static/nav-assets/codex_nav_20260621_175310_museum_default_centerline_finalxy`,页面和展示组件不要直接依赖静态包 JSON 文件。
+当前馆内导览、设施搜索、位置预览统一通过 Provider / Adapter / Repository / UseCase 数据边界读取 `static/nav-assets/codex_nav_20260621_175310_museum_default_centerline_finalxy`,页面和展示组件不要直接依赖静态包 JSON 文件。
当前展厅、单元、讲解点、展品、讲解词和 POI 业务内容统一通过 `static/guide-data` 静态数据包读取,默认数据源模式为 `VITE_GUIDE_CONTENT_SOURCE_MODE=static`。
diff --git a/src/components/explain/ExplainHallSelect.vue b/src/components/explain/ExplainHallSelect.vue
index 8476af5..b713227 100644
--- a/src/components/explain/ExplainHallSelect.vue
+++ b/src/components/explain/ExplainHallSelect.vue
@@ -24,11 +24,6 @@
-
- 请选择展厅
- {{ filteredHalls.length }} 个展厅
-
-
{{ hallIconText(hall.name) }}
@@ -96,8 +91,26 @@ const emit = defineEmits<{
back: []
}>()
+const HALL_ICON_BASE = '/static/icons/halls'
+
+const hallIconMap: Record = {
+ 宇宙厅: `${HALL_ICON_BASE}/universe.jpg`,
+ 地球厅: `${HALL_ICON_BASE}/earth.jpg`,
+ 演化厅: `${HALL_ICON_BASE}/evolution.jpg`,
+ 恐龙厅: `${HALL_ICON_BASE}/dinosaur.jpg`,
+ 人类厅: `${HALL_ICON_BASE}/human.jpg`,
+ 动物厅: `${HALL_ICON_BASE}/animal.jpg`,
+ 生物厅: `${HALL_ICON_BASE}/biology.jpg`,
+ 生态厅: `${HALL_ICON_BASE}/ecology.jpg`,
+ 家园厅: `${HALL_ICON_BASE}/homeland.jpg`
+}
+
const filteredHalls = computed(() => props.halls)
+const hallIconUrl = (hall: ExplainHallSelectItem) => {
+ return hallIconMap[hall.name.trim()] || hall.image || ''
+}
+
const hallIconText = (name: string) => name.trim().slice(0, 1) || '讲'
const handleHallClick = (hallId: string) => {
@@ -124,8 +137,6 @@ const handleBack = () => {
box-sizing: border-box;
}
-.section-title,
-.section-count,
.state-title,
.state-desc,
.hall-name,
@@ -180,29 +191,6 @@ const handleBack = () => {
color: #151713;
}
-.section-heading {
- margin-top: 0;
- margin-bottom: 16px;
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
-}
-
-.section-title {
- font-size: 19px;
- line-height: 27px;
- font-weight: 800;
- color: #151713;
-}
-
-.section-count {
- flex-shrink: 0;
- font-size: 12px;
- line-height: 18px;
- color: #7b8275;
-}
-
.hall-list {
display: flex;
flex-direction: column;
@@ -231,7 +219,10 @@ const handleBack = () => {
width: 68px;
height: 68px;
border-radius: 8px;
- background: #f1f2ee;
+ background: #ffffff;
+ border: 1px solid rgba(36, 49, 42, 0.06);
+ box-sizing: border-box;
+ object-fit: contain;
}
.hall-thumb.placeholder {
diff --git a/src/components/map/TencentMap.vue b/src/components/map/TencentMap.vue
index acb0b68..0741c12 100644
--- a/src/components/map/TencentMap.vue
+++ b/src/components/map/TencentMap.vue
@@ -38,10 +38,10 @@
-
+
🏛
- 室内
+ 馆内
📍
@@ -129,7 +129,7 @@ const emit = defineEmits<{
// POI ID: 7043042949572197975
const mapCenter = ref({
latitude: 22.692763, // 深圳自然博物馆纬度
- longitude: 114.363572 // 深圳自然博物馆经度(调整到室外 2D 视觉中心)
+ longitude: 114.363572 // 深圳自然博物馆经度(调整到馆外 2D 视觉中心)
})
// 地图缩放级别(调整为适中比例,既能看到建筑全貌又能看清细节)
@@ -378,9 +378,9 @@ const handleZoomOut = () => {
}
}
-// 进入 3D 室内模式
+// 进入 3D 馆内模式
const handleEnter3D = () => {
- console.log('进入 3D 室内模式')
+ console.log('进入 3D 馆内模式')
emit('enter3DMode')
}
@@ -490,7 +490,7 @@ onMounted(async () => {
color: #333333;
}
-// 室内模式按钮
+// 馆内模式按钮
.indoor-mode-btn {
width: 46px;
height: 64px !important;
diff --git a/src/components/map/ThreeMap.vue b/src/components/map/ThreeMap.vue
index b05f21b..b94e071 100644
--- a/src/components/map/ThreeMap.vue
+++ b/src/components/map/ThreeMap.vue
@@ -72,6 +72,7 @@ type TouchGestureMode = 'orbit' | 'pan'
type ZoomCameraSource = 'button' | 'gesture'
const CANVAS_FONT_FAMILY = '"HarmonyOS Sans SC", "HarmonyOS Sans", "鸿蒙黑体", "Microsoft YaHei", "PingFang SC", "Noto Sans CJK SC", Arial, sans-serif'
+const FLOOR_CAMERA_DISTANCE_FACTOR = 0.78
interface CameraFitOptions {
distanceFactor?: number
@@ -203,7 +204,7 @@ const props = withDefaults(defineProps<{
showPoi: false,
targetFocus: null,
touchGestureMode: 'orbit',
- targetFocusDistanceFactor: 0.22,
+ targetFocusDistanceFactor: 0.36,
routePreview: null,
showRoute: false,
autoSwitch: true,
@@ -327,6 +328,9 @@ const syncControlInteractionOptions = () => {
}
const corePoiCategories = new Set([
+ 'touring_poi',
+ 'exhibition_hall',
+ 'exhibition_hall_entrance',
'basic_service_facility',
'transport_circulation',
'accessibility_special_service'
@@ -334,10 +338,12 @@ const corePoiCategories = new Set([
const poiCategoryPriority: Record = {
target_preview: 100,
+ exhibition_hall: 96,
+ exhibition_hall_entrance: 92,
+ touring_poi: 90,
accessibility_special_service: 88,
basic_service_facility: 82,
transport_circulation: 76,
- touring_poi: 58,
operation_experience: 46
}
@@ -351,6 +357,9 @@ const poiGlyphMap: Record = {
stair: '梯',
experience: '展',
theater: '演',
+ exhibition_hall: '展',
+ hall_entrance: '门',
+ entrance_exit: '门',
target_preview: '位'
}
@@ -2217,6 +2226,11 @@ const fitCameraToObject = (object: THREE.Object3D, preset: CameraPreset = 'obliq
screenOffsetRatio: new THREE.Vector2(-0.045, 0.02),
up: new THREE.Vector3(0, 1, 0)
}
+ : activeView.value === 'floor'
+ ? {
+ distanceFactor: FLOOR_CAMERA_DISTANCE_FACTOR,
+ up: new THREE.Vector3(0, 1, 0)
+ }
: {}
setCameraView(center, maxDim, direction, overviewFitOptions)
@@ -2918,6 +2932,8 @@ const showFocusPoiAffordances = (poi: RenderPoi) => {
const getPoiColor = (category: string) => {
const colorMap: Record = {
touring_poi: '#2f6fed',
+ exhibition_hall: '#2f6fed',
+ exhibition_hall_entrance: '#4659d8',
basic_service_facility: '#1f8f5f',
transport_circulation: '#d77b20',
accessibility_special_service: '#8a5cf6',
@@ -3166,7 +3182,7 @@ const triggerPoiTapFeedback = (sprite: THREE.Sprite) => {
getPoiSpriteUserData(sprite).feedbackUntil = performance.now() + poiTapFeedbackDurationMs
}
-const handleSceneTap = (event: PointerEvent) => {
+const handleSceneTap = async (event: PointerEvent) => {
if (!camera || !renderer || !getContainerElement()) return
const rect = renderer.domElement.getBoundingClientRect()
@@ -3177,7 +3193,7 @@ const handleSceneTap = (event: PointerEvent) => {
const raycaster = new THREE.Raycaster()
raycaster.setFromCamera(pointer, camera)
- if (activeView.value === 'floor' && poiGroup) {
+ if ((activeView.value === 'floor' || activeView.value === 'overview') && poiGroup) {
const poiHits = raycaster.intersectObjects([...getPoiHitTargets(), ...getPoiSprites()], false)
const hit = poiHits.find((item) => (
item.object.visible
@@ -3187,10 +3203,27 @@ const handleSceneTap = (event: PointerEvent) => {
const hitMarker = hit ? getPoiMarkerFromHit(hit.object) : findNearestPoiMarkerByScreenPoint(event, rect)
if (hitMarker?.userData.poi) {
- const poi = hitMarker.userData.poi as RenderPoi
+ const selectedPoi = hitMarker.userData.poi as RenderPoi
+ if (activeView.value === 'overview') {
+ disableAutoSwitchTemporarily(10000)
+ try {
+ await loadFloor(selectedPoi.floorId, {
+ preserveCurrentSceneUntilReady: hasRenderableSceneForFloorTransition(),
+ suppressProgress: true,
+ allowOverviewFallback: true
+ })
+ emit('floorChange', selectedPoi.floorId)
+ } catch (error) {
+ if (isStaleModelLoadError(error)) return
+ console.error('展厅点位楼层切换失败:', error)
+ }
+ }
+
+ const poi = findLoadedPoi(selectedPoi.id) || selectedPoi
+ const focusMarker = findPoiSprite(poi.id) || hitMarker
selectedPOI.value = poi
activeFocusPoiId.value = poi.id
- triggerPoiTapFeedback(hitMarker)
+ triggerPoiTapFeedback(focusMarker)
updatePoiMarkerFocus()
showFocusPoiAffordances(poi)
focusCameraOnPoi(poi)
@@ -3231,7 +3264,7 @@ const handlePointerUp = (event: PointerEvent) => {
if (moveDistance > 8) return
- handleSceneTap(event)
+ void handleSceneTap(event)
}
const emitTargetFocus = (
@@ -3302,9 +3335,12 @@ const focusTargetPoi = async (request: TargetPoiFocusRequest) => {
try {
if (activeView.value !== 'floor' || currentFloor.value !== request.floorId) {
- isLoading.value = true
loadError.value = false
- await loadFloor(request.floorId)
+ await loadFloor(request.floorId, {
+ preserveCurrentSceneUntilReady: hasRenderableSceneForFloorTransition(),
+ suppressProgress: true,
+ allowOverviewFallback: true
+ })
isLoading.value = false
emit('floorChange', request.floorId)
} else if (shouldRenderPoiMarkers.value && !getPoiSprites().length) {
@@ -3419,31 +3455,19 @@ const init3DScene = async () => {
const handleFloorChange = async (floorId: string) => {
const hasSceneToKeep = hasRenderableSceneForFloorTransition()
- const showLoading = !hasSceneToKeep && !canLoadFloorSilently(floorId)
try {
- if (showLoading) {
- isLoading.value = true
- }
loadError.value = false
await loadFloor(floorId, {
preserveCurrentSceneUntilReady: hasSceneToKeep,
- suppressProgress: !showLoading
+ suppressProgress: true,
+ allowOverviewFallback: true
})
updatePoiVisibilityByDistance()
- if (showLoading) {
- isLoading.value = false
- } else {
- isLoading.value = false
- }
+ isLoading.value = false
emit('floorChange', floorId)
} catch (error) {
- if (isStaleModelLoadError(error)) {
- if (showLoading) {
- isLoading.value = false
- }
- return
- }
+ if (isStaleModelLoadError(error)) return
console.error('楼层模型加载失败:', error)
loadError.value = true
diff --git a/src/components/navigation/GuideMapShell.vue b/src/components/navigation/GuideMapShell.vue
index 0f0fe87..3c62321 100644
--- a/src/components/navigation/GuideMapShell.vue
+++ b/src/components/navigation/GuideMapShell.vue
@@ -126,11 +126,15 @@
{{ layerModeActionLabel }}
-
- {{ floor }}
+ {{ floor.label }}
-
+
@@ -364,7 +369,7 @@ const props = withDefaults(defineProps<{
dimmed: false,
targetFocusRequest: null,
touchGestureMode: 'orbit',
- targetFocusDistanceFactor: 0.22,
+ targetFocusDistanceFactor: 0.36,
routePreview: null,
showRoute: false,
disableAutoExit: false,
@@ -415,7 +420,11 @@ const indoorRendererRef = ref<{
} | null>(null)
const indoorFloors = computed(() => props.floors.filter((floor) => isIndoorNavigableFloor(floor)))
-const floorLabels = computed(() => indoorFloors.value.map((floor) => floor.label))
+const createFloorScrollId = (floorId: string) => `guide-floor-${floorId.replace(/[^a-zA-Z0-9_-]/g, '-')}`
+const floorItems = computed(() => indoorFloors.value.map((floor) => ({
+ ...floor,
+ scrollId: createFloorScrollId(floor.id)
+})))
// #ifdef H5
const effectiveIndoorModelSource = computed(() => props.indoorModelSource)
// #endif
@@ -435,6 +444,29 @@ const activeFloorId = computed(() => {
))
return normalizedFloor?.id || normalizedFloorId
})
+const activeFloorScrollIntoView = ref('')
+
+const syncActiveFloorScroll = () => {
+ if (props.mapType !== 'indoor' || props.indoorView === 'overview' || props.layerMode === 'multi') {
+ activeFloorScrollIntoView.value = ''
+ return
+ }
+
+ const matchedFloor = floorItems.value.find((floor) => floor.id === activeFloorId.value)
+ activeFloorScrollIntoView.value = matchedFloor?.scrollId || ''
+}
+
+watch(
+ () => [
+ activeFloorId.value,
+ props.indoorView,
+ props.layerMode,
+ props.mapType,
+ floorItems.value.map((floor) => floor.id).join('|')
+ ],
+ syncActiveFloorScroll,
+ { immediate: true }
+)
const searchFieldStyle = computed(() => ({
top: props.searchTop
diff --git a/src/components/navigation/LocationPreview.vue b/src/components/navigation/LocationPreview.vue
index c831a8b..eb0827a 100644
--- a/src/components/navigation/LocationPreview.vue
+++ b/src/components/navigation/LocationPreview.vue
@@ -22,7 +22,7 @@
- 室外入口参考
+ 馆外入口参考
查看三维位置
diff --git a/src/components/navigation/OutdoorNavigationPanel.vue b/src/components/navigation/OutdoorNavigationPanel.vue
index f221539..2a56cb8 100644
--- a/src/components/navigation/OutdoorNavigationPanel.vue
+++ b/src/components/navigation/OutdoorNavigationPanel.vue
@@ -220,7 +220,7 @@ const canNavigate = computed(() => {
const navigateButtonText = computed(() => {
if (props.loading) return '规划中...'
- if (hasRoute.value) return '开始导航'
+ if (hasRoute.value) return '打开地图'
return '规划路线'
})
diff --git a/src/components/navigation/RoutePlannerPanel.vue b/src/components/navigation/RoutePlannerPanel.vue
index 4056887..020af73 100644
--- a/src/components/navigation/RoutePlannerPanel.vue
+++ b/src/components/navigation/RoutePlannerPanel.vue
@@ -195,17 +195,11 @@ const canViewRoute = computed(() => Boolean(
const canPrimaryAction = computed(() => canViewRoute.value && !props.error)
-const primaryActionText = computed(() => (
- props.hasRoutePreview
- ? '开始导览'
- : (props.routeReady ? '查看路线' : '查看位置关系')
-))
+const primaryActionText = computed(() => '查看位置关系')
const routeOptionsTitle = computed(() => '馆内导览')
-const loadingText = computed(() => (
- props.routeReady ? '正在规划馆内导览路线' : '正在生成馆内位置关系'
-))
+const loadingText = computed(() => '正在生成馆内位置关系')
const collapsedSummary = computed(() => {
if (props.startPoint && props.endPoint) {
@@ -217,7 +211,7 @@ const collapsedSummary = computed(() => {
const routeOptions = computed(() => {
const unavailable = Boolean(props.error)
- const summaryText = props.summary || (props.routeReady ? '选择后规划真实路线' : '选择后查看位置关系')
+ const summaryText = props.summary || '选择后查看位置关系'
return [
{
id: 'recommended',
@@ -226,23 +220,21 @@ const routeOptions = computed(() => {
? '当前暂不可用'
: props.hasRoutePreview
? summaryText
- : props.routeReady
- ? '调用 SGS SDK 路线规划'
- : '查看起点终点位置关系',
+ : '查看起点终点位置关系',
active: !unavailable,
disabled: unavailable
},
{
id: 'stairs',
title: '少走楼梯',
- meta: props.routeReady ? '偏好能力待接入' : '偏好预览,待路线数据验证',
+ meta: '偏好预览,待路线数据验证',
active: false,
disabled: true
},
{
id: 'elevator',
title: '电梯优先',
- meta: props.routeReady ? '偏好能力待接入' : '偏好预览,待路线数据验证',
+ meta: '偏好预览,待路线数据验证',
active: false,
disabled: true
}
diff --git a/src/data/adapters/navAssetsAdapter.ts b/src/data/adapters/navAssetsAdapter.ts
index f1bb3ab..5412a7e 100644
--- a/src/data/adapters/navAssetsAdapter.ts
+++ b/src/data/adapters/navAssetsAdapter.ts
@@ -67,5 +67,7 @@ export const toMuseumPoi = (poi: StaticNavPoiPayload): MuseumPoi => ({
sourceObjectName: poi.sourceObjectName,
sourceConfidence: poi.sourceConfidence,
navigationReadiness: poi.navigationReadiness,
- accessible: isPoiAccessible(poi)
+ accessible: isPoiAccessible(poi),
+ kind: poi.primaryCategory === 'touring_poi' ? 'hall' : 'facility',
+ hallName: poi.primaryCategory === 'touring_poi' ? poi.name : undefined
})
diff --git a/src/data/adapters/sgsSdkRouteAdapter.ts b/src/data/adapters/sgsSdkRouteAdapter.ts
index 2496688..67e7ea2 100644
--- a/src/data/adapters/sgsSdkRouteAdapter.ts
+++ b/src/data/adapters/sgsSdkRouteAdapter.ts
@@ -7,6 +7,7 @@ import type {
GuideRouteTarget
} from '@/domain/museum'
import type {
+ SgsRoutePathPointPayload,
SgsRoutePlanResponsePayload
} from '@/data/providers/sgsSdkApiProvider'
import type {
@@ -31,10 +32,25 @@ type GeoJsonLineString = {
coordinates?: Array<[number, number] | [number, number, number]>
}
+const finiteNumber = (value: unknown) => {
+ const numericValue = Number(value)
+ return Number.isFinite(numericValue) ? numericValue : undefined
+}
+
+const stringifyRouteId = (value: unknown, fallback = '') => {
+ if (value === null || typeof value === 'undefined' || value === '') return fallback
+ return String(value)
+}
+
+const samePosition = (
+ a: [number, number, number],
+ b: [number, number, number]
+) => a.every((value, index) => Math.abs(value - b[index]) < 0.001)
+
const pointPosition = (point: SgsRoutePathNode): [number, number, number] => [
- Number(point.x || 0),
- Number(point.y || 0),
- Number(point.z || 0)
+ finiteNumber(point.x) ?? 0,
+ finiteNumber(point.y) ?? 0,
+ finiteNumber(point.z) ?? 0
]
const targetEndpoint = (target: GuideRouteTarget): GuideRouteEndpoint => ({
@@ -165,8 +181,10 @@ export const toGuideRouteResultFromSgs = (
const floorLabelFor = (
floorId: string,
startTarget: GuideRouteTarget,
- endTarget: GuideRouteTarget
+ endTarget: GuideRouteTarget,
+ fallbackLabel?: string | null
) => {
+ if (fallbackLabel) return fallbackLabel
if (floorId === startTarget.floorId) return startTarget.floorLabel
if (floorId === endTarget.floorId) return endTarget.floorLabel
return floorId
@@ -176,19 +194,54 @@ const pointFromBackendNode = (
node: NonNullable[number],
index: number,
fallbackFloorId: string
-): GuideRoutePoint => ({
- nodeId: String(node.id || `sgs-route-node-${index}`),
- floorId: toAppFloorId(node.floorId ?? fallbackFloorId),
- position: [
- Number(node.x || 0),
- 0,
- Number(node.y || 0)
- ]
-})
+): GuideRoutePoint | null => {
+ const x = finiteNumber(node.x)
+ const z = finiteNumber(node.y)
+ if (typeof x !== 'number' || typeof z !== 'number') return null
+
+ return {
+ nodeId: stringifyRouteId(node.id, `sgs-route-node-${index}`),
+ floorId: stringifyRouteId(node.floorId, fallbackFloorId),
+ position: [
+ x,
+ finiteNumber(node.z) ?? 0,
+ z
+ ]
+ }
+}
+
+const pointFromPathPoint = (
+ point: SgsRoutePathPointPayload,
+ floorId: string,
+ nodeId: string
+): GuideRoutePoint | null => {
+ const x = finiteNumber(point.x)
+ const z = finiteNumber(point.z)
+ if (typeof x !== 'number' || typeof z !== 'number') return null
+
+ return {
+ nodeId,
+ floorId,
+ position: [
+ x,
+ finiteNumber(point.y) ?? 0,
+ z
+ ]
+ }
+}
+
+const pointsFromPathPoints = (
+ pathPoints: SgsRoutePathPointPayload[] | null | undefined,
+ floorId: string,
+ nodeIdPrefix: string
+) => (pathPoints || [])
+ .map((point, index) => pointFromPathPoint(point, floorId, `${nodeIdPrefix}-pp-${index}`))
+ .filter((point): point is GuideRoutePoint => Boolean(point))
const pointsFromGeoJson = (
geoJson: string | null | undefined,
- floorId: string
+ floorId: string,
+ nodeIdPrefix = floorId
): GuideRoutePoint[] => {
if (!geoJson) return []
@@ -198,20 +251,147 @@ const pointsFromGeoJson = (
? parsed.coordinates
: []
- return coordinates.map((coordinate, index) => ({
- nodeId: `${floorId}-geo-${index}`,
- floorId,
- position: [
- Number(coordinate[0] || 0),
- 0,
- Number(coordinate[1] || 0)
- ]
- }))
+ return coordinates
+ .map((coordinate, index) => {
+ const x = finiteNumber(coordinate[0])
+ const z = finiteNumber(coordinate[1])
+ if (typeof x !== 'number' || typeof z !== 'number') return null
+
+ return {
+ nodeId: `${nodeIdPrefix}-geo-${index}`,
+ floorId,
+ position: [
+ x,
+ finiteNumber(coordinate[2]) ?? 0,
+ z
+ ]
+ }
+ })
+ .filter((point): point is GuideRoutePoint => Boolean(point))
} catch {
return []
}
}
+const routePointFromEndpoint = (
+ endpoint: GuideRouteEndpoint,
+ suffix: string
+): GuideRoutePoint => ({
+ nodeId: endpoint.routeNodeId || `${endpoint.poiId}-${suffix}`,
+ floorId: endpoint.floorId,
+ position: endpoint.position
+})
+
+const prependIfDifferent = (
+ points: GuideRoutePoint[],
+ point: GuideRoutePoint
+) => {
+ const firstPoint = points[0]
+ return firstPoint && samePosition(firstPoint.position, point.position)
+ ? points
+ : [point, ...points]
+}
+
+const appendIfDifferent = (
+ points: GuideRoutePoint[],
+ point: GuideRoutePoint
+) => {
+ const lastPoint = points[points.length - 1]
+ return lastPoint && samePosition(lastPoint.position, point.position)
+ ? points
+ : [...points, point]
+}
+
+const createPlanFloorSegments = (
+ points: GuideRoutePoint[],
+ startTarget: GuideRouteTarget,
+ endTarget: GuideRouteTarget
+): GuideRouteFloorSegment[] => points.reduce((segments, point) => {
+ const current = segments[segments.length - 1]
+ if (current && current.floorId === point.floorId) {
+ current.points.push(point)
+ return segments
+ }
+
+ segments.push({
+ floorId: point.floorId,
+ floorLabel: floorLabelFor(point.floorId, startTarget, endTarget),
+ points: [point]
+ })
+ return segments
+}, [])
+
+const isWalkSegment = (
+ segment: NonNullable[number]
+) => {
+ const type = stringifyRouteId(segment.transferType || segment.segmentType || segment.type).toUpperCase()
+ return !type || type === 'WALK'
+}
+
+const createSegmentFloorSegments = (
+ route: SgsRoutePlanResponsePayload,
+ start: GuideRouteEndpoint,
+ end: GuideRouteEndpoint,
+ startTarget: GuideRouteTarget,
+ endTarget: GuideRouteTarget,
+ nodePoints: GuideRoutePoint[]
+): GuideRouteFloorSegment[] => {
+ const segments = (route.segments || [])
+ .filter(isWalkSegment)
+ .map((segment, segmentIndex) => {
+ const floorId = stringifyRouteId(segment.floorId, start.floorId)
+ const nodeIdPrefix = `${floorId}-segment-${segmentIndex}`
+ const nodePathIds = new Set(
+ (segment.nodePathIds || [])
+ .map((nodeId) => stringifyRouteId(nodeId))
+ .filter(Boolean)
+ )
+ const points = pointsFromPathPoints(segment.pathPoints, floorId, nodeIdPrefix)
+ const geoPoints = points.length
+ ? points
+ : pointsFromGeoJson(segment.pathGeoJson, floorId, nodeIdPrefix)
+ const fallbackPoints = geoPoints.length
+ ? geoPoints
+ : nodePoints.filter((point) => (
+ point.floorId === floorId
+ && (!nodePathIds.size || nodePathIds.has(point.nodeId))
+ ))
+
+ if (!fallbackPoints.length) return null
+
+ return {
+ floorId,
+ floorLabel: floorLabelFor(floorId, startTarget, endTarget, segment.floorName),
+ points: fallbackPoints.map((point, index) => ({
+ ...point,
+ nodeId: point.nodeId || `${nodeIdPrefix}-${index}`
+ }))
+ }
+ })
+ .filter((segment): segment is GuideRouteFloorSegment => Boolean(segment))
+
+ if (!segments.length) return []
+
+ const startPoint = routePointFromEndpoint(start, 'start')
+ const endPoint = routePointFromEndpoint(end, 'end')
+ const anchoredSegments = segments.map((segment) => ({
+ ...segment,
+ points: [...segment.points]
+ }))
+ const firstSegment = anchoredSegments[0]
+ const lastSegment = anchoredSegments[anchoredSegments.length - 1]
+
+ if (firstSegment?.floorId === start.floorId) {
+ firstSegment.points = prependIfDifferent(firstSegment.points, startPoint)
+ }
+
+ if (lastSegment?.floorId === end.floorId) {
+ lastSegment.points = appendIfDifferent(lastSegment.points, endPoint)
+ }
+
+ return anchoredSegments
+}
+
export const toGuideRouteResultFromSgsPlan = (
route: SgsRoutePlanResponsePayload,
startTarget: GuideRouteTarget,
@@ -219,67 +399,39 @@ export const toGuideRouteResultFromSgsPlan = (
): GuideRouteResult => {
const start = targetEndpoint(startTarget)
const end = targetEndpoint(endTarget)
- const nodePoints = (route.nodePaths || []).map((node, index) => (
- pointFromBackendNode(node, index, start.floorId)
- ))
- const routePoints = nodePoints.length
- ? [
- {
- nodeId: start.routeNodeId,
- floorId: start.floorId,
- position: start.position
- },
- ...nodePoints,
- {
- nodeId: end.routeNodeId,
- floorId: end.floorId,
- position: end.position
- }
- ]
- : pointsFromGeoJson(route.pathGeoJson, start.floorId)
-
- const fallbackPoints = routePoints.length
- ? routePoints
- : [
- {
- nodeId: start.routeNodeId,
- floorId: start.floorId,
- position: start.position
- },
- {
- nodeId: end.routeNodeId,
- floorId: end.floorId,
- position: end.position
- }
- ]
-
- const floorSegments = route.segments?.length
- ? route.segments.map((segment, segmentIndex) => {
- const floorId = String(segment.floorId || start.floorId)
- const points = pointsFromGeoJson(segment.pathGeoJson, floorId)
-
- return {
- floorId,
- floorLabel: segment.floorName || floorLabelFor(floorId, startTarget, endTarget),
- points: points.length
- ? points
- : fallbackPoints.filter((point) => point.floorId === floorId).map((point, index) => ({
- ...point,
- nodeId: point.nodeId || `${floorId}-segment-${segmentIndex}-${index}`
- }))
- }
- }).filter((segment) => segment.points.length)
- : createFloorSegments(fallbackPoints, startTarget, endTarget)
+ const startPoint = routePointFromEndpoint(start, 'start')
+ const endPoint = routePointFromEndpoint(end, 'end')
+ const nodePoints = (route.nodePaths || [])
+ .map((node, index) => pointFromBackendNode(node, index, start.floorId))
+ .filter((point): point is GuideRoutePoint => Boolean(point))
+ const segmentFloorSegments = createSegmentFloorSegments(
+ route,
+ start,
+ end,
+ startTarget,
+ endTarget,
+ nodePoints
+ )
+ const routeGeoPoints = pointsFromGeoJson(route.pathGeoJson, start.floorId, 'sgs-route')
+ const fallbackPoints = nodePoints.length
+ ? [startPoint, ...nodePoints, endPoint]
+ : routeGeoPoints.length
+ ? [startPoint, ...routeGeoPoints, endPoint]
+ : [startPoint, endPoint]
+ const floorSegments = segmentFloorSegments.length
+ ? segmentFloorSegments
+ : createPlanFloorSegments(fallbackPoints, startTarget, endTarget)
+ const routePoints = floorSegments.flatMap((segment) => segment.points)
return {
id: `sgs-api-route-${start.poiId}-${end.poiId}`,
start,
end,
distanceMeters: Number(route.distance || 0),
- nodeIds: fallbackPoints.map((point) => point.nodeId),
- points: fallbackPoints,
+ nodeIds: routePoints.map((point) => point.nodeId),
+ points: routePoints,
floorSegments,
- connectorPoints: extractFloorConnectorPoints(fallbackPoints, startTarget, endTarget)
+ connectorPoints: extractFloorConnectorPoints(routePoints, startTarget, endTarget)
}
}
@@ -297,8 +449,8 @@ const extractFloorConnectorPoints = (
])
for (let i = 0; i < points.length - 1; i++) {
- const currentFloorId = toAppFloorId(points[i].floorId)
- const nextFloorId = toAppFloorId(points[i + 1].floorId)
+ const currentFloorId = points[i].floorId
+ const nextFloorId = points[i + 1].floorId
if (currentFloorId !== nextFloorId) {
const fromLabel = labels.get(currentFloorId) || currentFloorId
diff --git a/src/data/providers/sgsSdkApiProvider.ts b/src/data/providers/sgsSdkApiProvider.ts
index b32cd10..7db0c2c 100644
--- a/src/data/providers/sgsSdkApiProvider.ts
+++ b/src/data/providers/sgsSdkApiProvider.ts
@@ -161,19 +161,30 @@ export interface SgsRouteStepNodePayload {
nodeType?: string | null
x?: number | null
y?: number | null
+ z?: number | null
+}
+
+export interface SgsRoutePathPointPayload {
+ x?: number | null
+ y?: number | null
+ z?: number | null
}
export interface SgsRouteSegmentPayload {
floorId?: string | number | null
+ floorCode?: string | null
floorName?: string | null
startNodeId?: string | number | null
endNodeId?: string | number | null
startNodeName?: string | null
endNodeName?: string | null
+ segmentType?: string | null
+ type?: string | null
transferType?: string | null
distance?: number | null
duration?: number | null
pathGeoJson?: string | null
+ pathPoints?: SgsRoutePathPointPayload[]
nodePathIds?: Array
}
diff --git a/src/data/providers/staticNavAssetsProvider.ts b/src/data/providers/staticNavAssetsProvider.ts
index ec67d77..d77a1b9 100644
--- a/src/data/providers/staticNavAssetsProvider.ts
+++ b/src/data/providers/staticNavAssetsProvider.ts
@@ -230,7 +230,7 @@ export const createStaticNavAssetsProvider = (
manifestRequest = requestJson(provider.assetUrl('app_nav_manifest.json'))
.then((data) => {
if (data.status !== 'pass') {
- throw new Error('室内导览资源包状态不是 pass')
+ throw new Error('馆内导览资源包状态不是 pass')
}
manifestCache = data
diff --git a/src/domain/guideFloor.ts b/src/domain/guideFloor.ts
index 28cd26a..b9bcf08 100644
--- a/src/domain/guideFloor.ts
+++ b/src/domain/guideFloor.ts
@@ -18,7 +18,7 @@ interface FloorLike {
const indoorFloorCodePattern = /^L-?\d+(?:\.\d+)?$/i
const indoorFloorLabelPattern = /^(?:B\d+(?:\.\d+)?|\d+(?:\.\d+)?F)$/i
const rawNumericIdPattern = /^\d{6,}$/
-const nonNavigableFloorPattern = /(EXTERIOR|OUTDOOR|BUILDING|FACADE|EXTERNAL|外立面|建筑外观|建筑外立面|室外模型|室外场景|室外底图|室外建筑|室外外观|室外)$/i
+const nonNavigableFloorPattern = /(EXTERIOR|OUTDOOR|BUILDING|FACADE|EXTERNAL|外立面|建筑外观|建筑外立面|馆外模型|馆外场景|馆外底图|馆外建筑|馆外外观|馆外)$/i
const normalizeValue = (value: unknown) => (
value === null || typeof value === 'undefined' ? '' : String(value).trim()
diff --git a/src/domain/guideModel.ts b/src/domain/guideModel.ts
index 988765a..18f3b99 100644
--- a/src/domain/guideModel.ts
+++ b/src/domain/guideModel.ts
@@ -1,3 +1,8 @@
+import type {
+ MuseumPoiEntrance,
+ MuseumPoiKind
+} from '@/domain/museum'
+
export interface GuideRenderPoi {
id: string
name: string
@@ -7,6 +12,10 @@ export interface GuideRenderPoi {
iconType: string
positionGltf?: [number, number, number]
sourceObjectName?: string
+ kind?: MuseumPoiKind
+ hallId?: string
+ hallName?: string
+ entrances?: MuseumPoiEntrance[]
}
export interface GuideModelFloorAsset {
diff --git a/src/domain/guideReadiness.ts b/src/domain/guideReadiness.ts
index aee5e72..951d8b4 100644
--- a/src/domain/guideReadiness.ts
+++ b/src/domain/guideReadiness.ts
@@ -4,7 +4,7 @@ import type {
export const NAV_ROUTE_GRAPH_READY = false
-export const NAV_ROUTE_UNAVAILABLE_MESSAGE = '当前暂不支持正式导航,可查看位置预览、路线示意和室外参考'
+export const NAV_ROUTE_UNAVAILABLE_MESSAGE = '当前可查看位置预览、位置关系和馆外参考,暂不提供正式室内导航'
export const NAV_ROUTE_READINESS: GuideRouteReadiness = {
ready: NAV_ROUTE_GRAPH_READY,
diff --git a/src/pages/exhibit/detail.vue b/src/pages/exhibit/detail.vue
index b1dee1f..15bd5d6 100644
--- a/src/pages/exhibit/detail.vue
+++ b/src/pages/exhibit/detail.vue
@@ -61,7 +61,7 @@