tours.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. <template>
  2. <div
  3. class="tour-list 333"
  4. v-if="tours.length > 0"
  5. :class="{ ban: flying || isSelect, barshow: showTours }"
  6. >
  7. <teleport to=".kankan-app">
  8. <!-- 导览字幕 -->
  9. <div
  10. class="tours-captions"
  11. :class="{ active: showTours }"
  12. v-if="tours.length > 0 && isPlay && !tours[partId].list[frameId].tagId"
  13. >
  14. <!-- <div class="tours-captions" :class="{ active: showTours }" > -->
  15. <div class="captions-title" v-if="tours[partId] && tours[partId].title">
  16. {{ tours[partId].title || "" }}
  17. </div>
  18. <div
  19. class="captions-desc"
  20. v-if="tours[partId] && tours[partId].description"
  21. >
  22. {{ tours[partId].description || "" }}
  23. </div>
  24. </div>
  25. </teleport>
  26. <div class="part-content" ref="tourScroll">
  27. <!-- 多个片段 -->
  28. <ul class="part-list" v-if="tours.length > 1">
  29. <li
  30. class="part-item"
  31. v-for="(item, index) in tours"
  32. :key="index"
  33. @click="changeFrame(1, index)"
  34. :class="{
  35. active: partId == index && progressNum > 0,
  36. loopspan: item.name.length > spanlength && partId == index,
  37. disabled: isPlay && partId != index,
  38. }"
  39. :name="index"
  40. >
  41. <span v-if="partId == index">{{ item.name }}</span>
  42. <span v-else>{{
  43. item.name.length > spanlength
  44. ? item.name.slice(0, spanlength)
  45. : item.name
  46. }}</span>
  47. <div v-if="partId == index && progressNum > 0" class="tourbar">
  48. <div :style="`width:${progressNum}%;`" class="tourline"></div>
  49. </div>
  50. </li>
  51. </ul>
  52. <!-- 只有一个片段 -->
  53. <ul class="part-list part-frame" v-else>
  54. <li
  55. class="part-item"
  56. v-for="(item, index) in tours[0].list"
  57. :key="index"
  58. @click="changeFrame(2, index)"
  59. :style="`background-image:url(${common.changeUrl(
  60. item.enter.cover
  61. )});`"
  62. :class="{
  63. active: frameId == index && progressNum > 0,
  64. activeborder: frameId == index && progressNum <= 0,
  65. disabled: isPlay && frameId != index,
  66. }"
  67. :name="index"
  68. >
  69. <div v-if="frameId == index && progressNum > 0" class="tourbar">
  70. <div :style="`width:${progressNum}%;`" class="tourline"></div>
  71. </div>
  72. </li>
  73. </ul>
  74. </div>
  75. </div>
  76. </template>
  77. <script setup>
  78. import { computed, inject, onMounted, watch, ref, nextTick } from "vue";
  79. import { Scrollbar, Dialog } from "@/global_components";
  80. import { useApp, getApp } from "@/app";
  81. import { useStore } from "vuex";
  82. import common from "@/utils/common";
  83. import { useMusicPlayer } from "@/utils/sound";
  84. const musicPlayer = useMusicPlayer();
  85. const spanlength = ref(5);
  86. const triggerTour = inject("triggerTour");
  87. const isOpenTours = inject("isOpenTours");
  88. let timer = null;
  89. const isSelect = ref(false);
  90. const store = useStore();
  91. const tourScroll = ref(null);
  92. const flying = computed(() => store.getters["flying"]);
  93. const controls = computed(() => store.getters["scene/metadata"].controls || {});
  94. const showTours = computed(() => store.getters["tour/showTours"]);
  95. const partId = computed(() => {
  96. let id = store.getters["tour/partId"];
  97. if (isPlay.value) {
  98. slideScroll();
  99. }
  100. return id;
  101. });
  102. const frameId = computed(() => {
  103. let id = store.getters["tour/frameId"];
  104. if (isPlay.value) {
  105. slideScroll();
  106. }
  107. return id;
  108. });
  109. const progressNum = ref(0);
  110. const isPlay = computed(() => {
  111. let status = store.getters["tour/isPlay"];
  112. let map = document.querySelector(".kankan-app div[xui_min_map]");
  113. if (map) {
  114. if (status) {
  115. map.classList.add("disabled");
  116. } else {
  117. map.classList.remove("disabled");
  118. }
  119. }
  120. return status;
  121. });
  122. const isInit = ref(false);
  123. const tours = computed(() => {
  124. let tours = store.getters["tour/tours"];
  125. if (tours.length > 0) {
  126. if (tourScroll.value && !isInit.value) {
  127. isInit.value = true;
  128. new Scrollbar(tourScroll.value, { onlyHorizontal: true });
  129. }
  130. }
  131. return tours;
  132. });
  133. const onModeChange = (name) => {
  134. store.commit("setMode", name);
  135. };
  136. const playTour = async () => {
  137. let player = await getApp().TourManager.player;
  138. if (isPlay.value) {
  139. store.commit("tour/setData", { isPlay: true });
  140. player.pause();
  141. } else {
  142. store.commit("tour/setData", { isPlay: true });
  143. player.play(partId.value);
  144. }
  145. };
  146. const hanlderTourPartPlay = (time) => {
  147. if (!timer) {
  148. timer = KanKan.Animate.transitions.start((progress) => {
  149. progressNum.value = progress * 100;
  150. }, time);
  151. }
  152. };
  153. const cancelTimer = () => {
  154. if (timer) {
  155. KanKan.Animate.transitions.cancel(timer);
  156. timer = null;
  157. }
  158. };
  159. const slideScroll = () => {
  160. nextTick(() => {
  161. let t = setTimeout(() => {
  162. clearTimeout(t);
  163. let id = tours.value.length > 1 ? partId.value : frameId.value;
  164. let item = document.querySelector(`.part-item[name="${id}"]`);
  165. item.scrollIntoView({
  166. block: "center",
  167. behavior: "smooth",
  168. inline: "center",
  169. });
  170. }, 100);
  171. });
  172. };
  173. const hanlderTour = async () => {
  174. let player = await getApp().TourManager.player;
  175. player.on("play", (data) => {
  176. // musicPlayer.pause(true)
  177. window.parent.postMessage(
  178. {
  179. source: "qjkankan",
  180. event: "toggleBgmStatus",
  181. params: {
  182. status: false,
  183. },
  184. },
  185. "*"
  186. );
  187. });
  188. player.on("pause", (tours) => {
  189. console.log("pause", player);
  190. // musicPlayer.resume()
  191. window.parent.postMessage(
  192. {
  193. source: "qjkankan",
  194. event: "toggleBgmStatus",
  195. params: {
  196. status: true,
  197. },
  198. },
  199. "*"
  200. );
  201. progressNum.value = 0;
  202. cancelTimer();
  203. store.commit("tour/setData", { isPlay: false });
  204. });
  205. player.on("end", (tours) => {
  206. // musicPlayer.resume()
  207. window.parent.postMessage(
  208. {
  209. source: "qjkankan",
  210. event: "toggleBgmStatus",
  211. params: {
  212. status: true,
  213. },
  214. },
  215. "*"
  216. );
  217. progressNum.value = 100;
  218. slideScroll();
  219. store.commit("tour/setData", { isPlay: false });
  220. cancelTimer();
  221. });
  222. let currPartId = null;
  223. let currProgress = 0;
  224. let currFrames = 0;
  225. player.on("progress", (data) => {
  226. if (tours.value.length == 1) {
  227. progressNum.value = data.progress * 100;
  228. } else {
  229. if (currPartId != data.partId) {
  230. currPartId = data.partId;
  231. currFrames = tours.value[data.partId].list.length;
  232. currProgress = 0;
  233. } else {
  234. currProgress += data.progress / currFrames;
  235. if (currProgress >= 100) {
  236. currProgress = 100;
  237. }
  238. progressNum.value = currProgress;
  239. }
  240. }
  241. store.commit("tour/setData", {
  242. partId: data.partId,
  243. frameId: data.frameId,
  244. isPlay: true,
  245. });
  246. });
  247. };
  248. const getPartTime = (partId) => {
  249. cancelTimer();
  250. let time = 0;
  251. for (let i = 0; i < tours.value[partId].list.length; i++) {
  252. if (!tours.value[partId].list[i]._end) {
  253. time += tours.value[partId].list[i].time - 0;
  254. if (!tours.value[partId].list[i]._notrans) {
  255. time += 1000;
  256. }
  257. }
  258. }
  259. return time;
  260. };
  261. const openTours = () => {
  262. // showTours.value = !showTours.value
  263. store.commit("tour/setData", { showTours: !showTours.value });
  264. nextTick(() => {
  265. if (isPlay.value) {
  266. slideScroll();
  267. }
  268. });
  269. };
  270. const changeFrame = async (type, id) => {
  271. progressNum.value = 0;
  272. // recorder.selectFrame(id)
  273. let player = await getApp().TourManager.player;
  274. // player.selectFrame(id)
  275. isSelect.value = true;
  276. if (type == 1) {
  277. player.selectPart(id);
  278. console.log(tours.value[id].frameId);
  279. let f_id = 0;
  280. if (tours.value[id].frameId) {
  281. f_id = tours.value[id].frameId;
  282. }
  283. player.selectFrame(f_id).then(() => {
  284. isSelect.value = false;
  285. });
  286. store.commit("tour/setData", {
  287. frameId: f_id,
  288. partId: id,
  289. });
  290. } else {
  291. player.selectFrame(id).then(() => {
  292. isSelect.value = false;
  293. });
  294. store.commit("tour/setData", {
  295. frameId: id,
  296. });
  297. }
  298. slideScroll();
  299. };
  300. const onClickHandler = async () => {
  301. if (isPlay.value) {
  302. let player = await getApp().TourManager.player;
  303. player.pause();
  304. // musicPlayer.resume()
  305. window.parent.postMessage(
  306. {
  307. source: "qjkankan",
  308. event: "toggleBgmStatus",
  309. params: {
  310. status: true,
  311. },
  312. },
  313. "*"
  314. );
  315. }
  316. };
  317. watch(triggerTour, () => {
  318. playTour();
  319. });
  320. watch(isOpenTours, () => {
  321. openTours();
  322. });
  323. watch(isPlay, () => {
  324. window.parent.postMessage(
  325. {
  326. source: "qjkankan",
  327. event: "isPlayTours",
  328. params: {
  329. isPlay: isPlay.value,
  330. },
  331. },
  332. "*"
  333. );
  334. });
  335. onMounted(() => {
  336. useApp().then(async (sdk) => {
  337. hanlderTour();
  338. });
  339. nextTick(() => {
  340. let player = document.querySelector('.player[name="main"]');
  341. player.addEventListener("click", onClickHandler);
  342. });
  343. });
  344. </script>
  345. <style lang="scss" scoped>
  346. $width: 1150px;
  347. .controls-left-buttons {
  348. margin-left: 20px;
  349. margin-bottom: 20px;
  350. display: flex;
  351. }
  352. .buttons.tour {
  353. margin-right: 10px;
  354. > div {
  355. margin-left: 0px;
  356. margin-right: 0px;
  357. padding: 0 10px;
  358. &.show-list {
  359. border-left: solid 1px var(--editor-font-color);
  360. }
  361. .icon-pull-down {
  362. font-size: 12px;
  363. }
  364. span {
  365. right: -10px;
  366. }
  367. }
  368. }
  369. .tour-list {
  370. position: fixed;
  371. bottom: 68px;
  372. left: 50%;
  373. transform: translateX(-50%);
  374. text-align: center;
  375. max-width: $width;
  376. overflow: hidden;
  377. max-height: 0;
  378. transition: 0.3s all ease;
  379. z-index: 9;
  380. &.ban {
  381. pointer-events: none;
  382. }
  383. .part-content {
  384. display: flex;
  385. flex-direction: row;
  386. overflow: hidden;
  387. padding: 10px 30px;
  388. background: linear-gradient(
  389. 268deg,
  390. rgba(0, 0, 0, 0) 0%,
  391. rgba(0, 0, 0, 0.5) 8%,
  392. rgba(0, 0, 0, 0.5) 92%,
  393. rgba(0, 0, 0, 0) 100%
  394. );
  395. .part-list {
  396. display: flex;
  397. > li {
  398. width: 90px;
  399. height: 40px;
  400. background: rgba(24, 24, 24, 0.5);
  401. line-height: 40px;
  402. margin: 0 6px;
  403. cursor: pointer;
  404. border-radius: 4px;
  405. > span,
  406. > div > span {
  407. cursor: pointer;
  408. display: inline-block;
  409. color: rgba(255, 255, 255, 0.8);
  410. }
  411. &.loopspan {
  412. > span,
  413. > div > span {
  414. animation: 5s wordsLoop linear infinite normal;
  415. }
  416. }
  417. &.active {
  418. position: relative;
  419. > span {
  420. color: var(--colors-primary-base);
  421. }
  422. .tourbar {
  423. position: absolute;
  424. width: 78px;
  425. left: 6px;
  426. right: 6px;
  427. bottom: 4px;
  428. height: 2px;
  429. border-radius: 2px;
  430. background: #000;
  431. overflow: hidden;
  432. .tourline {
  433. width: 50%;
  434. height: 100%;
  435. background: var(--colors-primary-base);
  436. }
  437. }
  438. }
  439. &:hover {
  440. opacity: 0.7;
  441. }
  442. }
  443. .activeborder {
  444. border: 1px solid var(--editor-main-color);
  445. }
  446. }
  447. .part-frame {
  448. > li {
  449. width: 120px;
  450. height: 80px;
  451. background-size: cover;
  452. &.active {
  453. .tourbar {
  454. position: absolute;
  455. width: 100%;
  456. left: 0;
  457. right: 0;
  458. bottom: 0;
  459. height: 4px;
  460. background: #000;
  461. overflow: hidden;
  462. opacity: 0.5;
  463. .tourline {
  464. width: 50%;
  465. height: 100%;
  466. background: var(--colors-primary-base);
  467. }
  468. }
  469. }
  470. }
  471. }
  472. }
  473. }
  474. .barshow {
  475. max-height: 102px;
  476. }
  477. @keyframes wordsLoop {
  478. 0% {
  479. transform: translateX(100%);
  480. -webkit-transform: translateX(100%);
  481. }
  482. 100% {
  483. transform: translateX(-180%);
  484. -webkit-transform: translateX(-180%);
  485. }
  486. }
  487. </style>
  488. <style lang="scss">
  489. .tours-captions {
  490. position: absolute;
  491. bottom: 64px;
  492. left: 20px;
  493. width: 480px;
  494. word-break: break-all;
  495. text-shadow: 0 2px 4px rgba(0, 0, 0, 0.25);
  496. text-align: justify;
  497. color: #fff;
  498. pointer-events: none;
  499. z-index: 1;
  500. transition: all 0.3s;
  501. &.active {
  502. bottom: 5rem;
  503. }
  504. .captions-title {
  505. font-size: 28px;
  506. margin-bottom: 10px;
  507. font-weight: 700;
  508. }
  509. .captions-desc {
  510. font-size: 16px;
  511. line-height: 24px;
  512. }
  513. }
  514. </style>