소스 검색

feat: 样式定制化

chenlei 11 달 전
부모
커밋
ff627f132c
56개의 변경된 파일1213개의 추가작업 그리고 102개의 파일을 삭제
  1. 1 0
      auto-imports.d.ts
  2. 1 0
      components.d.ts
  3. BIN
      hotspot/assets/images/Volume-off.png
  4. BIN
      hotspot/assets/images/Volume-on.png
  5. BIN
      hotspot/assets/images/icon-image-1@2x.png
  6. BIN
      hotspot/assets/images/icon-image@2x.png
  7. BIN
      hotspot/assets/images/icon-model-1@2x.png
  8. BIN
      hotspot/assets/images/icon-model@2x.png
  9. BIN
      hotspot/assets/images/icon-next@2x-min.png
  10. BIN
      hotspot/assets/images/icon-previous@2x-min.png
  11. BIN
      hotspot/assets/images/icon-video-1@2x.png
  12. BIN
      hotspot/assets/images/icon-video@2x.png
  13. 1 0
      hotspot/main.ts
  14. 43 66
      hotspot/views/hotspot/index.scss
  15. 34 32
      hotspot/views/hotspot/index.vue
  16. 16 1
      src/api/home.ts
  17. 1 1
      src/app.scss
  18. BIN
      src/assets/images/zgrs/Subtract-min.png
  19. BIN
      src/assets/images/zgrs/Volume btn_off.png
  20. BIN
      src/assets/images/zgrs/Volume btn_on.png
  21. BIN
      src/assets/images/zgrs/active-menu-2.png
  22. BIN
      src/assets/images/zgrs/active-menu.png
  23. BIN
      src/assets/images/zgrs/auto-suspend.png
  24. BIN
      src/assets/images/zgrs/auto.png
  25. BIN
      src/assets/images/zgrs/close.png
  26. BIN
      src/assets/images/zgrs/dollhouse-active.png
  27. BIN
      src/assets/images/zgrs/dollhouse.png
  28. BIN
      src/assets/images/zgrs/floor-active.png
  29. BIN
      src/assets/images/zgrs/floor.png
  30. BIN
      src/assets/images/zgrs/good-active.png
  31. BIN
      src/assets/images/zgrs/good.png
  32. BIN
      src/assets/images/zgrs/helper.png
  33. BIN
      src/assets/images/zgrs/hotlist-active.png
  34. BIN
      src/assets/images/zgrs/hotlist.png
  35. BIN
      src/assets/images/zgrs/pause.png
  36. BIN
      src/assets/images/zgrs/play.png
  37. BIN
      src/assets/images/zgrs/share.png
  38. BIN
      src/assets/images/zgrs/title-bg.png
  39. BIN
      src/assets/images/zgrs/title-down.png
  40. BIN
      src/assets/images/zgrs/title-right.png
  41. BIN
      src/assets/images/zgrs/title-up.png
  42. 7 0
      src/types/home.ts
  43. 1 0
      src/utils/services.ts
  44. 198 0
      src/views/home/components/guide/index.zgrs.scss
  45. 26 0
      src/views/home/components/guide/index.zgrs.tsx
  46. 47 0
      src/views/home/components/hot-spot-list/index.zgrs.scss
  47. 13 0
      src/views/home/components/hot-spot-list/index.zgrs.vue
  48. 191 0
      src/views/home/components/menu/index.zgrs.scss
  49. 210 0
      src/views/home/components/menu/index.zgrs.vue
  50. 50 0
      src/views/home/components/other/index.scss
  51. 5 2
      src/views/home/components/other/index.tsx
  52. 36 0
      src/views/home/components/popup/index.zgrs.scss
  53. 14 0
      src/views/home/components/popup/index.zgrs.tsx
  54. 32 0
      src/views/home/components/title/index.zgrs.scss
  55. 197 0
      src/views/home/components/title/index.zgrs.vue
  56. 89 0
      src/views/home/index.zgrs.tsx

+ 1 - 0
auto-imports.d.ts

@@ -1,5 +1,6 @@
 // Generated by 'unplugin-auto-import'
 export {}
 declare global {
+  const ElMessage: typeof import('element-plus/es')['ElMessage']
   const ElNotification: typeof import('element-plus/es')['ElNotification']
 }

+ 1 - 0
components.d.ts

