gemercheung 2 anos atrás
pai
commit
917719e7cf

+ 2 - 1
package.json

@@ -32,7 +32,8 @@
     "vue": "^3.2.41",
     "vue-i18n": "^9.2.2",
     "vue-types": "^4.2.1",
-    "vue3-otp-input": "^0.4.1"
+    "vue3-otp-input": "^0.4.1",
+    "vue3-tabs-component": "^1.3.2"
   },
   "devDependencies": {
     "@types/node": "^18.11.7",

+ 11 - 0
pnpm-lock.yaml

@@ -50,6 +50,9 @@ dependencies:
   vue3-otp-input:
     specifier: ^0.4.1
     version: 0.4.1(vue@3.2.41)
+  vue3-tabs-component:
+    specifier: ^1.3.2
+    version: 1.3.2(vue@3.2.41)
 
 devDependencies:
   '@types/node':
@@ -6234,6 +6237,14 @@ packages:
       vue: 3.2.41
     dev: false
 
+  /vue3-tabs-component@1.3.2(vue@3.2.41):
+    resolution: {integrity: sha512-ztugVoigD4vK2MXPVhkUboJseEILqGB3P0f/+DdQp2H/cITNc4J/pYmQJTHISOmbTWk53WGqICGEAi5Kx79XCQ==}
+    peerDependencies:
+      vue: ^3.0.0
+    dependencies:
+      vue: 3.2.41
+    dev: false
+
   /vue@3.2.41:
     resolution: {integrity: sha512-uuuvnrDXEeZ9VUPljgHkqB5IaVO8SxhPpqF2eWOukVrBnRBx2THPSGQBnVRt0GrIG1gvCmFXMGbd7FqcT1ixNQ==}
     dependencies:

+ 6 - 2
src/App.vue

@@ -1,8 +1,6 @@
 <script setup lang="ts">
   import { onMounted, ref, computed, unref, watchEffect, onUnmounted } from 'vue';
   import { createApp } from '/@/hooks/userApp';
-  // import tagView from '/@/components/custom/tagView.vue';
-  // import tag from '/@/components/custom/tag.vue';
   import LoadingLogo from '/@/components/basic/loading.vue';
   import MiniMap from '/@/components/basic/miniMap.vue';
   import FloorplanView from '/@/components/basic/floorplan.vue';
@@ -26,10 +24,14 @@
   import BaseDialog from '/@/components/chatRoom/dialog/base.vue';
   import PasswordDialog from '/@/components/chatRoom/dialog/password.vue';
   import Hotspot from '/@/components/hotspot/index.vue';
+  import IntroPanel from '/@/components/custom/introPanel/index.vue';
+  import { useAppHeight } from '/@/hooks/globalViewHeight';
+
   import { useRoom, roomId, currentSceneIndex } from './hooks/useRoom';
   import dayjs from 'dayjs';
   import Dialog from './components/basic/dialog';
   import 'kankan-components/theme-chalk/index.css';
+
   import { useSocket } from './hooks/userSocket';
   const { createTourPlayer } = useTourPlayer();
   const showDebug = ref(Number(import.meta.env.VITE_SHOW_DEBUGPANEL) === 1);
@@ -77,6 +79,7 @@
   watchEffect(() => {
     console.warn('mode', mode);
   });
+  useAppHeight();
   // 强制开https
   console.log('当前protocol', location.protocol);
   if (location.protocol !== 'https:') {
@@ -399,6 +402,7 @@
     </template>
     <Hotspot @set-current-tag="handleSyncTags" />
   </div>
+  <IntroPanel />
   <BaseDialog />
 </template>
 

+ 1 - 0
src/assets/theme.editor.scss

@@ -93,6 +93,7 @@ body {
   height: 100%;
   overflow: hidden;
   z-index: 1;
+  min-height: calc(var(--vh) * 100);
 }
 .slide-right-enter-active,
 .slide-right-leave-active {

+ 45 - 0
src/components/custom/introPanel/image-grid.vue

@@ -0,0 +1,45 @@
+<template>
+  <div class="image-grid">
+    <div :key="index" v-for="(i, index) in data" class="image" @click="emits('clickImage', index)">
+      <div class="item" :style="{ backgroundImage: `url(${i.url})` }"></div>
+    </div>
+  </div>
+</template>
+<script setup lang="ts">
+  import { onMounted, ref } from 'vue';
+
+  const emits = defineEmits(['clickImage']);
+  interface ImageProps {
+    url: string;
+    id?: string;
+  }
+  defineProps({
+    data: {
+      type: Array as PropType<ImageProps[]>,
+      default: () => [],
+    },
+  });
+
+  onMounted(() => {});
+</script>
+
+<style lang="scss" scoped>
+  .image-grid {
+    width: 100%;
+    display: flex;
+    flex-direction: row;
+    flex-wrap: wrap;
+    .image {
+      flex: 0 0 33.33333%;
+      height: 130px;
+      padding: 5px;
+      .item {
+        width: 100%;
+        height: 100%;
+        border-radius: 5px;
+        background-repeat: no-repeat;
+        background-size: cover;
+      }
+    }
+  }
+</style>

+ 153 - 0
src/components/custom/introPanel/image-view.vue

@@ -0,0 +1,153 @@
+<!--  -->
+<template>
+  <div class="image-view">
+    <i
+      @click.stop="handleClose"
+      class="iconfont ui-kankan-icon icon tip-h-center tip-v-bottom icon-close close-btn"
+    ></i>
+    <div class="swiper mySwiper">
+      <div class="swiper-wrapper">
+        <div class="swiper-slide" v-for="(i, index) in imageList" :key="index">
+          <div class="swiper-zoom-container">
+            <div
+              :id="`vmRef_${index}`"
+              class="swiper-zoom-target"
+              :style="`background-image: url(${i.url})`"
+            ></div>
+          </div>
+        </div>
+      </div>
+      <div class="swiper-pagination"></div>
+      <!-- <div class="swiper-button-next"></div>
+      <div class="swiper-button-prev"></div> -->
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { watchEffect } from 'vue';
+  import { onMounted, computed, defineProps } from 'vue';
+
+  const props = defineProps({
+    data: {
+      type: Object,
+      default: {},
+    },
+    current: {
+      type: Number,
+      default: 0,
+    },
+  });
+  const emits = defineEmits(['close']);
+  const handleClose = () => {
+    console.log('close');
+    emits('close');
+  };
+
+  const imageList = computed(() => {
+    return props.data;
+  });
+
+  // const vmZoom = ref([]);
+  // const zoomList = [];
+  onMounted(() => {
+    // let urls = [];
+
+    (window as any).globalSwiper = new (window as any).Swiper('.mySwiper', {
+      zoom: {
+        toggle: false,
+        maxRatio: 5,
+      },
+      pagination: {
+        el: '.swiper-pagination',
+      },
+      on: {
+        init: function (_) {
+          // for (let i = 0; i < imageList.value.length; i++) {
+          //     vmZoom.value[i] = document.getElementById(`vmRef_${i}`)
+          //     zoomElement(vmZoom.value[i])
+          // }
+        },
+        transitionStart: function (_) {
+          // alert(swiper.previousIndex)
+          // console.log(vmZoom.value[swiper.previousIndex].style.transform)
+          // let scale = getTransform(vmZoom.value[swiper.previousIndex])
+        },
+
+        touchStart: function () {
+          // console.log(swiper.previousIndex)
+        },
+      },
+      navigation: {
+        nextEl: '.swiper-button-next',
+        prevEl: '.swiper-button-prev',
+      },
+    });
+    watchEffect(() => {
+      if (props.current) {
+        if ((window as any).globalSwiper) {
+          (window as any).globalSwiper.slideTo(props.current);
+        }
+      }
+    });
+  });
+</script>
+
+<style lang="scss" scoped>
+  .close-btn {
+    position: fixed;
+    right: 36px;
+    top: 36px;
+    font-size: 18px;
+    cursor: pointer;
+    z-index: 100;
+  }
+  .image-view {
+    width: 100%;
+    height: 100%;
+    position: fixed;
+    bottom: 0;
+    left: 0;
+    background-color: rgba($color: #000000, $alpha: 0.8);
+    z-index: 100;
+    // transform: translate3d(0, 0, 0);
+    // overflow: hidden;
+
+    .swiper {
+      width: 100%;
+      height: 100%;
+      .swiper-slide {
+        transform: translate3d(0, 0, 0);
+        overflow: hidden;
+      }
+      .swiper-zoom-container {
+        width: 100%;
+        height: 100%;
+        transform: translate3d(0, 0, 0);
+        // overflow: hidden;
+        .swiper-zoom-target {
+          width: 100%;
+          height: 100%;
+          background-position: center;
+          background-size: contain;
+          margin: 0 auto;
+          transform: translate3d(0, 0, 0);
+          // img {
+          //     width: 100%;
+          //     height: 100%;
+          // }
+        }
+      }
+    }
+  }
+</style>
+<style lang="scss">
+  .image-view {
+    .swiper-pagination-bullet {
+      background: #f2f2f2;
+    }
+    .swiper-pagination-bullet-active {
+      background: var(--editor-main-color);
+    }
+  }
+</style>

+ 194 - 0
src/components/custom/introPanel/index.vue

@@ -0,0 +1,194 @@
+<template>
+  <div class="layer" v-if="showPanel">
+    <div class="layer-content animated" :class="animateActive ? 'fadeInUpBig' : 'fadeOutDownBig'">
+      <div class="layer-title"> xxxx</div>
+      <div class="main">
+        <tabs>
+          <tab name="简介">
+            <IntroText />
+          </tab>
+          <tab name="相册">
+            <ImageGrid :data="MockImageList" @click-image="handleViewer" />
+          </tab>
+          <tab name="地图">
+            <div></div>
+          </tab>
+        </tabs>
+      </div>
+    </div>
+    <ImageViewer
+      :current="currentImage"
+      :data="MockImageList"
+      v-if="showViewer"
+      @close="showViewer = false"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { Tabs, Tab } from 'vue3-tabs-component';
+  import { useAppStore } from '/@/store/modules/app';
+  import { computed, ref, watchEffect } from 'vue';
+  import ImageGrid from './image-grid.vue';
+  import IntroText from './intro-text.vue';
+  import ImageViewer from './image-view.vue';
+
+  import {} from 'vue';
+  import { unref } from 'vue';
+  import { onMounted } from 'vue';
+  const appStore = useAppStore();
+  (window as any).appStore = appStore;
+  const showPanel = computed(() => appStore.isShowIntroPanel);
+  const animateActive = ref(false);
+  const showViewer = ref(false);
+  const currentImage = ref(0);
+  watchEffect(() => {
+    if (unref(showPanel)) {
+      animateActive.value = true;
+    } else {
+      animateActive.value = false;
+    }
+  });
+  const handleViewer = (index: number) => {
+    showViewer.value = true;
+    currentImage.value = index;
+  };
+  const MockImageList = [
+    {
+      url: 'https://4dkk.4dage.com/head/15915816041/head_1673602197881.png?m=1673602198194',
+    },
+    {
+      url: 'https://4dkk.4dage.com/head/15915816041/head_1673602197881.png?m=1673602198194',
+    },
+    {
+      url: 'https://4dkk.4dage.com/head/15915816041/head_1673602197881.png?m=1673602198194',
+    },
+    {
+      url: 'https://4dkk.4dage.com/head/15915816041/head_1673602197881.png?m=1673602198194',
+    },
+    {
+      url: 'https://4dkk.4dage.com/head/15915816041/head_1673602197881.png?m=1673602198194',
+    },
+    {
+      url: 'https://4dkk.4dage.com/head/15915816041/head_1673602197881.png?m=1673602198194',
+    },
+    {
+      url: 'https://4dkk.4dage.com/head/15915816041/head_1673602197881.png?m=1673602198194',
+    },
+    {
+      url: 'https://4dkk.4dage.com/head/15915816041/head_1673602197881.png?m=1673602198194',
+    },
+    {
+      url: 'https://4dkk.4dage.com/head/15915816041/head_1673602197881.png?m=1673602198194',
+    },
+    {
+      url: 'https://4dkk.4dage.com/head/15915816041/head_1673602197881.png?m=1673602198194',
+    },
+    {
+      url: 'https://4dkk.4dage.com/head/15915816041/head_1673602197881.png?m=1673602198194',
+    },
+    {
+      url: 'https://4dkk.4dage.com/head/15915816041/head_1673602197881.png?m=1673602198194',
+    },
+  ];
+
+  onMounted(() => {
+    setTimeout(() => {
+      appStore.setShowIntroPanel(true);
+    }, 5000);
+  });
+</script>
+
+<style lang="scss" scoped>
+  .layer {
+    width: 100%;
+    position: fixed;
+    bottom: 0;
+    z-index: 10000;
+    // display: none;
+    .layer-content {
+      position: absolute;
+      width: 100%;
+      font-size: 14px;
+      height: calc(var(--vh) * 60);
+      bottom: 0;
+      color: white;
+      display: flex;
+      flex-direction: column;
+      z-index: 0;
+      &::before {
+        content: ' ';
+        background: rgba(0, 0, 0, 0.9);
+        border-radius: 0.14rem 0.14rem 0px 0px;
+        filter: blur(1px);
+        position: absolute;
+        width: 100%;
+        height: 100%;
+        left: 0;
+        top: 0;
+        z-index: -1;
+      }
+    }
+    .layer-title {
+      min-height: 50px;
+      line-height: 50px;
+      border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+      font-size: 18px;
+      //   color: white;
+    }
+    .main {
+      flex: 1;
+      display: flex;
+      overflow: hidden;
+    }
+  }
+</style>
+<style lang="scss">
+  .tabs-component {
+    width: 100%;
+    min-height: 100%;
+    display: flex;
+    flex-direction: column;
+  }
+  .tabs-component-tabs {
+    display: flex;
+    flex-direction: row;
+    .tabs-component-tab {
+      padding: 15px 20px;
+      font-size: 16px;
+      > a {
+        color: rgba(255, 255, 255, 0.5);
+      }
+      &.is-active {
+        > a {
+          color: rgba(255, 255, 255, 1);
+          position: relative;
+          &::before {
+            content: '';
+            width: 50%;
+            height: 2px;
+            background-color: #ed5d18;
+            position: absolute;
+            bottom: -8px;
+            border-radius: 2px;
+            transform: translate(50%, 50%);
+          }
+        }
+      }
+    }
+  }
+  .tabs-component-panels {
+    display: flex;
+    justify-content: center;
+    align-items: flex-start;
+    width: 100%;
+    height: 100%;
+    .tabs-component-panel {
+      flex: 1;
+      height: 100%;
+      width: 100%;
+      overflow-y: scroll;
+      padding-bottom: 80px;
+    }
+  }
+</style>

+ 69 - 0
src/components/custom/introPanel/intro-text.vue

@@ -0,0 +1,69 @@
+<template>
+  <div class="intro-textarea">
+    <p>
+      1837年,第一家蒂芙尼精品店在纽约开业;1845年,蒂芙尼发布了美国境内首份直邮目录;1866年,蒂芙尼推出美国第一款计时码表——蒂芙尼计时器;1868年,蒂芙尼开始制造黄金时尚珠宝设计;1969年,Return
+      to Tiffany™钥匙扣问世;1980年,Paloma Picasso首个蒂芙尼系列问世;2009年,Tiffany
+      Keys系列问世;2014年,Tiffany T系列作为新时代的标志而问世;
+    </p>
+    <p>
+      1837年,第一家蒂芙尼精品店在纽约开业;1845年,蒂芙尼发布了美国境内首份直邮目录;1866年,蒂芙尼推出美国第一款计时码表——蒂芙尼计时器;1868年,蒂芙尼开始制造黄金时尚珠宝设计;1969年,Return
+      to Tiffany™钥匙扣问世;1980年,Paloma Picasso首个蒂芙尼系列问世;2009年,Tiffany
+      Keys系列问世;2014年,Tiffany T系列作为新时代的标志而问世;
+    </p>
+    <p>
+      1837年,第一家蒂芙尼精品店在纽约开业;1845年,蒂芙尼发布了美国境内首份直邮目录;1866年,蒂芙尼推出美国第一款计时码表——蒂芙尼计时器;1868年,蒂芙尼开始制造黄金时尚珠宝设计;1969年,Return
+      to Tiffany™钥匙扣问世;1980年,Paloma Picasso首个蒂芙尼系列问世;2009年,Tiffany
+      Keys系列问世;2014年,Tiffany T系列作为新时代的标志而问世;
+    </p>
+    <p>
+      1837年,第一家蒂芙尼精品店在纽约开业;1845年,蒂芙尼发布了美国境内首份直邮目录;1866年,蒂芙尼推出美国第一款计时码表——蒂芙尼计时器;1868年,蒂芙尼开始制造黄金时尚珠宝设计;1969年,Return
+      to Tiffany™钥匙扣问世;1980年,Paloma Picasso首个蒂芙尼系列问世;2009年,Tiffany
+      Keys系列问世;2014年,Tiffany T系列作为新时代的标志而问世;
+    </p>
+    <p>
+      1837年,第一家蒂芙尼精品店在纽约开业;1845年,蒂芙尼发布了美国境内首份直邮目录;1866年,蒂芙尼推出美国第一款计时码表——蒂芙尼计时器;1868年,蒂芙尼开始制造黄金时尚珠宝设计;1969年,Return
+      to Tiffany™钥匙扣问世;1980年,Paloma Picasso首个蒂芙尼系列问世;2009年,Tiffany
+      Keys系列问世;2014年,Tiffany T系列作为新时代的标志而问世;
+    </p>
+    <p>
+      1837年,第一家蒂芙尼精品店在纽约开业;1845年,蒂芙尼发布了美国境内首份直邮目录;1866年,蒂芙尼推出美国第一款计时码表——蒂芙尼计时器;1868年,蒂芙尼开始制造黄金时尚珠宝设计;1969年,Return
+      to Tiffany™钥匙扣问世;1980年,Paloma Picasso首个蒂芙尼系列问世;2009年,Tiffany
+      Keys系列问世;2014年,Tiffany T系列作为新时代的标志而问世;
+    </p>
+    <p>
+      1837年,第一家蒂芙尼精品店在纽约开业;1845年,蒂芙尼发布了美国境内首份直邮目录;1866年,蒂芙尼推出美国第一款计时码表——蒂芙尼计时器;1868年,蒂芙尼开始制造黄金时尚珠宝设计;1969年,Return
+      to Tiffany™钥匙扣问世;1980年,Paloma Picasso首个蒂芙尼系列问世;2009年,Tiffany
+      Keys系列问世;2014年,Tiffany T系列作为新时代的标志而问世;
+    </p>
+    <p>
+      1837年,第一家蒂芙尼精品店在纽约开业;1845年,蒂芙尼发布了美国境内首份直邮目录;1866年,蒂芙尼推出美国第一款计时码表——蒂芙尼计时器;1868年,蒂芙尼开始制造黄金时尚珠宝设计;1969年,Return
+      to Tiffany™钥匙扣问世;1980年,Paloma Picasso首个蒂芙尼系列问世;2009年,Tiffany
+      Keys系列问世;2014年,Tiffany T系列作为新时代的标志而问世;
+    </p>
+  </div>
+</template>
+<script setup lang="ts">
+  interface ImageProps {
+    url: string;
+  }
+  defineProps({
+    data: {
+      type: Array as PropType<ImageProps[]>,
+      default: () => [],
+    },
+  });
+</script>
+
+<style lang="scss" scoped>
+  .intro-textarea {
+    word-wrap: break-word;
+    width: 100%;
+    text-align: left;
+    padding: 10px;
+    line-height: 32px;
+    overflow-y: scroll;
+    > * {
+      word-wrap: break-word;
+    }
+  }
+</style>

+ 0 - 38
src/components/custom/tag.vue

@@ -1,38 +0,0 @@
-<script setup lang="ts">
-  // import { computed } from 'vue';
-
-  defineProps({
-    sid: { type: String },
-    url: { type: String },
-    title: { type: String, default: '' },
-    content: { type: String, default: '' },
-    icon: { type: String, default: '' },
-  });
-
-  // const backgroundURL = computed(
-  //   () => props.url && props.url + '&x-oss-process=image/resize,m_fill,w_80,h_80',
-  // );
-</script>
-
-<template>
-  <span
-    class="tag-icon animate"
-    :style="{
-      backgroundImage: `url(${icon})`,
-    }"
-  ></span>
-  <!-- <div class="tag-body">
-    <div :data-id="sid" class="tag-commodity">
-      <div
-        :style="{
-          backgroundImage: `url(${backgroundURL})`,
-        }"
-        class="tag-avatar"
-      ></div>
-      <p class="tag-title">{{ title }}</p>
-      <div class="tag-info">
-        <div>{{ content.substring(0, 30) }}</div>
-      </div>
-    </div>
-  </div> -->
-</template>

+ 0 - 38
src/components/custom/tagView.vue

@@ -1,38 +0,0 @@
-<script setup lang="ts">
-  import { computed } from 'vue';
-
-  const props = defineProps({
-    sid: { type: String },
-    url: { type: String },
-    title: { type: String, default: '' },
-    content: { type: String, default: '' },
-    icon: { type: String, default: '' },
-  });
-
-  const backgroundURL = computed(
-    () => props.url && props.url + '&x-oss-process=image/resize,m_fill,w_80,h_80',
-  );
-</script>
-
-<template>
-  <span
-    class="tag-icon animate"
-    :style="{
-      backgroundImage: `url(${icon})`,
-    }"
-  ></span>
-  <div class="tag-body">
-    <div data-id="${data.sid}" class="tag-commodity">
-      <div
-        :style="{
-          backgroundImage: `url(${backgroundURL})`,
-        }"
-        class="tag-avatar"
-      ></div>
-      <p class="tag-title">{{ title }}</p>
-      <div class="tag-info">
-        <div>{{ content.substring(0, 30) }}</div>
-      </div>
-    </div>
-  </div>
-</template>

