Pārlūkot izejas kodu

feat: 元代的杭州与泉州

chenlei 1 nedēļu atpakaļ
vecāks
revīzija
05fe25ff5f
59 mainītis faili ar 1494 papildinājumiem un 2 dzēšanām
  1. 1 0
      components.d.ts
  2. 2 0
      package.json
  3. BIN
      public/images/hq/Volume btn_off.png
  4. BIN
      public/images/hq/Volume btn_on.png
  5. BIN
      public/images/hq/auto-suspend.png
  6. BIN
      public/images/hq/auto.png
  7. BIN
      public/images/hq/dollhouse.png
  8. BIN
      public/images/hq/enlarge_on.png
  9. BIN
      public/images/hq/floor.png
  10. BIN
      public/images/hq/hotlist.png
  11. BIN
      public/images/hq/inside.png
  12. BIN
      public/images/hq/like.png
  13. BIN
      public/images/hq/logo.png
  14. BIN
      public/images/hq/narrow_off.png
  15. BIN
      public/images/hq/pause.png
  16. BIN
      public/images/hq/play.png
  17. BIN
      public/images/hq/share.png
  18. BIN
      public/images/hq/unlike.png
  19. BIN
      public/images/hq/viewer.png
  20. BIN
      src/hotspot/assets/images/LOGO.png
  21. 182 0
      src/hotspot/views/hotspot/index.hq.scss
  22. 298 0
      src/hotspot/views/hotspot/index.hq.vue
  23. 1 1
      src/index/app.scss
  24. 7 0
      src/index/assets/el.hq.scss
  25. BIN
      src/index/assets/images/1-min.jpg
  26. BIN
      src/index/assets/images/2-min.jpg
  27. BIN
      src/index/assets/images/3-min.jpg
  28. BIN
      src/index/assets/images/4-min.jpg
  29. BIN
      src/index/assets/images/5-min.jpg
  30. BIN
      src/index/assets/images/left.png
  31. BIN
      src/index/assets/images/right.png
  32. BIN
      src/index/assets/images/unit-btn-min.png
  33. 21 0
      src/index/router/index.hq.ts
  34. BIN
      src/index/views/cover/images/bg-min.jpg
  35. BIN
      src/index/views/cover/images/btn-min.png
  36. BIN
      src/index/views/cover/images/date-min.png
  37. BIN
      src/index/views/cover/images/logo-min.png
  38. BIN
      src/index/views/cover/images/m-bg-min.jpg
  39. BIN
      src/index/views/cover/images/m-date-min.png
  40. BIN
      src/index/views/cover/images/m-img-min.png
  41. BIN
      src/index/views/cover/images/m-logo-min.png
  42. BIN
      src/index/views/cover/images/m-subtitle-min.png
  43. BIN
      src/index/views/cover/images/subtitle-min.png
  44. BIN
      src/index/views/cover/images/title-min.png
  45. 93 0
      src/index/views/cover/index.scss
  46. 39 0
      src/index/views/cover/index.vue
  47. BIN
      src/index/views/home/components/hot-spot-list/images/bg-min.jpg
  48. BIN
      src/index/views/home/components/hot-spot-list/images/close.png
  49. BIN
      src/index/views/home/components/hot-spot-list/images/title-min.png
  50. 149 0
      src/index/views/home/components/hot-spot-list/index.hq.scss
  51. 24 0
      src/index/views/home/components/hot-spot-list/index.hq.tsx
  52. 138 0
      src/index/views/home/components/menu/index.hq.scss
  53. 155 0
      src/index/views/home/components/menu/index.hq.tsx
  54. 46 0
      src/index/views/home/components/popup/index.hq.scss
  55. 16 0
      src/index/views/home/components/popup/index.hq.tsx
  56. 1 1
      src/index/views/home/components/share-popup/index.vue
  57. 95 0
      src/index/views/home/components/unit-dialog/index.vue
  58. 103 0
      src/index/views/home/index.hq.scss
  59. 123 0
      src/index/views/home/index.hq.tsx

+ 1 - 0
components.d.ts