@@ -10,6 +10,7 @@ declare module '@vue/runtime-core' {
     ElDropdown: typeof import('element-plus/es')['ElDropdown']
     ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
     ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
+    ElImage: typeof import('element-plus/es')['ElImage']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']

BIN
hotspot/assets/images/Volume-off.png


BIN
hotspot/assets/images/Volume-on.png


BIN
hotspot/assets/images/icon-image-1@2x.png


BIN
hotspot/assets/images/icon-image@2x.png


BIN
hotspot/assets/images/icon-model-1@2x.png


BIN
hotspot/assets/images/icon-model@2x.png


BIN
hotspot/assets/images/icon-next@2x-min.png


BIN
hotspot/assets/images/icon-previous@2x-min.png


BIN
hotspot/assets/images/icon-video-1@2x.png


BIN
hotspot/assets/images/icon-video@2x.png


+ 1 - 0
hotspot/main.ts

@@ -1,5 +1,6 @@
 import { createApp } from 'vue';
 import App from './views/hotspot/index.vue';
+import '@/app.scss';
 
 export const app = createApp(App);
 

+ 43 - 66
hotspot/views/hotspot/index.scss

@@ -4,13 +4,18 @@
   left: 0;
   right: 0;
   bottom: 0;
-  background: rgba(255, 252, 247, 0.8);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 22px 0 71px;
+  background: rgba(0, 0, 0, 0.8);
   z-index: var(--z-index-popper);
 
   .audioIcon {
     position: absolute;
-    top: 40px;
-    right: 128px;
+    right: 20px;
+    bottom: 5px;
 
     img {
       width: 52px;
@@ -19,55 +24,45 @@
     }
   }
   &-info {
-    position: absolute;
-    top: 42px;
-    left: 40px;
-    max-width: 50%;
+    color: white;
+    max-width: 1320px;
+    width: calc(100vw - 30vw);
 
     h3 {
-      margin-bottom: 14px;
-      color: #c41800;
-      font-size: 24px;
-      line-height: 28px;
+      margin-bottom: 18px;
+      font-size: 16px;
+      font-weight: bold;
+      text-align: center;
     }
     p {
-      width: 214px;
-      height: 213px;
-      color: #333333;
-      font-size: 14px;
-      overflow-y: auto;
-
-      &::-webkit-scrollbar {
-        width: 6px;
-      }
-      &::-webkit-scrollbar-thumb {
-        border-radius: 10px;
-        background-color: #c41800;
-      }
-      &::-webkit-scrollbar-track {
-        border-radius: 10px;
-      }
+      margin: 0 auto;
+      width: 80%;
+      font-size: 12px;
     }
   }
 
-  &-main {
-    position: absolute;
-    top: 50%;
-    left: 50%;
+  &-container {
+    position: relative;
+    flex: 1;
+    flex-shrink: 1;
+    height: 0;
     display: flex;
+    flex-direction: column;
     align-items: center;
-    justify-content: center;
-    max-width: 1100px;
-    width: calc(100vw - 548px);
-    transform: translate(-50%, -50%);
+    padding: 9vh 0;
+    max-width: 1320px;
+    width: calc(100vw - 30vw);
+    background: rgba(53, 53, 53, 1);
+    box-sizing: border-box;
   }
+
   &-swiper {
     &__left,
     &__right {
       position: absolute;
       top: 50%;
-      width: 50px;
-      height: 50px;
+      width: 25px;
+      height: 24px;
       cursor: pointer;
       transform: translateY(-50%);
       z-index: 1;
@@ -83,7 +78,7 @@
   }
   &-model {
     width: 100%;
-    height: 600px;
+    height: 100%;
 
     iframe {
       width: 100%;
@@ -92,7 +87,7 @@
   }
   &-video {
     width: 100%;
-    height: 561px;
+    height: 100%;
     object-fit: cover;
   }
   &-img {
@@ -102,22 +97,19 @@
     height: inherit;
 
     &-swiper {
-      height: 567px;
-    }
-    img {
+      flex: 1;
       width: 100%;
-      height: 100%;
-      object-fit: contain;
+      height: 0;
     }
   }
 
   &-nav {
     position: absolute;
     left: 50%;
-    bottom: 104px;
+    bottom: 5px;
     display: flex;
     align-items: center;
-    gap: 30px;
+    gap: 10px;
     transform: translateX(-50%);
 
     &__item {
@@ -125,31 +117,16 @@
       align-items: center;
       justify-content: center;
       gap: 9px;
-      width: 120px;
-      height: 36px;
-      border-radius: 18px;
-      color: #666666;
-      background: #d0cec6;
-      border: 1px solid #666666;
+      width: 57px;
+      height: 57px;
       box-sizing: border-box;
       cursor: pointer;
 
       &.active {
-        color: #ddaf35;
-        border-width: 0;
-        background: linear-gradient(#d67729, #c41800);
-      }
-      .model-icon {
-        width: 19px;
-        height: 22px;
       }
-      .video-icon {
-        width: 21px;
-        height: 18px;
-      }
-      .img-icon {
-        width: 21px;
-        height: 18px;
+      img {
+        width: 100%;
+        height: 100%;
       }
     }
   }

+ 34 - 32
hotspot/views/hotspot/index.vue

@@ -1,21 +1,6 @@
 <template>
   <div class="hotspot-page">
-    <div class="hotspot-page-info">
-      <h3>{{ myTitle }}</h3>
-      <p>{{ myTxt }}</p>
-    </div>
-
-    <!-- 音频图标 -->
-    <div
-      v-if="audio && !isOneAduio"
-      class="audioIcon"
-      :title="audioSta ? '关闭音频' : '打开音频'"
-      @click="audioSta = !audioSta"
-    >
-      <img :src="audioSta ? VolumeOff : VolumeOn" alt="" />
-    </div>
-
-    <div class="hotspot-page-main">
+    <div class="hotspot-page-container">
       <!-- 音频播放器 -->
       <audio
         id="myAudio"
@@ -66,7 +51,7 @@
       >
         <SwiperSlide v-for="item in curList" :key="item">
           <div class="hotspot-page-img">
-            <img :src="item" alt="" />
+            <el-image :src="item" fit="contain" />
           </div>
         </SwiperSlide>
       </Swiper>
@@ -75,26 +60,43 @@
         <div class="hotspot-page-swiper__left" @click="handlePre" />
         <div class="hotspot-page-swiper__right" @click="handleNext" />
       </template>
-    </div>
 
-    <!-- 底部的tab -->
-    <div v-if="flooTab.length > 1" class="hotspot-page-nav">
+      <!-- 底部的tab -->
+      <div v-if="flooTab.length > 1" class="hotspot-page-nav">
+        <div
+          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="myType === item.type ? item.acIcon : item.icon" />
+          <!-- {{ item.name }}
+          {{ item.type === 'img' ? `${myInd + 1}/${data.img.length}` : '' }} -->
+        </div>
+      </div>
+
+      <!-- 音频图标 -->
       <div
-        v-for="item in flooTab"
-        :key="item.id"
-        :class="[
-          'hotspot-page-nav__item',
-          {
-            active: myType === item.type,
-          },
-        ]"
-        @click="handleTab(item)"
+        v-if="audio && !isOneAduio"
+        class="audioIcon"
+        :title="audioSta ? '关闭音频' : '打开音频'"
+        @click="audioSta = !audioSta"
       >
-        <img :class="`${item.type}-icon`" :src="myType === item.type ? item.acIcon : item.icon" />
-        {{ item.name }}
-        {{ item.type === 'img' ? `${myInd + 1}/${data.img.length}` : '' }}
+        <img :src="audioSta ? VolumeOff : VolumeOn" alt="" />
       </div>
     </div>
+
+    <el-scrollbar :height="150" style="margin-top: 20px; height: 150px; flex-shrink: 0">
+      <div class="hotspot-page-info">
+        <h3>{{ myTitle }}</h3>
+        <p>{{ myTxt }}</p>
+      </div>
+    </el-scrollbar>
   </div>
 </template>
 

+ 16 - 1
src/api/home.ts

@@ -1,10 +1,25 @@
-import { GetHotSpotListResponse } from '@/types/home';
+import { GetHotSpotListResponse, GetSceneDetailResponse } from '@/types/home';
 import service from '@/utils/services';
 
+const SCENE_BASE_URL = 'https://count.4dage.com/api';
+
 export const homeApi = {
   getHotSpotList() {
     return service.get<GetHotSpotListResponse>(
       `${window.g_Prefix}/data/${window.number}/hot/js/data.js`
     );
   },
+
+  getSceneDetail(sceneCode: string) {
+    return service.get<GetSceneDetailResponse | null>(
+      `${SCENE_BASE_URL}/count/detail/${sceneCode}`
+    );
+  },
+
+  /**
+   * 点赞
+   */
+  saveStar(sceneCode: string) {
+    return service.get(`${SCENE_BASE_URL}/count/saveStar/${sceneCode}`);
+  },
 };

+ 1 - 1
src/app.scss

@@ -2,7 +2,7 @@
   --z-index-normal: 1;
   --z-index-top: 1000;
   --z-index-popper: 2000;
-  --z-hot-popper: 2001;
+  --z-hot-popper: 3000;
 }
 
 body,

BIN
src/assets/images/zgrs/Subtract-min.png


BIN
src/assets/images/zgrs/Volume btn_off.png


BIN
src/assets/images/zgrs/Volume btn_on.png


BIN
src/assets/images/zgrs/active-menu-2.png


BIN
src/assets/images/zgrs/active-menu.png


BIN
src/assets/images/zgrs/auto-suspend.png


BIN
src/assets/images/zgrs/auto.png


BIN
src/assets/images/zgrs/close.png


BIN
src/assets/images/zgrs/dollhouse-active.png


BIN
src/assets/images/zgrs/dollhouse.png


BIN
src/assets/images/zgrs/floor-active.png


BIN
src/assets/images/zgrs/floor.png


BIN
src/assets/images/zgrs/good-active.png


BIN
src/assets/images/zgrs/good.png


BIN
src/assets/images/zgrs/helper.png


BIN
src/assets/images/zgrs/hotlist-active.png


BIN
src/assets/images/zgrs/hotlist.png


BIN
src/assets/images/zgrs/pause.png


BIN
src/assets/images/zgrs/play.png


BIN
src/assets/images/zgrs/share.png


BIN
src/assets/images/zgrs/title-bg.png


BIN
src/assets/images/zgrs/title-down.png


BIN
src/assets/images/zgrs/title-right.png


BIN
src/assets/images/zgrs/title-up.png


+ 7 - 0
src/types/home.ts

@@ -4,3 +4,10 @@ export type GetHotSpotListResponse = Record<
     title: string;
   }
 >;
+
+export interface GetSceneDetailResponse {
+  id: number;
+  starSum: number;
+  shareSum: number;
+  visitSum: number;
+}

+ 1 - 0
src/utils/services.ts

@@ -50,6 +50,7 @@ const showMessage = (
     unknown
   > = 'error'
 ) => {
+  // @ts-ignore
   ElMessage({
     showClose: true,
     type,

+ 198 - 0
src/views/home/components/guide/index.zgrs.scss

@@ -0,0 +1,198 @@
+#drawer-container {
+  position: absolute;
+  left: 0;
+  bottom: 0px;
+  width: 100%;
+  height: 200px;
+  overflow: hidden;
+  pointer-events: none;
+  transition-property: bottom, opacity;
+  transition-duration: 0.5s;
+  z-index: var(--z-index-normal);
+
+  #drawer.playing {
+    bottom: 20px;
+  }
+}
+
+#drawer {
+  position: absolute;
+  left: 0;
+  bottom: 0;
+  width: 100%;
+  height: 0;
+  overflow: hidden;
+  color: #fff;
+  pointer-events: all;
+  transition-duration: 0.5s;
+  transition-property: height, bottom;
+
+  &.open {
+    height: 150px;
+  }
+}
+
+#drawer.open.fadeOut {
+  pointer-events: none;
+}
+
+.fullWidth .frame-container {
+  width: 100%;
+}
+.frame-container {
+  display: flex;
+  align-items: center;
+  width: calc(100% - 58px);
+  height: 100%;
+  background: rgba(27, 27, 28, 0.6);
+}
+
+.frame {
+  margin: 0 10px;
+  width: calc(100% - 20px);
+}
+
+.frame.noScroll {
+  margin: 17px 10px;
+}
+
+.frame .slidee {
+  display: flex;
+  height: 100%;
+  list-style: none;
+}
+
+.frame .slidee li {
+  position: relative;
+  flex-shrink: 0;
+  display: flex;
+  flex-direction: column-reverse;
+  align-items: center;
+  justify-content: center;
+  width: 186px;
+  height: 130px;
+  cursor: pointer;
+}
+
+.frame .slidee li .overlay {
+  margin-top: 5px;
+  color: #fff;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  font-size: 12px;
+  text-align: center;
+}
+
+.frame .slidee li .mark360View,
+.frame .slidee li .markInsideView {
+  position: absolute;
+  top: 2px;
+  left: 2px;
+  width: 50px;
+  max-height: 25px;
+  color: #fff;
+  background-color: rgba(0, 0, 0, 1);
+  z-index: 100;
+  transform: translate3d(0, 0, 0);
+}
+
+.frame .slidee li img {
+  width: 144px;
+  height: 83px;
+  object-fit: cover;
+  border-radius: 5px;
+  overflow: hidden;
+  border: 3px solid #c39f6b;
+}
+
+.frame .slidee li.thumbImg.active {
+  position: relative;
+  top: -5px;
+
+  > img {
+    width: 100%;
+    height: 100%;
+    border: 0;
+    mask: url('@/assets/images/zgrs/active-menu.png') no-repeat center / contain;
+  }
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: url('@/assets/images/zgrs/active-menu-2.png') no-repeat center / contain;
+    z-index: 1;
+  }
+  .overlay {
+    display: none;
+  }
+}
+
+#playHead {
+  display: table;
+  position: absolute;
+  bottom: -20px;
+  left: 0;
+  height: 20px;
+  display: flex;
+  align-items: center;
+  width: 100%;
+  transition-property: bottom;
+  transition-duration: 0.5s;
+  background-color: #000;
+
+  &.playing {
+    bottom: 0;
+  }
+}
+
+#status {
+  width: 65px;
+  color: #fff;
+  font-family: OpenSans, 'Helvetica Neue', Arial, sans-serif;
+  font-weight: 700;
+  font-size: 11px;
+  padding-left: 10px;
+}
+
+#progressBar {
+  padding: 0 10px;
+  flex: 1;
+  pointer-events: all;
+
+  .step {
+    height: 6px;
+    float: left;
+
+    &::before {
+      content: '';
+      display: block;
+      width: 100%;
+      height: 100%;
+      background-color: #575757;
+    }
+    &.active::before {
+      background-color: #00b4ed;
+    }
+  }
+}
+
+@media only screen and (max-width: 487px), (max-height: 487px) {
+  #thumb-container .thumbImg img,
+  .frame {
+    height: 77px;
+  }
+  .frame .slidee li {
+    width: 103px;
+  }
+  #drawer-container.drawerOpen #drawer.open.noScroll {
+    height: 94px;
+  }
+  #drawer.open.noScroll,
+  #drawer.open.noScroll.playing {
+    height: 88px;
+  }
+}