+ 20 - 0
src/hooks/globalViewHeight.ts

@@ -0,0 +1,20 @@
+import { onMounted, onUnmounted } from 'vue';
+
+const appHeight = () => {
+  const doc = document.documentElement;
+  const windowVH = window.innerHeight / 100;
+  doc.style.setProperty('--vh', windowVH + 'px');
+  doc.style.setProperty('--imageHeight', `${window.innerHeight}px`);
+};
+
+export const useAppHeight = () => {
+  onMounted(() => {
+    appHeight();
+    window.addEventListener('resize', appHeight);
+    window.addEventListener('orientationchange', appHeight);
+  });
+  onUnmounted(() => {
+    window.removeEventListener('resize', appHeight);
+    window.removeEventListener('orientationchange', appHeight);
+  });
+};

+ 8 - 0
src/store/modules/app.ts

@@ -24,6 +24,7 @@ interface AppState {
   isTourMode: boolean; // 自由观看模式
   ttl: number;
   passWordConfirm: boolean;
+  showIntroPanel: boolean;
 }
 
 export const useAppStore = defineStore({
@@ -50,11 +51,15 @@ export const useAppStore = defineStore({
     isTourMode: true,
     ttl: -1,
     passWordConfirm: false,
+    showIntroPanel: false,
   }),
   getters: {
     isPassWordConfirm(): boolean {
       return this.passWordConfirm;
     },
+    isShowIntroPanel(): boolean {
+      return this.showIntroPanel;
+    },
   },
   actions: {
     setRoomValidTime(ttl: number): void {
@@ -143,5 +148,8 @@ export const useAppStore = defineStore({
       console.log('设置输入密码状态', status);
       this.passWordConfirm = status;
     },
+    setShowIntroPanel(status: boolean) {
+      this.showIntroPanel = status;
+    },
   },
 });