@@ -7,6 +7,7 @@ export {}
 /* prettier-ignore */
 declare module 'vue' {
   export interface GlobalComponents {
+    ElDialog: typeof import('element-plus/es')['ElDialog']
     ElImage: typeof import('element-plus/es')['ElImage']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']

+ 2 - 0
package.json

@@ -7,6 +7,8 @@
     "serve": "cross-env VITE_APP_TITLE=大理洱海科普馆 VITE_APP_HOT_DOMAIN=./hotspot.html vite",
     "build:test": "cross-env VITE_APP_TITLE=大理洱海科普馆 VITE_APP_HOT_DOMAIN=./hotspot.html run-p type-check \"build-only {@}\" --",
     "build:offline": "cross-env VITE_APP_OFFLINE=1 npm run build:test && cross-env node ./scripts/generate-offline-config.js && replace-in-file --configFile ./scripts/offline-replace-config.json",
+    "serve:hq": "cross-env VITE_APP_SCENE=hq VITE_APP_TITLE=元代的杭州与泉州 VITE_APP_HOT_DOMAIN=./hotspot.html vite",
+    "build:hq:test": "cross-env VITE_APP_SCENE=hq VITE_APP_TITLE=元代的杭州与泉州 VITE_APP_HOT_DOMAIN=./hotspot.html run-p type-check \"build-only {@}\" --",
     "push:test": "cross-env node ./scripts/publish.js",
     "preview": "vite preview",
     "build-only": "vite build",

BIN
public/images/hq/Volume btn_off.png


BIN
public/images/hq/Volume btn_on.png


BIN
public/images/hq/auto-suspend.png


BIN
public/images/hq/auto.png


BIN
public/images/hq/dollhouse.png


BIN
public/images/hq/enlarge_on.png


BIN
public/images/hq/floor.png


BIN
public/images/hq/hotlist.png


BIN
public/images/hq/inside.png


BIN
public/images/hq/like.png


BIN
public/images/hq/logo.png


BIN
public/images/hq/narrow_off.png


BIN
public/images/hq/pause.png


BIN
public/images/hq/play.png


BIN
public/images/hq/share.png


BIN
public/images/hq/unlike.png


BIN
public/images/hq/viewer.png


BIN
src/hotspot/assets/images/LOGO.png


+ 182 - 0
src/hotspot/views/hotspot/index.hq.scss

@@ -0,0 +1,182 @@
+@use '@/assets/utils.scss';
+
+.hotspot-page {
+  position: fixed;
+  top: 50%;
+  left: 50%;
+  display: flex;
+  flex-direction: column;
+  padding-bottom: 52px;
+  width: utils.vw-calc(1712);
+  height: utils.vw-calc(824);
+  color: white;
+  transform: translate(-50%, -50%);
+  background: #6b8b81;
+  z-index: var(--z-index-popper);
+
+  &::before {
+    content: '';
+    position: absolute;
+    top: utils.vh-calc(10);
+    left: utils.vw-calc(60);
+    width: utils.vw-calc(213);
+    height: utils.vw-calc(64);
+    background: url('@hotspot/assets/images/LOGO.png') no-repeat center / contain;
+  }
+  h3 {
+    margin: utils.vw-calc(90) 0 utils.vw-calc(20);
+    font-size: utils.vw-calc(48);
+    letter-spacing: 4px;
+    text-align: center;
+    font-family: 'SourceHanSerifCN-BOLD';
+  }
+  &-container {
+    width: 100%;
+    height: utils.vw-calc(400);
+
+    .swiper {
+      width: 100%;
+      height: 100%;
+    }
+    iframe {
+      max-width: 1000px;
+      width: calc(100% - 120px);
+      height: 570px;
+      border-radius: 14px;
+    }
+    video,
+    .el-image {
+      max-height: 570px;
+      border-radius: 14px;
+      object-fit: contain;
+    }
+  }
+
+  &-scrollbar {
+    margin: utils.vh-calc(30) auto 0;
+    max-height: 188px;
+    overflow: auto;
+    width: utils.vw-calc(1200);
+    font-family: 'SourceHanSerifCN-Medium';
+
+    &.isTop {
+      max-height: 65%;
+      height: 65%;
+      padding: 50px 0;
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+    }
+    div,
+    p {
+      font-size: utils.vw-calc(18);
+      line-height: utils.vw-calc(35);
+      text-indent: 2em;
+    }
+  }
+
+  .swiper-slide {
+    text-align: center;
+    font-size: 18px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    transition: 0.3s;
+    transform: scale(0.8);
+    position: relative;
+    opacity: 0.5;
+  }
+  .swiper-slide-active,
+  .swiper-slide-duplicate-active {
+    transform: scale(1);
+    opacity: 1;
+    z-index: 999;
+  }
+
+  &-nav {
+    position: absolute;
+    right: 30px;
+    bottom: 10px;
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    gap: 10px;
+    z-index: 1;
+
+    &__item {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      gap: 4px;
+      color: #fff;
+      white-space: nowrap;
+      font-size: 14px;
+      width: 90px;
+      height: 32px;
+      line-height: 32px;
+      cursor: pointer;
+      border-radius: 10px;
+      border: 1px solid #fff;
+
+      &.active {
+        background: #19bbed;
+        border: none;
+      }
+    }
+  }
+}
+
+@media only screen and (max-width: 600px) {
+  .hotspot-page {
+    top: 20px;
+    left: 20px;
+    right: 20px;
+    bottom: 20px;
+    width: unset;
+    height: unset;
+    transform: none;
+
+    &::before {
+      display: none;
+    }
+    h3 {
+      margin: 40px 0;
+    }
+    &-container {
+      height: 200px;
+
+      video,
+      .el-image {
+        max-height: 80vh;
+        width: 90%;
+      }
+      iframe {
+        width: 100%;
+        height: calc(100vh - 90px);
+      }
+    }
+    &-nav {
+      position: relative;
+      right: unset;
+      bottom: unset;
+      flex-wrap: wrap;
+      margin-top: 5px;
+      justify-content: center;
+
+      &__item {
+        width: 70px;
+      }
+    }
+    &-scrollbar {
+      padding: 0 20px;
+      width: 100%;
+      max-height: calc(100vh - 90px);
+
+      div,
+      p {
+        font-size: 12px;
+        line-height: 18px;
+      }
+    }
+  }
+}

+ 298 - 0
src/hotspot/views/hotspot/index.hq.vue

@@ -0,0 +1,298 @@
+<template>
+  <div class="hotspot-page">
+    <h3>{{ myTitle }}</h3>
+
+    <div v-if="!isTextType" class="hotspot-page-container">
+      <!-- 音频播放器 -->
+      <audio
+        id="myAudio"
+        v-if="audio"
+        ref="volumeRef"
+        v-show="isOneAduio"
+        :src="audio"
+        controls
+      ></audio>
+
+      <!-- 模型页面 -->
+      <Swiper
+        :modules="modules"
+        class="hotspot-page-swiper"
+        :slides-per-view="myType === 'model' || isMobile ? 1 : 3"
+        :centered-slides="true"
+        :navigation="Boolean(curList.length) && !isMobile"
+        :pagination="
+          isMobile
+            ? false
+            : {
+                clickable: true,
+              }
+        "
+        @swiper="initSwiper"
+        @slideChange="handleChange"
+      >
+        <SwiperSlide v-for="(item, index) in curList" :key="item.url">
+          <template v-if="swiperInited">
+            <template v-if="myType === 'model'">
+              <iframe v-if="index === myInd" :src="item" frameborder="0" />
+            </template>
+            <template v-else-if="myType === 'video'">
+              <video ref="videos" controls :src="item.url" />
+            </template>
+            <template v-else-if="myType === 'img'">
+              <el-image :src="item" fit="contain" @click="handlePreview(index)" />
+            </template>
+          </template>
+        </SwiperSlide>
+      </Swiper>
+    </div>
+
+    <!-- 底部的tab -->
+    <ul v-if="flooTab.length > 1" class="hotspot-page-nav">
+      <!-- 音频图标 -->
+      <li
+        v-if="audio && !isOneAduio"
+        :class="[
+          'hotspot-page-nav__item',
+          {
+            active: audioSta,
+          },
+        ]"
+        @click="audioSta = !audioSta"
+      >
+        <img :src="VolumeOn" :alt="audioSta ? '关闭音频' : '打开音频'" />
+        <span>音频</span>
+      </li>
+      <li
+        v-for="item in flooTab"
+        :key="item.id"
+        :class="[
+          'hotspot-page-nav__item',
+          {
+            active: myType === item.type,
+          },
+        ]"
+        @click="handleTab(item)"
+      >
+        <img :class="`${item.type}-icon`" :src="item.icon" />
+        <span>{{ item.name }}</span>
+      </li>
+    </ul>
+
+    <div class="hotspot-page-scrollbar" :class="{ isTop: !flooTab.length && !isMobile }">
+      <div v-html="myTxt" />
+    </div>
+  </div>
+</template>
+
+<script>
+  import { Swiper, SwiperSlide } from 'swiper/vue';
+  import { Navigation, Pagination } from 'swiper/modules';
+  import 'swiper/css';
+  import 'swiper/css/navigation';
+  import 'swiper/css/pagination';
+
+  import { parseUrlParams, judgeIsMobile } from '@/utils';
+  import { MESSAGE_KEY } from '@/types';
+
+  import ModelIcon from '@hotspot/assets/images/model-icon.png';
+  import ImageIcon from '@hotspot/assets/images/img-icon.png';
+  import VideoIcon from '@hotspot/assets/images/video-icon.png';
+  import VolumeOn from '@hotspot/assets/images/audio-icon.png';
+  import infoIcon from '@hotspot/assets/images/info-icon.png';
+
+  const urlParams = parseUrlParams(window.location.href);
+  const isMobile = judgeIsMobile();
+
+  export default {
+    name: 'hotspot',
+    components: {
+      Swiper,
+      SwiperSlide,
+    },
+    data() {
+      return {
+        isMobile,
+        VolumeOn,
+        m: urlParams.m,
+        id: urlParams.id,
+        // 音频地址
+        audio: '',
+        // 如果只有单独的音频
+        isOneAduio: false,
+        // 音频状态
+        audioSta: false,
+        swiperInited: false,
+
+        data: {
+          // 模型数组
+          model: [],
+          // 视频数组
+          video: [],
+          // 图片数组
+          img: [],
+        },
+        // 当前 type
+        myType: '',
+
+        // 当前索引
+        myInd: 0,
+
+        // 底部的tab
+        flooTab: [],
+
+        // 标题
+        myTitle: '',
+        // 内容
+        myTxt: '',
+        // 视频内容
+        videoTxt: [],
+        imgTxt: [],
+
+        // 只有标题和文字(没有视频,没有模型,没有图片)
+        oneTxt: false,
+
+        modules: [Navigation, Pagination],
+      };
+    },
+    computed: {
+      curList() {
+        return this.data[this.myType] || [];
+      },
+      isTextType() {
+        return this.myType === 'text';
+      },
+    },
+    watch: {
+      audioSta(val) {
+        if (val) {
+          this.$refs.volumeRef.play();
+          this.$refs.volumeRef.onended = () => {
+            // console.log("----音频播放完毕");
+            this.audioSta = false;
+          };
+        } else this.$refs.volumeRef.pause();
+      },
+    },
+    mounted() {
+      this.getData();
+    },
+    methods: {
+      async getData() {
+        // https://www.4dmodel.com/
+        let url = `${
+          Boolean(Number(import.meta.env.VITE_APP_OFFLINE)) ? '.' : 'https://super.4dage.com'
+        }/data/${this.id}/hot/js/data.js?time=${Math.random()}`;
+        let result = await fetch(url).then((response) => response.json());
+        const resData = result[this.m];
+
+        if (resData) {
+          this.audio = resData.backgroundMusic;
+          // 只有单独的音频上传
+          if (resData.backgroundMusic && !resData.model && !resData.video && !resData.images) {
+            this.isOneAduio = true;
+          }
+          // 底部的tab
+          const arr = [];
+          const obj = {};
+          if (resData.images) {
+            obj.img = resData.images;
+            arr.push({ id: 3, type: 'img', name: '图片', icon: ImageIcon });
+          }
+          if (resData.video) {
+            obj.video = resData.video;
+            arr.push({ id: 2, type: 'video', name: '视频', icon: VideoIcon });
+          } else {
+            this.$nextTick(() => {
+              if (
+                !window.navigator.userAgent.match(
+                  /(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i
+                )
+              ) {
+                this.audioSta = true;
+                this.$refs.volumeRef.play();
+              }
+            });
+          }
+          if (resData.model) {
+            obj.model = resData.model;
+            arr.push({ id: 1, type: 'model', name: '模型', icon: ModelIcon });
+          }
+
+          this.flooTab = arr;
+          this.data = obj;
+
+          // 当前type的值 应该为
+          if (resData.images) this.myType = 'img';
+          else if (resData.model) this.myType = 'model';
+          else if (resData.video) {
+            this.myType = 'video';
+            this.$nextTick(() => {
+              this.handleVideoPlay(this.data.video[0].url);
+            });
+          } else this.myType = 'text';
+
+          this.myTitle = resData.title || '';
+          this.myTxt = resData.content || '';
+          this.videoTxt = resData.videosDesc || [];
+          this.imgTxt = resData.imagesDesc || [];
+
+          // 只有 标题和 文字介绍(没有视频,没有模型,没有图片)
+          if (!obj.model && !obj.video && !obj.img && !resData.backgroundMusic) {
+            this.oneTxt = true;
+          }
+        }
+      },
+
+      handleTab(item) {
+        this.myInd = 0;
+        this.myType = item.type;
+        this.swiper?.slideTo(0);
+
+        switch (this.myType) {
+          case 'video':
+            this.$nextTick(() => {
+              this.handleVideoPlay(this.data.video[0].url);
+            });
+            break;
+        }
+      },
+
+      initSwiper(swiper) {
+        this.swiper = swiper;
+        this.swiperInited = true;
+      },
+      handleChange({ activeIndex }) {
+        this.myInd = activeIndex;
+
+        switch (this.myType) {
+          case 'video':
+            this.handleVideoPlay(this.data.video[activeIndex].url);
+            break;
+        }
+      },
+      handleVideoPlay(url) {
+        const video = this.$refs.videos?.find((i) => i.src === url);
+
+        this.lastVideo?.pause();
+        if (!video) return;
+
+        video.play();
+        this.lastVideo = video;
+      },
+      handlePreview(idx) {
+        window.parent.postMessage(
+          { type: MESSAGE_KEY.SHOW_VIEWER, images: [this.curList[idx]] },
+          '*'
+        );
+      },
+    },
+  };
+</script>
+
+<style lang="scss">
+  @use './index.hq.scss';
+
+  .viewer-button.viewer-close {
+    display: none;
+  }
+</style>

+ 1 - 1
src/index/app.scss

@@ -138,6 +138,6 @@ iframe {
 @media only screen and (max-width: 600px) {
   :root {
     --design-width: 750;
-    --design-height: 1440;
+    --design-height: 1624;
   }
 }

+ 7 - 0
src/index/assets/el.hq.scss

@@ -0,0 +1,7 @@
+@forward 'element-plus/theme-chalk/src/common/var.scss' with (
+  $colors: (
+    'primary': (
+      'base': #ffa629,
+    ),
+  )
+);

BIN
src/index/assets/images/1-min.jpg


BIN
src/index/assets/images/2-min.jpg


BIN
src/index/assets/images/3-min.jpg


BIN
src/index/assets/images/4-min.jpg


BIN
src/index/assets/images/5-min.jpg


BIN
src/index/assets/images/left.png


BIN
src/index/assets/images/right.png


BIN
src/index/assets/images/unit-btn-min.png


+ 21 - 0
src/index/router/index.hq.ts

@@ -0,0 +1,21 @@
+import { createRouter, createWebHashHistory, type RouteRecordRaw } from 'vue-router';
+
+const routes: Array<RouteRecordRaw> = [
+  {
+    path: '/',
+    name: 'cover',
+    component: () => import('@/views/cover/index.vue'),
+  },
+  {
+    path: '/scene',
+    name: 'home',
+    component: () => import('@/views/home'),
+  },
+];
+
+const router = createRouter({
+  history: createWebHashHistory(import.meta.env.BASE_URL),
+  routes,
+});
+
+export default router;

BIN
src/index/views/cover/images/bg-min.jpg


BIN
src/index/views/cover/images/btn-min.png


BIN
src/index/views/cover/images/date-min.png


BIN
src/index/views/cover/images/logo-min.png


BIN
src/index/views/cover/images/m-bg-min.jpg


BIN
src/index/views/cover/images/m-date-min.png


BIN
src/index/views/cover/images/m-img-min.png


BIN
src/index/views/cover/images/m-logo-min.png


BIN
src/index/views/cover/images/m-subtitle-min.png


BIN
src/index/views/cover/images/subtitle-min.png


BIN
src/index/views/cover/images/title-min.png


+ 93 - 0
src/index/views/cover/index.scss

@@ -0,0 +1,93 @@
+@use '@/assets/utils.scss';
+
+.cover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: url('./images/bg-min.jpg') no-repeat top left / cover;
+
+  &__logo {
+    position: absolute;
+    top: utils.vh-calc(87);
+    left: utils.vw-calc(78);
+    width: utils.vw-calc(1720);
+    height: utils.vw-calc(592);
+  }
+  &__title {
+    position: absolute;
+    left: utils.vw-calc(87);
+    bottom: utils.vh-calc(40);
+    width: utils.vw-calc(751);
+    height: utils.vw-calc(127);
+  }
+  &__btn {
+    position: absolute;
+    left: utils.vw-calc(948);
+    bottom: utils.vh-calc(112);
+    width: utils.vw-calc(414);
+    height: utils.vw-calc(51);
+    cursor: pointer;
+  }
+  &__date {
+    position: absolute;
+    right: utils.vw-calc(100);
+    bottom: utils.vh-calc(109);
+    width: utils.vw-calc(296);
+    height: utils.vw-calc(43);
+  }
+  &__subtitle {
+    position: absolute;
+    right: utils.vw-calc(96);
+    bottom: utils.vh-calc(33);
+    width: utils.vw-calc(873);
+    height: utils.vw-calc(56);
+  }
+}
+
+.m-cover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: url('./images/m-bg-min.jpg') no-repeat top left / cover;
+
+  &__img {
+    position: absolute;
+    top: utils.vh-calc(44);
+    left: utils.vw-calc(45);
+    width: utils.vw-calc(213);
+    height: utils.vw-calc(64);
+  }
+  &__logo {
+    position: absolute;
+    top: utils.vh-calc(153);
+    left: 50%;
+    width: utils.vh-calc(722);
+    height: utils.vh-calc(1099);
+    transform: translateX(-50%);
+  }
+  &__btn {
+    position: absolute;
+    left: utils.vw-calc(163);
+    bottom: utils.vh-calc(287);
+    width: utils.vh-calc(414);
+    height: utils.vh-calc(51);
+  }
+  &__date {
+    position: absolute;
+    left: utils.vw-calc(37);
+    bottom: utils.vh-calc(160);
+    width: utils.vh-calc(540);
+    height: utils.vh-calc(43);
+  }
+  &__subtitle {
+    position: absolute;
+    left: utils.vw-calc(35);
+    bottom: utils.vh-calc(50);
+    width: utils.vh-calc(669);
+    height: utils.vh-calc(85);
+  }
+}