+ 26 - 0
src/views/home/components/guide/index.zgrs.tsx

@@ -0,0 +1,26 @@
+import { defineComponent } from 'vue';
+import './index.zgrs.scss';
+
+export default defineComponent({
+  name: 'HomeGuide',
+  render() {
+    return (
+      <div id="drawer-container">
+        <div id="drawer" class="fullWidth">
+          <div class="frame-container">
+            <div id="scrollFrame" class="frame">
+              <ul id="thumb-container" class="slidee"></ul>
+            </div>
+          </div>
+        </div>
+        <div id="playHead">
+          <div id="status">
+            <span class="curIdx">1</span>
+            of <span class="totalSteps"></span>
+          </div>
+          <div id="progressBar"></div>
+        </div>
+      </div>
+    );
+  },
+});

+ 47 - 0
src/views/home/components/hot-spot-list/index.zgrs.scss

@@ -0,0 +1,47 @@
+#hotListContent {
+  ul {
+    width: 200px;
+  }
+  li {
+    position: relative;
+    height: 44px;
+    color: white;
+    align-items: center;
+    cursor: pointer;
+    display: flex;
+    font-size: var(--el-font-size-base);
+    line-height: 22px;
+    list-style: none;
+    outline: none;
+    padding: 5px 35px 5px 16px;
+
+    span {
+      display: inline-block;
+      width: 100%;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+    }
+
+    &.active {
+      font-weight: bold;
+      background: rgba(203, 25, 29, 0.5);
+    }
+    &:not(.is-disabled):hover,
+    &:not(.is-disabled):focus {
+      font-weight: bold;
+      background: rgba(203, 25, 29, 0.5);
+
+      &::after {
+        content: '';
+        position: absolute;
+        top: 50%;
+        right: 0;
+        width: 35px;
+        height: 35px;
+        background: url('@/assets/images/zgrs/title-right.png') no-repeat center / contain;
+        transform: translateY(-50%);
+      }
+    }
+  }
+}

