PanoView.vue 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124
  1. <template>
  2. <div class="pano-view">
  3. <div
  4. class="pano-wrap"
  5. :class="{
  6. hide: !haveShownSceneEffect
  7. }"
  8. >
  9. <iframe
  10. ref="panoIframe"
  11. :src="iframeSrc"
  12. frameborder="0"
  13. />
  14. <button
  15. class="return-home"
  16. @click="router.push({
  17. name: 'HomeView',
  18. })"
  19. />
  20. <CameraDesc
  21. v-if="isShowCameraDesc"
  22. class="camera-desc"
  23. @close="isShowCameraDesc = false"
  24. />
  25. <CharacterDesc
  26. v-if="isShowCharacterDesc"
  27. class="character-desc"
  28. @close="isShowCharacterDesc = false"
  29. />
  30. <div
  31. class="character-wrap"
  32. draggable="false"
  33. >
  34. <button
  35. class="name"
  36. @click="isShowCharacterDesc = true"
  37. >
  38. <span>趙孟頫</span>
  39. </button>
  40. <div
  41. class="character-frames-wrapper"
  42. @click="onClickCharacter"
  43. >
  44. <img
  45. v-show="animationType === 1"
  46. class="default-frames"
  47. src="@/assets/images/A1-min.png"
  48. alt=""
  49. draggable="false"
  50. >
  51. <img
  52. v-show="animationType === 2"
  53. class="frames frames-2"
  54. :class="{
  55. animating: isCharacterSpecialMoving,
  56. state1: isCharacterSpecialMoving === 0,
  57. state2: isCharacterSpecialMoving === 1,
  58. }"
  59. src="@/assets/images/A2-min.png"
  60. alt=""
  61. draggable="false"
  62. >
  63. <img
  64. v-show="animationType === 3"
  65. class="frames frames-3"
  66. :class="{
  67. animating: isCharacterSpecialMoving,
  68. state1: isCharacterSpecialMoving === 0,
  69. state2: isCharacterSpecialMoving === 1,
  70. }"
  71. src="@/assets/images/A3-min.png"
  72. alt=""
  73. draggable="false"
  74. >
  75. </div>
  76. <img
  77. class="btn-track"
  78. :src="require(`@/assets/images/people-btn-track-${sceneIdx + 1}.png`)"
  79. alt=""
  80. draggable="false"
  81. >
  82. <button
  83. class="one btn-on-track"
  84. @click="showingContentIdx = 1"
  85. >
  86. <span>{{ btnOnTrack1Name }}</span>
  87. </button>
  88. <button
  89. class="two btn-on-track"
  90. @click="showingContentIdx = 2"
  91. >
  92. <span>{{ btnOnTrack2Name }}</span>
  93. </button>
  94. <button
  95. class="three btn-on-track"
  96. @click="showingContentIdx = 3"
  97. >
  98. <span>{{ btnOnTrack3Name }}</span>
  99. </button>
  100. <button
  101. class="four btn-on-track"
  102. @click="router.push({
  103. name: 'RelicList',
  104. query: {
  105. sceneIdx: route.query.sceneIdx,
  106. cameraIdx: route.query.cameraIdx,
  107. }
  108. })"
  109. >
  110. <span>文物长卷</span>
  111. </button>
  112. </div>
  113. <div
  114. class="camera-list"
  115. >
  116. <button
  117. v-for="(item, idx) in currentCameraList"
  118. :key="idx"
  119. class="camera-entry"
  120. :class="{
  121. active: idx === (mouseEnterCameraItemIdx === -1 ? cameraIdx : mouseEnterCameraItemIdx)
  122. }"
  123. @mouseenter="mouseEnterCameraItemIdx = idx"
  124. @mouseleave="mouseEnterCameraItemIdx = -1"
  125. @click="router.push({
  126. name: route.name,
  127. query:{
  128. sceneIdx: route.query.sceneIdx,
  129. cameraIdx: idx,
  130. }
  131. })"
  132. >
  133. <span>{{ item.name }}</span>
  134. <img
  135. class="bg-normal"
  136. :src="require(`@/assets/images/camera-list-item-bg-${sceneIdx + 1}.png`)"
  137. alt=""
  138. draggable="false"
  139. >
  140. <img
  141. class="bg-active"
  142. :src="require(`@/assets/images/camera-list-item-bg-active-${sceneIdx + 1}.png`)"
  143. alt=""
  144. draggable="false"
  145. >
  146. </button>
  147. <button
  148. class="next-scene"
  149. @click="onClickNextScene"
  150. />
  151. </div>
  152. </div>
  153. <CameraContent1
  154. v-if="showingContentIdx === 1"
  155. class="camera-content"
  156. @close="showingContentIdx = 0"
  157. />
  158. <CameraContent2
  159. v-if="showingContentIdx === 2"
  160. class="camera-content"
  161. @close="showingContentIdx = 0"
  162. />
  163. <CameraContent3
  164. v-if="showingContentIdx === 3"
  165. class="camera-content"
  166. @close="showingContentIdx = 0"
  167. />
  168. <RelicDetailForHotspot
  169. v-if="isShowHotspotDetail"
  170. :relic-info="hotspotRelicInfo"
  171. @close="isShowHotspotDetail = false"
  172. />
  173. <!-- 镜头切换过渡 -->
  174. <transition name="fade-in-out-slow">
  175. <div
  176. v-if="isShowCameraIntro"
  177. class="text-wrap"
  178. >
  179. <div
  180. class="text"
  181. v-html="cameraIntroText"
  182. />
  183. <button
  184. class="skip"
  185. @click="skipCameraIntro"
  186. />
  187. </div>
  188. </transition>
  189. <transition name="cloud-top">
  190. <img
  191. v-if="isShowCameraIntro"
  192. class="cloud cloud-top"
  193. src="@/assets/images/cloud-top.png"
  194. alt=""
  195. draggable="false"
  196. >
  197. </transition>
  198. <transition name="cloud-left-bottom">
  199. <img
  200. v-if="isShowCameraIntro"
  201. class="cloud-left-bottom"
  202. src="@/assets/images/cloud-left-bottom.png"
  203. alt=""
  204. draggable="false"
  205. >
  206. </transition>
  207. <transition name="cloud-right-bottom">
  208. <img
  209. v-if="isShowCameraIntro"
  210. class="cloud-right-bottom"
  211. src="@/assets/images/cloud-right-bottom.png"
  212. alt=""
  213. draggable="false"
  214. >
  215. </transition>
  216. <!-- end of 镜头切换过渡 -->
  217. <!-- 场景切换过渡 -->
  218. <video
  219. v-show="isShowSceneIntroVideoStart"
  220. ref="sceneIntrovideoStartEl"
  221. class="scene-intro-video scene-intro-video-start"
  222. :src="require(`@/assets/videos/scene-${sceneIdx + 1}-introduction-start.mp4`)"
  223. playsinline
  224. webkit-playsinline="true"
  225. x5-video-player-type="h5"
  226. @play="onPlayedSceneIntroVideoStart"
  227. />
  228. <button
  229. v-show="isShowSceneIntroVideoStart"
  230. class="skip-scene-intro"
  231. :style="{
  232. left: skipBtnLeftTop.x + 'px',
  233. top: skipBtnLeftTop.y + 'px',
  234. width: skipBtnRightBottom.x - skipBtnLeftTop.x + 'px',
  235. height: skipBtnRightBottom.y - skipBtnLeftTop.y + 'px',
  236. }"
  237. @click="skipSceneIntro"
  238. />
  239. <transition name="fade-out">
  240. <video
  241. v-show="isShowSceneIntroVideoEnd"
  242. ref="sceneIntrovideoEndEl"
  243. class="scene-intro-video scene-intro-video-end"
  244. :src="require(`@/assets/videos/scene-${sceneIdx + 1}-introduction-end.mp4`)"
  245. playsinline
  246. webkit-playsinline="true"
  247. x5-video-player-type="h5"
  248. @ended="onSceneIntroVideoEndEnd"
  249. />
  250. </transition>
  251. <!-- end of 场景切换过渡 -->
  252. </div>
  253. </template>
  254. <script setup>
  255. import { ref, computed, watch, onMounted, nextTick, watchEffect, onUnmounted } from "vue"
  256. import { useRoute, useRouter, onBeforeRouteUpdate } from "vue-router"
  257. import { useStore } from "vuex"
  258. import * as krfn from "@/libs/pano-core/index.js"
  259. import CameraDesc from '@/components/CameraDesc.vue'
  260. import CharacterDesc from '@/components/CharacterDesc.vue'
  261. import { defineAsyncComponent } from 'vue'
  262. import sceneTree from '/public/sceneTree.js'
  263. import RelicDetailForHotspot from '@/components/RelicDetailForHotspot.vue'
  264. import { useWindowSize } from '@vueuse/core'
  265. const btnReturnHomeImgUrl = computed(() => {
  266. return `url(${process.env.VUE_APP_CLI_MODE === 'dev' ? '' : '../'}` + require(`@/assets/images/btn-return-home-${sceneIdx.value + 1}.png`) + ')'
  267. })
  268. const btnReturnHomeActiveImgUrl = computed(() => {
  269. return `url(${process.env.VUE_APP_CLI_MODE === 'dev' ? '' : '../'}` + require(`@/assets/images/btn-return-home-active-${sceneIdx.value + 1}.png`) + ')'
  270. })
  271. const btnOnTrack1ImgUrl = computed(() => {
  272. return `url(${process.env.VUE_APP_CLI_MODE === 'dev' ? '' : '../'}` + require(`@/assets/images/camera-btn-${sceneIdx.value + 1}-${cameraIdx.value + 1}-1.png`) + ')'
  273. })
  274. const btnOnTrack1ActiveImgUrl = computed(() => {
  275. return `url(${process.env.VUE_APP_CLI_MODE === 'dev' ? '' : '../'}` + require(`@/assets/images/camera-btn-${sceneIdx.value + 1}-${cameraIdx.value + 1}-1-active.png`) + ')'
  276. })
  277. const btnOnTrack2ImgUrl = computed(() => {
  278. return `url(${process.env.VUE_APP_CLI_MODE === 'dev' ? '' : '../'}` + require(`@/assets/images/camera-btn-${sceneIdx.value + 1}-${cameraIdx.value + 1}-2.png`) + ')'
  279. })
  280. const btnOnTrack2ActiveImgUrl = computed(() => {
  281. return `url(${process.env.VUE_APP_CLI_MODE === 'dev' ? '' : '../'}` + require(`@/assets/images/camera-btn-${sceneIdx.value + 1}-${cameraIdx.value + 1}-2-active.png`) + ')'
  282. })
  283. const btnOnTrack3ImgUrl = computed(() => {
  284. return `url(${process.env.VUE_APP_CLI_MODE === 'dev' ? '' : '../'}` + require(`@/assets/images/camera-btn-${sceneIdx.value + 1}-${cameraIdx.value + 1}-3.png`) + ')'
  285. })
  286. const btnOnTrack3ActiveImgUrl = computed(() => {
  287. return `url(${process.env.VUE_APP_CLI_MODE === 'dev' ? '' : '../'}` + require(`@/assets/images/camera-btn-${sceneIdx.value + 1}-${cameraIdx.value + 1}-3-active.png`) + ')'
  288. })
  289. const btnOnTrack4ImgUrl = computed(() => {
  290. return `url(${process.env.VUE_APP_CLI_MODE === 'dev' ? '' : '../'}` + require(`@/assets/images/camera-btn-${sceneIdx.value + 1}-${cameraIdx.value + 1}-4.png`) + ')'
  291. })
  292. const btnOnTrack4ActiveImgUrl = computed(() => {
  293. return `url(${process.env.VUE_APP_CLI_MODE === 'dev' ? '' : '../'}` + require(`@/assets/images/camera-btn-${sceneIdx.value + 1}-${cameraIdx.value + 1}-4-active.png`) + ')'
  294. })
  295. const cameraListBgUrl = computed(() => {
  296. return `url(${process.env.VUE_APP_CLI_MODE === 'dev' ? '' : '../'}` + require(`@/assets/images/camera-list-bg-${sceneIdx.value + 1}.png`) + ')'
  297. })
  298. const btnOnTrack1Name = computed(() => {
  299. return currentCameraList.value[cameraIdx.value].contentPageBtnNameList[0]
  300. })
  301. const btnOnTrack2Name = computed(() => {
  302. return currentCameraList.value[cameraIdx.value].contentPageBtnNameList[1]
  303. })
  304. const btnOnTrack3Name = computed(() => {
  305. return currentCameraList.value[cameraIdx.value].contentPageBtnNameList[2]
  306. })
  307. let CameraContent1 = defineAsyncComponent(() =>
  308. import(`@/components/CameraContent-${Number(route.query.sceneIdx) + 1}-${Number(route.query.cameraIdx) + 1}-1.vue`)
  309. )
  310. let CameraContent2 = defineAsyncComponent(() =>
  311. import(`@/components/CameraContent-${Number(route.query.sceneIdx) + 1}-${Number(route.query.cameraIdx) + 1}-2.vue`)
  312. )
  313. let CameraContent3 = defineAsyncComponent(() =>
  314. import(`@/components/CameraContent-${Number(route.query.sceneIdx) + 1}-${Number(route.query.cameraIdx) + 1}-3.vue`)
  315. )
  316. onBeforeRouteUpdate((to, from) => {
  317. console.log('to: ', to)
  318. if (to.name === route.name) {
  319. CameraContent1 = defineAsyncComponent(() =>
  320. import(`@/components/CameraContent-${Number(route.query.sceneIdx) + 1}-${Number(route.query.cameraIdx) + 1}-1.vue`)
  321. )
  322. CameraContent2 = defineAsyncComponent(() =>
  323. import(`@/components/CameraContent-${Number(route.query.sceneIdx) + 1}-${Number(route.query.cameraIdx) + 1}-2.vue`)
  324. )
  325. CameraContent3 = defineAsyncComponent(() =>
  326. import(`@/components/CameraContent-${Number(route.query.sceneIdx) + 1}-${Number(route.query.cameraIdx) + 1}-3.vue`)
  327. )
  328. }
  329. })
  330. const {
  331. windowSizeInCssForRef,
  332. windowSizeWhenDesignForRef,
  333. } = useSizeAdapt()
  334. const route = useRoute()
  335. const router = useRouter()
  336. const store = useStore()
  337. const sceneIdx = computed(() => {
  338. return Number(route.query.sceneIdx)
  339. })
  340. const cameraIdx = computed(() => {
  341. return Number(route.query.cameraIdx)
  342. })
  343. /**
  344. * 左下角人物相关
  345. */
  346. const isCharacterSpecialMoving = ref(0)
  347. const animationType = ref(1)
  348. const isShowCameraDesc = ref(false)
  349. const isShowCharacterDesc = ref(false)
  350. const showingContentIdx = ref(0)
  351. function onClickCharacter() {
  352. // isShowCameraDesc.value = true
  353. if (!isCharacterSpecialMoving.value) {
  354. animationType.value = Math.floor(Math.random() * 2) + 2
  355. let duration = 0
  356. switch (animationType.value) {
  357. case 2:
  358. duration = 2000
  359. break
  360. case 3:
  361. duration = 1500
  362. break
  363. default:
  364. break
  365. }
  366. setTimeout(() => {
  367. isCharacterSpecialMoving.value = 1
  368. setTimeout(() => {
  369. isCharacterSpecialMoving.value = 0
  370. animationType.value = 1
  371. }, duration)
  372. }, 200)
  373. }
  374. }
  375. /**
  376. * end of 左下角人物相关
  377. */
  378. /**
  379. * 右下角镜头列表
  380. */
  381. const currentCameraList = computed(() => {
  382. return sceneTree[sceneIdx.value].cameraList
  383. })
  384. const mouseEnterCameraItemIdx = ref(-1)
  385. /**
  386. * end of 右下角镜头列表
  387. */
  388. /**
  389. * 镜头切换过渡
  390. */
  391. const isShowCameraIntro = ref(false)
  392. const cameraIntroText = computed(() => {
  393. return sceneTree[sceneIdx.value].cameraList[cameraIdx.value].desc
  394. })
  395. let cameraIntroAudioTimeoutId = null
  396. let cameraIntroAudio = null
  397. watch(cameraIdx, (vNew) => {
  398. if (haveShownSceneEffect.value) {
  399. isShowCameraIntro.value = true
  400. cameraIntroAudioTimeoutId = setTimeout(() => {
  401. cameraIntroAudio?.pause()
  402. cameraIntroAudio = new Audio(require(`@/assets/audios/camera-intro-${sceneIdx.value + 1}-${cameraIdx.value + 1}.mp3`))
  403. cameraIntroAudio.play()
  404. cameraIntroAudio.addEventListener('ended', () => {
  405. isShowCameraIntro.value = false
  406. })
  407. }, 1000)
  408. }
  409. })
  410. // 跳过按钮 功能
  411. function skipCameraIntro() {
  412. clearTimeout(cameraIntroAudioTimeoutId)
  413. cameraIntroAudio?.pause()
  414. isShowCameraIntro.value = false
  415. }
  416. onUnmounted(() => {
  417. skipCameraIntro()
  418. })
  419. onBeforeRouteUpdate(() => {
  420. skipCameraIntro()
  421. })
  422. /**
  423. * end of 镜头切换过渡
  424. */
  425. const isShowSceneIntroVideoStart = ref(false)
  426. const isShowSceneIntroVideoEnd = ref(false)
  427. const sceneIntrovideoStartEl = ref(null)
  428. const sceneIntrovideoEndEl = ref(null)
  429. /**
  430. * 第一次进入某个场景时展示动效
  431. */
  432. const haveShownSceneEffect = computed(() => {
  433. return store.state.haveShownSceneEffect[sceneIdx.value]
  434. })
  435. watch(sceneIdx, (vNew) => {
  436. if (!haveShownSceneEffect.value) {
  437. isShowSceneIntroVideoStart.value = true
  438. nextTick(() => {
  439. sceneIntrovideoStartEl.value.play()
  440. })
  441. }
  442. }, {
  443. immediate: true
  444. })
  445. let sceneIntroAudioTimeoutId = null
  446. let audio = null
  447. function onPlayedSceneIntroVideoStart() {
  448. sceneIntroAudioTimeoutId = setTimeout(() => {
  449. audio?.pause()
  450. audio = new Audio(require(`@/assets/audios/camera-intro-${sceneIdx.value + 1}-1.mp3`))
  451. audio.play()
  452. audio.addEventListener('ended', () => {
  453. store.commit('setHaveShownSceneEffect', {
  454. idx: sceneIdx.value,
  455. value: true,
  456. })
  457. isShowSceneIntroVideoStart.value = false
  458. isShowSceneIntroVideoEnd.value = true
  459. sceneIntrovideoEndEl.value.play()
  460. })
  461. }, 4000)
  462. }
  463. function onSceneIntroVideoEndEnd() {
  464. isShowSceneIntroVideoEnd.value = false
  465. }
  466. // 跳过按钮 位置
  467. const { width: windowWidth, height: windowHeight } = useWindowSize()
  468. const skipBtnLeftTop = ref({
  469. x: 0,
  470. y: 0,
  471. })
  472. const skipBtnRightBottom = ref({
  473. x: 0,
  474. y: 0,
  475. })
  476. watchEffect(() => {
  477. skipBtnLeftTop.value = utils.mapPosFromDraftToWindow({
  478. x: 850,
  479. y: 660,
  480. }, 'cover', 1920, 970, windowWidth.value, windowHeight.value)
  481. skipBtnRightBottom.value = utils.mapPosFromDraftToWindow({
  482. x: 1070,
  483. y: 720,
  484. }, 'cover', 1920, 970, windowWidth.value, windowHeight.value)
  485. })
  486. // 跳过按钮 功能
  487. function skipSceneIntro() {
  488. clearTimeout(sceneIntroAudioTimeoutId)
  489. audio?.pause()
  490. store.commit('setHaveShownSceneEffect', {
  491. idx: sceneIdx.value,
  492. value: true,
  493. })
  494. isShowSceneIntroVideoStart.value = false
  495. isShowSceneIntroVideoEnd.value = false
  496. sceneIntrovideoStartEl.value?.pause()
  497. sceneIntrovideoEndEl.value?.pause()
  498. }
  499. onUnmounted(() => {
  500. skipSceneIntro()
  501. })
  502. onBeforeRouteUpdate(() => {
  503. skipSceneIntro()
  504. })
  505. /**
  506. * end of 第一次进入某个场景时展示动效
  507. */
  508. /**
  509. * 背景音乐
  510. */
  511. let bgAudio = null
  512. watch(sceneIdx, () => {
  513. bgAudio?.pause()
  514. bgAudio = new Audio(require(`@/assets/audios/scene-bg-${sceneIdx.value + 1}.mp3`))
  515. setTimeout(() => {
  516. bgAudio.play()
  517. }, 300)
  518. }, {
  519. immediate: true,
  520. })
  521. onUnmounted(() => {
  522. bgAudio?.pause()
  523. })
  524. onBeforeRouteUpdate(() => {
  525. // bgAudio?.pause()
  526. })
  527. /**
  528. * end of 背景音乐
  529. */
  530. /**
  531. * 点击“下一个场景”按钮的逻辑
  532. */
  533. function onClickNextScene() {
  534. if (sceneIdx.value === 0) {
  535. router.push({
  536. name: route.name,
  537. query: {
  538. sceneIdx: Number(route.query.sceneIdx) + 1,
  539. cameraIdx: 0,
  540. }
  541. })
  542. } else if (sceneIdx.value === 1) {
  543. router.push({
  544. name: route.name,
  545. query: {
  546. sceneIdx: Number(route.query.sceneIdx) + 1,
  547. cameraIdx: 0,
  548. }
  549. })
  550. } else if (sceneIdx.value === 2) {
  551. router.push({
  552. name: 'EpilogueView',
  553. })
  554. }
  555. }
  556. /**
  557. * end of 点击“下一个场景”按钮的逻辑
  558. */
  559. /**
  560. * iframe的逻辑
  561. */
  562. const iframeSrc = `${process.env.VUE_APP_CLI_MODE === 'dev' ? 'http://192.168.0.27:8081/' : 'https://houseoss.4dkankan.com/project/yzdyh-dadu/pano/'}show.html?id=WK1730428603763576832&lang=zh`
  563. const panoIframe = ref(null)
  564. watch(cameraIdx, (vNew) => {
  565. console.log('parent window: post message!')
  566. panoIframe.value.contentWindow.postMessage({
  567. msgName: 'change-scene',
  568. sceneId: store.getters.catalogTopology[sceneIdx.value].children[0].children[cameraIdx.value].id
  569. }, '*')
  570. }, {
  571. deep: true,
  572. })
  573. // window.addEventListener('message', (e) => {
  574. // console.log('parent window: received message!', e)
  575. // })
  576. const isShowHotspotDetail = ref(false)
  577. const hotspotRelicInfo = ref({})
  578. window.showHotspotDetail = function(hotspotInfo) {
  579. console.log('parent window: 展示热点详情')
  580. console.log(hotspotInfo)
  581. console.log(hotspotInfo.hotspotTitle)
  582. hotspotRelicInfo.value = hotspotInfo
  583. isShowHotspotDetail.value = true
  584. }
  585. /**
  586. * end of iframe的逻辑
  587. */
  588. /**
  589. * 获取全景图somedata.json并处理 todo:改为从本地获取
  590. */
  591. function fixPanoData(panoData) {
  592. // 丢弃没有包含场景的二级分组
  593. let tmp = []
  594. panoData.scenes.forEach((item) => {
  595. panoData.catalogs.forEach((sub) => {
  596. if (item.category == sub.id) {
  597. if (tmp.indexOf(sub) < 0) {
  598. tmp.push(sub)
  599. }
  600. }
  601. })
  602. })
  603. tmp = utils.unique(tmp)
  604. panoData.catalogs = tmp
  605. // 丢弃没有包含二级分组的一级分组
  606. let rootmp = []
  607. tmp.forEach((item) => {
  608. panoData.catalogRoot.forEach((sub) => {
  609. sub.children = utils.unique(sub.children)
  610. if (sub.children.indexOf(item.id) > -1) {
  611. rootmp.push(sub)
  612. }
  613. })
  614. })
  615. rootmp = utils.unique(rootmp)
  616. // 一级分组按名称排序
  617. let sortArr = panoData.catalogRoot.map((item) => item.name)
  618. rootmp.sort((a, b) => {
  619. return sortArr.indexOf(a.name) - sortArr.indexOf(b.name)
  620. })
  621. // 各个一级分组的children去重,只留下有实际的二级分组相对应的那些children item。
  622. panoData.catalogRoot = rootmp.map((item) => {
  623. let temp = []
  624. item.children = utils.unique(item.children)
  625. item.children.forEach((sub) => {
  626. tmp.forEach((jj) => {
  627. if (jj.id == sub) {
  628. temp.push(sub)
  629. }
  630. })
  631. })
  632. return {
  633. ...item,
  634. children: temp,
  635. }
  636. })
  637. // 多余
  638. panoData.catalogs = tmp
  639. // 如果没有一级分组(一定也就没有二级分组)就创建一级分组和二级分组 有必要吗?
  640. let cid = "c_" + utils.randomWord(true, 8, 8)
  641. if (panoData.catalogRoot.length <= 0) {
  642. panoData.catalogRoot.push({
  643. id: "r_" + utils.randomWord(true, 8, 8),
  644. name: "全部场景",
  645. children: [cid],
  646. })
  647. }
  648. if (panoData.catalogs.length <= 0) {
  649. panoData.catalogs.push({
  650. id: cid,
  651. name: "默认二级分组",
  652. })
  653. }
  654. // 如果有初始场景,改为引用场景列表中对应的那个场景的js对象
  655. if (panoData.firstScene) {
  656. panoData.firstScene = panoData.scenes.find(
  657. (item) => item.sceneCode == panoData.firstScene.sceneCode
  658. )
  659. }
  660. }
  661. onMounted(() => {
  662. api.fetchPanoData('WK1730428603763576832').then((res) => {
  663. fixPanoData(res)
  664. store.commit('setPanoData', res)
  665. console.log('catalogTopology', store.getters.catalogTopology)
  666. })
  667. })
  668. /**
  669. * end of 获取全景图somedata.json并处理
  670. */
  671. </script>
  672. <style lang="less" scoped>
  673. *{
  674. user-select: none;
  675. }
  676. .pano-view{
  677. position: relative;
  678. height: 100%;
  679. .pano-wrap{
  680. position: absolute;
  681. left: 0;
  682. top: 0;
  683. width: 100%;
  684. height: 100%;
  685. &.hide{
  686. opacity: 0;
  687. }
  688. >iframe{
  689. position: absolute;
  690. left: 0;
  691. top: 0;
  692. width: 100%;
  693. height: 100%;
  694. }
  695. >button.return-home{
  696. position: absolute;
  697. width: 77px;
  698. height: 77px;
  699. top: 43px;
  700. right: 51px;
  701. background-image: v-bind(btnReturnHomeImgUrl);
  702. background-size: cover;
  703. background-repeat: no-repeat;
  704. background-position: center center;
  705. &:hover{
  706. background-image: v-bind(btnReturnHomeActiveImgUrl);
  707. }
  708. }
  709. >.camera-desc{
  710. z-index: 5;
  711. }
  712. >.character-desc{
  713. z-index: 5;
  714. }
  715. >.character-wrap{
  716. position: absolute;
  717. left: 40px;
  718. bottom: 0;
  719. width: 300px;
  720. height: 452px;
  721. >button.name{
  722. position: absolute;
  723. top: 50px;
  724. left: 0;
  725. transform: translateX(-50%);
  726. width: 36px;
  727. height: 178px;
  728. z-index: 3;
  729. font-size: 23px;
  730. font-family: Source Han Serif SC, Source Han Serif SC;
  731. font-weight: 400;
  732. color: #43310E;
  733. line-height: 27px;
  734. letter-spacing: 7px;
  735. writing-mode: vertical-lr;
  736. background-image: url(@/assets/images/people-name-bg.png);
  737. background-size: contain;
  738. background-repeat: no-repeat;
  739. background-position: center center;
  740. >span{
  741. position: absolute;
  742. left: 45%;
  743. top: 50%;
  744. transform: translate(-50%, -50%);
  745. }
  746. }
  747. @frame-width: 256px;
  748. @frame-height: 512px;
  749. @duration-1: 3s;
  750. @frame-num-1: 72;
  751. @duration-2: 2s;
  752. @frame-num-2: 48;
  753. @duration-3: 1.5s;
  754. @frame-num-3: 36;
  755. >.character-frames-wrapper {
  756. position: absolute;
  757. left: 0;
  758. top: -60px;
  759. height: @frame-height;
  760. width: @frame-width;
  761. overflow: hidden;
  762. z-index: 1;
  763. cursor: pointer;
  764. >.default-frames{
  765. position: absolute;
  766. left: 0;
  767. height: 100%;
  768. animation-name: character-default-animation;
  769. animation-timing-function: steps(73, end);
  770. animation-duration: 3s;
  771. animation-iteration-count: infinite;
  772. }
  773. >.frames {
  774. position: absolute;
  775. height: 100%;
  776. transition-property: none;
  777. &.animating{
  778. transition-property: left;
  779. }
  780. &.state1 {
  781. left: 0;
  782. }
  783. }
  784. >.frames-2{
  785. transition-duration: @duration-2;
  786. transition-timing-function: steps(@frame-num-2 - 1, jump-end);
  787. &.state2 {
  788. left: calc(-100% * (@frame-num-2 - 1));
  789. }
  790. }
  791. >.frames-3{
  792. transition-duration: @duration-3;
  793. transition-timing-function: steps(@frame-num-3 - 1, jump-end);
  794. &.state2 {
  795. left: calc(-100% * (@frame-num-3 - 1));
  796. }
  797. }
  798. }
  799. >img.btn-track{
  800. position: absolute;
  801. width: 598px;
  802. height: 598px;
  803. left: -150px;
  804. bottom: -101px;
  805. }
  806. >button.btn-on-track{
  807. position: absolute;
  808. width: 78px;
  809. height: 78px;
  810. background-size: cover;
  811. background-repeat: no-repeat;
  812. background-position: center center;
  813. text-align: left;
  814. z-index: 3;
  815. >span{
  816. margin-left: 120px;
  817. display: none;
  818. font-size: 23px;
  819. font-family: Source Han Serif SC, Source Han Serif SC;
  820. font-weight: 600;
  821. color: #FFF1BE;
  822. line-height: 27px;
  823. letter-spacing: 5px;
  824. }
  825. &:hover{
  826. width: 397px;
  827. height: 91px;
  828. transform: translate(-13px, -5px);
  829. >span{
  830. display: initial;
  831. }
  832. }
  833. }
  834. >button.one{
  835. left: 210px;
  836. top: -42px;
  837. background-image: v-bind(btnOnTrack1ImgUrl);
  838. &:hover{
  839. background-image: v-bind(btnOnTrack1ActiveImgUrl);
  840. }
  841. }
  842. >button.two{
  843. left: 336px;
  844. top: 62px;
  845. background-image: v-bind(btnOnTrack2ImgUrl);
  846. &:hover{
  847. background-image: v-bind(btnOnTrack2ActiveImgUrl);
  848. }
  849. }
  850. >button.three{
  851. left: 385px;
  852. top: 205px;
  853. background-image: v-bind(btnOnTrack3ImgUrl);
  854. &:hover{
  855. background-image: v-bind(btnOnTrack3ActiveImgUrl);
  856. }
  857. }
  858. >button.four{
  859. left: 352px;
  860. top: 353px;
  861. background-image: v-bind(btnOnTrack4ImgUrl);
  862. &:hover{
  863. background-image: v-bind(btnOnTrack4ActiveImgUrl);
  864. }
  865. }
  866. }
  867. >div.camera-list{
  868. position: absolute;
  869. bottom: 0;
  870. right: 0;
  871. width: calc(1346 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  872. height: calc(161 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  873. background-image: v-bind(cameraListBgUrl);
  874. background-size: cover;
  875. background-repeat: no-repeat;
  876. background-position: center center;
  877. display: flex;
  878. justify-content: flex-end;
  879. align-items: center;
  880. padding-top: calc(10 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  881. padding-right: calc(204 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  882. >button.camera-entry{
  883. width: calc(198 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  884. height: calc(41 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  885. font-size: calc(21 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  886. font-family: Source Han Serif SC, Source Han Serif SC;
  887. font-weight: 600;
  888. color: #FFED87;
  889. line-height: calc(25 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  890. letter-spacing: calc(9 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  891. position: relative;
  892. z-index: 0;
  893. >img.bg-normal{
  894. display: initial;
  895. position: absolute;
  896. left: 50%;
  897. top: 50%;
  898. transform: translate(-50%, -50%);
  899. width: calc(198/ v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  900. height: calc(55 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  901. z-index: -1;
  902. }
  903. >img.bg-active{
  904. display: none;
  905. position: absolute;
  906. left: 50%;
  907. top: 0%;
  908. transform: translate(-50%, -50%);
  909. width: calc(230 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  910. height: auto;
  911. z-index: -1;
  912. }
  913. }
  914. >button.camera-entry.active{
  915. font-size: calc(21 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  916. font-family: Source Han Serif SC, Source Han Serif SC;
  917. font-weight: 600;
  918. color: #794A00;
  919. line-height: calc(25 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  920. letter-spacing: calc(9 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  921. >img.bg-normal{
  922. display: none;
  923. }
  924. >img.bg-active{
  925. display: initial;
  926. }
  927. }
  928. >button.next-scene{
  929. position: absolute;
  930. top: calc(30 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  931. right: calc(45 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  932. width: calc(70 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  933. height: calc(100 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  934. }
  935. }
  936. }
  937. >.camera-content{
  938. z-index: 10;
  939. }
  940. /**
  941. 镜头切换过渡
  942. */
  943. >.text-wrap{
  944. position: absolute;
  945. left: 0;
  946. top: 0;
  947. width: 100%;
  948. height: 100%;
  949. backdrop-filter: blur(10px);
  950. z-index: 11;
  951. background-color: rgba(0, 0, 0, 0.3);
  952. >.text{
  953. position: absolute;
  954. left: 50%;
  955. top: 50%;
  956. max-width: 80%;
  957. max-height: 80%;
  958. overflow: auto;
  959. transform: translate(-50%, -50%);
  960. font-size: 23px;
  961. font-family: Source Han Sans CN, Source Han Sans CN;
  962. font-weight: 400;
  963. color: #FFFFFF;
  964. line-height: 39px;
  965. letter-spacing: 7px;
  966. text-shadow: 0px 15px 25px rgba(0,0,0,0.59);
  967. padding-right: 10px;
  968. }
  969. >button.skip{
  970. position: absolute;
  971. width: 220px;
  972. height: 58px;
  973. right: 50px;
  974. bottom: 50px;
  975. background-image: url(@/assets/images/startup-video-skip.png);
  976. background-size: cover;
  977. background-repeat: no-repeat;
  978. background-position: center center;
  979. }
  980. }
  981. .cloud-top{
  982. position: absolute;
  983. width: 100%;
  984. top: 0;
  985. left: 0;
  986. z-index: 12;
  987. pointer-events: none;
  988. }
  989. .cloud-top-enter-active {
  990. transition: all 1.5s;
  991. }
  992. .cloud-top-leave-active {
  993. transition: all 1.5s;
  994. pointer-events: none;
  995. }
  996. .cloud-top-enter-from {
  997. opacity: 0;
  998. translate: 0 -100%;
  999. }
  1000. .cloud-top-leave-to {
  1001. opacity: 0;
  1002. top: -50%;
  1003. translate: 0 -100%;
  1004. }
  1005. .cloud-left-bottom{
  1006. position: absolute;
  1007. left: 0;
  1008. bottom: 0;
  1009. height: 70%;
  1010. z-index: 12;
  1011. pointer-events: none;
  1012. }
  1013. .cloud-left-bottom-enter-active {
  1014. transition: all 1.5s;
  1015. }
  1016. .cloud-left-bottom-leave-active {
  1017. transition: all 1.5s;
  1018. pointer-events: none;
  1019. }
  1020. .cloud-left-bottom-enter-from {
  1021. opacity: 0;
  1022. translate: -100% 100%;
  1023. }
  1024. .cloud-left-bottom-leave-to {
  1025. opacity: 0;
  1026. pointer-events: none;
  1027. translate: -100% 100%;
  1028. }
  1029. .cloud-right-bottom{
  1030. position: absolute;
  1031. right: 0;
  1032. bottom: 0;
  1033. height: 95%;
  1034. z-index: 12;
  1035. pointer-events: none;
  1036. }
  1037. .cloud-right-bottom-enter-active {
  1038. transition: all 1.5s;
  1039. }
  1040. .cloud-right-bottom-leave-active {
  1041. transition: all 1.5s;
  1042. pointer-events: none;
  1043. }
  1044. .cloud-right-bottom-enter-from {
  1045. opacity: 0;
  1046. translate: 100% 100%;
  1047. }
  1048. .cloud-right-bottom-leave-to {
  1049. opacity: 0;
  1050. pointer-events: none;
  1051. translate: 100% 100%;
  1052. }
  1053. /**
  1054. end of 镜头切换过渡
  1055. */
  1056. /**
  1057. * 场景切换过渡
  1058. */
  1059. >video.scene-intro-video{
  1060. position: absolute;
  1061. left: 0;
  1062. top: 0;
  1063. width: 100%;
  1064. height: 100%;
  1065. object-fit: cover;
  1066. z-index: 20;
  1067. }
  1068. >button.skip-scene-intro{
  1069. position: absolute;
  1070. z-index: 21;
  1071. // background-color: red;
  1072. // opacity: 0.5;
  1073. }
  1074. /**
  1075. * end of 场景切换过渡
  1076. */
  1077. }
  1078. @keyframes character-default-animation {
  1079. 0% {
  1080. translate: 0 0;
  1081. }
  1082. 100% {
  1083. translate: -100% 0;
  1084. }
  1085. }
  1086. </style>