+ 39 - 0
src/index/views/cover/index.vue

@@ -0,0 +1,39 @@
+<template>
+  <div :class="isMobile ? 'm-cover' : 'cover'">
+    <template v-if="!isMobile">
+      <img class="cover__logo" src="./images/logo-min.png" draggable="false" />
+      <img class="cover__title" src="./images/title-min.png" draggable="false" />
+      <img
+        class="cover__btn"
+        src="./images/btn-min.png"
+        draggable="false"
+        @click="$router.push({ name: 'home' })"
+      />
+      <img class="cover__date" src="./images/date-min.png" draggable="false" />
+      <img class="cover__subtitle" src="./images/subtitle-min.png" draggable="false" />
+    </template>
+
+    <template v-else>
+      <img class="m-cover__img" src="./images/m-img-min.png" draggable="false" />
+      <img class="m-cover__logo" src="./images/m-logo-min.png" draggable="false" />
+      <img
+        class="m-cover__btn"
+        src="./images/btn-min.png"
+        draggable="false"
+        @click="$router.push({ name: 'home' })"
+      />
+      <img class="m-cover__date" src="./images/m-date-min.png" draggable="false" />
+      <img class="m-cover__subtitle" src="./images/m-subtitle-min.png" draggable="false" />
+    </template>
+  </div>
+</template>
+
+<script setup>
+  import { judgeIsMobile } from '@/utils';
+
+  const isMobile = judgeIsMobile();
+</script>
+
+<style lang="scss" scoped>
+  @use './index.scss';
+</style>