+ 13 - 0
src/views/home/components/hot-spot-list/index.zgrs.vue

@@ -0,0 +1,13 @@
+<template>
+  <el-scrollbar max-height="400px">
+    <div id="hotListWrap">
+      <div id="hotListContent">
+        <ul></ul>
+      </div>
+    </div>
+  </el-scrollbar>
+</template>
+
+<style lang="scss">
+  @import './index.zgrs.scss';
+</style>

+ 191 - 0
src/views/home/components/menu/index.zgrs.scss

@@ -0,0 +1,191 @@
+.pinBottom-container {
+  position: absolute;
+  bottom: 0px;
+  width: 100%;
+  transition: all 0.5s;
+  z-index: var(--z-index-top);
+
+  .pinBottom.playing {
+    bottom: 20px;
+  }
+}
+
+#gui .pinBottom.open.noScroll.playing {
+  bottom: 175px;
+}
+
+.pinBottom {
+  position: absolute;
+  bottom: 0;
+  line-height: 1;
+  transition: all 0.5s;
+
+  &.left {
+    left: 0;
+    width: 450px;
+    height: 67px;
+    background: url('@/assets/images/zgrs/Subtract-min.png') no-repeat center right / contain;
+  }
+  &.right {
+    right: 40px;
+    bottom: 10px;
+  }
+  &.open {
+    bottom: 155px;
+
+    &.playing {
+      bottom: 165px;
+    }
+  }
+}
+
+.viewContainer,
+.rightViewContainer,
+#gui-modes-map {
+  display: flex;
+  align-items: center;
+  gap: 5px;
+  height: 100%;
+}
+
+.viewContainer {
+  padding-left: 35px;
+}
+
+#play,
+#pause {
+  cursor: pointer;
+
+  img {
+    width: 57px;
+    height: 57px;
+  }
+}
+
+#pullTab,
+.helper-btn,
+.good-btn,
+.hotList,
+#gui-modes-dollhouse,
+#gui-modes-floorplan,
+#volume,
+#sharing {
+  display: block;
+  width: 57px;
+  height: 57px;
+  cursor: pointer;
+}
+
+#pullTab {
+  background: url('@/assets/images/zgrs/auto-suspend.png') no-repeat center / contain;
+
+  &.opened {
+    background-image: url('@/assets/images/zgrs/auto.png');
+  }
+}
+
+.hotList {
+  background: url('@/assets/images/zgrs/hotlist.png') no-repeat center / contain;
+
+  &.active {
+    background-image: url('@/assets/images/zgrs/hotlist-active.png');
+  }
+}
+
+#gui-modes-dollhouse {
+  background: url('@/assets/images/zgrs/dollhouse.png') no-repeat center / contain;
+
+  &.active {
+    background-image: url('@/assets/images/zgrs/dollhouse-active.png');
+  }
+}
+
+#gui-modes-floorplan {
+  background: url('@/assets/images/zgrs/floor.png') no-repeat center / contain;
+
+  &.active {
+    background-image: url('@/assets/images/zgrs/floor-active.png');
+  }
+}
+
+#volume {
+  background: url('@/assets/images/zgrs/Volume btn_off.png') no-repeat center / contain;
+
+  &.active {
+    background-image: url('@/assets/images/zgrs/Volume btn_on.png');
+  }
+}
+
+#sharing {
+  background: url('@/assets/images/zgrs/share.png') no-repeat center / contain;
+}
+
+.helper-btn {
+  background: url('@/assets/images/zgrs/helper.png') no-repeat center / contain;
+}
+
+.good-btn {
+  position: relative;
+
+  &.active div {
+    background-image: url('@/assets/images/zgrs/good-active.png');
+  }
+  div {
+    position: absolute;
+    left: 0;
+    bottom: 0;
+    width: 58px;
+    height: 67px;
+    transition: background-image 0.3s ease-in-out;
+    background: url('@/assets/images/zgrs/good.png') no-repeat center / contain;
+  }
+  span {
+    position: absolute;
+    top: -9px;
+    left: 50%;
+    font-size: 12px;
+    color: white;
+    transform: translateX(-50%);
+  }
+}
+
+.terms2 {
+  display: none;
+}
+
+@media only screen and (max-width: 600px) {
+  .pinBottom.right,
+  .pinBottom.left {
+    bottom: 5px;
+  }
+  .pinBottom.left {
+    left: 5px;
+  }
+  .pinBottom.right .ui-icon {
+    margin-right: 5px;
+  }
+  #play,
+  #pause {
+    width: 64px;
+  }
+  #gui-modes-map > div {
+    width: 64px;
+  }
+  .viewContainer,
+  #gui-modes-map {
+    display: flex;
+    flex-direction: column;
+  }
+}
+
+@media only screen and (max-width: 487px), (max-height: 487px) {
+  .pinBottom-container.drawerOpen.duringTour {
+    bottom: 6px;
+  }
+  #gui .pinBottom.open.noScroll {
+    bottom: 93px;
+  }
+  .pinBottom.open.noScroll.playing {
+    bottom: 108px;
+  }
+}

