JuKouShuiZhaPage.vue 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. <template>
  2. <div class="ju-kou-scene-page">
  3. <!-- 左侧视角切换按钮 -->
  4. <div class="view-switch-panel">
  5. <div class="view-switch-buttons">
  6. <el-button
  7. :type="viewMode === 'thirdPerson' ? 'primary' : 'default'"
  8. @click="switchToThirdPerson"
  9. class="view-btn"
  10. :title="viewMode === 'thirdPerson' ? '当前:第三人称' : '切换到第三人称'"
  11. >
  12. <el-icon><View /></el-icon>
  13. </el-button>
  14. <el-button
  15. :type="viewMode === 'firstPerson' ? 'primary' : 'default'"
  16. @click="switchToFirstPerson"
  17. class="view-btn"
  18. :title="viewMode === 'firstPerson' ? '当前:第一人称' : '切换到第一人称'"
  19. >
  20. <el-icon><User /></el-icon>
  21. </el-button>
  22. </div>
  23. <!-- 第一人称模式提示 -->
  24. <div v-if="viewMode === 'firstPerson'" class="mode-hint">
  25. <span>WASD移动 | ESC退出</span>
  26. </div>
  27. </div>
  28. <!-- 右侧闸门控制面板 -->
  29. <div class="right-panel">
  30. <GateControl
  31. ref="gateControlRef"
  32. :visible="showGateControl"
  33. :initial-position="gatePosition"
  34. @close="showGateControl = false"
  35. @position-change="handleGatePositionChange"
  36. />
  37. </div>
  38. <!-- 第一人称漫游控制器 -->
  39. <SuperMapFirstPerson
  40. ref="firstPersonRef"
  41. :viewer="viewer || window.viewer"
  42. :enabled="viewMode === 'firstPerson'"
  43. :initial-position="firstPersonPosition"
  44. :move-speed="5"
  45. :enable-collision="true"
  46. :enable-ground-follow="true"
  47. :show-instructions="true"
  48. @enter="onFirstPersonEnter"
  49. @exit="onFirstPersonExit"
  50. @position-change="onFirstPersonPositionChange"
  51. />
  52. <!-- 场景加载提示 -->
  53. <div v-if="isLoading" class="loading-overlay">
  54. <el-icon class="loading-icon"><Loading /></el-icon>
  55. <span>正在加载场景...</span>
  56. </div>
  57. </div>
  58. </template>
  59. <script>
  60. import { ref, onMounted, onUnmounted, watch } from 'vue'
  61. import { View, User, Loading } from '@element-plus/icons-vue'
  62. import { ElMessage } from 'element-plus'
  63. import GateControl from '../gate-control/GateControl.vue'
  64. import SuperMapFirstPerson from '../first-person/SuperMapFirstPerson.vue'
  65. import {
  66. loadJuKouScene,
  67. unloadJuKouScene,
  68. getController,
  69. JuKouSceneConfig
  70. } from '../../business-scenes/JuKouShuiZhaScene.js'
  71. /**
  72. * 莒口水闸业务场景独立页面组件
  73. *
  74. * 功能:
  75. * - 视角切换(第三人称/第一人称)
  76. * - 闸门启闭控制
  77. */
  78. export default {
  79. name: 'JuKouShuiZhaPage',
  80. components: {
  81. GateControl,
  82. SuperMapFirstPerson,
  83. View,
  84. User,
  85. Loading
  86. },
  87. props: {
  88. viewer: {
  89. type: Object,
  90. default: null
  91. },
  92. visible: {
  93. type: Boolean,
  94. default: false
  95. }
  96. },
  97. emits: ['scene-loaded', 'scene-unloaded', 'view-change', 'gate-change'],
  98. setup(props, { emit }) {
  99. const isLoading = ref(false)
  100. const viewMode = ref('thirdPerson')
  101. const showGateControl = ref(true)
  102. const gatePosition = ref(0)
  103. const firstPersonPosition = ref({
  104. lon: 119.144412,
  105. lat: 25.866431,
  106. height: 35,
  107. heading: 0,
  108. pitch: -10
  109. })
  110. const gateControlRef = ref(null)
  111. const firstPersonRef = ref(null)
  112. let thirdPersonCameraState = null
  113. const isInitialized = ref(false)
  114. const loadScene = async () => {
  115. if (!props.viewer) {
  116. ElMessage.warning('Viewer 未初始化')
  117. return
  118. }
  119. isLoading.value = true
  120. try {
  121. await loadJuKouScene(props.viewer, {
  122. initialPosition: gatePosition.value,
  123. onGateNodesReady: (gateNames) => {
  124. if (gateControlRef.value) {
  125. gateControlRef.value.updateGateList(gateNames)
  126. }
  127. isInitialized.value = true
  128. emit('scene-loaded')
  129. },
  130. onSceneReady: (entity) => {
  131. console.log('✅ 场景已加载:', entity)
  132. },
  133. onError: (error) => {
  134. console.error('❌ 场景加载失败:', error)
  135. ElMessage.error('场景加载失败: ' + error)
  136. isLoading.value = false
  137. }
  138. })
  139. isLoading.value = false
  140. } catch (error) {
  141. console.error('加载场景异常:', error)
  142. ElMessage.error('加载场景异常')
  143. isLoading.value = false
  144. }
  145. }
  146. const unloadScene = () => {
  147. if (viewMode.value === 'firstPerson') {
  148. viewMode.value = 'thirdPerson'
  149. }
  150. unloadJuKouScene()
  151. isInitialized.value = false
  152. emit('scene-unloaded')
  153. }
  154. const switchToThirdPerson = () => {
  155. if (viewMode.value === 'thirdPerson') return
  156. viewMode.value = 'thirdPerson'
  157. restoreThirdPersonCamera()
  158. emit('view-change', { mode: 'thirdPerson' })
  159. }
  160. const switchToFirstPerson = () => {
  161. if (viewMode.value === 'firstPerson') return
  162. // 使用 props.viewer 或 window.viewer
  163. const viewer = props.viewer || window.viewer
  164. if (!viewer) {
  165. ElMessage.warning('Viewer 未初始化')
  166. return
  167. }
  168. console.log('🔄 切换到第一人称视角...')
  169. saveThirdPersonCamera(viewer)
  170. viewMode.value = 'firstPerson'
  171. emit('view-change', { mode: 'firstPerson', position: firstPersonPosition.value })
  172. ElMessage.success('已进入第一人称漫游,点击画面开始')
  173. }
  174. const saveThirdPersonCamera = (viewer) => {
  175. if (!viewer) return
  176. thirdPersonCameraState = {
  177. position: viewer.camera.position.clone(),
  178. heading: viewer.camera.heading,
  179. pitch: viewer.camera.pitch,
  180. roll: viewer.camera.roll
  181. }
  182. console.log('📸 第三人称相机状态已保存')
  183. }
  184. const restoreThirdPersonCamera = () => {
  185. const viewer = props.viewer || window.viewer
  186. if (!viewer || !thirdPersonCameraState) return
  187. viewer.camera.setView({
  188. destination: thirdPersonCameraState.position,
  189. orientation: {
  190. heading: thirdPersonCameraState.heading,
  191. pitch: thirdPersonCameraState.pitch,
  192. roll: thirdPersonCameraState.roll
  193. }
  194. })
  195. console.log('📸 第三人称相机状态已恢复')
  196. }
  197. const handleGatePositionChange = (data) => {
  198. const controller = getController()
  199. if (!controller || !controller.isInitialized) return
  200. if (typeof data === 'number') {
  201. gatePosition.value = data
  202. controller.setAllGatePosition(data)
  203. } else if (data && data.gateName && typeof data.position === 'number') {
  204. controller.setSingleGatePosition(data.gateName, data.position)
  205. }
  206. emit('gate-change', data)
  207. }
  208. const onFirstPersonEnter = () => {
  209. console.log('✅ 进入第一人称漫游模式')
  210. }
  211. const onFirstPersonExit = () => {
  212. viewMode.value = 'thirdPerson'
  213. }
  214. const onFirstPersonPositionChange = (position) => {}
  215. watch(() => props.visible, (newVal) => {
  216. if (newVal && !isInitialized.value) {
  217. loadScene()
  218. } else if (!newVal && isInitialized.value) {
  219. unloadScene()
  220. }
  221. }, { immediate: true })
  222. onUnmounted(() => {
  223. if (isInitialized.value) {
  224. unloadScene()
  225. }
  226. })
  227. return {
  228. isLoading,
  229. viewMode,
  230. showGateControl,
  231. gatePosition,
  232. firstPersonPosition,
  233. gateControlRef,
  234. firstPersonRef,
  235. loadScene,
  236. unloadScene,
  237. switchToThirdPerson,
  238. switchToFirstPerson,
  239. handleGatePositionChange,
  240. onFirstPersonEnter,
  241. onFirstPersonExit,
  242. onFirstPersonPositionChange
  243. }
  244. }
  245. }
  246. </script>
  247. <style scoped>
  248. .ju-kou-scene-page {
  249. position: absolute;
  250. top: 0;
  251. left: 0;
  252. width: 100%;
  253. height: 100%;
  254. pointer-events: none;
  255. z-index: 100;
  256. }
  257. /* 左侧视角切换面板 */
  258. .view-switch-panel {
  259. position: absolute;
  260. top: 80px;
  261. left: 20px;
  262. display: flex;
  263. flex-direction: column;
  264. gap: 8px;
  265. pointer-events: auto;
  266. }
  267. /* 视角切换按钮组 */
  268. .view-switch-buttons {
  269. display: flex;
  270. flex-direction: row;
  271. gap: 8px;
  272. }
  273. .view-btn {
  274. width: 44px;
  275. height: 44px;
  276. padding: 0;
  277. border-radius: 8px;
  278. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  279. }
  280. /* 右侧闸门控制面板需要可点击 */
  281. .right-panel {
  282. position: absolute;
  283. top: 30px;
  284. right: 20px;
  285. pointer-events: auto;
  286. }
  287. /* 加载提示可点击 */
  288. .loading-overlay {
  289. pointer-events: auto;
  290. }
  291. .view-btn .el-icon {
  292. font-size: 20px;
  293. }
  294. .mode-hint {
  295. display: flex;
  296. flex-direction: column;
  297. align-items: center;
  298. gap: 2px;
  299. padding: 8px 10px;
  300. background: rgba(255, 255, 255, 0.95);
  301. border-radius: 6px;
  302. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  303. color: #d48806;
  304. font-size: 11px;
  305. text-align: center;
  306. line-height: 1.3;
  307. }
  308. /* 加载提示 */
  309. .loading-overlay {
  310. position: fixed;
  311. top: 0;
  312. left: 0;
  313. width: 100%;
  314. height: 100%;
  315. background: rgba(0, 0, 0, 0.5);
  316. display: flex;
  317. flex-direction: column;
  318. align-items: center;
  319. justify-content: center;
  320. gap: 15px;
  321. color: #fff;
  322. font-size: 16px;
  323. pointer-events: auto;
  324. z-index: 1000;
  325. }
  326. .loading-icon {
  327. font-size: 40px;
  328. animation: spin 1s linear infinite;
  329. }
  330. @keyframes spin {
  331. from { transform: rotate(0deg); }
  332. to { transform: rotate(360deg); }
  333. }
  334. </style>