BIN
src/index/views/home/components/hot-spot-list/images/bg-min.jpg


BIN
src/index/views/home/components/hot-spot-list/images/close.png


BIN
src/index/views/home/components/hot-spot-list/images/title-min.png


+ 149 - 0
src/index/views/home/components/hot-spot-list/index.hq.scss

@@ -0,0 +1,149 @@
+#hotListWrap {
+  display: flex;
+  flex-direction: column;
+  position: absolute;
+  top: 0;
+  right: -400px;
+  width: 365px;
+  max-width: 365px;
+  height: 100%;
+  font-family: 'SourceHanSerifCN-Regular';
+  transition: right 0.4s, width 0.5s;
+  background: url('./images/bg-min.jpg') no-repeat center bottom / cover;
+  z-index: var(--z-index-popper);
+}
+
+.hotListActive {
+  right: 0 !important;
+}
+
+#hotListTitle {
+  position: relative;
+  margin: 50px 0 25px;
+  width: 100%;
+  text-align: center;
+}
+
+#hotListContent {
+  width: 100%;
+  flex-grow: 1;
+  height: 100%;
+  overflow-y: scroll;
+  overflow-x: hidden;
+}
+
+#hotListBottom {
+  margin: 50px 0 70px;
+  text-align: center;
+}
+
+#hotListClose {
+  width: 40px;
+  height: 40px;
+  cursor: pointer;
+}
+
+#hotListContent ul {
+  padding: 0 30px;
+  font-size: 18px;
+  letter-spacing: 3px;
+}
+
+#hotListContent ul li {
+  margin: 17px 0;
+  text-align: center;
+  color: #0c1e19;
+  transition: color 0.3s, background 0.6s;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  cursor: pointer;
+}
+
+#hotListContent ul li:hover {
+  color: white;
+}
+
+#hotListContent ul li.active {
+  color: white;
+}
+
+#hotListText {
+  display: inline-block;
+  width: 233px;
+  height: 57px;
+  background: url('./images/title-min.png') no-repeat center / contain;
+}
+
+#hotListIcon {
+  width: 34px;
+  margin-left: 48px;
+}
+
+#hotListContent::-webkit-scrollbar {
+  width: 6px;
+}
+
+#hotListContent::-webkit-scrollbar-thumb {
+  border-radius: 10px;
+  background-color: #979cab;
+}
+
+#hotListContent::-webkit-scrollbar-track {
+  border-radius: 10px;
+}
+
+@media only screen and (max-width: 910px) {
+  #hotListWrap {
+    top: 20px;
+    right: -100%;
+    bottom: 20px;
+    width: calc(100% - 40px);
+    max-width: unset;
+    height: unset;
+    background: url('./images/bg-min.jpg') no-repeat center bottom / cover;
+  }
+  .hotListActive {
+    right: 20px !important;
+  }
+
+  #hotListTitle {
+    margin: 60px 0 20px;
+    height: auto;
+    position: relative;
+    background: none;
+    opacity: 1;
+  }
+  #hotListText {
+    width: 134px;
+    height: 33px;
+  }
+  #hotListIcon {
+    width: 24px;
+    margin-left: 26px;
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(165%, -50%);
+  }
+  #hotListContent ul li {
+    text-align: center;
+    height: 35px;
+    line-height: 35px;
+  }
+  #hotListContent ul {
+    padding: 0 30px;
+    font-size: 18px;
+  }
+  #hotListBottom {
+    margin: 20px 0 50px;
+  }
+  #hotListClose {
+    width: 20px;
+    height: 20px;
+  }
+  #hotListContent ul li.active {
+    color: white;
+    background: none;
+  }
+}

