浏览代码

feat: element theme color & hotspot style

chenlei 3 月之前
父节点
当前提交
bee14fc0bd
共有 32 个文件被更改,包括 2438 次插入2679 次删除
  1. 3 0
      components.d.ts
  2. 1 0
      package.json
  3. 2194 2452
      pnpm-lock.yaml
  4. 二进制
      src/hotspot/assets/images/Volume-off.png
  5. 二进制
      src/hotspot/assets/images/Volume-on.png
  6. 二进制
      src/hotspot/assets/images/audio-icon.png
  7. 二进制
      src/hotspot/assets/images/icon-image-1@2x.png
  8. 二进制
      src/hotspot/assets/images/icon-image@2x.png
  9. 二进制
      src/hotspot/assets/images/icon-left-min.png
  10. 二进制
      src/hotspot/assets/images/icon-model-1@2x.png
  11. 二进制
      src/hotspot/assets/images/icon-model@2x.png
  12. 二进制
      src/hotspot/assets/images/icon-next@2x-min.png
  13. 二进制
      src/hotspot/assets/images/icon-previous@2x-min.png
  14. 二进制
      src/hotspot/assets/images/icon-right-min.png
  15. 二进制
      src/hotspot/assets/images/icon-video-1@2x.png
  16. 二进制
      src/hotspot/assets/images/icon-video@2x.png
  17. 二进制
      src/hotspot/assets/images/img-icon.png
  18. 二进制
      src/hotspot/assets/images/info-icon.png
  19. 二进制
      src/hotspot/assets/images/model-icon.png
  20. 二进制
      src/hotspot/assets/images/video-icon.png
  21. 4 0
      src/hotspot/main.ts
  22. 83 103
      src/hotspot/views/hotspot/index.scss
  23. 98 107
      src/hotspot/views/hotspot/index.vue
  24. 1 1
      src/index/App.vue
  25. 2 0
      src/index/app.scss
  26. 7 0
      src/index/assets/el.scss
  27. 10 0
      src/index/utils/index.ts
  28. 2 2
      src/index/views/home/components/gui-loading/index.scss
  29. 2 2
      src/index/views/home/components/guide/index.scss
  30. 16 8
      src/index/views/home/components/popup/index.scss
  31. 4 2
      src/index/views/home/components/popup/index.tsx
  32. 11 2
      vite.config.ts

+ 3 - 0
components.d.ts

@@ -13,4 +13,7 @@ declare module 'vue' {
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
   }
+  export interface ComponentCustomProperties {
+    vLoading: typeof import('element-plus/es')['ElLoadingDirective']
+  }
 }

+ 1 - 0
package.json

@@ -20,6 +20,7 @@
     "qrcode": "^1.5.4",
     "query-string": "^9.1.1",
     "swiper": "^11.1.15",
+    "v-viewer": "^3.0.21",
     "vue": "^3.5.12",
     "vue-qrcode": "^2.2.2",
     "vue-router": "^4.4.5",

文件差异内容过多而无法显示
+ 2194 - 2452
pnpm-lock.yaml


二进制
src/hotspot/assets/images/Volume-off.png


二进制
src/hotspot/assets/images/Volume-on.png


二进制
src/hotspot/assets/images/audio-icon.png


二进制
src/hotspot/assets/images/icon-image-1@2x.png


二进制
src/hotspot/assets/images/icon-image@2x.png


二进制
src/hotspot/assets/images/icon-left-min.png


二进制
src/hotspot/assets/images/icon-model-1@2x.png


二进制
src/hotspot/assets/images/icon-model@2x.png


二进制
src/hotspot/assets/images/icon-next@2x-min.png


二进制
src/hotspot/assets/images/icon-previous@2x-min.png


二进制
src/hotspot/assets/images/icon-right-min.png


二进制
src/hotspot/assets/images/icon-video-1@2x.png


二进制
src/hotspot/assets/images/icon-video@2x.png


二进制
src/hotspot/assets/images/img-icon.png


二进制
src/hotspot/assets/images/info-icon.png


二进制
src/hotspot/assets/images/model-icon.png


二进制
src/hotspot/assets/images/video-icon.png