+ 210 - 0
src/views/home/components/menu/index.zgrs.vue

@@ -0,0 +1,210 @@
+<template>
+  <div class="pinBottom-container">
+    <div class="pinBottom left">
+      <div class="viewContainer">
+        <div id="previous" class="previous desktop-only ui-icon" style="display: none">
+          <a>
+            <img :src="PauseIcon" width="24" height="24" data-original-title="播放" />
+          </a>
+        </div>
+        <div id="play" class="ui-icon" data-original-title="播放">
+          <a>
+            <img :src="PauseIcon" width="24" height="24" title="播放" />
+          </a>
+        </div>
+        <div id="pause" class="ui-icon" style="display: none">
+          <a>
+            <img title="暂停" :src="PlayIcon" width="24" height="24" />
+          </a>
+        </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 id="gui-modes-map" class="ui-icon double active">
+          <div data-original-title="导览" id="pullTab" title="导览" />
+          <el-dropdown
+            trigger="click"
+            placement="top"
+            popper-class="scene-title-popper"
+            @visible-change="(v: boolean) => (hotspotActive = v)"
+          >
+            <div
+              data-original-title="热点列表"
+              class="hotList"
+              title="热点列表"
+              :class="{ active: hotspotActive }"
+            ></div>
+
+            <template #dropdown>
+              <hot-spot-list />
+            </template>
+          </el-dropdown>
+          <div
+            data-original-title="迷你模型"
+            id="gui-modes-dollhouse"
+            title="迷你模型"
+            class=""
+          ></div>
+          <div data-original-title="俯视图" id="gui-modes-floorplan" title="俯视图"></div>
+          <div data-original-title="VR" id="vr" title="" style="display: none"></div>
+          <div
+            data-original-title="消除外壳"
+            id="gui-remove-face"
+            title=""
+            style="display: none; float: left"
+          >
+            <img class="icon icon-inside" src="images/face.jpg" />
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="pinBottom right hideTarget">
+      <div class="rightViewContainer clearfix">
+        <div class="helper-btn" title="指引" @click="openHelper"></div>
+        <div class="good-btn" title="点赞" :class="{ active: staring }" @click="handleStar">
+          <div />
+          <span v-if="staring">{{ staringString }}</span>
+        </div>
+        <div
+          id="sharing"
+          class="ui-icon wide"
+          title="{[{ SOCIAL_SHARING }]}"
+          @click="copyUrl"
+        ></div>
+        <div id="volume" class="ui-icon wide"></div>
+        <div id="vr" class="ui-icon wide hidden" style="display: none">
+          <a>
+            <i title="{[{ VIEW_IN_VR }]}" class="icon icon-webvr"></i>
+          </a>
+        </div>
+        <div
+          id="gui-fullscreen"
+          class="ui-icon wide"
+          data-placement="top"
+          title="{[{ VIEW_FULLSCREEN }]}"
+          style="display: none"
+        >
+          <a>
+            <i class="icon icon-fullscreen"></i>
+          </a>
+        </div>
+        <div
+          id="gui-fullscreen-exit"
+          class="ui-icon wide"
+          data-placement="top"
+          title="{[{ EXIT_FULLSCREEN }]}"
+          style="display: none"
+        >
+          <a>
+            <i class="icon icon-fullscreen-exit"></i>
+          </a>
+        </div>
+        <div class="pull-right terms terms2"></div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { computed, onMounted, onUnmounted, ref } from 'vue';
+  import clipboard from 'clipboard';
+  import PlayIcon from '@/assets/images/zgrs/play.png';
+  import PauseIcon from '@/assets/images/zgrs/pause.png';
+  import { homeApi } from '@/api';
+  import HotSpotList from '../hot-spot-list';
+
+  let helperVisible = false;
+  const hotspotActive = ref(false);
+  const starSum = ref(0);
+  const staring = ref(false);
+  const staringString = computed(() => {
+    const value = starSum.value;
+    if (value >= 10000) {
+      return `${Math.floor(value / 10000)}w+`;
+    } else if (value >= 1000) {
+      return `${Math.floor(value / 1000)}k+`;
+    } else {
+      return value.toString();
+    }
+  });
+
+  const copyUrl = () => {
+    clipboard.copy(window.location.href);
+
+    ElNotification({
+      title: '提示',
+      type: 'success',
+      message: '链接已复制',
+      position: 'bottom-right',
+    });
+  };
+
+  const openHelper = () => {
+    window
+      .$('#interaction-modal')
+      // @ts-ignore
+      .addClass(`fadeIn ${window.browser.isMobile() ? 'mobile' : 'desktop'}`);
+    helperVisible = true;
+  };
+
+  const closeHelper = () => {
+    window.$('#interaction-modal').removeClass('fadeIn');
+    helperVisible = false;
+  };
+
+  const handleKeydown = () => {
+    helperVisible && closeHelper();
+  };
+  const handleClick = (e: MouseEvent) => {
+    const clickedElement = e.target;
+    // @ts-ignore
+    const modalElement = clickedElement?.closest('#interaction-modal');
+    // @ts-ignore
+    const btnElement = clickedElement?.closest('.helper-btn');
+    if (!modalElement && !btnElement && helperVisible) {
+      closeHelper();
+    }
+  };
+
+  const getDetail = async () => {
+    const { data } = await homeApi.getSceneDetail(window.number);
+
+    if (!data) return;
+    starSum.value = data.starSum;
+  };
+
+  const handleStar = async () => {
+    if (staring.value) return;
+
+    staring.value = true;
+    starSum.value++;
+
+    try {
+      await homeApi.saveStar(window.number);
+
+      setTimeout(() => {
+        staring.value = false;
+      }, 1000);
+    } catch (err) {
+      starSum.value--;
+    }
+  };
+
+  onMounted(() => {
+    getDetail();
+
+    window.addEventListener('keydown', handleKeydown);
+    window.addEventListener('click', handleClick);
+  });
+
+  onUnmounted(() => {
+    window.removeEventListener('keydown', handleKeydown);
+    window.removeEventListener('click', handleClick);
+  });
+</script>
+
+<style lang="scss" scoped>
+  @import './index.zgrs.scss';
+</style>