+ 24 - 0
src/index/views/home/components/hot-spot-list/index.hq.tsx

@@ -0,0 +1,24 @@
+import { defineComponent } from 'vue';
+import closeIcon from './images/close.png';
+import './index.hq.scss';
+
+export default defineComponent({
+  name: 'HomeHotSpotList',
+  render() {
+    return (
+      <div id="hotListWrap">
+        <div id="hotListTitle">
+          <div>
+            <span id="hotListText"></span>
+          </div>
+        </div>
+        <div id="hotListContent">
+          <ul></ul>
+        </div>
+        <div id="hotListBottom">
+          <img id="hotListClose" src={closeIcon} alt="" />
+        </div>
+      </div>
+    );
+  },
+});

+ 138 - 0
src/index/views/home/components/menu/index.hq.scss

@@ -0,0 +1,138 @@
+.pinBottom-container {
+  position: absolute;
+  left: 50%;
+  bottom: 40px;
+  display: flex;
+  align-items: center;
+  padding: 0 40px;
+  height: 67px;
+  transition: all 0.5s;
+  transform: translateX(-50%);
+  font-family: 'SourceHanSerifCN-Medium';
+  background: #a55031;
+  box-shadow: 0px 2px 3px 0px rgba(0, 0, 0, 0.48);
+  border-radius: 30px;
+  border: 1px solid #ffffff;
+  z-index: var(--z-index-top);
+
+  &__logo {
+    margin: 0 20px;
+    width: 59px;
+    height: 44px;
+  }
+  &.open {
+    bottom: 170px;
+
+    &.playing {
+      bottom: 190px;
+    }
+    &.noScroll {
+      bottom: 150px;
+
+      &.playing {
+        bottom: 170px;
+      }
+    }
+  }
+  &.playing:not(.open) {
+    bottom: 60px;
+  }
+}
+
+.pinBottom {
+  display: flex;
+  align-items: center;
+  flex-wrap: nowrap;
+  line-height: 1;
+  transition: all 0.5s;
+  white-space: nowrap;
+
+  > div {
+    float: left;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    margin: 0 20px;
+    cursor: pointer;
+
+    &.active,
+    &:hover,
+    &.opened {
+      color: #f9c396;
+    }
+    img,
+    .icon {
+      margin-bottom: 5px;
+      width: 30px;
+      height: 30px;
+    }
+  }
+}
+
+#thumb {
+  .icon-slot {
+    transition: background ease-in 0.2s;
+    background: url('/images/hq/unlike.png') no-repeat center / contain;
+  }
+
+  &.active .icon-slot {
+    background-image: url('/images/hq/like.png');
+  }
+}
+
+@media only screen and (max-width: 600px) {
+  .pinBottom-container {
+    bottom: 10px;
+    padding: 0 10px;
+    align-items: flex-end;
+    justify-content: space-between;
+    width: 100%;
+    height: 0;
+    border: none;
+    background: none;
+
+    &__logo {
+      display: none;
+    }
+    &.open {
+      bottom: 105px;
+
+      &.noScroll {
+        bottom: 95px;
+
+        &.playing {
+          bottom: 115px;
+        }
+      }
+    }
+    &.playing:not(.open) {
+      bottom: 25px;
+    }
+  }
+  .pinBottom {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding: 15px 0;
+    width: 50px;
+    background: #a55031;
+    border-radius: 30px;
+    border: 1px solid #ffffff;
+
+    > div {
+      margin: 7px 0;
+      font-size: 10px;
+
+      img,
+      .icon {
+        width: 20px;
+        height: 20px;
+      }
+    }
+  }
+  #play,
+  #pause {
+    width: 64px;
+  }
+}