+ 4 - 0
src/hotspot/main.ts

@@ -1,8 +1,12 @@
 import { createApp } from 'vue';
 // @ts-ignore
 import App from './views/hotspot/index';
+// @ts-ignore
+import VueViewer from 'v-viewer';
+import 'viewerjs/dist/viewer.css';
 import '@/app.scss';
 
 export const app = createApp(App);
 
 app.mount('#app');
+app.use(VueViewer);

+ 83 - 103
src/hotspot/views/hotspot/index.scss

@@ -6,23 +6,9 @@
   bottom: 0;
   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;
-    right: 20px;
-    bottom: 5px;
-
-    img {
-      width: 57px;
-      height: 57px;
-      cursor: pointer;
-    }
-  }
   &-info {
     color: white;
     max-width: 1320px;
@@ -42,96 +28,99 @@
   }
 
   &-container {
-    position: relative;
-    flex: 1;
-    flex-shrink: 1;
-    height: 0;
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    padding: 9vh 20px;
-    max-width: 1320px;
-    min-width: 400px;
-    width: auto;
-    background: rgba(53, 53, 53, 1);
-    box-sizing: border-box;
-  }
+    width: 100%;
+    height: 80%;
 
-  &-swiper {
-    &__left,
-    &__right {
-      position: absolute;
-      top: 50%;
-      width: 25px;
-      height: 24px;
-      cursor: pointer;
-      transform: translateY(-50%);
-      z-index: 1;
+    .swiper {
+      width: 100%;
+      height: 100%;
     }
-    &__left {
-      left: -70px;
-      background: url('@hotspot/assets/images/icon-previous@2x-min.png') no-repeat center / contain;
+    iframe {
+      max-width: 1000px;
+      width: calc(100% - 120px);
+      height: 570px;
+      border-radius: 14px;
     }
-    &__right {
-      right: -70px;
-      background: url('@hotspot/assets/images/icon-next@2x-min.png') no-repeat center / contain;
+    video,
+    .el-image {
+      max-height: 570px;
+      border-radius: 14px;
+      object-fit: contain;
     }
   }
-  &-model {
-    width: 100%;
-    height: 100%;
 
-    iframe {
-      width: 100%;
-      height: 100%;
+  &-scrollbar {
+    max-height: 19vh;
+    overflow: auto;
+    width: 70%;
+    color: #fff;
+    margin: 0 auto;
+
+    &.isTop {
+      max-height: 65%;
+      height: 65%;
+      padding: 50px 0;
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+    }
+    h3 {
+      font-size: 20px;
+      font-weight: 600;
+    }
+    p {
+      line-height: 1.5;
+      margin-top: 10px;
+      font-size: 16px;
+      text-indent: 32px;
     }
   }
+
   .swiper-slide {
+    text-align: center;
+    font-size: 18px;
     display: flex;
-    align-items: center;
     justify-content: center;
-  }
-  &-video {
-    height: 100%;
-    max-height: 100%;
-  }
-  &-img {
-    display: flex;
     align-items: center;
-    justify-content: center;
-    height: inherit;
-
-    &-swiper {
-      flex: 1;
-      width: 100%;
-      height: 0;
-    }
+    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;
-    left: 50%;
-    bottom: 5px;
+    right: 30px;
+    bottom: calc(20vh - 20px);
     display: flex;
     align-items: center;
+    justify-content: flex-end;
     gap: 10px;
-    transform: translateX(-50%);
 
     &__item {
       display: flex;
       align-items: center;
       justify-content: center;
-      gap: 9px;
-      width: 57px;
-      height: 57px;
-      box-sizing: border-box;
+      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 {
-      }
-      img {
-        width: 100%;
-        height: 100%;
+        background: #19bbed;
+        border: none;
       }
     }
   }
@@ -140,40 +129,31 @@
 @media only screen and (max-width: 600px) {
   .hotspot-page {
     &-container {
-      max-width: 100%;
-      width: 100%;
-      background: none;
-    }
-    &-swiper {
-      &__left,
-      &__right {
-        top: unset;
-        bottom: 12px;
-        transform: none;
-      }
-      &__left {
-        left: 14px;
+      height: calc(100vh - 90px);
+
+      video,
+      .el-image {
+        max-height: 80vh;
+        width: 90%;
       }
-      &__right {
-        right: 14px;
+      iframe {
+        width: 100%;
+        height: calc(100vh - 90px);
       }
     }
     &-nav {
-      bottom: 0;
+      flex-wrap: wrap;
+      right: 10px;
+      bottom: 10px;
 
       &__item {
-        width: 48px;
-        height: 48px;
+        width: 70px;
       }
     }
-    .audioIcon {
-      right: 35px;
-      bottom: 1px;
-
-      img {
-        width: 48px;
-        height: 48px;
-      }
+    &-scrollbar {
+      padding: 10px;
+      width: 100%;
+      max-height: calc(100vh - 90px);
     }
   }
 }

+ 98 - 107
src/hotspot/views/hotspot/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="hotspot-page">
-    <div class="hotspot-page-container">
+    <div v-if="!isTextType" class="hotspot-page-container">
       <!-- 音频播放器 -->
       <audio
         id="myAudio"
@@ -13,110 +13,95 @@
 
       <!-- 模型页面 -->
       <Swiper
-        v-if="myType === 'model'"
-        class="hotspot-page-swiper hotspot-page-model"
+        :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">
-          <iframe v-if="index === myInd" :src="item" frameborder="0" />
-        </SwiperSlide>
-      </Swiper>
-
-      <!-- 视频页面 -->
-      <div v-if="myType === 'video'" class="hotspot-page-swiper hotspot-page-video">
-        <template v-for="(item, index) in curList" :key="item.url">
-          <video
-            v-if="index === myInd"
-            id="videoID"
-            class="hotspot-page-video"
-            controls
-            :src="item.url"
-            autoplay
-          />
-        </template>
-      </div>
-
-      <!-- 图片页面 -->
-      <Swiper
-        v-if="myType === 'img'"
-        class="hotspot-page-swiper hotspot-page-img-swiper"
-        @swiper="initSwiper"
-        @slideChange="handleChange"
-      >
-        <SwiperSlide v-for="(item, idx) in curList" :key="item">
-          <div class="hotspot-page-img">
-            <el-image
-              :src="item"
-              fit="contain"
-              style="width: 100%; height: 100%"
-              preview-teleported
-              :preview-src-list="curList"
-              :initial-index="idx"
-            />
-          </div>
+          <template v-if="myType === 'model'">
+            <iframe v-if="index === myInd" :src="item" frameborder="0" />
+          </template>
+          <template v-else-if="myType === 'video'">
+            <video controls :src="item.url" />
+          </template>
+          <template v-else-if="myType === 'img'">
+            <el-image :src="item" fit="contain" @click="handlePreview(index)" />
+          </template>
         </SwiperSlide>
       </Swiper>
+    </div>
 
-      <template v-if="curList.length > 1">
-        <div class="hotspot-page-swiper__left" @click="handlePre" />
-        <div class="hotspot-page-swiper__right" @click="handleNext" />
-      </template>
-
-      <!-- 底部的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>
-
+    <!-- 底部的tab -->
+    <ul v-if="flooTab.length > 1" class="hotspot-page-nav">
       <!-- 音频图标 -->
-      <div
+      <li
         v-if="audio && !isOneAduio"
-        class="audioIcon"
-        :title="audioSta ? '关闭音频' : '打开音频'"
+        :class="[
+          'hotspot-page-nav__item',
+          {
+            active: audioSta,
+          },
+        ]"
         @click="audioSta = !audioSta"
       >
-        <img :src="audioSta ? VolumeOff : VolumeOn" alt="" />
-      </div>
-    </div>
+        <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>
 
-    <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
+      v-if="isMobile ? isTextType : true"
+      class="hotspot-page-scrollbar"
+      :class="{ isTop: !flooTab.length && !isMobile }"
+    >
+      <h3>{{ myTitle }}</h3>
+      <p>{{ myTxt }}</p>
+    </div>
   </div>
 </template>
 
 <script>
   import { Swiper, SwiperSlide } from 'swiper/vue';
+  import { Navigation, Pagination } from 'swiper/modules';
   import 'swiper/css';
-  import { parseUrlParams } from '@/utils';
+  import 'swiper/css/navigation';
+  import 'swiper/css/pagination';
 
-  import ModelIcon from '@hotspot/assets/images/icon-model@2x.png';
-  import AcModelIcon from '@hotspot/assets/images/icon-model-1@2x.png';
-  import ImageIcon from '@hotspot/assets/images/icon-image@2x.png';
-  import AcImageIcon from '@hotspot/assets/images/icon-image-1@2x.png';
-  import VideoIcon from '@hotspot/assets/images/icon-video@2x.png';
-  import AcVideoIcon from '@hotspot/assets/images/icon-video-1@2x.png';
-  import VolumeOn from '@hotspot/assets/images/Volume-on.png';
-  import VolumeOff from '@hotspot/assets/images/Volume-off.png';
+  import { parseUrlParams, judgeIsMobile } from '@/utils';
+
+  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',
@@ -126,8 +111,8 @@
     },
     data() {
       return {
+        isMobile,
         VolumeOn,
-        VolumeOff,
         m: urlParams.m,
         id: urlParams.id,
         // 音频地址
@@ -164,12 +149,17 @@
 
         // 只有标题和文字(没有视频,没有模型,没有图片)
         oneTxt: false,
+
+        modules: [Navigation, Pagination],
       };
     },
     computed: {
       curList() {
         return this.data[this.myType] || [];
       },
+      isTextType() {
+        return this.myType === 'text';
+      },
     },
     watch: {
       audioSta(val) {
@@ -201,13 +191,13 @@
           // 底部的tab
           const arr = [];
           const obj = {};
-          if (resData.model) {
-            obj.model = resData.model;
-            arr.push({ id: 1, type: 'model', name: '模型', icon: ModelIcon, acIcon: AcModelIcon });
+          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, acIcon: AcVideoIcon });
+            arr.push({ id: 2, type: 'video', name: '视频', icon: VideoIcon });
           } else {
             this.$nextTick(() => {
               if (
@@ -220,17 +210,22 @@
               }
             });
           }
-          if (resData.images) {
-            obj.img = resData.images;
-            arr.push({ id: 3, type: 'img', name: '图片', icon: ImageIcon, acIcon: AcImageIcon });
+          if (resData.model) {
+            obj.model = resData.model;
+            arr.push({ id: 1, type: 'model', name: '模型', icon: ModelIcon });
+          }
+          if (isMobile) {
+            arr.push({ id: 4, type: 'text', name: '介绍', icon: infoIcon });
           }
+
           this.flooTab = arr;
           this.data = obj;
 
           // 当前type的值 应该为
-          if (resData.model) this.myType = 'model';
+          if (resData.images) this.myType = 'img';
+          else if (resData.model) this.myType = 'model';
           else if (resData.video) this.myType = 'video';
-          else if (resData.images) this.myType = 'img';
+          else this.myType = 'text';
 
           this.myTitle = resData.title || '';
           this.myTxt = resData.content || '';
@@ -255,19 +250,11 @@
       handleChange({ activeIndex }) {
         this.myInd = activeIndex;
       },
-      handlePre() {
-        if (this.myType === 'video') {
-          this.myInd = this.myInd > 0 ? this.myInd - 1 : this.curList.length - 1;
-        } else {
-          this.swiper?.slidePrev();
-        }
-      },
-      handleNext() {
-        if (this.myType === 'video') {
-          this.myInd = this.myInd < this.curList.length - 1 ? this.myInd + 1 : 0;
-        } else {
-          this.swiper?.slideNext();
-        }
+
+      handlePreview(idx) {
+        this.$viewerApi({
+          images: [this.curList[idx]],
+        });
       },
     },
   };
@@ -275,4 +262,8 @@
 
 <style lang="scss">
   @use './index.scss';
+
+  .viewer-button.viewer-close {
+    display: none;
+  }
 </style>

+ 1 - 1
src/index/App.vue

@@ -1,5 +1,5 @@
 <template>
-  <router-view />
+  <router-view v-loading="false" />
 </template>
 
 <style lang="scss">

+ 2 - 0
src/index/app.scss

@@ -5,6 +5,8 @@
   --z-hot-popper: 3000;
   --design-width: 1920;
   --design-height: 920;
+  --swiper-theme-color: white;
+  --swiper-pagination-bullet-inactive-color: white;
 }
 
 body,

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

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

+ 10 - 0
src/index/utils/index.ts

@@ -19,3 +19,13 @@ export function parseUrlParams(url: string): Record<string, string> {
 
   return params;
 }
+
+export function judgeIsMobile() {
+  const userAgentInfo = navigator.userAgent;
+  const mobileAgents = ['Android', 'iPhone', 'SymbianOS', 'Windows Phone', 'iPad', 'iPod'];
+  const mobileFlag = mobileAgents.some((mobileAgent) => {
+    return userAgentInfo.indexOf(mobileAgent) > 0;
+  });
+
+  return mobileFlag;
+}

+ 2 - 2
src/index/views/home/components/gui-loading/index.scss

@@ -64,7 +64,7 @@
     height: 10px;
     top: 0px;
     left: 0px;
-    background: #00b4ed;
+    background: var(--el-color-primary);
     border-radius: 5px;
     overflow: hidden;
 
@@ -74,7 +74,7 @@
       width: 100%;
       height: 64px;
       border-radius: 16px;
-      background: #00b4ed;
+      background: var(--el-color-primary);
       opacity: 0.2;
     }
   }

+ 2 - 2
src/index/views/home/components/guide/index.scss

@@ -135,7 +135,7 @@
 .frame .slidee li.thumbImg.active > img,
 .frame .slidee li.thumbImg.hasHover.active > img:hover {
   opacity: 1;
-  border-color: #00b4ed;
+  border-color: var(--el-color-primary);
 }
 
 .scrollbar {
@@ -202,7 +202,7 @@
       background-color: #575757;
     }
     &.active::before {
-      background-color: #00b4ed;
+      background-color: var(--el-color-primary);
     }
   }
 }

+ 16 - 8
src/index/views/home/components/popup/index.scss

@@ -1,10 +1,10 @@
 #popup {
   display: none;
-  position: relative;
-  padding: 0;
-  width: 100%;
-  height: 100%;
-  text-align: center;
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
   background: rgba(0, 0, 0, 0.6);
   z-index: var(--z-hot-popper);
 
@@ -17,7 +17,7 @@
   height: 100%;
 }
 .popup-content {
-  position: relative;
+  position: absolute;
   width: 100%;
   height: 100%;
   overflow: hidden;
@@ -30,6 +30,14 @@
   height: 60px;
   cursor: pointer;
   text-indent: -999em;
-  background-size: 100% 100%;
-  background: url('/images/close1.png') no-repeat;
+  background: url('/images/close1.png') no-repeat center / contain;
+}
+
+@media only screen and (max-width: 600px) {
+  #closepop {
+    width: 36px;
+    height: 36px;
+    top: 0px;
+    right: 0px;
+  }
 }

+ 4 - 2
src/index/views/home/components/popup/index.tsx

@@ -6,8 +6,10 @@ export default defineComponent({
   render() {
     return (
       <div id="popup">
-        <div class="popup-content"></div>
-        <div id="closepop">close</div>
+        <div class="popup-wrap">
+          <div class="popup-content"></div>
+          <div id="closepop">close</div>
+        </div>
       </div>
     );
   },

+ 11 - 2
vite.config.ts

@@ -33,10 +33,18 @@ export default defineConfig(() => {
       vue(),
       vueJsx(),
       AutoImport({
-        resolvers: [ElementPlusResolver()],
+        resolvers: [
+          ElementPlusResolver({
+            importStyle: 'sass',
+          }),
+        ],
       }),
       Components({
-        resolvers: [ElementPlusResolver()],
+        resolvers: [
+          ElementPlusResolver({
+            importStyle: 'sass',
+          }),
+        ],
       }),
     ],
     resolve: {
@@ -89,6 +97,7 @@ export default defineConfig(() => {
       preprocessorOptions: {
         scss: {
           api: 'modern-compiler',
+          additionalData: `@use "@/assets/el.scss" as *;`,
         },
       },
     },