+ 50 - 0
src/views/home/components/other/index.scss

@@ -0,0 +1,50 @@
+#call-to-action #interaction-modal.desktop {
+  height: 350px;
+  width: 550px;
+  border-radius: 10px;
+}
+
+#interaction-modal.desktop hr,
+#interaction-modal.desktop img {
+  width: 100%;
+  height: 100%;
+}
+
+#call-to-action #interaction-modal.fadeIn,
+#call-to-action #pause-icon.fadeIn {
+  opacity: 1;
+  pointer-events: auto;
+}
+
+#call-to-action #interaction-modal {
+  position: fixed;
+  left: 50%;
+  top: 50%;
+  transform: translate(-50%, -50%);
+  opacity: 0;
+  -webkit-transition: all 0.5s;
+  transition: all 0.5s;
+  z-index: 201;
+  pointer-events: none;
+}
+
+.next-button {
+  right: 15px;
+  transform: translate(0, -50%) rotate(45deg);
+}
+
+.prev-button {
+  left: 15px;
+  transform: translate(0, -50%) rotate(-135deg);
+}
+
+.nav-help-page {
+  position: absolute;
+  top: 50%;
+  z-index: 10;
+  cursor: pointer;
+  width: 20px;
+  height: 20px;
+  border-top: 3px solid #ffffff;
+  border-right: 3px solid #ffffff;
+}