+ 155 - 0
src/index/views/home/components/menu/index.hq.tsx

@@ -0,0 +1,155 @@
+import { defineComponent, ref } from 'vue';
+import SharePopup from '../share-popup/index.vue';
+import './index.hq.scss';
+
+const visitCount = localStorage.getItem('visitCount');
+
+export default defineComponent({
+  name: 'HomeMenu',
+  setup() {
+    const shareVisible = ref(false);
+    const animationThumb = ref(false);
+
+    const handleThumb = () => {
+      if (animationThumb.value) return;
+
+      animationThumb.value = true;
+
+      setTimeout(() => {
+        animationThumb.value = false;
+      }, 200);
+    };
+
+    return {
+      shareVisible,
+      animationThumb,
+      handleThumb,
+    };
+  },
+  render() {
+    return (
+      <div class="pinBottom-container">
+        <div class="pinBottom left">
+          <div id="previous" class="previous desktop-only ui-icon" style="display: none;">
+            <a>
+              <img src="images/hq/play.png" width="24" height="24" data-original-title="播放" />
+            </a>
+          </div>
+          <div id="play" class="ui-icon" data-original-title="播放">
+            <a>
+              <img src="images/hq/play.png" width="24" height="24" />
+            </a>
+            <span>自动漫游</span>
+          </div>
+          <div id="pause" class="ui-icon" style="display: none;">
+            <a>
+              <img title="暂停" src="images/hq/pause.png" width="24" height="24" />
+            </a>
+            <span>自动漫游</span>
+          </div>
+          <div id="next" class="next desktop-only ui-icon wide" style="display: none;">
+            <a>
+              <i title="" class="icon icon-dpad-right" data-original-title="下一个"></i>
+            </a>
+          </div>
+          <div data-original-title="场景导览" id="pullTab" title="">
+            <img
+              class="icon icon-inside"
+              src="images/hq/auto.png"
+              title="场景导览"
+              data-default-url="images/hq/auto.png"
+              data-active-url="images/hq/auto-suspend.png"
+            />
+            <span>场景导览</span>
+          </div>
+          <div data-original-title="热点列表" id="hotList" title="" style="display: none">
+            <img class="icon icon-inside" src="images/hq/hotlist.png" title="热点列表" />
+            <span>热点列表</span>
+          </div>
+          <div data-original-title="全景漫游" id="gui-modes-inside" title="" class="">
+            <img class="icon icon-inside" src="images/hq/inside.png" title="全景漫游" />
+            <span>全景漫游</span>
+          </div>
+          <div data-original-title="迷你模型" id="gui-modes-dollhouse" title="" class="">
+            <img class="icon icon-inside" src="images/hq/dollhouse.png" title="迷你模型" />
+            <span>迷你模型</span>
+          </div>
+          <div data-original-title="俯视图" id="gui-modes-floorplan" title="">
+            <img class="icon icon-inside" src="images/hq/floor.png" title="俯视图" />
+            <span>俯视图</span>
+          </div>
+
+          <img class="pinBottom-container__logo" src="images/hq/logo.png" />
+
+          <div data-original-title="VR" id="vr" title="" style="display: none;">
+            <img class="icon icon-inside" src="images/VR.png" title="VR" />
+          </div>
+          <div
+            data-original-title="消除外壳"
+            id="gui-remove-face"
+            title=""
+            style="display: none; float: left;"
+          >
+            <img class="icon icon-inside" src="images/hq/face.jpg" />
+          </div>
+        </div>
+        <div class="pinBottom right">
+          <div id="thumb" class={{ active: this.animationThumb }} onClick={this.handleThumb}>
+            <div class="icon icon-slot" />
+            <span>点赞</span>
+          </div>
+          <div id="sharing" onClick={() => (this.shareVisible = true)}>
+            <img class="icon icon-inside" src="images/hq/share.png" title="分享" />
+            <span>分享</span>
+          </div>
+          <div id="volume" class="ui-icon wide" style="display: none">
+            <a>
+              <img
+                src="images/hq/Volume btn_on.png"
+                width="24"
+                height="24"
+                data-default-url="images/hq/Volume btn_on.png"
+                data-active-url="images/hq/Volume btn_off.png"
+              />
+            </a>
+            <span>音乐</span>
+          </div>
+          <div id="vr" class="ui-icon wide hidden">
+            <a>
+              <i title="{[{ VIEW_IN_VR }]}" class="icon icon-webvr"></i>
+            </a>
+          </div>
+          <div id="viewer" class="ui-icon wide">
+            <img class="icon icon-inside" src="images/hq/viewer.png" title="浏览量" />
+            <p>{visitCount}</p>
+          </div>
+          <div
+            id="gui-fullscreen"
+            class="ui-icon wide"
+            data-placement="top"
+            title="{[{ VIEW_FULLSCREEN }]}"
+          >
+            <a>
+              <img class="icon icon-fullscreen" src="images/hq/enlarge_on.png" />
+            </a>
+            <span>全屏</span>
+          </div>
+          <div
+            id="gui-fullscreen-exit"
+            class="ui-icon wide"
+            data-placement="top"
+            title="{[{ EXIT_FULLSCREEN }]}"
+            style="display: none;"
+          >
+            <a>
+              <img class="icon icon-fullscreen-exit" src="images/hq/narrow_off.png" />
+            </a>
+            <span>全屏</span>
+          </div>
+        </div>
+
+        <SharePopup visible={this.shareVisible} onUpdate:visible={(v) => (this.shareVisible = v)} />
+      </div>
+    );
+  },
+});

