feat: 添加视角跟随状态管理功能,集成 Pinia 以优化视角跟随逻辑,新增视角跟随通知组件,提升用户交互体验
This commit is contained in:
parent
02654b29e0
commit
5cdbcebcd3
@ -16,6 +16,7 @@
|
|||||||
"axios": "^1.8.4",
|
"axios": "^1.8.4",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
|
"pinia": "^3.0.3",
|
||||||
"rxjs": "^7.8.2",
|
"rxjs": "^7.8.2",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-i18n": "^11.1.3",
|
"vue-i18n": "^11.1.3",
|
||||||
|
|||||||
94
pnpm-lock.yaml
generated
94
pnpm-lock.yaml
generated
@ -29,6 +29,9 @@ importers:
|
|||||||
lodash-es:
|
lodash-es:
|
||||||
specifier: ^4.17.21
|
specifier: ^4.17.21
|
||||||
version: 4.17.21
|
version: 4.17.21
|
||||||
|
pinia:
|
||||||
|
specifier: ^3.0.3
|
||||||
|
version: 3.0.3(typescript@5.7.3)(vue@3.5.20(typescript@5.7.3))
|
||||||
rxjs:
|
rxjs:
|
||||||
specifier: ^7.8.2
|
specifier: ^7.8.2
|
||||||
version: 7.8.2
|
version: 7.8.2
|
||||||
@ -759,6 +762,15 @@ packages:
|
|||||||
'@vue/devtools-api@6.6.4':
|
'@vue/devtools-api@6.6.4':
|
||||||
resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
|
resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
|
||||||
|
|
||||||
|
'@vue/devtools-api@7.7.7':
|
||||||
|
resolution: {integrity: sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==}
|
||||||
|
|
||||||
|
'@vue/devtools-kit@7.7.7':
|
||||||
|
resolution: {integrity: sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==}
|
||||||
|
|
||||||
|
'@vue/devtools-shared@7.7.7':
|
||||||
|
resolution: {integrity: sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==}
|
||||||
|
|
||||||
'@vue/language-core@2.2.12':
|
'@vue/language-core@2.2.12':
|
||||||
resolution: {integrity: sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==}
|
resolution: {integrity: sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -879,6 +891,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
birpc@2.5.0:
|
||||||
|
resolution: {integrity: sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ==}
|
||||||
|
|
||||||
bl@4.1.0:
|
bl@4.1.0:
|
||||||
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
||||||
|
|
||||||
@ -963,6 +978,10 @@ packages:
|
|||||||
confbox@0.2.2:
|
confbox@0.2.2:
|
||||||
resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==}
|
resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==}
|
||||||
|
|
||||||
|
copy-anything@3.0.5:
|
||||||
|
resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
|
||||||
|
engines: {node: '>=12.13'}
|
||||||
|
|
||||||
core-js@3.45.1:
|
core-js@3.45.1:
|
||||||
resolution: {integrity: sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==}
|
resolution: {integrity: sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==}
|
||||||
|
|
||||||
@ -1318,6 +1337,9 @@ packages:
|
|||||||
help-me@3.0.0:
|
help-me@3.0.0:
|
||||||
resolution: {integrity: sha512-hx73jClhyk910sidBB7ERlnhMlFsJJIBqSVMFDwPN8o2v9nmp5KgLq1Xz1Bf1fCMMZ6mPrX159iG0VLy/fPMtQ==}
|
resolution: {integrity: sha512-hx73jClhyk910sidBB7ERlnhMlFsJJIBqSVMFDwPN8o2v9nmp5KgLq1Xz1Bf1fCMMZ6mPrX159iG0VLy/fPMtQ==}
|
||||||
|
|
||||||
|
hookable@5.5.3:
|
||||||
|
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
|
||||||
|
|
||||||
hookified@1.12.0:
|
hookified@1.12.0:
|
||||||
resolution: {integrity: sha512-hMr1Y9TCLshScrBbV2QxJ9BROddxZ12MX9KsCtuGGy/3SmmN5H1PllKerrVlSotur9dlE8hmUKAOSa3WDzsZmQ==}
|
resolution: {integrity: sha512-hMr1Y9TCLshScrBbV2QxJ9BROddxZ12MX9KsCtuGGy/3SmmN5H1PllKerrVlSotur9dlE8hmUKAOSa3WDzsZmQ==}
|
||||||
|
|
||||||
@ -1391,6 +1413,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
|
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
is-what@4.1.16:
|
||||||
|
resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
|
||||||
|
engines: {node: '>=12.13'}
|
||||||
|
|
||||||
isexe@2.0.0:
|
isexe@2.0.0:
|
||||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||||
|
|
||||||
@ -1526,6 +1552,9 @@ packages:
|
|||||||
mitt@2.1.0:
|
mitt@2.1.0:
|
||||||
resolution: {integrity: sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg==}
|
resolution: {integrity: sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg==}
|
||||||
|
|
||||||
|
mitt@3.0.1:
|
||||||
|
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
|
||||||
|
|
||||||
mlly@1.8.0:
|
mlly@1.8.0:
|
||||||
resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
|
resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
|
||||||
|
|
||||||
@ -1612,6 +1641,9 @@ packages:
|
|||||||
pathe@2.0.3:
|
pathe@2.0.3:
|
||||||
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
||||||
|
|
||||||
|
perfect-debounce@1.0.0:
|
||||||
|
resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
|
||||||
|
|
||||||
picocolors@1.1.1:
|
picocolors@1.1.1:
|
||||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||||
|
|
||||||
@ -1623,6 +1655,15 @@ packages:
|
|||||||
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
pinia@3.0.3:
|
||||||
|
resolution: {integrity: sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '>=4.4.4'
|
||||||
|
vue: ^2.7.0 || ^3.5.11
|
||||||
|
peerDependenciesMeta:
|
||||||
|
typescript:
|
||||||
|
optional: true
|
||||||
|
|
||||||
pkg-types@1.3.1:
|
pkg-types@1.3.1:
|
||||||
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
|
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
|
||||||
|
|
||||||
@ -1913,6 +1954,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
speakingurl@14.0.1:
|
||||||
|
resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
split2@3.2.2:
|
split2@3.2.2:
|
||||||
resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==}
|
resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==}
|
||||||
|
|
||||||
@ -2010,6 +2055,10 @@ packages:
|
|||||||
stylis@4.3.6:
|
stylis@4.3.6:
|
||||||
resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==}
|
resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==}
|
||||||
|
|
||||||
|
superjson@2.2.2:
|
||||||
|
resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==}
|
||||||
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
supports-color@7.2.0:
|
supports-color@7.2.0:
|
||||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -2782,6 +2831,24 @@ snapshots:
|
|||||||
|
|
||||||
'@vue/devtools-api@6.6.4': {}
|
'@vue/devtools-api@6.6.4': {}
|
||||||
|
|
||||||
|
'@vue/devtools-api@7.7.7':
|
||||||
|
dependencies:
|
||||||
|
'@vue/devtools-kit': 7.7.7
|
||||||
|
|
||||||
|
'@vue/devtools-kit@7.7.7':
|
||||||
|
dependencies:
|
||||||
|
'@vue/devtools-shared': 7.7.7
|
||||||
|
birpc: 2.5.0
|
||||||
|
hookable: 5.5.3
|
||||||
|
mitt: 3.0.1
|
||||||
|
perfect-debounce: 1.0.0
|
||||||
|
speakingurl: 14.0.1
|
||||||
|
superjson: 2.2.2
|
||||||
|
|
||||||
|
'@vue/devtools-shared@7.7.7':
|
||||||
|
dependencies:
|
||||||
|
rfdc: 1.4.1
|
||||||
|
|
||||||
'@vue/language-core@2.2.12(typescript@5.7.3)':
|
'@vue/language-core@2.2.12(typescript@5.7.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@volar/language-core': 2.4.15
|
'@volar/language-core': 2.4.15
|
||||||
@ -2921,6 +2988,8 @@ snapshots:
|
|||||||
|
|
||||||
binary-extensions@2.3.0: {}
|
binary-extensions@2.3.0: {}
|
||||||
|
|
||||||
|
birpc@2.5.0: {}
|
||||||
|
|
||||||
bl@4.1.0:
|
bl@4.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
buffer: 5.7.1
|
buffer: 5.7.1
|
||||||
@ -3019,6 +3088,10 @@ snapshots:
|
|||||||
|
|
||||||
confbox@0.2.2: {}
|
confbox@0.2.2: {}
|
||||||
|
|
||||||
|
copy-anything@3.0.5:
|
||||||
|
dependencies:
|
||||||
|
is-what: 4.1.16
|
||||||
|
|
||||||
core-js@3.45.1: {}
|
core-js@3.45.1: {}
|
||||||
|
|
||||||
cosmiconfig@9.0.0(typescript@5.7.3):
|
cosmiconfig@9.0.0(typescript@5.7.3):
|
||||||
@ -3409,6 +3482,8 @@ snapshots:
|
|||||||
glob: 7.2.3
|
glob: 7.2.3
|
||||||
readable-stream: 3.6.2
|
readable-stream: 3.6.2
|
||||||
|
|
||||||
|
hookable@5.5.3: {}
|
||||||
|
|
||||||
hookified@1.12.0: {}
|
hookified@1.12.0: {}
|
||||||
|
|
||||||
html-tags@3.3.1: {}
|
html-tags@3.3.1: {}
|
||||||
@ -3464,6 +3539,8 @@ snapshots:
|
|||||||
|
|
||||||
is-plain-object@5.0.0: {}
|
is-plain-object@5.0.0: {}
|
||||||
|
|
||||||
|
is-what@4.1.16: {}
|
||||||
|
|
||||||
isexe@2.0.0: {}
|
isexe@2.0.0: {}
|
||||||
|
|
||||||
js-sdsl@4.3.0: {}
|
js-sdsl@4.3.0: {}
|
||||||
@ -3574,6 +3651,8 @@ snapshots:
|
|||||||
|
|
||||||
mitt@2.1.0: {}
|
mitt@2.1.0: {}
|
||||||
|
|
||||||
|
mitt@3.0.1: {}
|
||||||
|
|
||||||
mlly@1.8.0:
|
mlly@1.8.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.15.0
|
acorn: 8.15.0
|
||||||
@ -3683,12 +3762,21 @@ snapshots:
|
|||||||
|
|
||||||
pathe@2.0.3: {}
|
pathe@2.0.3: {}
|
||||||
|
|
||||||
|
perfect-debounce@1.0.0: {}
|
||||||
|
|
||||||
picocolors@1.1.1: {}
|
picocolors@1.1.1: {}
|
||||||
|
|
||||||
picomatch@2.3.1: {}
|
picomatch@2.3.1: {}
|
||||||
|
|
||||||
picomatch@4.0.3: {}
|
picomatch@4.0.3: {}
|
||||||
|
|
||||||
|
pinia@3.0.3(typescript@5.7.3)(vue@3.5.20(typescript@5.7.3)):
|
||||||
|
dependencies:
|
||||||
|
'@vue/devtools-api': 7.7.7
|
||||||
|
vue: 3.5.20(typescript@5.7.3)
|
||||||
|
optionalDependencies:
|
||||||
|
typescript: 5.7.3
|
||||||
|
|
||||||
pkg-types@1.3.1:
|
pkg-types@1.3.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
confbox: 0.1.8
|
confbox: 0.1.8
|
||||||
@ -3951,6 +4039,8 @@ snapshots:
|
|||||||
|
|
||||||
source-map-js@1.2.1: {}
|
source-map-js@1.2.1: {}
|
||||||
|
|
||||||
|
speakingurl@14.0.1: {}
|
||||||
|
|
||||||
split2@3.2.2:
|
split2@3.2.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
readable-stream: 3.6.2
|
readable-stream: 3.6.2
|
||||||
@ -4085,6 +4175,10 @@ snapshots:
|
|||||||
|
|
||||||
stylis@4.3.6: {}
|
stylis@4.3.6: {}
|
||||||
|
|
||||||
|
superjson@2.2.2:
|
||||||
|
dependencies:
|
||||||
|
copy-anything: 3.0.5
|
||||||
|
|
||||||
supports-color@7.2.0:
|
supports-color@7.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
has-flag: 4.0.0
|
has-flag: 4.0.0
|
||||||
|
|||||||
@ -101,12 +101,16 @@
|
|||||||
<!-- 视角控制 -->
|
<!-- 视角控制 -->
|
||||||
<div class="action-group">
|
<div class="action-group">
|
||||||
<div class="action-group-title">视角控制</div>
|
<div class="action-group-title">视角控制</div>
|
||||||
<div v-if="!isFollowing" class="action-item" @click="handleRobotAction('follow_view', '视角跟随')">
|
<div
|
||||||
|
v-if="!followStore.isFollowing"
|
||||||
|
class="action-item"
|
||||||
|
@click="handleRobotAction('follow_view', '视角跟随')"
|
||||||
|
>
|
||||||
<span class="action-icon">👁️</span>
|
<span class="action-icon">👁️</span>
|
||||||
<span>视角跟随</span>
|
<span>视角跟随</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="isFollowing"
|
v-if="followStore.isFollowing"
|
||||||
class="action-item follow-active"
|
class="action-item follow-active"
|
||||||
@click="handleRobotAction('stop_follow_view', '停止跟随')"
|
@click="handleRobotAction('stop_follow_view', '停止跟随')"
|
||||||
>
|
>
|
||||||
@ -149,13 +153,11 @@ import type { RobotInfo } from '../../apis/robot';
|
|||||||
import type { RobotAction } from '../../services/context-menu/robot-menu.service';
|
import type { RobotAction } from '../../services/context-menu/robot-menu.service';
|
||||||
import {
|
import {
|
||||||
executeRobotAction,
|
executeRobotAction,
|
||||||
getGlobalFollowState,
|
|
||||||
getRobotStatusColor,
|
getRobotStatusColor,
|
||||||
getRobotStatusText,
|
getRobotStatusText,
|
||||||
startGlobalFollow,
|
|
||||||
stopGlobalFollow,
|
|
||||||
} from '../../services/context-menu/robot-menu.service';
|
} from '../../services/context-menu/robot-menu.service';
|
||||||
import { editorStore } from '../../stores/editor.store';
|
import { editorStore } from '../../stores/editor.store';
|
||||||
|
import { useFollowViewStore } from '../../stores/follow-view.store';
|
||||||
import RobotImageSettingsModal from '../modal/robot-image-settings-modal.vue';
|
import RobotImageSettingsModal from '../modal/robot-image-settings-modal.vue';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -181,11 +183,8 @@ const robotInfo = computed<RobotInfo | null>(() => {
|
|||||||
const imageSettingsVisible = ref(false);
|
const imageSettingsVisible = ref(false);
|
||||||
const selectedRobotName = ref('');
|
const selectedRobotName = ref('');
|
||||||
|
|
||||||
// 视角跟随状态 - 使用全局状态
|
// 使用 Pinia store 管理视角跟随状态
|
||||||
const isFollowing = computed(() => {
|
const followStore = useFollowViewStore();
|
||||||
const globalState = getGlobalFollowState();
|
|
||||||
return globalState.isFollowing;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 定义组件名称
|
// 定义组件名称
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@ -208,16 +207,17 @@ const startFollowView = () => {
|
|||||||
const editor = editorStore.getEditorValue();
|
const editor = editorStore.getEditorValue();
|
||||||
if (!editor) return;
|
if (!editor) return;
|
||||||
|
|
||||||
// 使用全局跟随功能(内部会处理切换逻辑)
|
// 获取机器人名称
|
||||||
startGlobalFollow(robotInfo.value.id, editor);
|
const robotName = robotInfo.value.label || `机器人${robotInfo.value.id}`;
|
||||||
message.success('开始视角跟随');
|
|
||||||
|
// 直接使用 store 开始跟随
|
||||||
|
followStore.startFollow(robotInfo.value.id, robotName, editor);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 停止视角跟随
|
// 停止视角跟随
|
||||||
const stopFollowView = () => {
|
const stopFollowView = () => {
|
||||||
// 使用全局停止功能
|
// 直接使用 store 停止跟随
|
||||||
stopGlobalFollow();
|
followStore.stopFollow();
|
||||||
message.success('停止视角跟随');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理机器人操作
|
// 处理机器人操作
|
||||||
|
|||||||
178
src/components/follow-view-notification.vue
Normal file
178
src/components/follow-view-notification.vue
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="followStore.isFollowing" class="follow-view-notification">
|
||||||
|
<div class="notification-content">
|
||||||
|
<div class="notification-text">
|
||||||
|
<div class="notification-title">视角跟随中</div>
|
||||||
|
<div class="notification-subtitle">正在跟随: {{ followStore.robotName }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="notification-actions">
|
||||||
|
<a-button type="text" size="small" @click="handleStopFollow" class="stop-button">
|
||||||
|
<template #icon>
|
||||||
|
<i class="icon exit" />
|
||||||
|
</template>
|
||||||
|
停止跟随
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useFollowViewStore } from '../stores/follow-view.store';
|
||||||
|
|
||||||
|
// 使用 Pinia store
|
||||||
|
const followStore = useFollowViewStore();
|
||||||
|
|
||||||
|
// 停止视角跟随
|
||||||
|
const handleStopFollow = () => {
|
||||||
|
followStore.stopFollow();
|
||||||
|
message.success('已停止视角跟随');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 定义组件名称
|
||||||
|
defineOptions({
|
||||||
|
name: 'FollowViewNotification',
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.follow-view-notification {
|
||||||
|
position: fixed;
|
||||||
|
top: 16px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 1000;
|
||||||
|
pointer-events: all;
|
||||||
|
animation: slideDown 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: linear-gradient(135deg, #0dbb8a 0%, #2ec796 100%);
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
|
||||||
|
color: white;
|
||||||
|
min-width: 300px;
|
||||||
|
max-width: 500px;
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: 16px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-text {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.2;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-subtitle {
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.9;
|
||||||
|
line-height: 1.2;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-actions {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stop-button {
|
||||||
|
color: white !important;
|
||||||
|
border-color: rgba(255, 255, 255, 0.3) !important;
|
||||||
|
background: rgba(255, 255, 255, 0.1) !important;
|
||||||
|
font-size: 12px;
|
||||||
|
height: 28px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.2) !important;
|
||||||
|
border-color: rgba(255, 255, 255, 0.5) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动画效果
|
||||||
|
@keyframes slideDown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-50%) translateY(-20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(-50%) translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应式设计
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.follow-view-notification {
|
||||||
|
top: 8px;
|
||||||
|
left: 8px;
|
||||||
|
right: 8px;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-content {
|
||||||
|
min-width: auto;
|
||||||
|
max-width: none;
|
||||||
|
padding: 10px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-icon {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-title {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-subtitle {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stop-button {
|
||||||
|
font-size: 11px;
|
||||||
|
height: 26px;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -12,16 +12,8 @@
|
|||||||
<a-form :model="formData" layout="vertical">
|
<a-form :model="formData" layout="vertical">
|
||||||
<!-- 机器人选择 -->
|
<!-- 机器人选择 -->
|
||||||
<a-form-item label="选择机器人">
|
<a-form-item label="选择机器人">
|
||||||
<a-select
|
<a-select v-model:value="selectedRobot" placeholder="请选择要设置图片的机器人" style="width: 100%">
|
||||||
v-model:value="selectedRobot"
|
<a-select-option v-for="robot in availableRobots" :key="robot.name" :value="robot.name">
|
||||||
placeholder="请选择要设置图片的机器人"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<a-select-option
|
|
||||||
v-for="robot in availableRobots"
|
|
||||||
:key="robot.name"
|
|
||||||
:value="robot.name"
|
|
||||||
>
|
|
||||||
{{ robot.name }} ({{ robot.type }})
|
{{ robot.name }} ({{ robot.type }})
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@ -99,7 +91,7 @@ interface Emits {
|
|||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
open: false,
|
open: false,
|
||||||
robots: () => [],
|
robots: () => [],
|
||||||
selectedRobotName: ''
|
selectedRobotName: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<Emits>();
|
const emit = defineEmits<Emits>();
|
||||||
@ -108,13 +100,13 @@ const emit = defineEmits<Emits>();
|
|||||||
const selectedRobot = ref<string>(props.selectedRobotName);
|
const selectedRobot = ref<string>(props.selectedRobotName);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const uploading = ref({
|
const uploading = ref({
|
||||||
normal: false
|
normal: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
images: {
|
images: {
|
||||||
normal: ''
|
normal: '',
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 获取编辑器服务实例
|
// 获取编辑器服务实例
|
||||||
@ -140,14 +132,19 @@ watch(selectedRobot, (newRobot) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 监听props变化
|
// 监听props变化
|
||||||
watch(() => props.selectedRobotName, (newName) => {
|
watch(
|
||||||
|
() => props.selectedRobotName,
|
||||||
|
(newName) => {
|
||||||
if (newName && newName !== selectedRobot.value) {
|
if (newName && newName !== selectedRobot.value) {
|
||||||
selectedRobot.value = newName;
|
selectedRobot.value = newName;
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// 监听open状态变化
|
// 监听open状态变化
|
||||||
watch(() => props.open, (isOpen) => {
|
watch(
|
||||||
|
() => props.open,
|
||||||
|
(isOpen) => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
// 如果有选中的机器人名称,则设置并加载数据
|
// 如果有选中的机器人名称,则设置并加载数据
|
||||||
if (props.selectedRobotName) {
|
if (props.selectedRobotName) {
|
||||||
@ -158,7 +155,8 @@ watch(() => props.open, (isOpen) => {
|
|||||||
// 关闭时重置所有状态
|
// 关闭时重置所有状态
|
||||||
handleCancel();
|
handleCancel();
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// 加载机器人图片
|
// 加载机器人图片
|
||||||
const loadRobotImages = (robotName: string) => {
|
const loadRobotImages = (robotName: string) => {
|
||||||
@ -253,7 +251,7 @@ const handleSave = async () => {
|
|||||||
try {
|
try {
|
||||||
emit('save', {
|
emit('save', {
|
||||||
robotName: selectedRobot.value,
|
robotName: selectedRobot.value,
|
||||||
images: formData.value.images
|
images: formData.value.images,
|
||||||
});
|
});
|
||||||
|
|
||||||
message.success('设置保存成功');
|
message.success('设置保存成功');
|
||||||
@ -273,8 +271,8 @@ const handleCancel = () => {
|
|||||||
selectedRobot.value = '';
|
selectedRobot.value = '';
|
||||||
formData.value = {
|
formData.value = {
|
||||||
images: {
|
images: {
|
||||||
normal: ''
|
normal: '',
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -323,7 +321,6 @@ const handleCancel = () => {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.ant-form-item {
|
.ant-form-item {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
@ -340,6 +337,4 @@ const handleCancel = () => {
|
|||||||
:deep(.ant-modal-mask) {
|
:deep(.ant-modal-mask) {
|
||||||
z-index: 999 !important;
|
z-index: 999 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -2,8 +2,12 @@ import './style.scss';
|
|||||||
|
|
||||||
import { i18n } from '@core/locale.service';
|
import { i18n } from '@core/locale.service';
|
||||||
import { router } from '@core/router';
|
import { router } from '@core/router';
|
||||||
|
import { createPinia } from 'pinia';
|
||||||
import { createApp } from 'vue';
|
import { createApp } from 'vue';
|
||||||
|
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
|
|
||||||
createApp(App).use(router).use(i18n).mount('#app');
|
const app = createApp(App);
|
||||||
|
const pinia = createPinia();
|
||||||
|
|
||||||
|
app.use(pinia).use(router).use(i18n).mount('#app');
|
||||||
|
|||||||
@ -7,11 +7,13 @@ import { useRoute } from 'vue-router';
|
|||||||
|
|
||||||
import type { RobotRealtimeInfo } from '../apis/robot';
|
import type { RobotRealtimeInfo } from '../apis/robot';
|
||||||
import { getSceneByGroupId, getSceneById, monitorRealSceneById, monitorSceneById } from '../apis/scene';
|
import { getSceneByGroupId, getSceneById, monitorRealSceneById, monitorSceneById } from '../apis/scene';
|
||||||
|
import FollowViewNotification from '../components/follow-view-notification.vue';
|
||||||
import { autoDoorSimulationService, type AutoDoorWebSocketData } from '../services/auto-door-simulation.service';
|
import { autoDoorSimulationService, type AutoDoorWebSocketData } from '../services/auto-door-simulation.service';
|
||||||
import {
|
import {
|
||||||
type ContextMenuState,
|
type ContextMenuState,
|
||||||
createContextMenuManager,
|
createContextMenuManager,
|
||||||
handleContextMenuFromPenData} from '../services/context-menu.service';
|
handleContextMenuFromPenData,
|
||||||
|
} from '../services/context-menu.service';
|
||||||
import { EditorService } from '../services/editor.service';
|
import { EditorService } from '../services/editor.service';
|
||||||
import { StorageLocationService } from '../services/storage-location.service';
|
import { StorageLocationService } from '../services/storage-location.service';
|
||||||
import { useViewState } from '../services/useViewState';
|
import { useViewState } from '../services/useViewState';
|
||||||
@ -122,7 +124,7 @@ const monitorScene = async () => {
|
|||||||
const newY = y - 60;
|
const newY = y - 60;
|
||||||
// 后端 angle 为逆时针,把转换改为“先取反再加偏移”:
|
// 后端 angle 为逆时针,把转换改为“先取反再加偏移”:
|
||||||
const rotate = angle == null ? undefined : -angle + 180;
|
const rotate = angle == null ? undefined : -angle + 180;
|
||||||
return { id, x: newX, y: newY, rotate, visible: true ,locked: LockState.None,};
|
return { id, x: newX, y: newY, rotate, visible: true, locked: LockState.None };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -363,12 +365,10 @@ const handleEditorContextMenu = (penData: Record<string, unknown>) => {
|
|||||||
console.log('EditorService自定义右键菜单事件:', penData);
|
console.log('EditorService自定义右键菜单事件:', penData);
|
||||||
handleContextMenuFromPenData(penData, contextMenuManager, {
|
handleContextMenuFromPenData(penData, contextMenuManager, {
|
||||||
storageLocationService: storageLocationService.value,
|
storageLocationService: storageLocationService.value,
|
||||||
robotService: editor.value // 传递EditorService作为机器人服务
|
robotService: editor.value, // 传递EditorService作为机器人服务
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关闭右键菜单
|
* 关闭右键菜单
|
||||||
*/
|
*/
|
||||||
@ -463,6 +463,9 @@ const handleGlobalKeydown = (event: KeyboardEvent) => {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- 视角跟随提示 -->
|
||||||
|
<FollowViewNotification />
|
||||||
|
|
||||||
<!-- 右键菜单 -->
|
<!-- 右键菜单 -->
|
||||||
<ContextMenu
|
<ContextMenu
|
||||||
:visible="contextMenuState.visible"
|
:visible="contextMenuState.visible"
|
||||||
|
|||||||
@ -21,7 +21,8 @@ export {
|
|||||||
} from './storage-menu.service';
|
} from './storage-menu.service';
|
||||||
|
|
||||||
// 机器人菜单服务
|
// 机器人菜单服务
|
||||||
export type { RobotInfo, RobotMenuConfig } from './robot-menu.service';
|
export type { RobotMenuConfig } from './robot-menu.service';
|
||||||
|
export type { RobotInfo } from '../../apis/robot';
|
||||||
export {
|
export {
|
||||||
executeRobotAction,
|
executeRobotAction,
|
||||||
getRobotInfo,
|
getRobotInfo,
|
||||||
|
|||||||
@ -3,19 +3,10 @@
|
|||||||
* 处理机器人相关的菜单逻辑和操作
|
* 处理机器人相关的菜单逻辑和操作
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { reactive } from 'vue';
|
|
||||||
|
|
||||||
import * as AmrApi from '../../apis/amr';
|
import * as AmrApi from '../../apis/amr';
|
||||||
import type { RobotInfo } from '../../apis/robot';
|
import type { RobotInfo } from '../../apis/robot';
|
||||||
import { RobotState } from '../../apis/robot';
|
import { RobotState } from '../../apis/robot';
|
||||||
|
|
||||||
// 全局跟随状态管理 - 使用Vue响应式系统
|
|
||||||
const globalFollowState = reactive({
|
|
||||||
isFollowing: false,
|
|
||||||
robotId: '',
|
|
||||||
timer: null as NodeJS.Timeout | null,
|
|
||||||
});
|
|
||||||
|
|
||||||
export interface RobotMenuConfig {
|
export interface RobotMenuConfig {
|
||||||
menuType: 'robot' | 'default';
|
menuType: 'robot' | 'default';
|
||||||
robotInfo?: RobotInfo;
|
robotInfo?: RobotInfo;
|
||||||
@ -346,52 +337,3 @@ export function getRobotMenuConfig(robotInfo: RobotInfo): RobotMenuConfig {
|
|||||||
robotInfo,
|
robotInfo,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 开始全局视角跟随
|
|
||||||
* @param robotId 机器人ID
|
|
||||||
* @param editor 编辑器实例
|
|
||||||
*/
|
|
||||||
export function startGlobalFollow(robotId: string, editor: any): void {
|
|
||||||
// 如果已经在跟随其他机器人,先停止
|
|
||||||
if (globalFollowState.isFollowing && globalFollowState.robotId !== robotId) {
|
|
||||||
stopGlobalFollow();
|
|
||||||
}
|
|
||||||
|
|
||||||
globalFollowState.isFollowing = true;
|
|
||||||
globalFollowState.robotId = robotId;
|
|
||||||
|
|
||||||
// 立即执行一次聚焦
|
|
||||||
editor.gotoById(robotId);
|
|
||||||
|
|
||||||
// 设置定时器,每36毫秒执行一次(约27.8fps)
|
|
||||||
globalFollowState.timer = setInterval(() => {
|
|
||||||
if (globalFollowState.isFollowing) {
|
|
||||||
editor.gotoById(robotId);
|
|
||||||
}
|
|
||||||
}, 10);
|
|
||||||
|
|
||||||
console.log('开始全局视角跟随:', robotId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止全局视角跟随
|
|
||||||
*/
|
|
||||||
export function stopGlobalFollow(): void {
|
|
||||||
globalFollowState.isFollowing = false;
|
|
||||||
globalFollowState.robotId = '';
|
|
||||||
|
|
||||||
if (globalFollowState.timer) {
|
|
||||||
clearInterval(globalFollowState.timer);
|
|
||||||
globalFollowState.timer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('停止全局视角跟随');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取全局跟随状态
|
|
||||||
*/
|
|
||||||
export function getGlobalFollowState() {
|
|
||||||
return globalFollowState;
|
|
||||||
}
|
|
||||||
95
src/stores/follow-view.store.ts
Normal file
95
src/stores/follow-view.store.ts
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/**
|
||||||
|
* 视角跟随状态管理 Store
|
||||||
|
* 使用 Pinia 管理视角跟随的全局状态
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { computed,ref } from 'vue';
|
||||||
|
|
||||||
|
export interface FollowViewState {
|
||||||
|
isFollowing: boolean;
|
||||||
|
robotId: string;
|
||||||
|
robotName: string;
|
||||||
|
timer: NodeJS.Timeout | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useFollowViewStore = defineStore('followView', () => {
|
||||||
|
// 状态
|
||||||
|
const state = ref<FollowViewState>({
|
||||||
|
isFollowing: false,
|
||||||
|
robotId: '',
|
||||||
|
robotName: '',
|
||||||
|
timer: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const isFollowing = computed(() => state.value.isFollowing);
|
||||||
|
const robotId = computed(() => state.value.robotId);
|
||||||
|
const robotName = computed(() => state.value.robotName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始视角跟随
|
||||||
|
* @param robotId 机器人ID
|
||||||
|
* @param robotName 机器人名称
|
||||||
|
* @param editor 编辑器实例
|
||||||
|
*/
|
||||||
|
const startFollow = (robotId: string, robotName: string, editor: any) => {
|
||||||
|
// 如果已经在跟随其他机器人,先停止
|
||||||
|
if (state.value.isFollowing && state.value.robotId !== robotId) {
|
||||||
|
stopFollow();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新状态
|
||||||
|
state.value.isFollowing = true;
|
||||||
|
state.value.robotId = robotId;
|
||||||
|
state.value.robotName = robotName;
|
||||||
|
|
||||||
|
// 立即执行一次聚焦
|
||||||
|
editor.gotoById(robotId);
|
||||||
|
|
||||||
|
// 设置定时器,每10毫秒执行一次
|
||||||
|
state.value.timer = setInterval(() => {
|
||||||
|
if (state.value.isFollowing) {
|
||||||
|
editor.gotoById(robotId);
|
||||||
|
}
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
console.log('开始视角跟随:', robotId, robotName);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停止视角跟随
|
||||||
|
*/
|
||||||
|
const stopFollow = () => {
|
||||||
|
state.value.isFollowing = false;
|
||||||
|
state.value.robotId = '';
|
||||||
|
state.value.robotName = '';
|
||||||
|
|
||||||
|
if (state.value.timer) {
|
||||||
|
clearInterval(state.value.timer);
|
||||||
|
state.value.timer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('停止视角跟随');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置状态
|
||||||
|
*/
|
||||||
|
const reset = () => {
|
||||||
|
stopFollow();
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
// 状态
|
||||||
|
state,
|
||||||
|
// 计算属性
|
||||||
|
isFollowing,
|
||||||
|
robotId,
|
||||||
|
robotName,
|
||||||
|
// 方法
|
||||||
|
startFollow,
|
||||||
|
stopFollow,
|
||||||
|
reset,
|
||||||
|
};
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user