+ 5 - 2
src/views/home/components/other/index.tsx

@@ -1,11 +1,12 @@
 import { defineComponent } from 'vue';
+import './index.scss';
 
 export default defineComponent({
   name: 'HomeOther',
   render() {
     return (
       <div>
-        <div id="share-modal" style="display: none">
+        <div id="share-modal">
           <div id="share-outer">
             <div class="share-images">
               <a id="facebook-share">
@@ -59,9 +60,11 @@ export default defineComponent({
               </a>
             </div>
           </div>
-          <div id="interaction-modal" v-show="0">
+          <div id="interaction-modal">
             <div id="interaction-modal-inner">
               <div class="nav-icon">
+                <img src="images/pc_step1.png" class="icon" title="导览" data-page="1" />
+
                 <div class="nav-help-button">
                   <div class="next-button nav-help-page" data-id="plus"></div>
                   <div class="prev-button nav-help-page"></div>

+ 36 - 0
src/views/home/components/popup/index.zgrs.scss

@@ -0,0 +1,36 @@
+#popup {
+  display: none;
+  position: relative;
+  padding: 0;
+  width: 100%;
+  height: 100%;
+  text-align: center;
+  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: relative;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+}
+#closepop {
+  position: absolute;
+  left: 50%;
+  bottom: 25px;
+  width: 37px;
+  height: 37px;
+  cursor: pointer;
+  text-indent: -999em;
+  background-size: 100% 100%;
+  transform: translateX(-50%);
+  background: url('@/assets/images/zgrs/close.png') no-repeat;
+}

+ 14 - 0
src/views/home/components/popup/index.zgrs.tsx

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

+ 32 - 0
src/views/home/components/title/index.zgrs.scss

@@ -0,0 +1,32 @@
+.scene-title {
+  position: fixed;
+  top: 0;
+  left: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 720px;
+  height: 44px;
+  font-size: 14px;
+  font-weight: bold;
+  color: white;
+  transform: translateX(-50%);
+  cursor: pointer;
+  background: url('@/assets/images/zgrs/title-bg.png') no-repeat center / contain;
+  z-index: var(--z-index-top);
+
+  p {
+    position: relative;
+    width: 160px;
+    text-align: center;
+  }
+  &::after {
+    content: '';
+    position: absolute;
+    top: 0;
+    right: 244px;
+    width: 37px;
+    height: 37px;
+    background: url('@/assets/images/zgrs/title-down.png') no-repeat center / contain;
+  }
+}

+ 197 - 0
src/views/home/components/title/index.zgrs.vue

@@ -0,0 +1,197 @@
+<template>
+  <div class="pinTop" style="display: none">
+    <div id="model-title">
+      <div class="title-row">
+        <div id="title-toggle">
+          <a>
+            <i class="icon icon-dpad-left"></i>
+          </a>
+        </div>
+        <div id="title-container-wrapper" data-placement="bottom" data-html="true">
+          <div class="title-container meta-toggle">
+            <div class="co-brand">
+              {'{[{ PRESENTED_BY }]}'}
+              <span class="title" id="cobrandTitle"></span>
+            </div>
+            <div id="title-logo">
+              <i></i>
+            </div>
+            <a id="more-hint">
+              <i class="icon icon-dpad-down"></i>
+            </a>
+            <a id="less-hint">
+              <i class="icon icon-dpad-up"></i>
+            </a>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div id="meta-info-wrapper">
+      <div id="meta-info" class="darkGlass">
+        <div id="meta-description"></div>
+        <div class="contact-info">
+          <i class="icon icon-user pull-left"></i>
+          &#xA0;
+          <div id="contact-data"></div>
+        </div>
+        <div class="address">
+          <i class="icon icon-pin"></i>
+          <span id="addressTxt"></span>
+        </div>
+        <div id="tag-toggles" class="menu-toggles hidden">
+          <span>{'{[{ MATTERTAG_CONTENT }]}'}</span>
+          <div id="tag-inputs" class="menu-radios">
+            <div id="show-tag" class="menu-radio-show">
+              <input id="radio-tag-show" type="radio" name="tags" value="show" />
+              <label for="radio-tag-show">{'{[{ SHOW }]}'}</label>
+            </div>
+            <div id="hide-tag" class="menu-radio-hide">
+              <input id="radio-tag-hide" type="radio" name="tags" value="hide" />
+              <label for="radio-tag-hide">{'{[{ HIDE }]}'}</label>
+            </div>
+          </div>
+        </div>
+        <div id="labels-toggles" class="menu-toggles hidden">
+          <span>Labels</span>
+          <div id="labels-inputs" class="menu-radios">
+            <div id="show-label" class="menu-radio-show">
+              <input id="radio-labels-show" type="radio" name="labels" value="show" />
+              <label for="radio-labels-show">Show</label>
+            </div>
+            <div id="hide-label" class="menu-radio-hide">
+              <input id="radio-labels-hide" type="radio" name="labels" value="hide" />
+              <label for="radio-labels-hide">Hide</label>
+            </div>
+          </div>
+        </div>
+        <div id="share-origin" class="hidden">
+          <div>
+            <i class="icon icon-ext-link"></i>
+            <div id="share-link-wrapper"></div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <el-dropdown trigger="click" popper-class="scene-title-popper">
+    <div class="scene-title">
+      <p id="gui-name" class="limit-line titleText" />
+    </div>
+
+    <template #dropdown>
+      <el-scrollbar max-height="400px">
+        <el-dropdown-menu>
+          <el-dropdown-item
+            v-for="item in LIST"
+            :key="item.sceneCode"
+            class="scene-list-item"
+            @click="changeScene(item.sceneCode)"
+            >{{ item.label }}</el-dropdown-item
+          >
+        </el-dropdown-menu>
+      </el-scrollbar>
+    </template>
+  </el-dropdown>
+</template>
+
+<script setup lang="ts">
+  const LIST = [
+    {
+      label: '光大汇晨-3号楼外景',
+      sceneCode: 'KJ-In9jsvCe86s',
+    },
+    {
+      label: '光大汇晨-4号、5号楼外景',
+      sceneCode: 'KJ-ZMFs8MxP2SB',
+    },
+    {
+      label: '光大4号楼-前台大厅',
+      sceneCode: 'KJ-WklncxbyqUz',
+    },
+    {
+      label: '光大4-1号楼-1F',
+      sceneCode: 'KJ-8yXDk9BK7NQ',
+    },
+    {
+      label: '光大4-1号楼-3F',
+      sceneCode: 'KJ-InMhgveLjSN',
+    },
+    {
+      label: '光大4-1号楼-4F',
+      sceneCode: 'KJ-Z3umjaDYj54',
+    },
+    {
+      label: '光大4-1号楼-5F',
+      sceneCode: 'KJ-KWa3ofq5VtT',
+    },
+    {
+      label: '光大4-1号楼-6F',
+      sceneCode: 'KJ-f3lOsooRQEL',
+    },
+    {
+      label: '光大4-2号楼-2F',
+      sceneCode: 'KJ-mK2fUzsBliv',
+    },
+    {
+      label: '光大4-2号楼-3F',
+      sceneCode: 'KJ-HuSSF9cJYpY',
+    },
+    {
+      label: '光大4-2号楼-4F',
+      sceneCode: 'KJ-SfNcYJffCof',
+    },
+    {
+      label: '光大4-2号楼-5F',
+      sceneCode: 'KJ-nOTU5Eg4Y0Y',
+    },
+    {
+      label: '光大4-2号楼-6F',
+      sceneCode: 'KJ-483Cb7j9OD0',
+    },
+    {
+      label: '光大5号楼-前台大厅',
+      sceneCode: 'KJ-YNQbf4hmdhp',
+    },
+  ];
+
+  const changeScene = (code: string) => {
+    window.location.href = `${window.origin}?m=${code}`;
+  };
+</script>
+
+<style lang="scss" scoped>
+  @import './index.zgrs.scss';
+</style>
+
+<style lang="scss">
+  .el-dropdown__popper.el-popper.scene-title-popper {
+    --el-bg-color-overlay: rgba(0, 0, 0, 0.4);
+    --el-border-color-light: transparent;
+
+    .scene-list-item {
+      position: relative;
+      padding-right: 35px;
+      height: 44px;
+      color: white;
+
+      &:not(.is-disabled):hover,
+      &:not(.is-disabled):focus {
+        --el-dropdown-menuItem-hover-color: white;
+        --el-dropdown-menuItem-hover-fill: rgba(203, 25, 29, 0.5);
+        font-weight: bold;
+
+        &::after {
+          content: '';
+          position: absolute;
+          top: 50%;
+          right: 0;
+          width: 35px;
+          height: 35px;
+          background: url('@/assets/images/zgrs/title-right.png') no-repeat center / contain;
+          transform: translateY(-50%);
+        }
+      }
+    }
+  }
+</style>

+ 89 - 0
src/views/home/index.zgrs.tsx

@@ -0,0 +1,89 @@
+import { defineComponent, ref } from 'vue';
+import JsScript from '@/components/js-script';
+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 './index.scss';
+
+export default defineComponent({
+  name: 'home',
+  components: {
+    Title,
+    WebVr,
+    Other,
+    Vrcon,
+    GuiLoading,
+    JsScript,
+    Popup,
+  },
+  setup() {
+    const manageJsLoaded = ref(false);
+    const hotJsLoaded = ref(false);
+
+    return {
+      manageJsLoaded,
+      hotJsLoaded,
+    };
+  },
+  render() {
+    return (
+      <div class="home">
+        {/* 进度条加载 */}
+        <GuiLoading />
+
+        {/* 加载初始页面 */}
+        <div id="gui-thumb" />
+
+        {/* 热点弹出框 */}
+        <Popup />
+
+        {/* 场景canvs主容器 */}
+        <div id="player" />
+
+        {/* 底部菜单 */}
+        <div id="gui-parent">
+          {/* 热点气泡 */}
+          <div id="hot" />
+
+          <div id="gui" style="display: none;">
+            {/* 标题 */}
+            <Title />
+
+            {/* 底部菜单 */}
+            <Menu />
+
+            {/* 导览 */}
+            <Guide />
+          </div>
+
+          <WebVr />
+          <Vrcon />
+          <Other />
+        </div>
+
+        {/* TODO: 没有控制权,耦合严重;放在此处为了防止元素未渲染导致报错 */}
+        <JsScript src="/js/manage.js" onLoad={() => (this.manageJsLoaded = true)} />
+        {this.manageJsLoaded && (
+          <>
+            <JsScript src="/js/Hot.js" onLoad={() => (this.hotJsLoaded = true)} />
+            {this.hotJsLoaded && (
+              <>
+                <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>
+    );
+  },
+});