+ 46 - 0
src/index/views/home/components/popup/index.hq.scss

@@ -0,0 +1,46 @@
+#popup {
+  display: none;
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.6);
+  z-index: var(--z-hot-popper);
+
+  &.wait {
+    opacity: 0.1;
+  }
+}
+#id1 {
+  width: 100%;
+  height: 100%;
+}
+.popup-content {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+}
+#closepop {
+  position: absolute;
+  top: 40px;
+  right: 40px;
+  width: 40px;
+  height: 40px;
+  cursor: pointer;
+  text-indent: -999em;
+  background: url('../hot-spot-list/images/close.png') no-repeat center / contain;
+}
+
+@media only screen and (max-width: 600px) {
+  #closepop {
+    width: 20px;
+    height: 20px;
+    top: unset;
+    left: 50%;
+    right: unset;
+    bottom: 80px;
+    transform: translateX(-50%);
+  }
+}

+ 16 - 0
src/index/views/home/components/popup/index.hq.tsx

@@ -0,0 +1,16 @@
+import { defineComponent } from 'vue';
+import './index.hq.scss';
+
+export default defineComponent({
+  name: 'HomePopup',
+  render() {
+    return (
+      <div id="popup">
+        <div class="popup-wrap">
+          <div class="popup-content"></div>
+          <div id="closepop">close</div>
+        </div>
+      </div>
+    );
+  },
+});

+ 1 - 1
src/index/views/home/components/share-popup/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <el-dialog class="share-popup" v-model="show" title="分享">
+  <el-dialog class="share-popup" v-model="show" title="分享" append-to-body>
     <p>请使用手机扫描二维码或 复制分享链接</p>
 
     <vue-qrcode

+ 95 - 0
src/index/views/home/components/unit-dialog/index.vue

@@ -0,0 +1,95 @@
+<template>
+  <el-dialog class="unit-dialog" v-model="show" append-to-body :show-close="false">
+    <Swiper
+      class="unit-dialog-swiper"
+      slides-per-view="auto"
+      :modules="[Navigation]"
+      :space-between="5"
+      :slides-offset-after="100"
+      :slides-offset-before="100"
+      :navigation="true"
+    >
+      <SwiperSlide v-for="(item, i) of LIST" :key="i">
+        <img :src="item.img" />
+      </SwiperSlide>
+    </Swiper>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+  import { computed } from 'vue';
+  import { Swiper, SwiperSlide } from 'swiper/vue';
+  import { Navigation } from 'swiper/modules';
+  import Img1 from '@/assets/images/1-min.jpg';
+  import Img2 from '@/assets/images/2-min.jpg';
+  import Img3 from '@/assets/images/3-min.jpg';
+  import Img4 from '@/assets/images/4-min.jpg';
+  import Img5 from '@/assets/images/5-min.jpg';
+  import 'swiper/css';
+  import 'swiper/css/navigation';
+
+  const props = defineProps<{
+    visible: boolean;
+  }>();
+  const emits = defineEmits(['update:visible']);
+  const LIST = [
+    {
+      img: Img1,
+    },
+    {
+      img: Img2,
+    },
+    {
+      img: Img3,
+    },
+    {
+      img: Img4,
+    },
+    {
+      img: Img5,
+    },
+  ];
+
+  const show = computed({
+    get() {
+      return props.visible;
+    },
+    set(v) {
+      emits('update:visible', v);
+    },
+  });
+</script>
+
+<style lang="scss">
+  @use '@/assets/utils.scss';
+
+  .unit-dialog {
+    --el-dialog-width: 100%;
+    --el-dialog-bg-color: none;
+    --el-dialog-padding-primary: 0;
+    margin-top: utils.vh-calc(83);
+
+    &-swiper {
+      display: flex;
+
+      .swiper-slide {
+        width: auto;
+      }
+    }
+    .swiper-button-prev,
+    .swiper-button-next {
+      width: 25px;
+      height: 41px;
+
+      &::after {
+        display: none;
+      }
+    }
+    .swiper-button-prev {
+      background: url('@/assets/images/left.png') no-repeat center / contain;
+    }
+    .swiper-button-next {
+      background: url('@/assets/images/right.png') no-repeat center / contain;
+    }
+  }
+</style>

