PanoView.vue 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605
  1. <template>
  2. <div :class="['pano-view', (msgVisible || isShowHotspotDetail2) && 'reduceZ']">
  3. <button
  4. class="logo"
  5. >
  6. <img
  7. src="@/assets/images/logo-bright.png"
  8. alt=""
  9. draggable="false"
  10. >
  11. </button>
  12. <div
  13. class="pano-wrap"
  14. :class="{
  15. hide: !haveShownSceneEffect
  16. }"
  17. >
  18. <iframe
  19. ref="panoIframe"
  20. :src="iframeSrc"
  21. frameborder="0"
  22. />
  23. <button
  24. class="return-home"
  25. @click="router.push({
  26. name: 'HomeView',
  27. })"
  28. />
  29. <button
  30. v-if="sceneIdx === 1"
  31. class="ship-btn"
  32. @click="showShipGame = true"
  33. />
  34. <button
  35. v-if="sceneIdx === 0 && cameraIdx === 0"
  36. class="guide-btn"
  37. @click="showGuide = true"
  38. />
  39. <CameraDesc
  40. v-if="isShowCameraDesc"
  41. class="camera-desc"
  42. @close="isShowCameraDesc = false"
  43. />
  44. <CharacterDesc
  45. v-if="isShowCharacterDesc"
  46. class="character-desc"
  47. @close="isShowCharacterDesc = false"
  48. />
  49. <div
  50. class="character-wrap"
  51. draggable="false"
  52. >
  53. <button
  54. class="name"
  55. @click="isShowCharacterDesc = true"
  56. >
  57. <span>赵孟頫</span>
  58. </button>
  59. <div
  60. :class="['character-frames-wrapper']"
  61. @click="onClickCharacter()"
  62. >
  63. <img
  64. v-show="animationType === 1"
  65. class="default-frames"
  66. src="@/assets/images/pose1-min.png"
  67. alt=""
  68. draggable="false"
  69. >
  70. <img
  71. v-show="animationType === 2"
  72. class="frames frames-2"
  73. :class="{
  74. animating: isCharacterSpecialMoving,
  75. state1: isCharacterSpecialMoving === 0,
  76. state2: isCharacterSpecialMoving === 1,
  77. }"
  78. src="@/assets/images/pose2-min.png"
  79. alt=""
  80. draggable="false"
  81. >
  82. <img
  83. v-show="animationType === 3"
  84. class="frames frames-3"
  85. :class="{
  86. animating: isCharacterSpecialMoving,
  87. state1: isCharacterSpecialMoving === 0,
  88. state2: isCharacterSpecialMoving === 1,
  89. }"
  90. src="@/assets/images/pose3-min.png"
  91. alt=""
  92. draggable="false"
  93. >
  94. <!-- <img
  95. v-show="animationType === 4"
  96. class="frames frames-4"
  97. :class="{
  98. animating: isCharacterSpecialMoving,
  99. state1: isCharacterSpecialMoving === 0,
  100. state2: isCharacterSpecialMoving === 1,
  101. }"
  102. src="@/assets/images/bow-min.png"
  103. alt=""
  104. draggable="false"
  105. > -->
  106. <img
  107. v-show="animationType === 5"
  108. class="frames frames-5"
  109. :class="{
  110. animating: isCharacterSpecialMoving,
  111. state1: isCharacterSpecialMoving === 0,
  112. state2: isCharacterSpecialMoving === 1,
  113. }"
  114. src="@/assets/images/pose4-min.png"
  115. alt=""
  116. draggable="false"
  117. >
  118. </div>
  119. <img
  120. class="btn-track"
  121. :src="require(`@/assets/images/people-btn-track-${sceneIdx + 1}.png`)"
  122. alt=""
  123. draggable="false"
  124. >
  125. <button
  126. class="one btn-on-track"
  127. @click="showingContentIdx = 1"
  128. >
  129. <span>{{ btnOnTrack1Name }}</span>
  130. </button>
  131. <button
  132. class="two btn-on-track"
  133. @click="showingContentIdx = 2"
  134. >
  135. <span>{{ btnOnTrack2Name }}</span>
  136. </button>
  137. <button
  138. v-if="!(sceneIdx === 1 && cameraIdx === 1)"
  139. class="three btn-on-track"
  140. @click="showingContentIdx = 3"
  141. >
  142. <span>{{ btnOnTrack3Name }}</span>
  143. </button>
  144. <button
  145. :class="['btn-on-track four', (sceneIdx === 1 && cameraIdx === 1) && 'four2']"
  146. @click="router.push({
  147. name: 'RelicList',
  148. query: {
  149. sceneIdx: route.query.sceneIdx,
  150. cameraIdx: route.query.cameraIdx,
  151. }
  152. })"
  153. >
  154. <span>文物长卷</span>
  155. </button>
  156. </div>
  157. <div
  158. v-show="isShowCameraList"
  159. class="camera-list"
  160. >
  161. <button
  162. v-for="(item, idx) in currentCameraList"
  163. :key="idx"
  164. class="camera-entry"
  165. :class="{
  166. active: idx === (mouseEnterCameraItemIdx === -1 ? cameraIdx : mouseEnterCameraItemIdx)
  167. }"
  168. @mouseenter="mouseEnterCameraItemIdx = idx"
  169. @mouseleave="mouseEnterCameraItemIdx = -1"
  170. @click="router.push({
  171. name: route.name,
  172. query:{
  173. sceneIdx: route.query.sceneIdx,
  174. cameraIdx: idx,
  175. }
  176. })"
  177. >
  178. <span>{{ item.name }}</span>
  179. <img
  180. class="bg-normal"
  181. :src="require(`@/assets/images/camera-list-item-bg-${sceneIdx + 1}.png`)"
  182. alt=""
  183. draggable="false"
  184. >
  185. <!-- <template v-if="sceneIdx === 0 && idx > 0">
  186. <img
  187. class="bg-active"
  188. :src="require(`@/assets/images/camera-list-item-bg-active-1-${idx}.png`)"
  189. alt=""
  190. draggable="false"
  191. >
  192. </template> -->
  193. <img
  194. class="bg-active"
  195. :src="require(`@/assets/images/camera-list-item-bg-active-${sceneIdx + 1}.png`)"
  196. alt=""
  197. draggable="false"
  198. >
  199. </button>
  200. <button
  201. class="next-scene"
  202. @click="onClickNextScene"
  203. />
  204. </div>
  205. </div>
  206. <CameraContent1
  207. v-if="showingContentIdx === 1"
  208. class="camera-content"
  209. @close="showingContentIdx = 0"
  210. />
  211. <CameraContent2
  212. v-if="showingContentIdx === 2"
  213. class="camera-content"
  214. @close="showingContentIdx = 0"
  215. />
  216. <CameraContent3
  217. v-if="showingContentIdx === 3"
  218. class="camera-content"
  219. @close="showingContentIdx = 0"
  220. />
  221. <RelicDetailForHotspot
  222. v-if="isShowHotspotDetail"
  223. :relic-info="hotspotRelicInfo"
  224. :relic-index="hotspotIndex"
  225. @close="isShowHotspotDetail = false"
  226. />
  227. <HotspotDialog1
  228. v-if="isShowHotspotDetail2"
  229. :relic-info="hotspotRelicInfo"
  230. @close="isShowHotspotDetail2 = false"
  231. @open-relic-detail="(e) => {
  232. hotspotIndex = e
  233. isShowHotspotDetail = true
  234. }"
  235. @open-ship-game="showShipGame = true"
  236. />
  237. <!-- <OpenState /> -->
  238. <!-- 镜头切换过渡 -->
  239. <transition name="fade-in-out-slow">
  240. <div
  241. v-if="isShowCameraIntro"
  242. class="text-wrap"
  243. >
  244. <div
  245. class="text text-indent"
  246. v-html="cameraIntroText"
  247. />
  248. <button
  249. class="skip"
  250. @click="skipCameraIntro"
  251. />
  252. <template v-if="showScene0Camera1To2Video || showScene0Camera2To3Video || showScene1Camera1To2Video || showScene1Camera2To3Video || showScene2Camera1To2Video">
  253. <video
  254. :src="require(
  255. showScene0Camera1To2Video ? '@/assets/videos/scene-1-camera-1.mp4' :
  256. showScene0Camera2To3Video ? '@/assets/videos/scene-1-camera-2.mp4' :
  257. showScene1Camera1To2Video ? '@/assets/videos/scene-2-camera-1.mp4' :
  258. showScene1Camera2To3Video ? '@/assets/videos/scene-2-camera-2.mp4' :
  259. '@/assets/videos/scene-3-camera-1.mp4'
  260. )"
  261. playsinline
  262. autoplay
  263. webkit-playsinline="true"
  264. x5-video-player-type="h5"
  265. style="position: relative; z-index: -2; width: 100vw; height: 100vh; object-fit: cover;"
  266. />
  267. <div
  268. style="position:absolute; z-index: -1; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,.5)"
  269. />
  270. </template>
  271. <!-- 人物 -->
  272. <div :class="['text-wrap__character', charactorSpeackPositionLeft && 'left']">
  273. <img
  274. draggable="false"
  275. :src="require(charactorSpeackPositionLeft ? '@/assets/images/talk2-min.png' : `@/assets/images/slow_talking.png`)"
  276. >
  277. </div>
  278. </div>
  279. </transition>
  280. <transition name="cloud-top">
  281. <img
  282. v-if="isShowCameraIntro"
  283. class="cloud cloud-top"
  284. src="@/assets/images/cloud-top.png"
  285. alt=""
  286. draggable="false"
  287. >
  288. </transition>
  289. <transition name="cloud-left-bottom">
  290. <img
  291. v-if="isShowCameraIntro"
  292. class="cloud-left-bottom"
  293. src="@/assets/images/cloud-left-bottom.png"
  294. alt=""
  295. draggable="false"
  296. >
  297. </transition>
  298. <transition name="cloud-right-bottom">
  299. <img
  300. v-if="isShowCameraIntro"
  301. class="cloud-right-bottom"
  302. src="@/assets/images/cloud-right-bottom.png"
  303. alt=""
  304. draggable="false"
  305. >
  306. </transition>
  307. <!-- end of 镜头切换过渡 -->
  308. <!-- 场景切换过渡 -->
  309. <video
  310. v-show="isShowSceneIntroVideoStart"
  311. ref="sceneIntrovideoStartEl"
  312. class="scene-intro-video scene-intro-video-start"
  313. :src="require(`@/assets/videos/scene-${sceneIdx + 1}-introduction.mp4`)"
  314. playsinline
  315. webkit-playsinline="true"
  316. x5-video-player-type="h5"
  317. @ended="onPlayedFirstSceneIntroVideoEnded"
  318. />
  319. <button
  320. v-show="isShowSceneIntroVideoStart"
  321. class="skip-scene-intro"
  322. :style="{
  323. left: skipBtnLeftTop.x + 'px',
  324. top: skipBtnLeftTop.y + 'px',
  325. width: skipBtnRightBottom.x - skipBtnLeftTop.x + 'px',
  326. height: skipBtnRightBottom.y - skipBtnLeftTop.y + 'px',
  327. }"
  328. @click="skipFirstSceneIntro"
  329. />
  330. <!-- end of 场景切换过渡 -->
  331. <MsgContent
  332. v-if="msgVisible"
  333. v-model:cur-index="curMsgIndex"
  334. :list="ZMFMsgList"
  335. @end="msgVisible = false"
  336. />
  337. </div>
  338. <UserGuide
  339. v-if="showGuide"
  340. @close="() => {
  341. showGuide = false
  342. // isShowCharacterDesc = true
  343. }"
  344. />
  345. <MutiRelicHotSpot
  346. v-if="isShowHotspotDetail3"
  347. :relic-info="hotspotRelicInfo"
  348. @close="isShowHotspotDetail3 = false"
  349. @open-relic-detail="(e) => {
  350. hotspotIndex = e
  351. isShowHotspotDetail = true
  352. }"
  353. />
  354. <GameEntryPage
  355. v-if="store.state.gameEntryPageAttrs"
  356. style="z-index: 998;"
  357. class="game-entry-page"
  358. @playShipGame="showShipGame = true"
  359. />
  360. <div
  361. v-if="showShipGame"
  362. style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 999;"
  363. >
  364. <ShipGameView
  365. @close="showShipGame = false"
  366. />
  367. </div>
  368. </template>
  369. <script setup>
  370. import { ref, computed, watch, onMounted, nextTick, watchEffect, onUnmounted, inject } from "vue"
  371. import { useRoute, useRouter, onBeforeRouteUpdate } from "vue-router"
  372. import { useStore } from "vuex"
  373. import CameraDesc from '@/components/CameraDesc.vue'
  374. import CharacterDesc from '@/components/CharacterDesc.vue'
  375. import { defineAsyncComponent } from 'vue'
  376. import sceneTree from '/public/sceneTree.js'
  377. import RelicDetailForHotspot from '@/components/RelicDetailForHotspot.vue'
  378. import HotspotDialog1 from '@/components/HotspotDialog-1.vue'
  379. import { useWindowSize } from '@vueuse/core'
  380. import MsgContent from '@/components/MsgContent.vue'
  381. import UserGuide from "@/components/UserGuide.vue"
  382. // import OpenState from './OpenState.vue'
  383. import MutiRelicHotSpot from "@/components/MutiRelicHotSpot.vue"
  384. import ShipGameView from '@/views/ShipGame/ShipGameView.vue'
  385. import GameEntryPage from '@/components/GameEntryPage.vue'
  386. const GUIDE_KEY = 'is-open-guide'
  387. const msgVisible = ref(false)
  388. const curMsgIndex = ref(0)
  389. const showGuide = ref(false)
  390. const showShipGame = ref(false)
  391. const charactorSpeackPositionLeft = ref(false)
  392. const btnReturnHomeImgUrl = computed(() => {
  393. return `url(${process.env.VUE_APP_CLI_MODE === 'dev' ? '' : '../'}` + require(`@/assets/images/btn-return-home-${sceneIdx.value + 1}.png`) + ')'
  394. })
  395. const btnReturnHomeActiveImgUrl = computed(() => {
  396. return `url(${process.env.VUE_APP_CLI_MODE === 'dev' ? '' : '../'}` + require(`@/assets/images/btn-return-home-active-${sceneIdx.value + 1}.png`) + ')'
  397. })
  398. const btnOnTrack1ImgUrl = computed(() => {
  399. return `url(${process.env.VUE_APP_CLI_MODE === 'dev' ? '' : '../'}` + require(`@/assets/images/camera-btn-${sceneIdx.value + 1}-${cameraIdx.value + 1}-1.png`) + ')'
  400. })
  401. const btnOnTrack1ActiveImgUrl = computed(() => {
  402. return `url(${process.env.VUE_APP_CLI_MODE === 'dev' ? '' : '../'}` + require(`@/assets/images/camera-btn-${sceneIdx.value + 1}-${cameraIdx.value + 1}-1-active.png`) + ')'
  403. })
  404. const btnOnTrack2ImgUrl = computed(() => {
  405. return `url(${process.env.VUE_APP_CLI_MODE === 'dev' ? '' : '../'}` + require(`@/assets/images/camera-btn-${sceneIdx.value + 1}-${cameraIdx.value + 1}-2.png`) + ')'
  406. })
  407. const btnOnTrack2ActiveImgUrl = computed(() => {
  408. return `url(${process.env.VUE_APP_CLI_MODE === 'dev' ? '' : '../'}` + require(`@/assets/images/camera-btn-${sceneIdx.value + 1}-${cameraIdx.value + 1}-2-active.png`) + ')'
  409. })
  410. const btnOnTrack3ImgUrl = computed(() => {
  411. return `url(${process.env.VUE_APP_CLI_MODE === 'dev' ? '' : '../'}` + require(`@/assets/images/camera-btn-${sceneIdx.value + 1}-${cameraIdx.value + 1}-3.png`) + ')'
  412. })
  413. const btnOnTrack3ActiveImgUrl = computed(() => {
  414. return `url(${process.env.VUE_APP_CLI_MODE === 'dev' ? '' : '../'}` + require(`@/assets/images/camera-btn-${sceneIdx.value + 1}-${cameraIdx.value + 1}-3-active.png`) + ')'
  415. })
  416. const btnOnTrack4ImgUrl = computed(() => {
  417. return `url(${process.env.VUE_APP_CLI_MODE === 'dev' ? '' : '../'}` + require(`@/assets/images/camera-btn-${sceneIdx.value + 1}-1-4.png`) + ')'
  418. })
  419. const btnOnTrack4ActiveImgUrl = computed(() => {
  420. return `url(${process.env.VUE_APP_CLI_MODE === 'dev' ? '' : '../'}` + require(`@/assets/images/camera-btn-${sceneIdx.value + 1}-1-4-active.png`) + ')'
  421. })
  422. const cameraListBgUrl = computed(() => {
  423. return `url(${process.env.VUE_APP_CLI_MODE === 'dev' ? '' : '../'}` + require(`@/assets/images/camera-list-bg-${sceneIdx.value + 1}.png`) + ')'
  424. })
  425. const btnOnTrack1Name = computed(() => {
  426. return currentCameraList.value[cameraIdx.value].contentPageBtnNameList[0]
  427. })
  428. const btnOnTrack2Name = computed(() => {
  429. return currentCameraList.value[cameraIdx.value].contentPageBtnNameList[1]
  430. })
  431. const btnOnTrack3Name = computed(() => {
  432. return currentCameraList.value[cameraIdx.value].contentPageBtnNameList[2]
  433. })
  434. let CameraContent1 = defineAsyncComponent(() =>
  435. import(`@/components/CameraContent-${Number(route.query.sceneIdx) + 1}-${Number(route.query.cameraIdx) + 1}-1.vue`)
  436. )
  437. let CameraContent2 = defineAsyncComponent(() =>
  438. import(`@/components/CameraContent-${Number(route.query.sceneIdx) + 1}-${Number(route.query.cameraIdx) + 1}-2.vue`)
  439. )
  440. let CameraContent3 = defineAsyncComponent(() =>
  441. import(`@/components/CameraContent-${Number(route.query.sceneIdx) + 1}-${Number(route.query.cameraIdx) + 1}-3.vue`)
  442. )
  443. onBeforeRouteUpdate((to, from) => {
  444. console.log('to: ', to)
  445. if (to.name === route.name) {
  446. CameraContent1 = defineAsyncComponent(() =>
  447. import(`@/components/CameraContent-${Number(route.query.sceneIdx) + 1}-${Number(route.query.cameraIdx) + 1}-1.vue`)
  448. )
  449. CameraContent2 = defineAsyncComponent(() =>
  450. import(`@/components/CameraContent-${Number(route.query.sceneIdx) + 1}-${Number(route.query.cameraIdx) + 1}-2.vue`)
  451. )
  452. CameraContent3 = defineAsyncComponent(() =>
  453. import(`@/components/CameraContent-${Number(route.query.sceneIdx) + 1}-${Number(route.query.cameraIdx) + 1}-3.vue`)
  454. )
  455. }
  456. })
  457. const {
  458. windowSizeInCssForRef,
  459. windowSizeWhenDesignForRef,
  460. } = useSizeAdapt()
  461. const route = useRoute()
  462. const router = useRouter()
  463. const store = useStore()
  464. const sceneIdx = computed(() => {
  465. return Number(route.query.sceneIdx)
  466. })
  467. const cameraIdx = computed(() => {
  468. return Number(route.query.cameraIdx)
  469. })
  470. const ZMFMsgList = computed(() => {
  471. switch (sceneIdx.value) {
  472. case 0:
  473. switch (cameraIdx.value) {
  474. case 0:
  475. return [
  476. {
  477. inner: '<p>平地而起的元大都,是“大汗之城”,也是“天下大都”。</p><p>元世祖至元四年(1267)正月丁未,元大都城破土动工。刘秉忠以太保兼领中书省而总负新都城兴造之责。至元十一年(1274),元大都宫城建成,世祖皇帝忽必烈御正殿,在此接受皇太子和诸王百官的朝贺。两年之后的至元十三年(1276),元大都城建成。</p>',
  478. audio: 'scene-1-msg-1.mp3'
  479. },
  480. {
  481. inner: '请大家跟随我的脚步,一起游览这座13世纪世界上最伟大的都城建筑——元大都。',
  482. audio: 'follow-me.mp3'
  483. },
  484. {
  485. inner: '请进入场景开始游览。',
  486. audio: 'gogogo.mp3'
  487. }
  488. ]
  489. case 1:
  490. return [
  491. {
  492. inner: '<p>宫城在皇城的东部,成长方形,“周回九里三十步,东西四百八十步,南北六百十五步,高三十五尺”。</p><p>宫城南墙有三门,中央为宫城正门崇天门,约当今故宫太和殿址。宫城四角有角楼,上下三层,用琉璃瓦覆盖。宫城内主要建筑分为南北两部分,南面以大明殿为主体,北面以延春阁为主体,大明殿、延春阁平面皆采用“工”字殿(或阁)形制。</p>',
  493. audio: 'scene-1-msg-2.mp3'
  494. },
  495. ]
  496. default:
  497. return [
  498. {
  499. inner: '<p>元大都采用了自北宋以来的开放式街巷布局,衙署和民居就分布在这样的街道系统。</p><p>元大都内佛教寺院林立,大圣寿万安寺(白塔寺)、大庆寿寺(双塔寺)等都是元大都城的历史见证。</p>',
  500. audio: 'scene-1-msg-3.mp3'
  501. },
  502. ]
  503. }
  504. case 1:
  505. switch (cameraIdx.value) {
  506. case 0:
  507. return [
  508. {
  509. inner: '元代著名水利学家郭守敬向元世祖忽必烈面陈的水利六事,第一项就是修通惠河,以使运至通州的物资可以通过水路直通大都城内。至元三十年(1293)通惠河工程完成,总长 164 里,沿河设坝闸10 处。至此,从钱塘江边的杭州,沿着大运河北上,可以一直通行至大都城内的海子(积水潭)。',
  510. audio: 'scene-2-msg-1.mp3'
  511. },
  512. {
  513. inner: '请大家跟随我的脚步,一起游览流动的文化,滋养元大都的通惠河。',
  514. audio: 'follow-me2.mp3'
  515. },
  516. {
  517. inner: '请进入场景开始游览。',
  518. audio: 'gogogo.mp3'
  519. }
  520. ]
  521. case 1:
  522. return [
  523. {
  524. inner: '元大都的规划、建设,以及城市内从皇帝百官到寻常百姓所需的物资供给,也都与运河这条交通经济命脉紧密相连。充满活力的运河,不仅给元大都带来了便利的交通和山水交映的景观,更使全国的物资汇聚于元大都。',
  525. audio: 'scene-2-msg-2.mp3'
  526. }
  527. ]
  528. default:
  529. return [
  530. {
  531. inner: '四方宝货,齐集大都。水路的畅通,使得元代景德镇等地的瓷器可以比较便利地通过水路运至大都。积水潭中舳舻蔽水,大都城内繁华富庶,形成众多非常繁华的商业区。',
  532. audio: 'scene-2-msg-3.mp3'
  533. }
  534. ]
  535. }
  536. default:
  537. switch (cameraIdx.value) {
  538. case 0:
  539. return [
  540. {
  541. inner: '元大都汇聚了不少文人雅士,更萃集了历代的书画与珍宝。众多文人与艺术家和他们的收藏与创作集粹于元大都,不仅开启了元代文化新的篇章,更影响了此后艺术的发展。笔墨之间、舞台之上,是精神的寄托,是心灵的归宿。',
  542. audio: 'scene-3-msg-1.mp3'
  543. },
  544. {
  545. inner: '请大家跟随我的脚步,一起游览笔墨之间,舞台之上的文化魅力。',
  546. audio: 'follow-me3.mp3'
  547. },
  548. {
  549. inner: '请进入场景开始游览。',
  550. audio: 'gogogo.mp3'
  551. }
  552. ]
  553. default:
  554. return [
  555. {
  556. inner: '元代戏剧艺术走向成熟,特别是剧本创作的成就代表了当时文学的最高水平。散曲也大盛于元,登坛树帜,独领风骚。元大都更是集聚了关汉卿、马致远、王实甫等一批具有很高成就的杂剧创作作家。',
  557. audio: 'scene-3-msg-2.mp3'
  558. }
  559. ]
  560. }
  561. }
  562. })
  563. /**
  564. * 左下角人物相关
  565. */
  566. const isCharacterSpecialMoving = ref(0)
  567. const animationType = ref(1)
  568. const isShowCameraDesc = ref(false)
  569. const isShowCharacterDesc = ref(false)
  570. const showingContentIdx = ref(0)
  571. function onClickCharacter(anType) {
  572. // isShowCameraDesc.value = true
  573. if (!isCharacterSpecialMoving.value) {
  574. if (!msgVisible.value && !anType) {
  575. // 打开弹窗指定伸手动画
  576. animationType.value = 3
  577. } else {
  578. animationType.value = anType ? anType : Math.floor(Math.random() * 2) + 2
  579. }
  580. let duration = 0
  581. switch (animationType.value) {
  582. case 2:
  583. duration = 4000
  584. break
  585. case 3:
  586. duration = 5000
  587. break
  588. case 4:
  589. duration = 5000
  590. break
  591. case 5:
  592. duration = 3120
  593. break
  594. default:
  595. break
  596. }
  597. setTimeout(() => {
  598. isCharacterSpecialMoving.value = 1
  599. setTimeout(() => {
  600. isCharacterSpecialMoving.value = 0
  601. animationType.value = 1
  602. }, duration)
  603. }, 200)
  604. }
  605. if (!anType && !isShowHotspotDetail2.value) {
  606. msgVisible.value = true
  607. }
  608. }
  609. /**
  610. * end of 左下角人物相关
  611. */
  612. /**
  613. * 右下角镜头列表
  614. */
  615. const isShowCameraList = ref(false)
  616. // 防止场景还没加载完用户就点击
  617. setTimeout(() => {
  618. isShowCameraList.value = true
  619. }, 666)
  620. const currentCameraList = computed(() => {
  621. return sceneTree[sceneIdx.value].cameraList
  622. })
  623. const mouseEnterCameraItemIdx = ref(-1)
  624. /**
  625. * end of 右下角镜头列表
  626. */
  627. /**
  628. * 镜头切换过渡
  629. */
  630. const isShowCameraIntro = ref(false)
  631. const cameraIntroText = computed(() => {
  632. return sceneTree[sceneIdx.value].cameraList[cameraIdx.value].desc
  633. })
  634. let cameraIntroAudioTimeoutId = null
  635. let cameraIntroAudio = null
  636. const showScene0Camera1To2Video = ref(false)
  637. const showScene0Camera2To3Video = ref(false)
  638. const showScene1Camera1To2Video = ref(false)
  639. const showScene1Camera2To3Video = ref(false)
  640. const showScene2Camera1To2Video = ref(false)
  641. const clearCameraIntroMedia = (showAction = true) => {
  642. isShowCameraIntro.value = false
  643. showScene0Camera1To2Video.value = false
  644. showScene0Camera2To3Video.value = false
  645. showScene1Camera1To2Video.value = false
  646. showScene1Camera2To3Video.value = false
  647. showScene2Camera1To2Video.value = false
  648. if (sceneIdx.value === 2 && cameraIdx.value === 1 && showAction) {
  649. setTimeout(() => {
  650. onClickCharacter(5)
  651. }, 2000)
  652. }
  653. }
  654. watch(cameraIdx, (vNew, pVal) => {
  655. if (haveShownSceneEffect.value) {
  656. console.log(vNew, pVal)
  657. if (sceneIdx.value === 0) {
  658. if (vNew === 1 && pVal === 0) {
  659. showScene0Camera1To2Video.value = true
  660. } else if (vNew === 2 && pVal === 1) {
  661. showScene0Camera2To3Video.value = true
  662. }
  663. } else if (sceneIdx.value === 1) {
  664. if (vNew === 1 && pVal === 0) {
  665. showScene1Camera1To2Video.value = true
  666. } else if (vNew === 2 && pVal === 1) {
  667. showScene1Camera2To3Video.value = true
  668. }
  669. } else if (sceneIdx.value === 2) {
  670. if (vNew === 1 && pVal === 0) {
  671. showScene2Camera1To2Video.value = true
  672. }
  673. }
  674. isShowCameraIntro.value = true
  675. charactorSpeackPositionLeft.value = !charactorSpeackPositionLeft.value
  676. cameraIntroAudioTimeoutId = setTimeout(() => {
  677. cameraIntroAudio?.pause()
  678. cameraIntroAudio = new Audio(require(`@/assets/audios/camera-intro-${sceneIdx.value + 1}-${cameraIdx.value + 1}.mp3`))
  679. cameraIntroAudio.play()
  680. cameraIntroAudio.addEventListener('ended', clearCameraIntroMedia)
  681. }, 1000)
  682. }
  683. })
  684. // 跳过按钮 功能
  685. function skipCameraIntro(showAction) {
  686. clearTimeout(cameraIntroAudioTimeoutId)
  687. cameraIntroAudio?.pause()
  688. clearCameraIntroMedia(showAction)
  689. }
  690. onUnmounted(() => {
  691. skipCameraIntro()
  692. })
  693. onBeforeRouteUpdate(() => {
  694. skipCameraIntro(false)
  695. })
  696. /**
  697. * end of 镜头切换过渡
  698. */
  699. const isShowSceneIntroVideoStart = ref(false)
  700. const isShowSceneIntroVideoEnd = ref(false)
  701. const sceneIntrovideoStartEl = ref(null)
  702. const sceneIntrovideoEndEl = ref(null)
  703. /**
  704. * 第一次进入某个场景时展示动效
  705. */
  706. const haveShownSceneEffect = computed(() => {
  707. return store.state.haveShownSceneEffect[sceneIdx.value]
  708. })
  709. watch(sceneIdx, (vNew) => {
  710. if (!haveShownSceneEffect.value) {
  711. isShowSceneIntroVideoStart.value = true
  712. nextTick(() => {
  713. sceneIntrovideoStartEl.value.play()
  714. })
  715. }
  716. }, {
  717. immediate: true
  718. })
  719. let sceneIntroAudioTimeoutId = null
  720. let audio = null
  721. /**
  722. * 禁用讲解视频跳过按钮
  723. */
  724. let jumpIntroduceDisable = false
  725. function onPlayedFirstSceneIntroVideoEnded() {
  726. store.commit('setHaveShownSceneEffect', {
  727. idx: sceneIdx.value,
  728. value: true,
  729. })
  730. isShowSceneIntroVideoStart.value = false
  731. if (sceneIdx.value === 0 && cameraIdx.value === 0) {
  732. showGuide.value = !localStorage.getItem(GUIDE_KEY)
  733. }
  734. handleBgAudio({
  735. sceneIdx: sceneIdx.value,
  736. cameraIdx: cameraIdx.value,
  737. canPlay: !isShowSceneIntroVideoStart.value
  738. })
  739. jumpIntroduceDisable = false
  740. }
  741. // 跳过按钮 位置
  742. const { width: windowWidth, height: windowHeight } = useWindowSize()
  743. const skipBtnLeftTop = ref({
  744. x: 0,
  745. y: 0,
  746. })
  747. const skipBtnRightBottom = ref({
  748. x: 0,
  749. y: 0,
  750. })
  751. watchEffect(() => {
  752. skipBtnLeftTop.value = utils.mapPosFromDraftToWindow({
  753. x: 850,
  754. y: 660,
  755. }, 'cover', 1920, 970, windowWidth.value, windowHeight.value)
  756. skipBtnRightBottom.value = utils.mapPosFromDraftToWindow({
  757. x: 1070,
  758. y: 720,
  759. }, 'cover', 1920, 970, windowWidth.value, windowHeight.value)
  760. })
  761. // 跳过按钮 功能
  762. function skipSceneIntro() {
  763. clearTimeout(sceneIntroAudioTimeoutId)
  764. audio?.pause()
  765. store.commit('setHaveShownSceneEffect', {
  766. idx: sceneIdx.value,
  767. value: true,
  768. })
  769. isShowSceneIntroVideoStart.value = false
  770. isShowSceneIntroVideoEnd.value = false
  771. sceneIntrovideoStartEl.value?.pause()
  772. sceneIntrovideoEndEl.value?.pause()
  773. }
  774. function skipFirstSceneIntro() {
  775. if (jumpIntroduceDisable) return
  776. jumpIntroduceDisable = true
  777. const video = sceneIntrovideoStartEl.value
  778. switch (sceneIdx.value) {
  779. case 0:
  780. video.currentTime = 34
  781. break
  782. case 1:
  783. video.currentTime = 42
  784. break
  785. default:
  786. video.currentTime = 32
  787. }
  788. }
  789. onUnmounted(() => {
  790. skipSceneIntro()
  791. })
  792. onBeforeRouteUpdate(() => {
  793. skipSceneIntro()
  794. })
  795. /**
  796. * end of 第一次进入某个场景时展示动效
  797. */
  798. const handleBgAudio = inject('handleBgAudio')
  799. watch(sceneIdx, () => {
  800. handleBgAudio({
  801. sceneIdx: sceneIdx.value,
  802. cameraIdx: cameraIdx.value,
  803. canPlay: !isShowSceneIntroVideoStart.value && !isShowCameraIntro.value
  804. })
  805. }, {
  806. immediate: true,
  807. })
  808. watch([cameraIdx, isShowCameraIntro], (vNew) => {
  809. if (sceneIdx.value > 0 && typeof vNew[0] === 'number') {
  810. handleBgAudio({
  811. sceneIdx: sceneIdx.value,
  812. cameraIdx: vNew[0],
  813. canPlay: !isShowSceneIntroVideoStart.value && !vNew[1]
  814. })
  815. }
  816. }, {
  817. immediate: true
  818. })
  819. /**
  820. * end of 背景音乐
  821. */
  822. /**
  823. * 点击“下一个场景”按钮的逻辑
  824. */
  825. function onClickNextScene() {
  826. if (sceneIdx.value === 0) {
  827. router.push({
  828. name: route.name,
  829. query: {
  830. sceneIdx: Number(route.query.sceneIdx) + 1,
  831. cameraIdx: 0,
  832. }
  833. })
  834. } else if (sceneIdx.value === 1) {
  835. router.push({
  836. name: route.name,
  837. query: {
  838. sceneIdx: Number(route.query.sceneIdx) + 1,
  839. cameraIdx: 0,
  840. }
  841. })
  842. } else if (sceneIdx.value === 2) {
  843. router.push({
  844. name: 'EpilogueView',
  845. })
  846. }
  847. }
  848. /**
  849. * end of 点击“下一个场景”按钮的逻辑
  850. */
  851. const currentVr = computed(() => {
  852. switch (sceneIdx.value) {
  853. case 0:
  854. switch (cameraIdx.value) {
  855. case 0:
  856. return 'fd720_kdDO8sPe6'
  857. case 1:
  858. return 'fd720_CKI5Ly4eo'
  859. default:
  860. return 'fd720_Ti8chaWqT'
  861. }
  862. case 1:
  863. switch (cameraIdx.value) {
  864. case 0:
  865. return 'fd720_f4R2wpbQC'
  866. case 1:
  867. return 'fd720_I2StypBk9'
  868. default:
  869. return 'fd720_tnhWSM7pp'
  870. }
  871. default:
  872. switch (cameraIdx.value) {
  873. case 0:
  874. return 'fd720_mi0ty84tJ'
  875. default:
  876. return 'fd720_0vzJY3UBA'
  877. }
  878. }
  879. })
  880. /**
  881. * iframe的逻辑
  882. */
  883. const iframeSrc = `${process.env.VUE_APP_CLI_MODE === 'dev' ? 'http://192.168.0.44:8081/' : 'https://houseoss.4dkankan.com/project/yzdyh-dadu/pano/'}show.html?id=WK1730428603763576832&lang=zh&vr=${currentVr.value}`
  884. const panoIframe = ref(null)
  885. watch(cameraIdx, (vNew) => {
  886. console.log('parent window: post message!', sceneIdx.value, cameraIdx.value, store.getters.catalogTopology)
  887. panoIframe.value.contentWindow.postMessage({
  888. msgName: 'change-scene',
  889. sceneId: store.getters.catalogTopology[sceneIdx.value].children[0].children[cameraIdx.value].id
  890. }, '*')
  891. }, {
  892. deep: true,
  893. })
  894. const startBgAudio = inject("startBgAudio")
  895. const stopBgAudio = inject("stopBgAudio")
  896. watch(() => store.getters.iframeAttrs, (v) => {
  897. if (v) {
  898. stopBgAudio()
  899. } else {
  900. startBgAudio()
  901. }
  902. })
  903. const isShowHotspotDetail = ref(false)
  904. const isShowHotspotDetail2 = ref(!!route.query.hotspot)
  905. const isShowHotspotDetail3 = ref(false)
  906. const hotspotRelicInfo = ref(route.query.hotspot ? {
  907. name: route.query.hotspot
  908. } : {})
  909. const hotspotIndex = ref(null)
  910. // window.addEventListener('message', (e) => {
  911. // console.log('parent window: received message!', e)
  912. // })
  913. window.showHotspotDetail2 = function(hotspotInfo) {
  914. console.log('parent window: 展示热点高亮')
  915. console.log(hotspotInfo)
  916. console.log(hotspotInfo.hotspotTitle)
  917. hotspotRelicInfo.value = hotspotInfo
  918. msgVisible.value = false
  919. isShowHotspotDetail2.value = true
  920. }
  921. window.showHotspotDetail = function(hotspotInfo) {
  922. console.log('parent window: 展示热点详情')
  923. console.log(hotspotInfo)
  924. console.log(hotspotInfo.hotspotTitle)
  925. // 约定 hotspotTitle 为场景中宝盒索引
  926. hotspotRelicInfo.value = hotspotInfo
  927. if (hotspotInfo.hotspotTitle.split(',').length > 1) {
  928. // 多文物
  929. isShowHotspotDetail3.value = true
  930. } else {
  931. hotspotIndex.value = Number(hotspotInfo.hotspotTitle) - 1
  932. isShowHotspotDetail.value = true
  933. }
  934. }
  935. window.showIframe = (hotspotInfo) => {
  936. console.log('parent window: showIframe...')
  937. store.dispatch('openIframePage', hotspotInfo)
  938. }
  939. window.showGameEntryPage = function(hotspotInfo) {
  940. console.log('parent window: showGameEntryPage. param: ', hotspotInfo)
  941. store.dispatch('openGameEntryPage', hotspotInfo)
  942. }
  943. /**
  944. * end of iframe的逻辑
  945. */
  946. /**
  947. * 获取全景图somedata.json并处理 todo:改为从本地获取
  948. */
  949. function fixPanoData(panoData) {
  950. // 丢弃没有包含场景的二级分组
  951. let tmp = []
  952. panoData.scenes.forEach((item) => {
  953. panoData.catalogs.forEach((sub) => {
  954. if (item.category == sub.id) {
  955. if (tmp.indexOf(sub) < 0) {
  956. tmp.push(sub)
  957. }
  958. }
  959. })
  960. })
  961. tmp = utils.unique(tmp)
  962. panoData.catalogs = tmp
  963. // 丢弃没有包含二级分组的一级分组
  964. let rootmp = []
  965. tmp.forEach((item) => {
  966. panoData.catalogRoot.forEach((sub) => {
  967. sub.children = utils.unique(sub.children)
  968. if (sub.children.indexOf(item.id) > -1) {
  969. rootmp.push(sub)
  970. }
  971. })
  972. })
  973. rootmp = utils.unique(rootmp)
  974. // 一级分组按名称排序
  975. let sortArr = panoData.catalogRoot.map((item) => item.name)
  976. rootmp.sort((a, b) => {
  977. return sortArr.indexOf(a.name) - sortArr.indexOf(b.name)
  978. })
  979. // 各个一级分组的children去重,只留下有实际的二级分组相对应的那些children item。
  980. panoData.catalogRoot = rootmp.map((item) => {
  981. let temp = []
  982. item.children = utils.unique(item.children)
  983. item.children.forEach((sub) => {
  984. tmp.forEach((jj) => {
  985. if (jj.id == sub) {
  986. temp.push(sub)
  987. }
  988. })
  989. })
  990. return {
  991. ...item,
  992. children: temp,
  993. }
  994. })
  995. // 多余
  996. panoData.catalogs = tmp
  997. // 如果没有一级分组(一定也就没有二级分组)就创建一级分组和二级分组 有必要吗?
  998. let cid = "c_" + utils.randomWord(true, 8, 8)
  999. if (panoData.catalogRoot.length <= 0) {
  1000. panoData.catalogRoot.push({
  1001. id: "r_" + utils.randomWord(true, 8, 8),
  1002. name: "全部场景",
  1003. children: [cid],
  1004. })
  1005. }
  1006. if (panoData.catalogs.length <= 0) {
  1007. panoData.catalogs.push({
  1008. id: cid,
  1009. name: "默认二级分组",
  1010. })
  1011. }
  1012. // 如果有初始场景,改为引用场景列表中对应的那个场景的js对象
  1013. if (panoData.firstScene) {
  1014. panoData.firstScene = panoData.scenes.find(
  1015. (item) => item.sceneCode == panoData.firstScene.sceneCode
  1016. )
  1017. }
  1018. }
  1019. onMounted(() => {
  1020. api.fetchPanoData('WK1730428603763576832').then((res) => {
  1021. fixPanoData(res)
  1022. store.commit('setPanoData', res)
  1023. console.log('catalogTopology', store.getters.catalogTopology)
  1024. })
  1025. })
  1026. /**
  1027. * end of 获取全景图somedata.json并处理
  1028. */
  1029. </script>
  1030. <style lang="less" scoped>
  1031. *{
  1032. user-select: none;
  1033. }
  1034. .pano-view{
  1035. position: relative;
  1036. height: 100%;
  1037. &.reduceZ {
  1038. .btn-on-track {
  1039. z-index: 2 !important;
  1040. }
  1041. }
  1042. .pano-wrap{
  1043. position: absolute;
  1044. left: 0;
  1045. top: 0;
  1046. width: 100%;
  1047. height: 100%;
  1048. &.hide{
  1049. opacity: 0;
  1050. }
  1051. >iframe{
  1052. position: absolute;
  1053. left: 0;
  1054. top: 0;
  1055. width: 100%;
  1056. height: 100%;
  1057. }
  1058. >button.guide-btn{
  1059. position: absolute;
  1060. width: 77px;
  1061. height: 77px;
  1062. top: 128px;
  1063. right: 51px;
  1064. background-image: url('@/assets/images/guide/tbn_help-min.png');
  1065. background-size: cover;
  1066. background-repeat: no-repeat;
  1067. background-position: center center;
  1068. z-index: 5;
  1069. }
  1070. >button.ship-btn{
  1071. position: absolute;
  1072. width: 77px;
  1073. height: 77px;
  1074. top: 128px;
  1075. right: 51px;
  1076. background-image: url('@/assets/images/ship-game/tbn_game_boat-min.png');
  1077. background-size: cover;
  1078. background-repeat: no-repeat;
  1079. background-position: center center;
  1080. z-index: 5;
  1081. &:hover{
  1082. background-image: url('@/assets/images/ship-game/btn_game boat_hover_green-min.png');
  1083. }
  1084. }
  1085. >button.return-home{
  1086. position: absolute;
  1087. width: 77px;
  1088. height: 77px;
  1089. top: 43px;
  1090. right: 51px;
  1091. background-image: v-bind(btnReturnHomeImgUrl);
  1092. background-size: cover;
  1093. background-repeat: no-repeat;
  1094. background-position: center center;
  1095. z-index: 5;
  1096. &:hover{
  1097. background-image: v-bind(btnReturnHomeActiveImgUrl);
  1098. }
  1099. }
  1100. >.camera-desc{
  1101. z-index: 7;
  1102. }
  1103. >.character-desc{
  1104. z-index: 7;
  1105. }
  1106. >.character-wrap{
  1107. position: absolute;
  1108. left: 40px;
  1109. bottom: 0;
  1110. width: 300px;
  1111. height: 452px;
  1112. >button.name{
  1113. position: absolute;
  1114. top: 50px;
  1115. left: 0;
  1116. transform: translateX(-50%);
  1117. width: 36px;
  1118. height: 178px;
  1119. z-index: 6;
  1120. font-size: 23px;
  1121. font-family: Source Han Serif SC, Source Han Serif SC;
  1122. font-weight: 400;
  1123. color: #43310E;
  1124. background-image: url(@/assets/images/people-name-bg.png);
  1125. background-size: contain;
  1126. background-repeat: no-repeat;
  1127. background-position: center center;
  1128. >span{
  1129. position: absolute;
  1130. left: 45%;
  1131. top: 50%;
  1132. transform: translate(-50%, -50%);
  1133. line-height: 27px;
  1134. letter-spacing: 7px;
  1135. writing-mode: vertical-lr;
  1136. }
  1137. }
  1138. @frame-width: 640px;
  1139. @frame-height: 480px;
  1140. @duration-1: 2.5s;
  1141. @frame-num-1: 60;
  1142. @duration-2: 4s;
  1143. @frame-num-2: 96;
  1144. @duration-3: 5s;
  1145. @frame-num-3: 121;
  1146. // @duration-4: 3s;
  1147. // @frame-num-4: 72;
  1148. @duration-5: 3.12s;
  1149. @frame-num-5: 161;
  1150. >.character-frames-wrapper {
  1151. position: absolute;
  1152. left: -200px;
  1153. top: -10px;
  1154. height: @frame-height;
  1155. width: @frame-width;
  1156. overflow: hidden;
  1157. z-index: 5;
  1158. cursor: pointer;
  1159. >.default-frames{
  1160. position: absolute;
  1161. left: 0;
  1162. height: 100%;
  1163. animation-name: character-default-animation;
  1164. animation-timing-function: steps(60, end);
  1165. animation-duration: 2.5s;
  1166. animation-iteration-count: infinite;
  1167. }
  1168. >.frames {
  1169. position: absolute;
  1170. height: 100%;
  1171. transition-property: none;
  1172. &.animating{
  1173. transition-property: left;
  1174. }
  1175. &.state1 {
  1176. left: 0;
  1177. }
  1178. }
  1179. >.frames-2{
  1180. transition-duration: @duration-2;
  1181. transition-timing-function: steps(@frame-num-2, jump-end);
  1182. &.state2 {
  1183. left: calc(-100% * (@frame-num-2));
  1184. }
  1185. }
  1186. >.frames-3{
  1187. transition-duration: @duration-3;
  1188. transition-timing-function: steps(@frame-num-3, jump-end);
  1189. &.state2 {
  1190. left: calc(-100% * (@frame-num-3));
  1191. }
  1192. }
  1193. // >.frames-4{
  1194. // transition-duration: @duration-4;
  1195. // transition-timing-function: steps(@frame-num-4 - 1, jump-end);
  1196. // &.state2 {
  1197. // left: calc(-100% * (@frame-num-4 - 1));
  1198. // }
  1199. // }
  1200. >.frames-5{
  1201. transition-duration: @duration-5;
  1202. transition-timing-function: steps(@frame-num-5, jump-end);
  1203. &.state2 {
  1204. left: calc(-100% * (@frame-num-5));
  1205. }
  1206. }
  1207. }
  1208. >img.btn-track{
  1209. position: absolute;
  1210. width: 598px;
  1211. height: 598px;
  1212. left: -150px;
  1213. bottom: -101px;
  1214. }
  1215. >button.btn-on-track{
  1216. position: absolute;
  1217. width: 78px;
  1218. height: 78px;
  1219. background-size: cover;
  1220. background-repeat: no-repeat;
  1221. background-position: center center;
  1222. text-align: left;
  1223. z-index: 6;
  1224. >span{
  1225. margin-left: 120px;
  1226. display: none;
  1227. font-size: 23px;
  1228. font-family: Source Han Serif SC, Source Han Serif SC;
  1229. font-weight: 600;
  1230. color: #FFF1BE;
  1231. line-height: 27px;
  1232. letter-spacing: 5px;
  1233. }
  1234. &:hover{
  1235. width: 390px;
  1236. height: 104px;
  1237. transform: translate(-13px, -5px);
  1238. >span{
  1239. display: initial;
  1240. }
  1241. }
  1242. }
  1243. >button.one{
  1244. left: 210px;
  1245. top: -42px;
  1246. background-image: v-bind(btnOnTrack1ImgUrl);
  1247. &:hover{
  1248. top: -49px;
  1249. left: 217px;
  1250. background-image: v-bind(btnOnTrack1ActiveImgUrl);
  1251. }
  1252. }
  1253. >button.two{
  1254. left: 336px;
  1255. top: 62px;
  1256. background-image: v-bind(btnOnTrack2ImgUrl);
  1257. &:hover{
  1258. top: 54px;
  1259. background-image: v-bind(btnOnTrack2ActiveImgUrl);
  1260. }
  1261. }
  1262. >button.three{
  1263. left: 385px;
  1264. top: 205px;
  1265. background-image: v-bind(btnOnTrack3ImgUrl);
  1266. &:hover{
  1267. top: 196px;
  1268. left: 391px;
  1269. background-image: v-bind(btnOnTrack3ActiveImgUrl);
  1270. }
  1271. }
  1272. >button.four{
  1273. left: 352px;
  1274. top: 353px;
  1275. background-image: v-bind(btnOnTrack4ImgUrl);
  1276. &:hover{
  1277. top: 344px;
  1278. left: 359px;
  1279. background-image: v-bind(btnOnTrack4ActiveImgUrl);
  1280. }
  1281. &.four2 {
  1282. left: 385px;
  1283. top: 205px;
  1284. &:hover {
  1285. top: 196px;
  1286. left: 391px;
  1287. }
  1288. }
  1289. }
  1290. }
  1291. >div.camera-list{
  1292. position: absolute;
  1293. bottom: 0;
  1294. right: 0;
  1295. width: calc(1346 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  1296. height: calc(161 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  1297. background-image: v-bind(cameraListBgUrl);
  1298. background-size: cover;
  1299. background-repeat: no-repeat;
  1300. background-position: center center;
  1301. display: flex;
  1302. justify-content: flex-end;
  1303. align-items: center;
  1304. padding-top: calc(10 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  1305. padding-right: calc(204 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  1306. >button.camera-entry{
  1307. width: calc(198 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  1308. height: calc(41 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  1309. font-size: calc(21 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  1310. font-family: Source Han Serif SC, Source Han Serif SC;
  1311. font-weight: 600;
  1312. color: #FFED87;
  1313. line-height: calc(25 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  1314. letter-spacing: calc(9 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  1315. position: relative;
  1316. z-index: 0;
  1317. >img.bg-normal{
  1318. display: initial;
  1319. position: absolute;
  1320. left: 50%;
  1321. top: 50%;
  1322. transform: translate(-50%, -50%);
  1323. width: calc(198/ v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  1324. height: calc(55 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  1325. z-index: -1;
  1326. }
  1327. >img.bg-active{
  1328. display: none;
  1329. position: absolute;
  1330. left: 50%;
  1331. top: 0%;
  1332. transform: translate(-50%, -50%);
  1333. width: calc(230 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  1334. height: auto;
  1335. z-index: -1;
  1336. }
  1337. }
  1338. >button.camera-entry.active{
  1339. font-size: calc(21 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  1340. font-family: Source Han Serif SC, Source Han Serif SC;
  1341. font-weight: 600;
  1342. color: #794A00;
  1343. line-height: calc(25 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  1344. letter-spacing: calc(9 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  1345. >img.bg-normal{
  1346. display: none;
  1347. }
  1348. >img.bg-active{
  1349. display: initial;
  1350. }
  1351. }
  1352. >button.next-scene{
  1353. position: absolute;
  1354. top: calc(30 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  1355. right: calc(45 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  1356. width: calc(70 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  1357. height: calc(100 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  1358. }
  1359. }
  1360. }
  1361. >.camera-content{
  1362. z-index: 10;
  1363. }
  1364. /**
  1365. 镜头切换过渡
  1366. */
  1367. .text-wrap__character {
  1368. position: absolute;
  1369. right: 0;
  1370. bottom: 0;
  1371. width: 512px;
  1372. height: 512px;
  1373. overflow: hidden;
  1374. &.left {
  1375. right: unset;
  1376. left: 0;
  1377. img {
  1378. animation-duration: 3;
  1379. animation-timing-function: steps(72);
  1380. }
  1381. }
  1382. img {
  1383. height: 100%;
  1384. animation-name: character-default-animation;
  1385. animation-timing-function: steps(121);
  1386. animation-duration: 8s;
  1387. animation-iteration-count: infinite;
  1388. }
  1389. }
  1390. >.text-wrap{
  1391. position: absolute;
  1392. left: 0;
  1393. top: 0;
  1394. width: 100%;
  1395. height: 100%;
  1396. backdrop-filter: blur(10px);
  1397. z-index: 11;
  1398. background-color: rgba(0, 0, 0, 0.3);
  1399. >.text{
  1400. position: absolute;
  1401. left: 50%;
  1402. top: 50%;
  1403. max-width: 80%;
  1404. max-height: 80%;
  1405. overflow: auto;
  1406. transform: translate(-50%, -50%);
  1407. font-size: 23px;
  1408. font-family: SimSun;
  1409. // font-family: Source Han Sans CN, Source Han Sans CN;
  1410. font-weight: 400;
  1411. color: #FFFFFF;
  1412. // line-height: 39px;
  1413. line-height: 1.5;
  1414. letter-spacing: 7px;
  1415. text-shadow: 0px 15px 25px rgba(0,0,0,0.59);
  1416. padding-right: 10px;
  1417. z-index: 1;
  1418. }
  1419. >button.skip{
  1420. position: absolute;
  1421. width: 220px;
  1422. height: 58px;
  1423. right: 50px;
  1424. bottom: 50px;
  1425. background-image: url(@/assets/images/startup-video-skip.png);
  1426. background-size: cover;
  1427. background-repeat: no-repeat;
  1428. background-position: center center;
  1429. z-index: 1;
  1430. }
  1431. }
  1432. .cloud-top{
  1433. position: absolute;
  1434. width: 100%;
  1435. top: 0;
  1436. left: 0;
  1437. z-index: 12;
  1438. pointer-events: none;
  1439. }
  1440. .cloud-top-enter-active {
  1441. transition: all 1.5s;
  1442. }
  1443. .cloud-top-leave-active {
  1444. transition: all 1.5s;
  1445. pointer-events: none;
  1446. }
  1447. .cloud-top-enter-from {
  1448. opacity: 0;
  1449. translate: 0 -100%;
  1450. }
  1451. .cloud-top-leave-to {
  1452. opacity: 0;
  1453. top: -50%;
  1454. translate: 0 -100%;
  1455. }
  1456. .cloud-left-bottom{
  1457. position: absolute;
  1458. left: 0;
  1459. bottom: 0;
  1460. height: 70%;
  1461. z-index: 12;
  1462. pointer-events: none;
  1463. }
  1464. .cloud-left-bottom-enter-active {
  1465. transition: all 1.5s;
  1466. }
  1467. .cloud-left-bottom-leave-active {
  1468. transition: all 1.5s;
  1469. pointer-events: none;
  1470. }
  1471. .cloud-left-bottom-enter-from {
  1472. opacity: 0;
  1473. translate: -100% 100%;
  1474. }
  1475. .cloud-left-bottom-leave-to {
  1476. opacity: 0;
  1477. pointer-events: none;
  1478. translate: -100% 100%;
  1479. }
  1480. .cloud-right-bottom{
  1481. position: absolute;
  1482. right: 0;
  1483. bottom: 0;
  1484. height: 95%;
  1485. z-index: 12;
  1486. pointer-events: none;
  1487. }
  1488. .cloud-right-bottom-enter-active {
  1489. transition: all 1.5s;
  1490. }
  1491. .cloud-right-bottom-leave-active {
  1492. transition: all 1.5s;
  1493. pointer-events: none;
  1494. }
  1495. .cloud-right-bottom-enter-from {
  1496. opacity: 0;
  1497. translate: 100% 100%;
  1498. }
  1499. .cloud-right-bottom-leave-to {
  1500. opacity: 0;
  1501. pointer-events: none;
  1502. translate: 100% 100%;
  1503. }
  1504. /**
  1505. end of 镜头切换过渡
  1506. */
  1507. /**
  1508. * 场景切换过渡
  1509. */
  1510. >video.scene-intro-video{
  1511. position: absolute;
  1512. left: 0;
  1513. top: 0;
  1514. width: 100%;
  1515. height: 100%;
  1516. object-fit: cover;
  1517. z-index: 20;
  1518. }
  1519. >button.skip-scene-intro{
  1520. position: absolute;
  1521. z-index: 21;
  1522. // background-color: red;
  1523. // opacity: 0.5;
  1524. }
  1525. /**
  1526. * end of 场景切换过渡
  1527. */
  1528. }
  1529. @keyframes character-default-animation {
  1530. 0% {
  1531. translate: 0 0;
  1532. }
  1533. 100% {
  1534. translate: -100% 0;
  1535. }
  1536. }
  1537. </style>