+ 103 - 0
src/index/views/home/index.hq.scss

@@ -0,0 +1,103 @@
+@use '@/assets/utils.scss';
+
+.home {
+  width: 100%;
+  height: 100%;
+
+  &_logo {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    position: absolute;
+    left: 50%;
+    bottom: 20px;
+    width: 300px;
+    text-align: center;
+    font-size: 14px;
+    transform: translateX(-50%);
+    color: rgba(255, 255, 255, 0.8);
+
+    img {
+      width: 50%;
+    }
+    span {
+      font-size: 16px;
+      padding: 5px 0;
+      color: rgba(255, 255, 255, 0.8);
+      border-bottom: 1px solid rgba(255, 255, 255, 0.8);
+    }
+  }
+  &_unit-btn {
+    position: absolute;
+    top: utils.vh-calc(47);
+    right: utils.vw-calc(10);
+    width: utils.vw-calc(252);
+    height: utils.vw-calc(60);
+    cursor: pointer;
+    z-index: 1;
+  }
+}
+
+#player {
+  position: absolute;
+  left: 0;
+  bottom: 0;
+  width: 100%;
+  height: 100%;
+
+  canvas {
+    position: relative;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%;
+  }
+}
+
+#hot {
+  position: absolute;
+  padding: 0;
+  width: 100%;
+  height: 100%;
+  pointer-events: none;
+
+  > div[pos='right'] {
+    transform: translate(20px, -50%);
+  }
+  > div[pos='top'] {
+    transform: translate(-50%, calc(-100% - 20px));
+  }
+  > div[pos='middle'] {
+    transform: translate(-50%, -50%);
+  }
+  > div[pos='bottom'] {
+    transform: translate(-50%, 20px);
+  }
+  > div[pos='left'] {
+    transform: translate(calc(-100% - 20px), -50%);
+  }
+  > div {
+    position: absolute;
+    color: #fff;
+    user-select: none;
+    border-radius: 5px;
+    background-color: rgba(34, 34, 34, 0.3);
+    padding: 10px;
+    max-width: 400px;
+    letter-spacing: 1px;
+    line-height: 20px;
+    z-index: var(--z-index-top);
+  }
+}
+
+@media only screen and (max-width: 600px) {
+  .home {
+    &_logo {
+      width: 200px;
+
+      span {
+        font-size: 14px;
+      }
+    }
+  }
+}

+ 123 - 0
src/index/views/home/index.hq.tsx

@@ -0,0 +1,123 @@
+import { defineComponent, ref } from 'vue';
+import JsScript from '@/components/js-script';
+import { judgeIsMobile } from '@/utils';
+import Title from './components/title';
+import WebVr from './components/web-vr';
+import Other from './components/other';
+import Guide from './components/guide';
+import Vrcon from './components/vrcon';
+import Menu from './components/menu';
+import GuiLoading from './components/gui-loading';
+import Popup from './components/popup';
+import HotSpotList from './components/hot-spot-list';
+import UnitIcon from '@/assets/images/unit-btn-min.png';
+import UnitDialog from './components/unit-dialog/index.vue';
+import './index.hq.scss';
+
+// 自定义热点图标
+// @ts-ignore
+// window.hoticon = {
+//   default: '/images/point.png',
+//   higt: '/images/point2.png',
+// };
+
+const isMobile = judgeIsMobile();
+
+export default defineComponent({
+  name: 'home',
+  components: {
+    Title,
+    WebVr,
+    Other,
+    Vrcon,
+    GuiLoading,
+    JsScript,
+    Popup,
+  },
+  setup() {
+    const manageJsLoaded = ref(false);
+    const hotJsLoaded = ref(false);
+    const unitVisible = ref(false);
+
+    return {
+      manageJsLoaded,
+      hotJsLoaded,
+      unitVisible,
+    };
+  },
+  render() {
+    return (
+      <div class="home">
+        {!isMobile && (
+          <img
+            class="home_unit-btn"
+            src={UnitIcon}
+            draggable="false"
+            onClick={() => (this.unitVisible = true)}
+          />
+        )}
+
+        {/* 进度条加载 */}
+        <GuiLoading />
+
+        {/* 加载初始页面 */}
+        <div id="gui-thumb" />
+
+        {/* 热点弹出框 */}
+        <Popup />
+
+        {/* 场景canvs主容器 */}
+        <div id="player" />
+
+        {/* 底部菜单 */}
+        <div id="gui-parent">
+          {/* 热点气泡 */}
+          <div id="hot" />
+
+          <div id="gui" style="display: none;">
+            {/* 标题 */}
+            <Title />
+
+            {/* 热点列表 */}
+            <HotSpotList />
+
+            {/* 底部菜单 */}
+            <Menu />
+
+            {/* 导览 */}
+            <Guide />
+
+            {/* <div class="home_logo">
+              <img src="images/btm_logo.png" />
+              <span>提供技术支持</span>
+            </div> */}
+          </div>
+
+          <WebVr />
+          <Vrcon />
+          <Other />
+        </div>
+
+        <UnitDialog visible={this.unitVisible} onUpdate:visible={(v) => (this.unitVisible = v)} />
+
+        {/* TODO: 没有控制权,耦合严重;放在此处为了防止元素未渲染导致报错 */}
+        <JsScript src="./js/manage.js" onLoad={() => (this.manageJsLoaded = true)} />
+        {this.manageJsLoaded && (
+          <div>
+            <JsScript src="./js/Hot.js" onLoad={() => (this.hotJsLoaded = true)} />
+            {this.hotJsLoaded && (
+              <div>
+                <JsScript src="./js/main_2020_show.js" />
+                {/* 延迟加载 */}
+                <JsScript src="./js/lib/player-0.0.12.min.js" />
+                <JsScript src="./js/lib/Tween.js" />
+                <JsScript src="./js/SpecialScene.js" />
+                <JsScript src="./js/loadCAD.js" />
+              </div>
+            )}
+          </div>
+        )}
+      </div>
+    );
+  },
+});