Browse Source

feat: 修改热点问题

rindy 9 months ago
parent
commit
934b20e31b

+ 1 - 1
packages/qjkankan-kankan-view/.env.testserve

@@ -17,5 +17,5 @@ VUE_APP_STATIC_DIR=viewer
 VUE_APP_REGION_URL=
 
 # 接口请求地址
-VUE_APP_APIS_URL=https://test.4dkankan.com/
+VUE_APP_APIS_URL=https://www.4dkankan.com/
 

+ 2 - 0
packages/qjkankan-kankan-view/public/spg.html

@@ -7,7 +7,9 @@
         <link rel="icon" href="<%= BASE_URL %><%= VUE_APP_STATIC_DIR %>/favicon.png" />
         <link rel="stylesheet" href="<%= BASE_URL %><%= VUE_APP_STATIC_DIR %>/static/lib/iconfont/iconfont.css" />
         <!-- <link rel="stylesheet" href="<%= BASE_URL %><%= VUE_APP_STATIC_DIR %>/static/lib/animate/animate.min.css" /> -->
+        <link rel="stylesheet" href="<%= BASE_URL %><%= VUE_APP_STATIC_DIR %>/static/lib/swiper/swiper-bundle.min.css" />
         <script src="<%= BASE_URL %><%= VUE_APP_STATIC_DIR %>/static/lib/mobile-detect.js"></script>
+        <script src="<%= BASE_URL %><%= VUE_APP_STATIC_DIR %>/static/lib/swiper/swiper-bundle.min.js"></script>
         <title>四维全景</title>
     </head>
 

+ 47 - 13
packages/qjkankan-kankan-view/src/components/Tags/constant.js

@@ -1,4 +1,5 @@
 import i18n from '@/i18n'
+import { ref } from 'vue'
 const { t } = i18n.global
 
 export const custom = () => {
@@ -13,7 +14,19 @@ export const custom = () => {
             maxSize: 5 * 1024 * 1024,
             maxNum: 9,
             // othPlaceholder: '支持JPG、PNG图片格式,单张不超过5MB,最多支持上传9张。',
-            othPlaceholder: t('tag.toolbox.metaImageTips', { form: 'jpg/png/gif', size: '5MB', maxlength: '9' }),
+            othPlaceholder: t('tag.toolbox.metaImageTips', { form: 'jpg/png', size: '5MB', maxlength: '9' }),
+        },
+        text: {
+            icon: 'pic',
+            upload: true,
+            uploadPlace: t('common.upload') + t('common.image'),
+            accept: `.jpg,.png`,
+            multiple: true,
+            name: t('common.image'),
+            maxSize: 5 * 1024 * 1024,
+            maxNum: 9,
+            // othPlaceholder: '支持JPG、PNG图片格式,单张不超过5MB,最多支持上传9张。',
+            othPlaceholder: t('tag.toolbox.metaImageTips', { form: 'jpg/png', size: '5MB', maxlength: '9' }),
         },
         video: {
             icon: 'video',
@@ -37,21 +50,42 @@ export const custom = () => {
             // othPlaceholder: '支持MP3、WAV格式,不超过5MB',
             othPlaceholder: t('tag.toolbox.metaAudioTips', { form: 'mp3/wav', size: '5MB' }),
         },
+        media: {
+            icon: 'b_music',
+            upload: true,
+            uploadPlace: t('common.upload') + t('common.audio'),
+            accept: '.mp3, .wav',
+            multiple: false,
+            name: t('common.audio'),
+            maxSize: 5 * 1024 * 1024,
+            // othPlaceholder: '支持MP3、WAV格式,不超过5MB',
+            othPlaceholder: t('tag.toolbox.metaAudioTips', { form: 'mp3/wav', size: '5MB' }),
+        },
         link: {
             icon: 'web',
             name: t('common.link'),
         },
-        text: {
-            icon: 'pic',
-            upload: true,
-            uploadPlace: t('common.upload') + t('common.image'),
-            accept: `.jpg,.png`,
-            multiple: true,
-            name: t('common.image'),
-            maxSize: 5 * 1024 * 1024,
-            maxNum: 9,
-            // othPlaceholder: '支持JPG、PNG图片格式,单张不超过5MB,最多支持上传9张。',
-            othPlaceholder: t('tag.toolbox.metaImageTips', { form: 'jpg/png/gif', size: '5MB', maxlength: '9' }),
-        },
     }
 }
+export const defaultStyles = ref([
+    {
+        name: 'hotpot_music.svg',
+        sid: 'hotpot_music.svg',
+        type: 'music',
+    },
+    {
+        name: 'hotpot_pic.svg',
+        sid: 'hotpot_pic.svg',
+        type: 'pic',
+    },
+    {
+        name: 'hotpot_video.svg',
+        sid: 'hotpot_video.svg',
+        type: 'video',
+    },
+    {
+        name: 'hotpot-text.svg',
+        sid: 'hotpot-text.svg',
+        type: 'text',
+    },
+])

+ 425 - 392
packages/qjkankan-kankan-view/src/components/Tags/index.vue

@@ -1,441 +1,474 @@
 <template>
-  <teleport :to="tags$" v-if="tags$">
-    <template v-for="(tag, index) in tags">
-      <div
-        :tag-sid="tag.sid"
-        @mouseleave.prevent="onMouseLeave($event, tag)"
-        :style="{
-          left: `${tag.x}px`,
-          top: `${tag.y}px`,
-          'z-index': hotData?.sid == tag.sid ? 1 : 0,
-        }"
-        :class="{
-          visible:
-            tag.visible ||
-            (tours.length &&
-              hotData &&
-              tours[partId].list[frameId].tagId == tag.sid &&
-              getApp().config.mobile),
-        }"
-      >
-        <!-- <div
-              :tag-sid="tag.sid"
-              @mouseleave.prevent="onMouseLeave($event, tag)"
-              :style="{ transform: `translate3d(${tag.x}px,${tag.y}px,0)`, 'z-index': hotData?.sid == tag.sid ? 1 : 0 }"
-              :class="{ visible: tag.visible || (tours.length && hotData && tours[partId].list[frameId].tagId == tag.sid && getApp().config.mobile) }"
-          > -->
-        <span
-          class="point zoom"
-          @mouseenter.prevent="onMouseEnter($event, tag, index)"
-          @click.stop="goTag($event, tag, index)"
-          :style="{ 'background-image': 'url(' + getUrl(tag.icon) + ')' }"
-        ></span>
-        <p class="tag-title" v-if="metadata.controls.showTagTitle">
-          {{ tag.title }}
-        </p>
-        <div class="content">
-          <div
-            class="trans"
-            :class="{
-              fixed: isPlay || getApp().config.mobile,
-              active:
-                (isFixed && hotData && tag.sid == hotData.sid) ||
-                (showInfo && hotData && tag.sid == hotData.sid),
-            }"
-          >
-            <template v-if="hotData && tag.sid == hotData.sid && !showMsg">
-              <div class="arrow" :id="`arrow_${tag.sid}`">
-                <ui-icon
-                  @click.stop="closeTag"
-                  v-if="getApp().config.mobile"
-                  type="close"
-                ></ui-icon>
-              </div>
-              <ShowTag
-                :moveDistance="moveDistance"
-                @click.stop=""
-                v-if="!isEdit && hotData"
-                @open="openInfo"
-              />
-            </template>
-          </div>
-        </div>
+    <teleport :to="tags$" v-if="tags$">
+        <template v-for="(tag, index) in tags">
+            <div
+                :tag-sid="tag.sid"
+                @mouseleave.prevent="onMouseLeave($event, tag)"
+                :style="{ left: `${tag.x}px`, top: `${tag.y}px`, 'z-index': hotData?.sid == tag.sid ? 1 : 0 }"
+                :class="{ visible: tag.visible || (tours.length && hotData && tours[partId].list[frameId].tagId == tag.sid && getApp().config.mobile) }"
+            >
+                <!-- <div
+                :tag-sid="tag.sid"
+                @mouseleave.prevent="onMouseLeave($event, tag)"
+                :style="{ transform: `translate3d(${tag.x}px,${tag.y}px,0)`, 'z-index': hotData?.sid == tag.sid ? 1 : 0 }"
+                :class="{ visible: tag.visible || (tours.length && hotData && tours[partId].list[frameId].tagId == tag.sid && getApp().config.mobile) }"
+            > -->
+                <span
+                    :class="{ 'no-zoom': showDetails && hotData && tag.sid == hotData.sid, visible: tag.visible }"
+                    class="point zoom"
+                    @mouseenter.prevent="onMouseEnter($event, tag, index)"
+                    @click.stop="goTag($event, tag, index)"
+                    :style="{ 'background-image': 'url(' + getUrl(tag.icon) + ')' }"
+                    ><i v-if="showDetails && hotData && tag.sid == hotData.sid" :style="`background-image:url(${tagFocusIcon});`" class="focus-icon"></i
+                ></span>
+                <p class="tag-title" v-if="metadata.controls.showTagTitle">{{ tag.title }}</p>
+                <div class="content">
+                    <div
+                        class="trans"
+                        :class="{
+                            fixed: isPlay || getApp().config.mobile,
+                            active: (isFixed && hotData && tag.sid == hotData.sid) || (showInfo && hotData && tag.sid == hotData.sid),
+                            hidden: !tag.visible && !isPlay,
+                        }"
+                    >
+                        <!-- <template v-if="hotData && tag.sid == hotData.sid && !showMsg && !showDetails"> -->
+                        <template v-if="hotData && tag.sid == hotData.sid && !showDetails">
+                            <div class="arrow" :id="`arrow_${tag.sid}`">
+                                <ui-icon @click.stop="closeTag" v-if="getApp().config.mobile" type="close"></ui-icon>
+                            </div>
+                            <ShowTag @openDetails="openDetails" :moveDistance="moveDistance" @click.stop="" v-if="!isEdit && hotData" @open="openInfo" />
+                        </template>
+                    </div>
+                </div>
 
-        <TagView
-          @click.stop=""
-          v-if="showMsg && toggleIndex == index"
-          @close="closeInfo"
-        />
-      </div>
-    </template>
-  </teleport>
+                <TagView @click.stop="" v-if="showMsg && toggleIndex == index" @close="closeInfo" />
+                <!-- <TagDeTails v-if="showDetails && toggleIndex == index && !showMsg" @open="openInfo" /> -->
+                <TagDeTails v-if="showDetails && toggleIndex == index" @open="openInfo" />
+            </div>
+        </template>
+    </teleport>
 </template>
 <script setup>
-import {
-  ref,
-  onMounted,
-  computed,
-  watch,
-  watchEffect,
-  onActivated,
-  onDeactivated,
-  getCurrentInstance,
-  nextTick,
-} from "vue";
-import { getApp, useApp } from "@/app";
-import { useStore } from "vuex";
-import common from "../../utils/common";
-import TagView from "./tag-view.vue";
-import ShowTag from "./show-tag.vue";
-import { useRoute } from "vue-router";
-import { useMusicPlayer } from "@/utils/sound";
-import browser from "@/utils/browser";
-
-// import { Track, startTrack, endTrack } from "@/utils/track.js";
-const musicPlayer = useMusicPlayer();
-let init = true;
+import { ref, onMounted, computed, watch, watchEffect, onActivated, onDeactivated, getCurrentInstance, nextTick } from 'vue'
+import { getApp, useApp } from '@/app'
+import { useStore } from 'vuex'
+import common from '../../utils/common'
+import TagView from './tag-view.vue'
+import ShowTag from './show-tag.vue'
+import TagDeTails from './tag-details.vue'
+import { useRoute } from 'vue-router'
+import { useMusicPlayer } from '@/utils/sound'
+import browser from '@/utils/browser'
+import { Track, startTrack, endTrack } from '@/utils/track.js'
+import { defaultStyles } from './constant.js'
+const musicPlayer = useMusicPlayer()
+let init = true
 const hotData = computed(() => {
-  let data = store.getters["tag/hotData"];
-  if (!data) {
-    // musicPlayer.play()
-    // musicPlayer.pause(true)
-    // if (!getApp().Scene.isCurrentPanoHasVideo) {
-    //     musicPlayer.resume()
-    // }
-  }
-  return data;
-});
-const metadata = computed(() => store.getters["scene/metadata"]);
-const isPlay = computed(() => store.getters["tour/isPlay"]);
-const tours = computed(() => store.getters["tour/tours"]);
-const partId = computed(() => store.getters["tour/partId"]);
-const frameId = computed(() => store.getters["tour/frameId"]);
-const router = computed(() => store.getters["router"]);
-const flying = computed(() => store.getters["flying"]);
-const leaveId = computed(() => store.getters["tag/leaveId"]);
-const isEdit = computed(() => store.getters["tag/isEdit"]);
-const isFixed = computed(() => store.getters["tag/isFixed"]);
-const enterVisible = computed(() => store.getters["tag/enterVisible"]);
-const editPosition = computed(() => store.getters["tag/editPosition"]);
-const toggleIndex = computed(() => store.getters["tag/toggleIndex"]);
-const isClick = computed(() => store.getters["tag/isClick"]);
-const editModule = computed(() => store.getters["editModule"]);
-const positionInfo = computed(() => store.getters["tag/positionInfo"]);
-const panoId = computed(() => store.getters["panoId"]);
-const store = useStore();
-const tags$ = ref(null);
+    let data = store.getters['tag/hotData']
+
+    return data
+})
+const metadata = computed(() => store.getters['scene/metadata'])
+const isPlay = computed(() => store.getters['tour/isPlay'])
+const tours = computed(() => store.getters['tour/tours'])
+const partId = computed(() => store.getters['tour/partId'])
+const frameId = computed(() => store.getters['tour/frameId'])
+const router = computed(() => store.getters['router'])
+const flying = computed(() => store.getters['flying'])
+const leaveId = computed(() => store.getters['tag/leaveId'])
+const isEdit = computed(() => store.getters['tag/isEdit'])
+const isFixed = computed(() => store.getters['tag/isFixed'])
+const enterVisible = computed(() => store.getters['tag/enterVisible'])
+const editPosition = computed(() => store.getters['tag/editPosition'])
+const toggleIndex = computed(() => store.getters['tag/toggleIndex'])
+const isClick = computed(() => store.getters['tag/isClick'])
+const editModule = computed(() => store.getters['editModule'])
+const positionInfo = computed(() => store.getters['tag/positionInfo'])
+const panoId = computed(() => store.getters['panoId'])
+const showDetails = computed(() => store.getters['tag/showDetails'])
+const store = useStore()
+const tags$ = ref(null)
 const tags = computed(() => {
-  return store.getters["tag/tags"] || [];
-});
+    console.log(store.getters['tag/tags'] || [])
+    return store.getters['tag/tags'] || []
+})
 
 // const flyClose = computed(() => store.getters['tag/flyClose'])
-
+const tagFocusIcon = ref('')
 watch(
-  () => router.value.name,
-  (val, old) => {
-    if (val !== "tag") {
-      store.commit("tag/setData", { isFixed: false });
+    () => router.value.name,
+    (val, old) => {
+        if (val !== 'tag') {
+            store.commit('tag/setData', { isFixed: false })
+        }
     }
-  }
-);
-
-const showInfo = ref(false);
-const showMsg = ref(false);
+)
+watch(
+    () => isFixed.value,
+    val => {
+        if (!val) {
+            moveDistance.value = 0
+        }
+    }
+)
+const openDetails = () => {
+    store.commit('tag/setData', { showDetails: true })
+}
+const showInfo = ref(false)
+const showMsg = ref(false)
 // const toggleIndex = ref(null)
 const openInfo = () => {
-  showMsg.value = true;
-  store.commit("tag/setData", { isFixed: false, isClick: true });
-  showInfo.value = false;
-};
+    showMsg.value = true
+    // store.commit('tag/setData', { isFixed: true, isClick: true })
+    store.commit('tag/setData', { isFixed: true, isClick: true }) //为了查看媒体文件不关闭热点。听音乐
+    showInfo.value = false
+}
 const closeInfo = () => {
-  showMsg.value = false;
-  if (isClick.value) {
-    //只有点击定位的才恢复显示
-    store.commit("tag/show", toggleIndex.value);
-    store.commit("tag/setFixed", true);
-    // showInfo.value = true
-    showInfo.value = false;
-  }
-  // store.commit('tag/setClick', false)
-};
+    showMsg.value = false
+    if (isClick.value) {
+        //只有点击定位的才恢复显示
+        store.commit('tag/show', toggleIndex.value)
+        store.commit('tag/setFixed', true)
+        // showInfo.value = true
+        showInfo.value = false
+    }
+}
 const closeTag = async () => {
-  const app = getApp();
-  const player = await app.TourManager.player;
-
-  //关闭热点面板时候,继续播放之前暂停的音频
-  if (!app.Scene.isCurrentPanoHasVideo && !player.isPlaying) {
-    if (hotData.value.type == "audio" || hotData.value.type == "video") {
-      // console.log('resume')
-      // if (!isPlay.value) {
-      //   musicPlayer.resume();
-      // }
-      window.parent.postMessage(
-        {
-          source: "qjkankan",
-          event: "toggleBgmStatus",
-          params: {
-            status: true,
-          },
-        },
-        "*"
-      );
+    const app = getApp()
+    const player = await app.TourManager.player
+    //关闭热点面板时候,继续播放之前暂停的音频
+    if (!app.Scene.isCurrentPanoHasVideo && !player.isPlaying) {
+        // if (hotData.value && (hotData.value.bgm || (hotData.value.media.length && hotData.value.media[0].type == 'video'))) {
+        if (!isPlay.value) {
+            musicPlayer.resume()
+        }
+        // }
     }
-  }
-  // store.commit('tag/setData', { isFixed: false, isClick: false, flyClose: false })
-  store.commit("tag/setData", { isFixed: false, isClick: false });
-  store.commit("tag/closeTag");
+    __fixed = false
+    // store.commit('tag/setData', { isFixed: false, isClick: false, flyClose: false })
+    store.commit('tag/setData', { isFixed: false, isClick: false })
+    store.commit('tag/closeTag')
 
-  // store.commit('tag/setFixed', false)
-  // store.commit('tag/closeTag')
-  // store.commit('tag/setClick', false)
-  showInfo.value = false;
-};
+    // store.commit('tag/setFixed', false)
+    // store.commit('tag/closeTag')
+    // store.commit('tag/setClick', false)
+    showInfo.value = false
+}
 const goTag = async (event, item, index) => {
-  // Track("view-tag", {
-  //   eventType: "click",
-  //   click: 1,
-  // });
-  let player = await getApp().TourManager.player;
-  if (isPlay.value && !event.fixed) {
-    player.pause();
-    store.commit("tour/setData", { isPlay: false });
-  }
-  if (flying.value) {
-    return;
-  }
-  if (
-    isFixed.value &&
-    !isEdit.value &&
-    hotData.value.sid == item.sid &&
-    !positionInfo.value
-  ) {
-    __close = true;
-    getApp().TagManager.close(hotData.value.sid);
-    closeTag();
-  } else {
-    if (!enterVisible.value && !editPosition.value) {
-      if (!isEdit.value && !positionInfo.value) {
-        store.commit("tag/show", index);
-        store.commit("tag/setFixed", true);
-        showInfo.value = true;
-        store.commit("tag/setToggleIndex", index);
-        if (event.fixed) {
-          store.commit("tag/gotoTag", { tag: item, fixed: true });
-          // store.commit('tag/gotoTag', { tag: item })
-        } else {
-          store.commit("tag/gotoTag", { tag: item });
-        }
-      }
-      //没有t_id时候 不需要延时
-      if (!browser.hasURLParam("t_id")) {
-        let timer = setTimeout(() => {
-          // store.commit('tag/setData', { flyClose: true })
-          clearTimeout(timer);
-          timer = null;
-        }, 1000);
-      }
+    Track('view-tag', {
+        eventType: 'click',
+        click: 1,
+    })
+    let player = await getApp().TourManager.player
 
-      store.commit("tag/setClick", true);
-    } else {
-      //热点可视操作
-      getApp().TagManager.edit.setTagVisi(item.sid);
+    if (isPlay.value && !event.fixed) {
+        player.pause()
+        store.commit('tour/setData', { isPlay: false })
     }
-  }
-};
-
-onMounted(async () => {
-  const app = await useApp();
-  app.Camera.on("flying.started", (pano) => {
-    if (!pano.isTagFlying && hotData.value && !isEdit.value) {
-      closeTag();
+    if (flying.value) {
+        return
+    }
+    if (showDetails.value && item.sid == hotData.value.sid) {
+        //展开详情的时候
+        store.commit('tag/setData', { showDetails: false })
+        return
     }
-    // if (flyClose.value && hotData.value && !isEdit.value) {
-    //     getApp().TagManager.close(hotData.value.sid)
-    //     closeTag()
+    // if (item.bgm) {
+    //     musicPlayer.pause(true)
     // }
-  });
-  app.Scene.on("loadeddata", () => {
-    if (browser.hasURLParam("t_id")) {
-      let t_id = browser.getURLParam("t_id");
-      for (let i = 0; i < tags.value.length; i++) {
-        if (tags.value[i].sid == t_id) {
-          goTag({}, tags.value[i], i);
+    if (isFixed.value && !isEdit.value && hotData.value.sid == item.sid && !positionInfo.value) {
+        __close = true
+        // getApp().TagManager.close(hotData.value.sid)
+        closeTag()
+    } else {
+        if (!enterVisible.value && !editPosition.value) {
+            if (!isEdit.value && !positionInfo.value) {
+                store.commit('tag/show', index)
+                store.commit('tag/setFixed', true)
+                showInfo.value = true
+                store.commit('tag/setToggleIndex', index)
+                if (event.fixed) {
+                    store.commit('tag/setData', { showDetails: false }) //先清除上一个的详情状态
+                    store.commit('tag/gotoTag', { tag: item, fixed: true })
+                    nextTick(() => {
+                        if (tours.value[partId.value].list[frameId.value].tagDetails) {
+                            openDetails()
+                        }
+                    })
+                    // store.commit('tag/gotoTag', { tag: item })
+                } else {
+                    store.commit('tag/gotoTag', { tag: item })
+                }
+            }
+            //没有t_id时候 不需要延时
+            if (!browser.hasURLParam('t_id')) {
+                let timer = setTimeout(() => {
+                    // store.commit('tag/setData', { flyClose: true })
+                    clearTimeout(timer)
+                    timer = null
+                }, 1000)
+            }
+
+            store.commit('tag/setClick', true)
+        } else {
+            //热点可视操作
+            getApp().TagManager.edit.setTagVisi(item.sid)
         }
-      }
     }
-  });
-  // app.Camera.on('flying.ended', ({ targetPano }) => {
-  // })
-  await app.TagManager.tag();
-  init = false;
+}
+const hanlderTour = async () => {
+    let player = await getApp().TourManager.player
+    // player.on('play', tours => {})
+    player.on('pause', data => {
+        __fixed = false
+    })
+    player.on('end', data => {
+        __fixed = false
+    })
+}
+
+onMounted(async () => {
+    const app = await useApp()
+    // app.TagManager.on('openTag', sid => {
+    //     for (let i = 0; i < tags.value.length; i++) {
+    //         if (tags.value[i].sid == sid) {
+    //             if (getApp().config.mobile) {
+    //                 goTag({ fixed: true }, tags.value[i], i)
+    //             } else {
+    //                 onMouseEnter({ fixed: true }, tags.value[i], i)
+    //             }
+    //         }
+    //     }
+    // })
+    tagFocusIcon.value = getApp().resource.getAppURL('images/tag/focus.svg')
+    hanlderTour()
+    app.Camera.on('flying.started', pano => {
+        if (!pano.isTagFlying && hotData.value && !isEdit.value) {
+            closeTag()
+        }
+        // if (flyClose.value && hotData.value && !isEdit.value) {
+        //     getApp().TagManager.close(hotData.value.sid)
+        //     closeTag()
+        // }
+    })
+    app.Scene.on('loadeddata', () => {
+        if (browser.hasURLParam('t_id')) {
+            let t_id = browser.getURLParam('t_id')
+            for (let i = 0; i < tags.value.length; i++) {
+                if (tags.value[i].sid == t_id) {
+                    goTag({}, tags.value[i], i)
+                }
+            }
+        }
+    })
+    // app.Camera.on('flying.ended', ({ targetPano }) => {
+    // })
+    await app.TagManager.tag()
+    init = false
 
-  tags$.value = "[xui_tags]";
-  app.TagManager.updatePosition(tags.value);
-  if (app.config.mobile) {
-    nextTick(() => {
-      // let player = document.querySelector('.player')
-      // player.addEventListener('touchstart', onClickHandler)
-    });
-  } else {
-    // window.addEventListener('click', onClickHandler)
-  }
-});
+    tags$.value = '[xui_tags]'
+    app.TagManager.updatePosition(tags.value)
+    if (app.config.mobile) {
+        nextTick(() => {
+            // let player = document.querySelector('.player')
+            // player.addEventListener('touchstart', onClickHandler)
+        })
+    } else {
+        // window.addEventListener('click', onClickHandler)
+    }
+})
 
 function getElemDis(el) {
-  var tp = document.documentElement.clientTop,
-    lt = document.documentElement.clientLeft,
-    rect = el.getBoundingClientRect();
-  return {
-    bodyWidth: document.body.clientWidth,
-    bodyHight: document.body.clientHeight,
-    width: rect.width,
-    height: rect.height,
-    top: rect.top - tp,
-    right: rect.right - lt,
-    bottom: rect.bottom - tp,
-    left: rect.left - lt,
-  };
+    var tp = document.documentElement.clientTop,
+        lt = document.documentElement.clientLeft,
+        rect = el.getBoundingClientRect()
+    return {
+        bodyWidth: document.body.clientWidth,
+        bodyHight: document.body.clientHeight,
+        width: rect.width,
+        height: rect.height,
+        top: rect.top - tp,
+        right: rect.right - lt,
+        bottom: rect.bottom - tp,
+        left: rect.left - lt,
+    }
+}
+const getUrl = icon => {
+    // let url = icon == '' || !icon ? getApp().resource.getAppURL('images/tag_icon_default.svg') : icon
+    let url
+    if (styleList.value.includes(icon)) {
+        url = getApp().resource.getAppURL('images/tag/' + icon)
+    } else {
+        url = icon == '' || !icon ? getApp().resource.getAppURL('images/tag/hotpot-default.svg') : icon
+    }
+    return common.changeUrl(url, true)
+    // return common.changeUrl(url, true)
 }
-const getUrl = (icon) => {
-  let url =
-    icon == "" || !icon
-      ? getApp().resource.getAppURL("images/tag_icon_default.svg")
-      : icon;
 
-  return common.changeUrl(url, true);
-};
-let __fixed = false;
-let __close = false;
-const moveDistance = ref(0);
-let moveTimer = null;
+const styleList = computed(() => defaultStyles.value.map(i => i.name))
+
+let __fixed = false
+let __close = false
+const moveDistance = ref(0)
+let moveTimer = null
 const onMouseEnter = (event, tag, index) => {
-  if (moveTimer) {
-    clearTimeout(moveTimer);
-    moveTimer = null;
-  }
-  moveTimer = setTimeout(() => {
-    let el = document.getElementById(`tagBox_${tag.sid}`);
-    if (!el) {
-      return;
+    moveDistance.value = 0
+    if (showDetails.value && !event.fixed) {
+        return
     }
-    let data = getElemDis(el);
-    if (data.top < 0) {
-      // console.error('超出上面', Math.abs(data.top))
-      moveDistance.value = Math.abs(data.top);
-    } else if (data.bottom > data.bodyHight) {
-      // console.error('超出下面', data.bodyHight - data.bottom)
-      moveDistance.value = data.bodyHight - data.bottom;
-    } else {
-      // moveDistance.value = 0
+
+    if (!isFixed.value) {
+        if (moveTimer) {
+            clearTimeout(moveTimer)
+            moveTimer = null
+        }
+        moveTimer = setTimeout(() => {
+            let el = document.getElementById(`tagBox_${tag.sid}`)
+            if (!el) {
+                return
+            }
+            let data = getElemDis(el)
+            if (data.top < 50) {
+                moveDistance.value = Math.abs(50 - data.top)
+            } else if (data.bottom > data.bodyHight) {
+                moveDistance.value = data.bodyHight - data.bottom
+            } else {
+                // moveDistance.value = 0
+            }
+            clearTimeout(moveTimer)
+            moveTimer = null
+        }, 50)
     }
-    clearTimeout(moveTimer);
-    moveTimer = null;
-  }, 50);
 
-  if (tag.type == "audio" || tag.type == "video") {
-    musicPlayer.pause(true);
-  }
-  if (__fixed) {
-    return;
-  }
-  if (__close) {
-    return;
-  }
-  if (event.fixed) {
-    // store.commit('tag/setData', { flyClose: true, isFixed: true })
-    store.commit("tag/setData", { isFixed: true });
-    __fixed = true;
-  }
+    // if (tag.bgm) {
+    //     musicPlayer.pause(true)
+    // }
+    if (__fixed && isPlay.value && !event.fixed) {
+        return
+    }
+    if (__close) {
+        return
+    }
+    // if (showDetails.value) {
+    //     return
+    // }
+    if (event.fixed) {
+        store.commit('tag/setData', { showDetails: false }) //先清除上一个的详情状态
+        // store.commit('tag/setData', { flyClose: true, isFixed: true })
 
-  if (!getApp().config.mobile) {
-    // if (flying.value || isPlay.value) {
-    if (flying.value) {
-      return;
+        store.commit('tag/setData', { isFixed: true })
+        __fixed = true
+        nextTick(() => {
+            if (tours.value[partId.value].list[frameId.value].tagDetails) {
+                openDetails()
+            }
+        })
     }
-    // if (!enterVisible.value && !editPosition.value && !isEdit.value && !positionInfo.value && !hotData.value) {
-    if (
-      !enterVisible.value &&
-      !editPosition.value &&
-      !isEdit.value &&
-      !positionInfo.value
-    ) {
-      // Track("view-tag", {
-      //   eventType: "hover",
-      //   hover: 1,
-      // });
-      showInfo.value = true;
-      store.commit("tag/show", index);
 
-      store.commit("tag/setToggleIndex", index);
-      if (leaveId.value && leaveId.value != tag.sid) {
-        //聚焦后 移到其他热点取消fixed
-        store.commit("tag/setFixed", false);
-      }
+    if (!getApp().config.mobile) {
+        // if (flying.value || isPlay.value) {
+        if (flying.value) {
+            return
+        }
+        // if (!enterVisible.value && !editPosition.value && !isEdit.value && !positionInfo.value && !hotData.value) {
+        if (!enterVisible.value && !editPosition.value && !isEdit.value && !positionInfo.value) {
+            Track('view-tag', {
+                eventType: 'hover',
+                hover: 1,
+            })
+            showInfo.value = true
+            store.commit('tag/show', index)
+
+            store.commit('tag/setToggleIndex', index)
+            if (leaveId.value && leaveId.value != tag.sid && event.relatedTarget && !__fixed) {
+                //聚焦后 移到其他热点取消fixed
+                store.commit('tag/setFixed', false)
+            }
+        }
     }
-  }
-};
+}
 
 const onMouseLeave = (event, tag) => {
-  if (moveTimer) {
-    clearTimeout(moveTimer);
-    moveTimer = null;
-  }
-  moveDistance.value = 0;
-  if (event.unfixed) {
-    __fixed = false;
-    store.commit("tag/setData", { isFixed: false });
-  }
-  if (__fixed) {
-    return;
-  }
-
-  if (!getApp().config.mobile) {
-    if (flying.value) {
-      return;
+    if (showDetails.value) {
+        return
+    }
+    if (!isFixed.value) {
+        moveDistance.value = 0
+        if (moveTimer) {
+            clearTimeout(moveTimer)
+            moveTimer = null
+        }
+    }
+    if (event.unfixed) {
+        // store.commit('tag/setData', { isFixed: false })
+        // store.commit('tag/closeTag')
+        closeTag()
+    }
+    if (__fixed) {
+        return
     }
-    if (event.relatedTarget != null) {
-      __close = false;
 
-      // if (!isEdit.value) {
-      showInfo.value = false;
-      // }
-      store.commit("tag/setLeaveId", tag.sid);
-      if (
-        !enterVisible.value &&
-        !isFixed.value &&
-        !showMsg.value &&
-        !editPosition.value &&
-        !positionInfo.value &&
-        hotData.value
-      ) {
-        closeTag();
-      }
+    if (!getApp().config.mobile) {
+        if (flying.value) {
+            return
+        }
+
+        if (event.relatedTarget != null) {
+            __close = false
+
+            // if (!isEdit.value) {
+            showInfo.value = false
+            // }
+            store.commit('tag/setLeaveId', tag.sid)
+            if (!enterVisible.value && !isFixed.value && !showMsg.value && !editPosition.value && !positionInfo.value && hotData.value) {
+                closeTag()
+            }
+        }
     }
-  }
-};
+}
 
 const onClickHandler = () => {
-  // if (flying.value) {
-  //     return
-  // }
+    // if (flying.value) {
+    //     return
+    // }
 
-  if (!isEdit.value && !positionInfo.value && isFixed.value) {
-    closeTag();
-  }
-};
+    if (!isEdit.value && !positionInfo.value && isFixed.value) {
+        closeTag()
+    }
+}
 </script>
 <style lang="scss" scoped>
 .tag-title {
-  position: absolute;
-  left: 50%;
-  transform: translateX(-50%);
-  top: 100%;
-  max-width: 200%;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-  overflow: hidden;
-  text-align: center;
-  pointer-events: none;
-  text-shadow: 0 0 4px rgba(0, 0, 0, 0.4);
+    position: absolute;
+    left: 50%;
+    transform: translateX(-50%);
+    top: 100%;
+    max-width: 200%;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    text-align: center;
+    pointer-events: none;
+    text-shadow: 0 0 4px rgba(0, 0, 0, 0.4);
+    // visibility: hidden;
+    // &.visible {
+    //     visibility: visible;
+    // }
+}
+.point {
+    &.no-zoom {
+        animation: none !important;
+    }
+    .focus-icon {
+        width: 140%;
+        height: 140%;
+        // background: rgba(255, 255, 255, 0.3);
+        position: absolute;
+        left: 50%;
+        top: 50%;
+        transform: translate(-50%, -50%);
+        z-index: -1;
+        background-size: 100% 100%;
+    }
 }
 </style>

+ 9 - 2
packages/qjkankan-kankan-view/src/components/Tags/metas/image-view.vue

@@ -5,7 +5,8 @@
             <div class="swiper-wrapper">
                 <div class="swiper-slide" v-for="(i, index) in imageList">
                     <div class="swiper-zoom-container">
-                        <div :id="`vmRef_${index}`" class="swiper-zoom-target" :style="`background-image: url(${common.changeUrl(i.src)})`"></div>
+                        <div v-if="i.type == 'image'" :id="`vmRef_${index}`" class="swiper-zoom-target" :style="`background-image: url(${common.changeUrl(i.src)})`"></div>
+                        <video preload="auto" controlslist="nodownload" x5-video-player-type="h5" playsinline webkit-playsinline controls poster v-else :src="common.changeUrl(i.src)"></video>
                     </div>
                 </div>
             </div>
@@ -27,7 +28,7 @@ import common from '@/utils/common'
 const store = useStore()
 const hotData = computed(() => store.getters['tag/hotData'])
 const imageList = computed(() => {
-    let list = hotData.value.media.image
+    let list = hotData.value.media
 
     return list
 })
@@ -138,6 +139,12 @@ onMounted(() => {
                 //     height: 100%;
                 // }
             }
+            video {
+                height: auto;
+                width: 100%;
+                object-fit: contain;
+                touch-action: none;
+            }
         }
     }
 }

+ 393 - 0
packages/qjkankan-kankan-view/src/components/Tags/metas/mediaInfo.vue

@@ -0,0 +1,393 @@
+<!--  -->
+<template>
+    <div class="media-info" v-if="hotData">
+        <ui-input
+            type="file"
+            v-if="hotData.type == 'media' && !hotData.media.length"
+            :placeholder="customer[hotData.type].uploadPlace"
+            :disable="customer[hotData.type].upload"
+            :scale="customer[hotData.type].scale"
+            :accept="customer[hotData.type].accept"
+            :multiple="customer[hotData.type].multiple"
+            :maxSize="customer[hotData.type].maxSize"
+            :maxLen="customer[hotData.type].maxNum"
+            :othPlaceholder="customer[hotData.type].othPlaceholder"
+            @update:modelValue="data => hanlderFiles(data)"
+        >
+        </ui-input>
+
+        <div v-else class="swiper" ref="ref_swiper">
+            <div class="swiper-wrapper">
+                <div class="swiper-slide" v-for="(i, index) in hotData.media" :key="index">
+                    <ui-icon v-show="loading" class="loading-icon" type="_loading_"></ui-icon>
+                    <div class="media-content">
+                        <div class="media-item" :style="`background-image:url(${common.changeUrl(i.src)});`" v-if="i.type == 'image'"></div>
+                        <div class="media-item" v-else-if="i.type == 'video'">
+                            <!-- :muted="hotData.bgm && isPlay ? true : false" -->
+                            <ui-icon v-if="index != activeIndex - 1" class="loading-icon" type="_loading_"></ui-icon>
+
+                            <!-- 非第一个视频,ios设置autoplay true 是因为能显示controls,实则在ios手机的浏览器是不能自动播放的 -->
+                            <video
+                                ref="vm_video"
+                                v-show="!hideTagVideo"
+                                v-if="index == activeIndex - 1"
+                                @click.stop=""
+                                :id="`video_${index}`"
+                                :controls="true"
+                                :src="common.changeUrl(i.src)"
+                                preload="auto"
+                                controlslist="nodownload"
+                                x5-video-player-type="h5"
+                                playsinline
+                                :autoplay="index == 0 ? true : browser.detectIOS() ? true : false"
+                                webkit-playsinline
+                                @play="onVideoPlay"
+                            ></video>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div @click.stop="" class="swiper-button-next-diy">
+                <ui-icon type="right"></ui-icon>
+            </div>
+            <div @click.stop="" class="swiper-button-prev-diy">
+                <ui-icon type="left"></ui-icon>
+            </div>
+            <div v-if="editShow" class="del-btn" @click="delPic"><ui-icon type="del"></ui-icon></div>
+
+            <div class="continue" v-if="editShow">
+                <ui-input
+                    v-if="hotData.media.length < customer[hotData.type].maxNum"
+                    type="file"
+                    :placeholder="customer[hotData.type].uploadPlace"
+                    :accept="customer[hotData.type].accept"
+                    :multiple="customer[hotData.type].multiple"
+                    :maxSize="customer[hotData.type].maxSize"
+                    :maxLen="customer[hotData.type].maxNum"
+                    @update:modelValue="files => hanlderFiles(files)"
+                >
+                    <template v-slot:replace>
+                        <span class="continue-tips">{{ $t('tag.toolbox.continueAdd') }}</span>
+                        <span class="edit-pic-num">
+                            <span class="cur">{{ hotData.media.length }}</span>
+                            <span> / 9</span>
+                        </span>
+                    </template>
+                </ui-input>
+            </div>
+            <div class="continue" v-if="!editShow && mediaLength > 1">
+                <span class="pic-num">
+                    <span class="cur">{{ activeIndex }}</span>
+                    <span> / {{ mediaLength }}</span>
+                </span>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import common from '@/utils/common'
+import { blobToDataURL, getMime } from '@/utils/file'
+import { custom } from '../constant.js'
+import { reactive, toRefs, onBeforeMount, onMounted, onBeforeUnmount, onActivated, ref, defineProps, computed, watch, nextTick } from 'vue'
+import { getApp, useApp } from '@/app'
+import { useStore } from 'vuex'
+import browser from '@/utils/browser'
+import { Loading, Dialog } from '@/global_components'
+import { useMusicPlayer } from '@/utils/sound'
+const musicPlayer = useMusicPlayer()
+const store = useStore()
+const customer = custom()
+//组件形参
+const props = defineProps({
+    editShow: {
+        type: Boolean,
+        defeault: false,
+    },
+})
+const vm_video = ref(null)
+const loading = ref(true)
+const ref_swiper = ref(null)
+const isPlay = computed(() => store.getters['tour/isPlay'])
+const hideTagVideo = computed(() => store.getters['scene/hideTagVideo'])
+const hotData = computed(() => {
+    let data = store.getters['tag/hotData']
+
+    nextTick(() => {
+        if (data && data.media.length && data.media[0].type == 'video') {
+            // let video = document.getElementById('video_0')
+            if (browser.detectWeixin()) {
+                if (typeof parent.WeixinJSBridge !== 'undefined') {
+                    // setTimeout(() => {
+                    //     playVideo()
+                    // }, 300)
+
+                    WeixinJSBridge.invoke('getNetworkType', {}, function (e) {
+                        playVideo()
+                    })
+                } else {
+                    parent.document.addEventListener('WeixinJSBridgeReady', playVideo)
+                }
+            } else {
+                playVideo()
+            }
+        }
+    })
+
+    return data
+})
+const onVideoPlay = () => {
+    musicPlayer.pause(true)
+}
+const playVideo = () => {
+    // if (data && data.media.length && data.media[0].type == 'video') {
+
+    let video = document.getElementById('video_0')
+    if (video) {
+        video.muted = true
+        video.play()
+
+        setTimeout(() => {
+            video.muted = false
+        }, 300)
+    }
+    // }
+}
+const mediaLength = computed(() => {
+    return hotData?.value?.media.length || 0
+})
+watch(
+    () => mediaLength.value,
+    (val, old) => {
+        nextTick(() => {
+            if (props.editShow) {
+                return
+            }
+            initSwiper()
+        })
+    }
+)
+const delPic = () => {
+    let index = mySwiper.activeIndex
+    store.commit('tag/delMetas', { index, type: 'media' })
+    nextTick(() => {
+        initSwiper()
+    })
+}
+
+const hanlderFiles = files => {
+    let filesData = files[0].map(file => {
+        let obj = {}
+        obj.src = blobToDataURL(file)
+        obj.file = file
+        if (['jpg', 'png'].includes(getMime(file.name))) {
+            obj.type = 'image'
+        } else if (['mp4', 'mov'].includes(getMime(file.name))) {
+            obj.type = 'video'
+        }
+        return obj
+    })
+    hotData.value.media = hotData.value.media.concat(filesData)
+    nextTick(() => {
+        initSwiper()
+        nextTick(() => {
+            mySwiper.slideTo(hotData.value.media.length - 1)
+        })
+    })
+}
+const activeIndex = ref(1)
+let mySwiper = null
+const initSwiper = () => {
+    if (mySwiper) {
+        mySwiper.destroy()
+        mySwiper = null
+    }
+
+    if (!mySwiper) {
+        mySwiper = new Swiper(ref_swiper.value, {
+            on: {
+                init: function (swiper) {},
+                transitionStart: function (swiper) {},
+                transitionEnd: function (swiper) {
+                    activeIndex.value = swiper.activeIndex + 1
+                },
+
+                touchStart: function (swiper, event) {},
+            },
+            navigation: {
+                nextEl: '.swiper-button-next-diy',
+                prevEl: '.swiper-button-prev-diy',
+            },
+        })
+    }
+}
+
+onMounted(() => {
+    nextTick(() => {
+        if (hotData.value?.media.length) {
+            if (hotData.value?.media[0].type == 'video') {
+                loading.value = false
+            } else {
+                let image = new Image()
+                image.onload = () => {
+                    loading.value = false
+                }
+                image.src = common.changeUrl(hotData.value?.media[0].src)
+            }
+        }
+
+        initSwiper()
+    })
+})
+onBeforeUnmount(() => {
+    // let video = document.getElementById(`video_${activeIndex.value - 1}`)
+    // console.error(vm_video.value)
+    if (vm_video.value) {
+        vm_video.value.src = ''
+    }
+})
+onActivated(() => {})
+</script>
+<style lang="scss" scoped>
+.loading-icon {
+    color: var(--editor-main-color);
+    animation: rotate 2s infinite linear;
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    font-size: 30px;
+}
+@keyframes rotate {
+    0% {
+        transform: translate(-50%, -50%) rotate(0deg);
+    }
+    100% {
+        transform: translate(-50%, -50%) rotate(360deg);
+    }
+}
+.continue {
+    width: 100%;
+    height: 32px;
+    background: linear-gradient(180deg, rgba(0, 0, 0, 0.1) 0%, #000000 200%);
+    border-radius: 0px 0px 4px 4px;
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    z-index: 1;
+    pointer-events: none;
+    .ui-input {
+        width: 100%;
+    }
+    .continue-tips {
+        font-size: 12px;
+        margin-right: 5px;
+    }
+    .edit-pic-num {
+        // position: absolute;
+        // right: 10px;
+        font-size: 12px;
+        .cur {
+            color: var(--editor-main-color);
+        }
+    }
+    .pic-num {
+        position: absolute;
+        right: 10px;
+        top: 50%;
+        transform: translateY(-50%);
+        font-size: 12px;
+        .cur {
+            color: var(--editor-main-color);
+        }
+    }
+}
+
+.del-btn {
+    width: 24px;
+    height: 24px;
+    background: rgba(0, 0, 0, 0.6);
+    border-radius: 50%;
+    position: absolute;
+    cursor: pointer;
+    top: 10px;
+    right: 10px;
+    z-index: 10;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: #fff;
+}
+.swiper-button-disabled {
+    opacity: 0 !important;
+    pointer-events: none;
+}
+.swiper-wrapper {
+    z-index: -1;
+}
+.swiper-button-next-diy {
+    width: 32px;
+    height: 32px;
+    background: rgba(0, 0, 0, 0.2);
+    border-radius: 50%;
+    position: absolute;
+    cursor: pointer;
+    top: 50%;
+    transform: translateY(-50%);
+    z-index: 10;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    right: 5px;
+}
+.swiper-button-prev-diy {
+    width: 32px;
+    height: 32px;
+    background: rgba(0, 0, 0, 0.2);
+    border-radius: 50%;
+    position: absolute;
+    cursor: pointer;
+    top: 50%;
+    transform: translateY(-50%);
+    z-index: 10;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    left: 5px;
+}
+.media-info {
+    width: 100%;
+    height: 100%;
+    pointer-events: auto;
+
+    .swiper {
+        width: 100%;
+        height: 100%;
+        background: rgba(255, 255, 255, 0.1);
+        border-radius: 4px 4px 4px 4px;
+        border: 1px solid rgba(255, 255, 255, 0.2);
+        .media-content {
+            width: 100%;
+            height: 100%;
+            .media-item {
+                width: 100%;
+                height: 100%;
+                background-repeat: no-repeat;
+                background-position: center center;
+                background-size: contain;
+                position: relative;
+                video {
+                    width: 100%;
+                    height: 100%;
+                    object-fit: fill;
+                    cursor: pointer;
+                    // &::-webkit-media-controls {
+                    //     width: 100%;
+                    //     height: 100%;
+                    // }
+                }
+            }
+        }
+    }
+}
+</style>

+ 10 - 11
packages/qjkankan-kankan-view/src/components/Tags/metas/metas-image.vue

@@ -93,16 +93,16 @@ const emit = defineEmits(['close'])
 const props = defineProps({
     metasHeight: {
         type: Number,
-        default: null,
+        default: null
     },
     viewer: {
         type: Boolean,
-        default: false,
+        default: false
     },
     scale: {
         type: Boolean,
-        default: false,
-    },
+        default: false
+    }
 })
 const isEdit = computed(() => store.getters['tag/isEdit'])
 const hotData = computed(() => store.getters['tag/hotData'])
@@ -151,7 +151,7 @@ const setImageList = data => {
         } else {
             Dialog.toast({
                 type: 'error',
-                content: `${t('limit.maxLengthFile', { length: customer['image'].maxNum })}`,
+                content: `${t('limit.maxLengthFile', { length: customer['image'].maxNum })}`
             })
             break
         }
@@ -171,13 +171,13 @@ let result = { width: 0, height: 0 },
 
 const drag = image => {
     // 绑定 pointerdown
-    image.addEventListener('pointerdown', function (e) {
+    image.addEventListener('pointerdown', function(e) {
         isPointerdown = true
         image.setPointerCapture(e.pointerId)
         lastPointermove = { x: e.clientX, y: e.clientY }
     })
     // 绑定 pointermove
-    image.addEventListener('pointermove', function (e) {
+    image.addEventListener('pointermove', function(e) {
         if (isPointerdown) {
             const current = { x: e.clientX, y: e.clientY }
             diff.x = current.x - lastPointermove.x
@@ -192,13 +192,13 @@ const drag = image => {
         e.preventDefault()
     })
     // 绑定 pointerup
-    image.addEventListener('pointerup', function (e) {
+    image.addEventListener('pointerup', function(e) {
         if (isPointerdown) {
             isPointerdown = false
         }
     })
     // 绑定 pointercancel
-    image.addEventListener('pointercancel', function (e) {
+    image.addEventListener('pointercancel', function(e) {
         if (isPointerdown) {
             isPointerdown = false
         }
@@ -223,11 +223,10 @@ const zoomFun = e => {
         scale = _scale
     }
     // 目标元素是img说明鼠标在img上,以鼠标位置为缩放中心,否则默认以图片中心点为缩放中心
-    console.log(e.target.tagName)
     if (e.target.tagName === 'IMG') {
         const origin = {
             x: (ratio - 1) * result.width * 0.5,
-            y: (ratio - 1) * result.height * 0.5,
+            y: (ratio - 1) * result.height * 0.5
         }
         // 计算偏移量
         x -= (ratio - 1) * (e.clientX - x) - origin.x

+ 512 - 0
packages/qjkankan-kankan-view/src/components/Tags/metas/metas-media.vue

@@ -0,0 +1,512 @@
+<!--  -->
+<template>
+    <div class="pic-box" :class="{ show: viewer }" :style="metasHeight ? `height:${metasHeight}px;` : ''">
+        <div>
+            <div class="ctrl-btn left-btn" v-if="mediaNum != 0" @click.stop="chengeImgae('pre')">
+                <ui-icon type="left"></ui-icon>
+            </div>
+            <div class="ctrl-btn right-btn" v-if="mediaNum < mediaList.length - 1" @click.stop="chengeImgae('next')">
+                <ui-icon type="right"></ui-icon>
+            </div>
+        </div>
+        <div class="over-box" ref="containerRef">
+            <div v-show="!loading" class="media-list" :style="`transform:translateX(${-100 * mediaNum}%);`">
+                <div :style="`transform:translateX(${100 * index}%);`" class="media-item" v-for="(i, index) in mediaList">
+                    <img v-if="i.type == 'image'" :id="`domImg${index}`" :src="common.changeUrl(i.src)" alt="" />
+                    <video preload="auto" controlslist="nodownload" x5-video-player-type="h5" playsinline webkit-playsinline controls poster v-else :src="common.changeUrl(i.src)"></video>
+                </div>
+            </div>
+            <ui-icon v-show="loading" class="loading-icon" type="_loading_"></ui-icon>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { onMounted, nextTick, ref, computed, defineProps, defineEmits } from 'vue'
+import { useStore } from 'vuex'
+import common from '@/utils/common'
+import { custom } from '../constant.js'
+import { getApp, useApp } from '@/app'
+import { Dialog } from '@/global_components'
+import { useI18n } from '../../../i18n'
+const { t } = useI18n({ useScope: 'global' })
+const isMobile = ref(false)
+const customer = custom()
+
+const store = useStore()
+const emit = defineEmits(['close'])
+const props = defineProps({
+    metasHeight: {
+        type: Number,
+        default: null,
+    },
+    viewer: {
+        type: Boolean,
+        default: false,
+    },
+    scale: {
+        type: Boolean,
+        default: false,
+    },
+})
+const hotData = computed(() => store.getters['tag/hotData'])
+const mediaList = computed(() => {
+    return hotData?.value?.media
+})
+const loading = ref(true)
+const mediaNum = ref(0)
+
+const chengeImgae = type => {
+    if (type == 'pre') {
+        mediaNum.value--
+    } else {
+        mediaNum.value++
+    }
+
+    if (props.viewer && !isMobile.value) {
+        resetScale()
+    }
+}
+
+let result = { width: 0, height: 0 },
+    x,
+    y,
+    scale = 1,
+    minScale = 0.5,
+    maxScale = 4,
+    isPointerdown = false, // 按下标识
+    diff = { x: 0, y: 0 }, // 相对于上一次pointermove移动差值
+    lastPointermove = { x: 0, y: 0 }, // 用于计算diff
+    transform = { x: 0, y: 0 }
+
+const drag = image => {
+    // 绑定 pointerdown
+    image.addEventListener('pointerdown', function (e) {
+        isPointerdown = true
+        image.setPointerCapture(e.pointerId)
+        lastPointermove = { x: e.clientX, y: e.clientY }
+    })
+    // 绑定 pointermove
+    image.addEventListener('pointermove', function (e) {
+        if (isPointerdown) {
+            const current = { x: e.clientX, y: e.clientY }
+            diff.x = current.x - lastPointermove.x
+            diff.y = current.y - lastPointermove.y
+            lastPointermove = { x: current.x, y: current.y }
+            x += diff.x
+            y += diff.y
+
+            image.style.transform = 'translate3d(' + x + 'px, ' + y + 'px, 0) scale(' + scale + ')'
+        }
+        e.preventDefault()
+    })
+    // 绑定 pointerup
+    image.addEventListener('pointerup', function (e) {
+        if (isPointerdown) {
+            isPointerdown = false
+        }
+    })
+    // 绑定 pointercancel
+    image.addEventListener('pointercancel', function (e) {
+        if (isPointerdown) {
+            isPointerdown = false
+        }
+    })
+}
+const containerRef = ref(null)
+
+const zoomFun = e => {
+    let ratio = 1.1
+    // 缩小
+    if (e.deltaY > 0) {
+        ratio = 1 / 1.1
+    }
+    const _scale = scale * ratio
+    if (_scale > maxScale) {
+        ratio = maxScale / scale
+        scale = maxScale
+    } else if (_scale < minScale) {
+        ratio = minScale / scale
+        scale = minScale
+    } else {
+        scale = _scale
+    }
+    // 目标元素是img说明鼠标在img上,以鼠标位置为缩放中心,否则默认以图片中心点为缩放中心
+    if (e.target.tagName === 'IMG') {
+        const origin = {
+            x: (ratio - 1) * result.width * 0.5,
+            y: (ratio - 1) * result.height * 0.5,
+        }
+        // 计算偏移量
+        x -= (ratio - 1) * (e.clientX - x) - origin.x
+        y -= (ratio - 1) * (e.clientY - y) - origin.y
+    }
+    imgEle.style.transform = 'translate3d(' + x + 'px, ' + y + 'px, 0) scale(' + scale + ')'
+    // log.innerHTML = `x = ${x.toFixed(0)}<br>y = ${y.toFixed(0)}<br>scale = ${scale.toFixed(5)}`
+    e.preventDefault()
+}
+
+// 滚轮缩放
+const initWheelZoom = imgEle => {
+    containerRef.value.addEventListener('wheel', zoomFun)
+}
+const removeWheelZoom = () => {
+    containerRef.value.removeEventListener('wheel', zoomFun)
+}
+
+const resetScale = () => {
+    result = { width: 0, height: 0 }
+    x = null
+    y = null
+    scale = 1
+    minScale = 0.5
+    maxScale = 4
+    isPointerdown = false // 按下标识
+    diff = { x: 0, y: 0 } // 相对于上一次pointermove移动差值
+    lastPointermove = { x: 0, y: 0 } // 用于计算diff
+
+    nextTick(() => {
+        removeWheelZoom()
+        initScale()
+    })
+}
+let imgEle = null
+const initScale = () => {
+    imgEle = document.getElementById(`domImg${mediaNum.value}`)
+    if (imgEle) {
+        x = 0
+        y = 0
+        imgEle.style.transform = 'translate3d(' + x + 'px, ' + y + 'px, 0) scale(1)'
+
+        result.width = imgEle.width
+        result.height = imgEle.height
+
+        nextTick(() => {
+            // 拖拽查看
+            drag(imgEle)
+            // 滚轮缩放
+            initWheelZoom(imgEle)
+        })
+    }
+}
+onMounted(async () => {
+    const app = await useApp()
+    isMobile.value = app.config.mobile
+
+    nextTick(() => {
+        let img = new Image()
+        if (mediaList.value[0].type == 'video') {
+            loading.value = false
+        } else {
+            img.onload = () => {
+                loading.value = false
+                if (props.viewer && !isMobile.value) {
+                    //pc端缩放
+                    nextTick(() => {
+                        initScale()
+                    })
+                }
+            }
+            img.src = common.changeUrl(mediaList.value[0].src)
+        }
+    })
+})
+</script>
+<style lang="scss" scoped>
+.showPicBox {
+    width: 100%;
+    height: 100%;
+    position: fixed;
+    z-index: 10000;
+    background: rgb(24, 22, 22);
+    top: 0;
+    left: 0;
+    .close {
+        position: absolute;
+        top: 10px;
+        right: 10px;
+        width: 20px;
+        height: 20px;
+        z-index: 100;
+        color: #fff;
+        .iconfont {
+            font-size: 20px;
+        }
+    }
+    .loading {
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        transform: translate(-50%, -50%);
+    }
+    .imgbox {
+        width: 100%;
+        height: 100%;
+        background-repeat: no-repeat;
+        background-size: contain;
+        background-position: center center;
+        #eleImg {
+            // position: absolute;
+
+            // top: 50%;
+            // left: 50%;
+            // transform: translate(-50%, -50%);
+            margin: 0 auto;
+            display: block;
+            &.s {
+                height: 100%;
+                width: auto;
+            }
+            &.h {
+                height: auto;
+                width: 100%;
+            }
+        }
+    }
+}
+.del-btn {
+    width: 24px;
+    height: 24px;
+    background: rgba(0, 0, 0, 0.6);
+    border-radius: 50%;
+    position: absolute;
+    cursor: pointer;
+    top: 10px;
+    right: 10px;
+    z-index: 10;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+.loading-icon {
+    color: var(--editor-main-color);
+    animation: rotate 2s infinite linear;
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    font-size: 30px;
+}
+@keyframes rotate {
+    0% {
+        transform: translate(-50%, -50%) rotate(0deg);
+    }
+    100% {
+        transform: translate(-50%, -50%) rotate(360deg);
+    }
+}
+.pic-box {
+    width: 100%;
+    height: 100%;
+    position: absolute;
+    border-radius: 4px;
+    border: 1px solid rgba(255, 255, 255, 0.2);
+    top: 0;
+    left: 0;
+    z-index: 10;
+
+    .over-box {
+        width: 100%;
+        height: 100%;
+        overflow: hidden;
+    }
+    .continue {
+        width: 100%;
+        height: 32px;
+        background: linear-gradient(180deg, rgba(0, 0, 0, 0.1) 0%, #000000 200%);
+        border-radius: 0px 0px 4px 4px;
+        position: absolute;
+        bottom: 0;
+        left: 0;
+
+        .ui-input {
+            width: 100%;
+        }
+        .continue-tips {
+            font-size: 12px;
+            margin-right: 5px;
+        }
+        .edit-pic-num {
+            // position: absolute;
+            // right: 10px;
+            font-size: 12px;
+            .cur {
+                color: var(--editor-main-color);
+            }
+        }
+        .pic-num {
+            position: absolute;
+            right: 10px;
+            top: 50%;
+            transform: translateY(-50%);
+            font-size: 12px;
+            .cur {
+                color: var(--editor-main-color);
+            }
+        }
+    }
+
+    .ctrl-btn {
+        width: 32px;
+        height: 32px;
+        background: rgba(0, 0, 0, 0.2);
+        border-radius: 50%;
+        position: absolute;
+        cursor: pointer;
+        top: 50%;
+        transform: translateY(-50%);
+        z-index: 10;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        .iconfont {
+            font-size: 14px;
+        }
+        &.left-btn {
+            left: 5px;
+        }
+        &.right-btn {
+            right: 5px;
+        }
+    }
+    .media-list {
+        width: 100%;
+        height: 100%;
+        position: relative;
+        transition: all 0.3s linear;
+        .media-item {
+            width: 100%;
+            height: 100%;
+            // background: red;
+            position: absolute;
+            transform: translateX(0);
+            text-align: center;
+            background-repeat: no-repeat;
+            background-size: contain;
+            background-position: center;
+            overflow: hidden;
+            img {
+                height: 100%;
+                width: 100%;
+                object-fit: contain;
+                touch-action: none;
+            }
+            video {
+                height: 100%;
+                width: 100%;
+                object-fit: contain;
+                touch-action: none;
+            }
+        }
+    }
+    &.show {
+        .ctrl-btn {
+            width: 40px;
+            height: 80px;
+            background: rgba(0, 0, 0, 0.6);
+            .iconfont {
+                font-size: 20px;
+            }
+            &.left-btn {
+                left: 0px;
+                border-radius: 0 40px 40px 0;
+                .icon {
+                    margin-right: 5px;
+                }
+            }
+            &.right-btn {
+                right: 0px;
+                border-radius: 40px 0 0 40px;
+                .icon {
+                    margin-left: 8px;
+                }
+            }
+        }
+        .continue {
+            width: 76px;
+            height: 36px;
+            background: rgba(0, 0, 0, 0.6);
+            border-radius: 20px;
+            position: absolute;
+            bottom: -5%;
+            left: 50%;
+            transform: translateX(-50%);
+
+            .pic-num {
+                width: 76px;
+                height: 36px;
+                display: inline-block;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                font-size: 20px;
+                top: 50%;
+                left: 50%;
+                transform: translate(-50%, -50%);
+                span {
+                    display: flex;
+                    align-items: center;
+                    justify-content: center;
+                }
+            }
+        }
+    }
+}
+
+[is-mobile] {
+    .pic-box {
+        &.show {
+            .ctrl-btn {
+                width: 40px;
+                height: 80px;
+                background: rgba(0, 0, 0, 0.6);
+                .iconfont {
+                    font-size: 20px;
+                }
+                &.left-btn {
+                    left: 0px;
+                    border-radius: 0 40px 40px 0;
+                    .icon {
+                        margin-right: 5px;
+                    }
+                }
+                &.right-btn {
+                    right: 0px;
+                    border-radius: 40px 0 0 40px;
+                    .icon {
+                        margin-left: 8px;
+                    }
+                }
+            }
+            .continue {
+                width: 76px;
+                height: 36px;
+                background: rgba(0, 0, 0, 0.6);
+                border-radius: 20px;
+                position: absolute;
+                bottom: -6%;
+                left: 50%;
+                transform: translateX(-50%);
+
+                .pic-num {
+                    width: 76px;
+                    height: 36px;
+                    display: inline-block;
+                    display: flex;
+                    align-items: center;
+                    justify-content: center;
+                    font-size: 20px;
+                    top: 50%;
+                    left: 50%;
+                    transform: translate(-50%, -50%);
+                    span {
+                        display: flex;
+                        align-items: center;
+                        justify-content: center;
+                    }
+                }
+            }
+        }
+    }
+}
+</style>

+ 0 - 229
packages/qjkankan-kankan-view/src/components/Tags/metas/metas-video copy.vue

@@ -1,229 +0,0 @@
-<!--  -->
-<template>
-    <!-- <div class="video-box" :style="metasHeight ? `height:${metasHeight}px;` : ''"> -->
-    <div class="video-box">
-        <div v-if="isEdit" class="del-btn" @click="delVideo()">
-            <ui-icon type="del"></ui-icon>
-        </div>
-        <ui-icon v-show="loading" class="loading-icon" type="_loading_"></ui-icon>
-        <video
-            @error="filesError"
-            v-show="!loading"
-            id="video"
-            v-for="(i, index) in videoSrc"
-            style="display: block"
-            class="video-item"
-            preload="auto"
-            controlslist="nodownload"
-            x5-video-player-type="h5"
-            playsinline
-            webkit-playsinline
-            :controls="playsinline ? true : false"
-            :muted="playsinline ? false : true"
-            :autoplay="playsinline ? true : false"
-            :src="common.changeUrl(i.src)"
-        ></video>
-        <div class="play-layer" v-if="!playsinline" @click.stop="playLayerVideo">
-            <div class="play-btn"><ui-icon type="rinfo_play"></ui-icon></div>
-        </div>
-    </div>
-    <teleport to="body">
-        <div class="layer-video" v-if="showLayerVideo" @click="showLayerVideo = false">
-            <video
-                @error="filesError"
-                v-show="!loading"
-                id="layerVideo"
-                v-for="(i, index) in videoSrc"
-                style="display: block"
-                class="video-item"
-                preload="auto"
-                controlslist="nodownload"
-                x5-video-player-type="h5"
-                playsinline
-                webkit-playsinline
-                controls
-                autoplay
-                :src="common.changeUrl(i.src)"
-            ></video>
-        </div>
-    </teleport>
-</template>
-
-<script setup>
-import { reactive, toRefs, onBeforeMount, onMounted, nextTick, ref, computed, defineProps } from 'vue'
-import { useStore } from 'vuex'
-import { custom } from '../constant.js'
-import { getApp, useApp } from '@/app'
-import common from '@/utils/common'
-import { Dialog } from '@/global_components'
-import browser from '@/utils/browser'
-const videoNum = ref(0)
-const store = useStore()
-const type = ref('video')
-const controls = ref(false)
-const showLayerVideo = ref(false)
-const hotData = computed(() => store.getters['tag/hotData'])
-const props = defineProps({
-    controls: {
-        type: Boolean,
-        default: true,
-    },
-    metasHeight: {
-        type: Number,
-        default: null,
-    },
-})
-const loading = ref(true)
-const playsinline = computed(() => {
-    let status
-    if (browser.detectAndroidMobile()) {
-        if (browser.detectWeixin() || browser.detectWeixinMiniProgram()) {
-            status = true
-        } else {
-            status = false
-        }
-    } else {
-        status = true
-    }
-
-    return status
-})
-
-const isEdit = computed(() => store.getters['tag/isEdit'])
-const videoSrc = computed(() => {
-    return hotData.value.media.video
-})
-const delVideo = () => {
-    store.commit('tag/delMetas', { type: type.value, index: videoNum.value })
-}
-
-const initVideo = file => {
-    store.commit('tag/setVideo', file)
-}
-const filesError = file => {
-    loading.value = false
-    // Dialog.toast({
-    //     content: '视频文件加载失败',
-    //     type: 'warn',
-    // })
-}
-const playLayerVideo = () => {
-    showLayerVideo.value = true
-    nextTick(() => {
-        let layerVideo = document.getElementById('layerVideo')
-        layerVideo.play()
-    })
-}
-onMounted(() => {
-    nextTick(() => {
-        let myVideo = document.getElementById('video')
-
-        myVideo.oncanplay = function () {
-            loading.value = false
-            if (playsinline.value) {
-                myVideo.play()
-            }
-        }
-        // myVideo.addEventListener('play', () => {
-        //     myVideo.requestFullscreen()
-        //     console.log('play')
-        //     myVideo.muted = true
-        // })
-        // myVideo.addEventListener('pause', () => {
-        //     console.log('play')
-        //     myVideo.muted = true
-        // })
-    })
-})
-</script>
-<style lang="scss" scoped>
-.layer-video {
-    width: 100%;
-    height: 100%;
-    position: absolute;
-    top: 0;
-    left: 0;
-    background: rgba(0, 0, 0, 0.7);
-    backdrop-filter: blur(4px);
-    z-index: 10000;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    video {
-        max-width: 100%;
-        max-height: 100%;
-        object-fit: fill;
-    }
-}
-.del-btn {
-    width: 24px;
-    height: 24px;
-    background: rgba(0, 0, 0, 0.3);
-    border-radius: 50%;
-    position: absolute;
-    cursor: pointer;
-    top: 10px;
-    right: 10px;
-    z-index: 10;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-}
-.video-box {
-    width: 100%;
-    height: 100%;
-    overflow: hidden;
-    position: absolute;
-    border-radius: 4px;
-    border: 1px solid rgba(255, 255, 255, 0.2);
-    top: 0;
-    left: 0;
-    z-index: 10;
-    .play-layer {
-        width: 100%;
-        height: 100%;
-        position: absolute;
-        top: 0;
-        left: 0;
-        z-index: 1;
-        .play-btn {
-            width: 0.8rem;
-            height: 0.8rem;
-            font-size: 0.4267rem;
-            position: absolute;
-            z-index: 100;
-            top: 50%;
-            left: 50%;
-            transform: translate(-50%, -50%);
-            border-radius: 50%;
-            // border: 0.0267rem solid #fff;
-            background: rgba(0, 0, 0, 0.7);
-            display: flex;
-            align-items: center;
-            justify-content: center;
-        }
-    }
-    .loading-icon {
-        color: var(--editor-main-color);
-        animation: rotate 2s infinite linear;
-        position: absolute;
-        top: 50%;
-        left: 50%;
-        transform: translate(-50%, -50%);
-        font-size: 30px;
-    }
-    @keyframes rotate {
-        0% {
-            transform: translate(-50%, -50%) rotate(0deg);
-        }
-        100% {
-            transform: translate(-50%, -50%) rotate(360deg);
-        }
-    }
-    .video-item {
-        width: 100%;
-        height: 100%;
-        object-fit: contain;
-    }
-}
-</style>

+ 124 - 8
packages/qjkankan-kankan-view/src/components/Tags/metas/metas-video.vue

@@ -11,18 +11,42 @@
             v-show="!loading"
             id="video"
             v-for="(i, index) in videoSrc"
+            style="display: block"
             class="video-item"
-            x5-video-player-type="h5-page"
+            preload="auto"
             controlslist="nodownload"
-            disablepictureinpicture=""
-            webkit-playsinline=""
-            x-webkit-airplay=""
-            playsinline=""
-            :controls="controls"
-            autoplay
+            x5-video-player-type="h5"
+            playsinline
+            webkit-playsinline
+            :controls="playsinline ? true : false"
+            :muted="hasMusicPlay ? true : playsinline ? false : true"
+            :autoplay="hasMusicPlay ? true : playsinline ? true : false"
             :src="common.changeUrl(i.src)"
         ></video>
+        <div class="play-layer" v-if="!playsinline" @click.stop="playLayerVideo">
+            <div class="play-btn"><ui-icon type="rinfo_play"></ui-icon></div>
+        </div>
     </div>
+    <teleport to="body">
+        <div class="layer-video" v-if="showLayerVideo" @click="showLayerVideo = false">
+            <video
+                @error="filesError"
+                v-show="!loading"
+                id="layerVideo"
+                v-for="(i, index) in videoSrc"
+                style="display: block"
+                class="video-item"
+                preload="auto"
+                controlslist="nodownload"
+                x5-video-player-type="h5"
+                playsinline
+                webkit-playsinline
+                controls
+                autoplay
+                :src="common.changeUrl(i.src)"
+            ></video>
+        </div>
+    </teleport>
 </template>
 
 <script setup>
@@ -32,10 +56,17 @@ import { custom } from '../constant.js'
 import { getApp, useApp } from '@/app'
 import common from '@/utils/common'
 import { Dialog } from '@/global_components'
+import browser from '@/utils/browser'
 const videoNum = ref(0)
 const store = useStore()
 const type = ref('video')
+const controls = ref(false)
+const showLayerVideo = ref(false)
 const hotData = computed(() => store.getters['tag/hotData'])
+const isPlay = computed(() => store.getters['tour/isPlay'])
+const tours = computed(() => store.getters['tour/tours'])
+const partId = computed(() => store.getters['tour/partId'])
+
 const props = defineProps({
     controls: {
         type: Boolean,
@@ -47,6 +78,38 @@ const props = defineProps({
     },
 })
 const loading = ref(true)
+const playsinline = computed(() => {
+    let status
+    if (browser.detectAndroidMobile()) {
+        if (browser.detectWeixin() || browser.detectWeixinMiniProgram()) {
+            status = true
+        } else {
+            status = false
+        }
+    } else {
+        // if (isPlay.value && tours.value[partId.value].music) {
+        //     if (browser.detectWeixin() || browser.detectWeixinMiniProgram()) {
+        //         status = true
+        //     } else {
+        //         status = false
+        //     }
+        // } else {
+        status = true
+        // }
+    }
+
+    return status
+})
+
+const hasMusicPlay = computed(() => {
+    let status
+    if (isPlay.value && tours.value[partId.value].music) {
+        status = true
+    } else {
+        status = false
+    }
+    return status
+})
 
 const isEdit = computed(() => store.getters['tag/isEdit'])
 const videoSrc = computed(() => {
@@ -66,17 +129,46 @@ const filesError = file => {
     //     type: 'warn',
     // })
 }
-
+const playLayerVideo = () => {
+    showLayerVideo.value = true
+    nextTick(() => {
+        let layerVideo = document.getElementById('layerVideo')
+        layerVideo.play()
+    })
+}
 onMounted(() => {
     nextTick(() => {
         let myVideo = document.getElementById('video')
         myVideo.oncanplay = function () {
             loading.value = false
+            if (playsinline.value) {
+                //修复bugID http://192.168.0.21/index.php?m=bug&f=view&bugID=42375
+                // myVideo.play()
+            }
         }
     })
 })
+onBeforeMount(() => {})
 </script>
 <style lang="scss" scoped>
+.layer-video {
+    width: 100%;
+    height: 100%;
+    position: absolute;
+    top: 0;
+    left: 0;
+    background: rgba(0, 0, 0, 0.7);
+    backdrop-filter: blur(4px);
+    z-index: 10000;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    video {
+        max-width: 100%;
+        max-height: 100%;
+        object-fit: fill;
+    }
+}
 .del-btn {
     width: 24px;
     height: 24px;
@@ -101,6 +193,30 @@ onMounted(() => {
     top: 0;
     left: 0;
     z-index: 10;
+    .play-layer {
+        width: 100%;
+        height: 100%;
+        position: absolute;
+        top: 0;
+        left: 0;
+        z-index: 1;
+        .play-btn {
+            width: 0.8rem;
+            height: 0.8rem;
+            font-size: 0.4267rem;
+            position: absolute;
+            z-index: 100;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+            border-radius: 50%;
+            // border: 0.0267rem solid #fff;
+            background: rgba(0, 0, 0, 0.7);
+            display: flex;
+            align-items: center;
+            justify-content: center;
+        }
+    }
     .loading-icon {
         color: var(--editor-main-color);
         animation: rotate 2s infinite linear;

+ 1 - 1
packages/qjkankan-kankan-view/src/components/Tags/metas/metas-web.vue

@@ -28,7 +28,7 @@ const store = useStore()
 const linkNum = ref(0)
 const hotData = computed(() => store.getters['tag/hotData'])
 const link = computed(() => {
-    return hotData.value.media.link
+    return hotData.value.media
 })
 const props = defineProps({
     metasHeight: {

+ 0 - 274
packages/qjkankan-kankan-view/src/components/Tags/old.vue

@@ -1,274 +0,0 @@
-<template>
-  <teleport :to="tags$" v-if="tags$">
-    <template v-for="(tag, index) in tags">
-      <div
-        :tag-sid="tag.sid"
-        @mouseleave="onMouseLeave($event, tag)"
-        :style="{ left: tag.x + 'px', top: tag.y + 'px' }"
-        :class="{ visible: tag.visible }"
-      >
-        <span
-          @click.stop="goTag(tag, index)"
-          class="point zoom"
-          @mouseenter.stop="onMouseEnter(tag, index)"
-          :style="{ 'background-image': 'url(' + getUrl(tag.icon) + ')' }"
-        ></span>
-        <div class="content">
-          <div
-            class="trans"
-            :class="{
-              active:
-                (isFixed && hotData && tag.sid == hotData.sid) ||
-                (showInfo && hotData && tag.sid == hotData.sid),
-            }"
-          >
-            <template v-if="hotData && tag.sid == hotData.sid && !showMsg">
-              <div class="arrow" :id="`arrow_${tag.sid}`">
-                <ui-icon
-                  @click.stop="closeTag"
-                  v-if="getApp().config.mobile"
-                  type="close"
-                ></ui-icon>
-              </div>
-              <ShowTag
-                @click.stop=""
-                v-if="!isEdit && hotData"
-                @open="openInfo"
-              />
-            </template>
-          </div>
-        </div>
-        <TagView
-          @click.stop=""
-          v-if="showMsg && toggleIndex == index"
-          @close="closeInfo"
-        />
-      </div>
-    </template>
-  </teleport>
-</template>
-
-<script setup>
-import {
-  ref,
-  onMounted,
-  computed,
-  watch,
-  watchEffect,
-  onActivated,
-  onDeactivated,
-  getCurrentInstance,
-  nextTick,
-} from "vue";
-import { getApp, useApp } from "@/app";
-import { useStore } from "vuex";
-import common from "../../utils/common";
-import TagView from "./tag-view.vue";
-import ShowTag from "./show-tag.vue";
-import { useRoute } from "vue-router";
-import { useMusicPlayer } from "@/utils/sound";
-const musicPlayer = useMusicPlayer();
-let init = true;
-const hotData = computed(() => {
-  let data = store.getters["tag/hotData"];
-  if (!data) {
-    // musicPlayer.play()
-    // musicPlayer.pause(true)
-    // if (!getApp().Scene.isCurrentPanoHasVideo) {
-    //     console.log('resume')
-    //     console.log(init)
-    //     musicPlayer.resume()
-    // }
-  }
-  return data;
-});
-const isPlay = computed(() => store.getters["tour/isPlay"]);
-const router = computed(() => store.getters["router"]);
-const flying = computed(() => store.getters["flying"]);
-const leaveId = computed(() => store.getters["tag/leaveId"]);
-const isEdit = computed(() => store.getters["tag/isEdit"]);
-const isFixed = computed(() => store.getters["tag/isFixed"]);
-const enterVisible = computed(() => store.getters["tag/enterVisible"]);
-const editPosition = computed(() => store.getters["tag/editPosition"]);
-const toggleIndex = computed(() => store.getters["tag/toggleIndex"]);
-const isClick = computed(() => store.getters["tag/isClick"]);
-const editModule = computed(() => store.getters["editModule"]);
-const positionInfo = computed(() => store.getters["tag/positionInfo"]);
-const store = useStore();
-const tags$ = ref(null);
-const tags = computed(() => {
-  return store.getters["tag/tags"] || [];
-});
-
-watch(
-  () => router.value.name,
-  (val, old) => {
-    // console.log(val)
-    if (val !== "tag") {
-      store.commit("tag/setFixed", false);
-    }
-  }
-);
-
-const showInfo = ref(false);
-const showMsg = ref(false);
-// const toggleIndex = ref(null)
-const openInfo = () => {
-  showMsg.value = true;
-  store.commit("tag/setFixed", false);
-  showInfo.value = false;
-};
-const closeInfo = () => {
-  showMsg.value = false;
-  if (isClick.value) {
-    //只有点击定位的才恢复显示
-    store.commit("tag/show", toggleIndex.value);
-    store.commit("tag/setFixed", true);
-    // showInfo.value = true
-    showInfo.value = false;
-  }
-  // store.commit('tag/setClick', false)
-};
-const closeTag = async () => {
-  const app = getApp();
-  const player = await app.TourManager.player;
-  //关闭热点面板时候,继续播放之前暂停的音频
-  if (!app.Scene.isCurrentPanoHasVideo && !player.isPlaying) {
-    if (
-      hotData.value &&
-      (hotData.value.type == "audio" || hotData.value.type == "video")
-    ) {
-      // console.log('resume')
-      window.parent.postMessage(
-        {
-          source: "qjkankan",
-          event: "toggleBgmStatus",
-          params: {
-            status: true,
-          },
-        },
-        "*"
-      );
-    }
-  }
-
-  store.commit("tag/setFixed", false);
-  store.commit("tag/closeTag");
-  store.commit("tag/setClick", false);
-  showInfo.value = false;
-};
-const goTag = async (item, index) => {
-  let player = await getApp().TourManager.player;
-  if (isPlay.value) {
-    player.pause();
-    store.commit("tour/setData", { isPlay: false });
-  }
-  if (flying.value) {
-    return;
-  }
-  if (
-    isFixed.value &&
-    !isEdit.value &&
-    hotData.value.sid == item.sid &&
-    !positionInfo.value
-  ) {
-    closeTag();
-  } else {
-    if (!enterVisible.value && !editPosition.value) {
-      if (!isEdit.value && !positionInfo.value) {
-        store.commit("tag/show", index);
-        store.commit("tag/setFixed", true);
-        showInfo.value = true;
-        store.commit("tag/setToggleIndex", index);
-        store.commit("tag/gotoTag", item);
-      }
-      store.commit("tag/setClick", true);
-    } else {
-      //热点可视操作
-      getApp().TagManager.edit.setTagVisi(item.sid);
-    }
-  }
-};
-
-onMounted(async () => {
-  const app = await useApp();
-  await app.TagManager.tag();
-  init = false;
-
-  tags$.value = "[xui_tags]";
-  app.TagManager.updatePosition(tags.value);
-  if (app.config.mobile) {
-    nextTick(() => {
-      // let player = document.querySelector('.player')
-      // player.addEventListener('touchstart', onClickHandler)
-    });
-  } else {
-    window.addEventListener("click", onClickHandler);
-  }
-});
-const getUrl = (icon) => {
-  let url =
-    icon == "" || !icon
-      ? getApp().resource.getAppURL("images/tag_icon_default.svg")
-      : icon;
-
-  return common.changeUrl(url);
-};
-const onMouseEnter = (tag, index) => {
-  if (!getApp().config.mobile) {
-    if (flying.value || isPlay.value) {
-      return;
-    }
-    if (
-      !enterVisible.value &&
-      !editPosition.value &&
-      !isEdit.value &&
-      !positionInfo.value
-    ) {
-      // console.log('onMouseEnter')
-
-      showInfo.value = true;
-      store.commit("tag/show", index);
-
-      store.commit("tag/setToggleIndex", index);
-      if (leaveId.value != tag.sid) {
-        //聚焦后 移到其他热点取消fixed
-        store.commit("tag/setFixed", false);
-      }
-    }
-  }
-};
-
-const onMouseLeave = (event, tag) => {
-  if (!getApp().config.mobile) {
-    if (flying.value) {
-      return;
-    }
-    if (event.relatedTarget != null) {
-      // if (!isEdit.value) {
-      showInfo.value = false;
-      // }
-      store.commit("tag/setLeaveId", tag.sid);
-      if (
-        !enterVisible.value &&
-        !isFixed.value &&
-        !showMsg.value &&
-        !editPosition.value &&
-        !positionInfo.value
-      ) {
-        closeTag();
-      }
-    }
-  }
-};
-
-const onClickHandler = () => {
-  // if (flying.value) {
-  //     return
-  // }
-
-  if (!isEdit.value && !positionInfo.value && isFixed.value) {
-    closeTag();
-  }
-};
-</script>

File diff suppressed because it is too large
+ 545 - 622
packages/qjkankan-kankan-view/src/components/Tags/share/entry.vue


File diff suppressed because it is too large
+ 569 - 478
packages/qjkankan-kankan-view/src/components/Tags/show-tag.vue


+ 609 - 0
packages/qjkankan-kankan-view/src/components/Tags/tag-details.vue

@@ -0,0 +1,609 @@
+<!--  -->
+<template>
+    <!-- <teleport to=".ui-editor-main"> -->
+    <teleport to="body">
+        <div class="tag-details" :class="{ showTours }" v-if="hotData">
+            <div class="details-header-mobile" v-if="getApp().config.mobile">
+                <div>
+                    <ui-icon v-if="!showBgm && hotData?.bgm" class="loading-icon" type="_loading_"></ui-icon>
+                    <ui-audio v-show="showBgm" v-if="hotData?.bgm" class="audio" @loaded="audioOnLoaded" ref="audio" :src="common.changeUrl(hotData?.bgm?.src)"></ui-audio>
+                </div>
+
+                <div class="contorl-box">
+                    <ui-icon v-if="controls.showTagshare && !browser.detectApp()" type="share" @click="openShare"></ui-icon>
+
+                    <ui-icon type="close" :tip="$t('common.close')" @click.stop="close"></ui-icon>
+                </div>
+            </div>
+            <template v-else>
+                <div class="header-details">
+                    <div class="audio-icon-box">
+                        <ui-icon v-if="!showBgm && hotData?.bgm" class="loading-icon" type="_loading_"></ui-icon>
+                        <ui-audio
+                            @loaded="audioOnLoaded"
+                            v-show="showBgm"
+                            v-if="!getApp().config.mobile && hotData?.bgm"
+                            class="audio"
+                            ref="audio"
+                            :src="common.changeUrl(hotData?.bgm?.src)"
+                        ></ui-audio>
+                    </div>
+                    <div class="pc-share">
+                        <ui-icon v-if="controls.showTagshare && !browser.detectApp()" type="share" @mousemove="openShare(true)" @mouseleave="openShare(false)"></ui-icon>
+
+                        <transition appear name="custom-classes-transition" enter-active-class="animated animate__fadeIn short faster" leave-active-class="animated animate__fadeOut short faster">
+                            <div @click.stop v-show="shareCode" class="share-code" @mousemove="openShare(true)" @mouseleave="openShare(false)">
+                                <!-- <div class="share-code"> -->
+                                <p class="share-titie">{{ $t('share.shareTag') }}</p>
+                                <canvas id="qrcode-share-tag"></canvas>
+                                <div class="share-line"></div>
+                                <div class="share-copyBtn">
+                                    <div ref="copy$" :data-clipboard-text="LinkUrl">
+                                        <ui-icon type="copy"></ui-icon> <span>{{ $t('share.copyLink') }}</span>
+                                    </div>
+                                </div>
+                                <div class="hover-box"></div>
+                            </div>
+                        </transition>
+                    </div>
+                    <ui-icon class="close-btn" type="close" @click.stop="close"></ui-icon>
+                </div>
+            </template>
+
+            <div class="tag-msg">
+                <div class="tag-title">{{ hotData.title }}</div>
+
+                <div class="tag-metas" @click.stop="open" :class="{ mask: hotData.type == 'link', nocursor: hotData.type == 'video' }" v-if="hotData.media.length">
+                    <!-- <metasImage v-if="hotData.type == 'image'" />
+                    <metasVideo :view="true" v-if="hotData.type == 'video'" /> -->
+                    <mediaInfo v-if="hotData.type == 'media'" />
+                    <metasWeb v-if="hotData.type == 'link'" />
+                </div>
+                <div class="tag-desc table-box" v-if="Array.isArray(hotData?.content)">
+                    <table cellspacing="0">
+                        <tr v-for="(i, index) in hotData?.content">
+                            <td colspan="3">
+                                <span>{{ i.title }}</span>
+                            </td>
+                            <td colspan="7">
+                                <span v-html="i.description.replace(/\n/g, '<br>')"></span>
+                                <!-- <span>
+                                {{ i.description }}
+                            </span> -->
+                            </td>
+                        </tr>
+                    </table>
+                </div>
+                <div class="tag-desc" v-if="!Array.isArray(hotData?.content)" v-html="hotData.content"></div>
+                <!-- <div class="msg-bottom">
+                    <div>
+                        <ui-audio v-if="!getApp().config.mobile && hotData.type == 'audio' && audioInfo.length > 0" class="audio" ref="audio" :src="common.changeUrl(audioInfo[0].src)"></ui-audio>
+                    </div>
+                    <div class="share-box">
+                      
+                    </div>
+                </div> -->
+            </div>
+            <div v-if="isPlay" class="details-mask"></div>
+        </div>
+    </teleport>
+</template>
+
+<script setup>
+import { reactive, ref, toRefs, onBeforeMount, onMounted, computed, defineEmits, nextTick, watchEffect } from 'vue'
+import { getApp, useApp } from '@/app'
+import metasImage from './metas/metas-image'
+import metasVideo from './metas/metas-video'
+import mediaInfo from './metas/mediaInfo'
+import metasWeb from './metas/metas-web'
+import { useStore } from 'vuex'
+import common from '@/utils/common'
+import browser from '@/utils/browser'
+import { useMusicPlayer } from '@/utils/sound'
+import { Dialog } from '@/global_components'
+import QRCode from 'qrcode'
+import ClipboardJS from 'clipboard'
+import { useI18n, getLocale } from '@/i18n'
+const { t } = useI18n({ useScope: 'global' })
+
+const musicPlayer = useMusicPlayer()
+const store = useStore()
+const emit = defineEmits(['open', 'close'])
+const toolbox = computed(() => store.getters['toolbox'])
+const router = computed(() => store.getters['router'])
+const tours = computed(() => store.getters['tour/tours'])
+const isPlay = computed(() => store.getters['tour/isPlay'])
+const showTours = computed(() => store.getters['tour/showTours'])
+const controls = computed(() => store.getters['scene/metadata'].controls || {})
+const partId = computed(() => store.getters['tour/partId'])
+const hotData = computed(() => {
+    let data = store.getters['tag/hotData']
+    //当热点有背景音乐或者第一个媒体文件是视频的时候,即停止播放场景音乐
+    if (data?.bgm || (data?.media.length && data?.media[0].type == 'video')) {
+        musicPlayer.pause(true)
+    } else {
+        if (!isPlay.value) {
+            musicPlayer.resume()
+        }
+    }
+    //当第一个媒体文件是视频且存在热点背景音乐的时候,即停止播放热点背景音乐
+    if (data && data.bgm && data.media.length && data.media[0].type == 'video') {
+        if (audio.value) {
+            audio.value.pause()
+        }
+    }
+    return data
+})
+const canPlay = computed(() => {
+    let status = true
+    // if (isPlay.value && tours.value[partId.value].music) {
+    //     status = false
+    // } else {
+    //     status = true
+    // }
+    if (hotData.value && hotData.value.bgm && hotData.value.media.length && hotData.value.media[0].type == 'video') {
+        status = false
+    }
+    return status
+})
+const showBgm = ref(false)
+const audioOnLoaded = () => {
+    // console.error('audioOnLoaded')
+    showBgm.value = true
+}
+const showShareEntry = ref(false)
+const closeShare = () => {
+    showShareEntry.value = false
+}
+const audio = ref(null)
+watchEffect(() => {
+    if (audio.value) {
+        if (canPlay.value) {
+            audio.value.play()
+        } else {
+            audio.value.pause()
+        }
+    }
+})
+const LinkUrl = computed(() => {
+    let url
+    if (browser.hasURLParam('t_id')) {
+        url = browser.changeURLArg(window.location.href, 't_id', hotData.value.sid)
+    } else {
+        url = window.location.href + `&t_id=${hotData.value.sid}`
+    }
+    return url
+})
+
+let timer = null
+const copy$ = ref(null)
+const shareCode = ref(false)
+const initQrcode = () => {
+    var canvas = document.getElementById('qrcode-share-tag')
+    let url = LinkUrl.value
+    QRCode.toCanvas(canvas, url, function (error) {
+        if (error) {
+            return
+        }
+    })
+}
+const openShare = status => {
+    nextTick(() => {
+        if (getApp().config.mobile) {
+            showShareEntry.value = true
+        } else {
+            initQrcode()
+            if (controls.value.showTagshare && !browser.detectApp()) {
+                new ClipboardJS(copy$.value).on('success', function (e) {
+                    e.clearSelection()
+                    Dialog.toast({ content: t('toast.copySuccess'), type: 'success' })
+                })
+            }
+            if (!status) {
+                timer = setTimeout(() => {
+                    shareCode.value = status
+                    clearTimeout(timer)
+                    timer = null
+                }, 0)
+            } else {
+                clearTimeout(timer)
+                timer = null
+                shareCode.value = status
+            }
+        }
+    })
+}
+const audioInfo = computed(() => {
+    return hotData.value.media.audio
+})
+const close = async () => {
+    const app = getApp()
+    const player = await app.TourManager.player
+    //关闭热点面板时候,继续播放之前暂停的音频
+    if (!app.Scene.isCurrentPanoHasVideo && !player.isPlaying) {
+        // if (hotData.value && (hotData.value.bgm || (hotData.value.media.length && hotData.value.media[0].type == 'video'))) {
+        if (!isPlay.value) {
+            musicPlayer.resume()
+        }
+        // }
+    }
+    store.commit('tag/setData', { showDetails: false, isClick: false })
+    store.commit('tag/closeTag')
+}
+const open = () => {
+    if (hotData.value.type != 'video') {
+        emit('open')
+    }
+}
+</script>
+<style lang="scss" scoped>
+.header-details {
+    width: 100%;
+    height: 60px;
+    position: absolute;
+    left: 0;
+    top: 0;
+    .pc-share {
+        font-size: 20px;
+        cursor: pointer;
+        color: rgba(255, 255, 255, 0.7);
+        position: absolute;
+        top: 22px;
+        right: 62px;
+        position: absolute;
+        > .iconfont {
+            &:hover {
+                color: #fff;
+            }
+        }
+    }
+    .audio-icon-box {
+        position: absolute;
+        left: 20px;
+        top: 22px;
+        height: 20px;
+        width: 50px;
+        .ui-audio {
+            position: absolute;
+            left: 0;
+            bottom: 0;
+        }
+        .loading-icon {
+            color: var(--editor-main-color) !important;
+            animation: rotate 2s infinite linear;
+            position: absolute;
+            top: -2px;
+            left: -2px;
+            // transform: translate(-50%, -50%);
+            font-size: 24px;
+        }
+        @keyframes rotate {
+            0% {
+                transform: rotate(0deg);
+            }
+            100% {
+                transform: rotate(360deg);
+            }
+        }
+    }
+}
+
+.share-code {
+    width: 168px;
+    // height: 194px;
+    height: 241px;
+    background: rgba(27, 27, 28, 0.9);
+    box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25), inset 0px 0px 0px 2px rgba(255, 255, 255, 0.1);
+    border-radius: 4px;
+    opacity: 1;
+    border: 1px solid #000000;
+    position: absolute;
+    z-index: 10;
+    pointer-events: auto;
+    // right: -5px;
+    // top: -180px;
+    right: 30px;
+    top: -9px;
+    padding: 20px;
+    box-sizing: border-box;
+    z-index: 1000;
+    cursor: auto;
+    .hover-box {
+        // position: absolute;
+        // width: 36px;
+        // height: 100%;
+        // z-index: 10;
+        // left: -32px;
+        // top: 0;
+        position: absolute;
+        width: 44px;
+        height: 100%;
+        z-index: 10;
+        left: -38px;
+        top: 0;
+    }
+    &::after {
+        content: '';
+        position: absolute;
+        left: 1px;
+        right: 1px;
+        bottom: 1px;
+        top: 1px;
+        border: 1px solid rgba(255, 255, 255, 0.1);
+        border-radius: 4px;
+        z-index: 0;
+        pointer-events: none;
+    }
+
+    &::before {
+        position: absolute;
+        content: '';
+        // left: 80px;
+        // bottom: -10px;
+        right: -10px;
+        top: 15px;
+        width: 0;
+        height: 0;
+        // border-left: 6px solid transparent;
+        // border-right: 6px solid transparent;
+        // border-top: 10px solid rgba(0, 0, 0, 0.8);
+
+        border-top: 6px solid transparent;
+        border-bottom: 6px solid transparent;
+        border-left: 10px solid rgba(0, 0, 0, 0.8);
+    }
+    canvas {
+        width: 128px !important;
+        height: 128px !important;
+        margin-bottom: 10px;
+    }
+    .share-copyBtn {
+        color: rgba(255, 255, 255, 0.7);
+        font-size: 14px;
+        margin-top: 14px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        > div {
+            cursor: pointer;
+        }
+        &:hover {
+            color: #fff;
+        }
+        .iconfont {
+            margin-right: 5px;
+        }
+    }
+    .share-line {
+        width: 128px;
+        height: 1px;
+        background: rgba(255, 255, 255, 0.16);
+    }
+    p.share-titie {
+        font-size: 12px;
+        color: rgba(255, 255, 255, 0.5);
+        text-align: center;
+        width: 120%;
+        margin-left: -10%;
+        margin-bottom: 10px;
+    }
+}
+.tag-details {
+    width: 400px;
+    height: 100%;
+    padding: 60px 0 20px 0;
+    position: fixed;
+    right: 0;
+    top: 0;
+    background-color: var(--editor-toolbox-back);
+    backdrop-filter: blur(4px);
+    z-index: 10001;
+    color: #fff;
+    &::after {
+        position: absolute;
+        content: '';
+        top: 0;
+        bottom: 0;
+        left: 0;
+        border-right: solid 1px rgba(255, 255, 255, 0.16);
+        border-left: solid 1px #000;
+    }
+
+    &.showTours {
+        // height: calc(100% - 120px);
+    }
+    // &::after {
+    //     position: absolute;
+    //     content: '';
+    //     top: 0;
+    //     bottom: 0;
+    //     left: 0;
+    //     border-right: solid 1px rgba(255, 255, 255, 0.16);
+    //     border-left: solid 1px #000;
+    // }
+    .close-btn {
+        font-size: 20px;
+        cursor: pointer;
+        color: rgba(255, 255, 255, 0.7);
+        position: absolute;
+        top: 22px;
+        right: 22px;
+        &:hover {
+            color: #fff;
+        }
+    }
+    .tag-msg {
+        width: 100%;
+        height: 100%;
+        overflow-y: auto;
+        padding: 0 22px 0 18px;
+        .msg-bottom {
+            width: 100%;
+            height: 20px;
+            position: absolute;
+            bottom: 30px;
+            left: 0;
+            padding: 0 22px 0 18px;
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            .share-box {
+                span {
+                    height: 18px;
+                    font-size: 14px;
+                    font-weight: bold;
+                }
+            }
+        }
+        .tag-title {
+            font-size: 16px;
+            line-height: 24px;
+            font-weight: bold;
+            word-break: break-all;
+        }
+        .tag-desc {
+            font-size: 14px;
+            font-weight: bold;
+            color: rgba(255, 255, 255, 0.7);
+            line-height: normal;
+            text-align: justify;
+            word-break: break-all;
+            margin-top: 20px;
+            &.table-box {
+                table {
+                    width: 100%;
+                    border-collapse: collapse;
+                    table-layout: fixed; //td均分
+
+                    tr {
+                        height: 34px;
+                    }
+
+                    td {
+                        border: 1px solid #4d4d4d;
+                        padding: 0 10px;
+                        line-height: 32px;
+                        span {
+                            display: block;
+                            font-size: 12px;
+                            line-height: 18px;
+                            padding: 8px 0;
+                        }
+                        &:first-of-type {
+                            span {
+                                // width: 100px;
+                                word-break: break-all;
+                            }
+                        }
+                        &:last-of-type {
+                            span {
+                                // width: 200px;
+                                word-break: break-all;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        .tag-metas {
+            width: 100%;
+            height: 224px;
+            background: rgba(255, 255, 255, 0.1);
+            border-radius: 4px;
+            overflow: hidden;
+            position: relative;
+            cursor: -webkit-zoom-in;
+            margin: 20px 0;
+            &.nocursor {
+                cursor: auto;
+            }
+            &.mask {
+                &::after {
+                    content: '';
+                    position: absolute;
+                    top: 0;
+                    left: 0;
+                    width: 100%;
+                    height: 100%;
+                    z-index: 100;
+                }
+            }
+        }
+    }
+}
+[is-mobile] {
+    .tag-details {
+        width: 100%;
+        height: 100%;
+        top: 0;
+        left: 0;
+        padding: 0.5333rem 0 0.6667rem 0;
+        z-index: 1001;
+        .details-mask {
+            position: absolute;
+
+            width: 100%;
+            height: 6.4rem;
+            bottom: 0;
+            left: 0;
+            z-index: 1;
+            background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, #000000 100%);
+        }
+        .details-header-mobile {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            padding: 0 0.2667rem;
+            position: relative;
+            margin-bottom: 0.5333rem;
+            .contorl-box {
+                font-size: 0.64rem;
+                font-weight: normal;
+                .iconfont {
+                    margin-left: 0.6667rem;
+                    color: rgba(255, 255, 255, 0.7);
+                }
+            }
+            .audio {
+                position: absolute;
+                bottom: 0;
+                left: 0.2667rem;
+            }
+            .loading-icon {
+                color: var(--editor-main-color) !important;
+                animation: rotate 2s infinite linear;
+                position: absolute;
+                top: 0.0533rem;
+                left: 0.2133rem;
+                // transform: translate(-50%, -50%);
+                font-size: 0.64rem;
+            }
+            @keyframes rotate {
+                0% {
+                    transform: rotate(0deg);
+                }
+                100% {
+                    transform: rotate(360deg);
+                }
+            }
+        }
+        .tag-msg {
+            padding: 0 0.2933rem 1rem 0.24rem;
+
+            .tag-title {
+                font-size: 0.4267rem;
+                line-height: 0.64rem;
+            }
+            .tag-metas {
+                height: 5.6rem;
+                margin: 0.2667rem 0;
+            }
+            .tag-desc {
+                font-size: 0.3733rem;
+                line-height: normal;
+                margin-top: 0.5333rem;
+            }
+        }
+    }
+}
+</style>

+ 129 - 140
packages/qjkankan-kankan-view/src/components/Tags/tag-view.vue

@@ -1,168 +1,157 @@
 <!--  -->
 <template>
-  <teleport to="body">
-    <div
-      class="tag-layer"
-      @click.stop=""
-      :class="{ mobile: isMobile, full: hotData.type == 'image' }"
-    >
-      <div class="tag-info" @click.stop="" id="tag-info">
-        <ui-icon class="close-btn" @click.stop="close" type="close"></ui-icon>
-        <div
-          class="tag-metas"
-          v-if="
-            hotData.media[hotData.type].length > 0 && hotData.type != 'audio'
-          "
-        >
-          <metasImage
-            @close="close"
-            :scale="true"
-            :viewer="true"
-            v-if="hotData.type == 'image' && !isMobile"
-          />
-          <imageView v-if="hotData.type == 'image' && isMobile"></imageView>
-          <metasWeb v-if="hotData.type == 'link'" />
+    <teleport to="body">
+        <div class="tag-layer" @click.stop="" :class="{ mobile: isMobile, full: hotData.type == 'image' }">
+            <div class="tag-info" @click.stop="" id="tag-info">
+                <ui-icon class="close-btn" @click.stop="close" type="close"></ui-icon>
+                <div class="tag-metas" v-if="hotData.media.length">
+                    <!-- <metasImage @close="close" :scale="true" :viewer="true" v-if="hotData.type == 'image' && !isMobile" />
+                    <imageView v-if="hotData.type == 'image' && isMobile"></imageView> -->
+                    <imageView v-if="hotData.type == 'media' && isMobile"></imageView>
+                    <metasMedia @close="close" :scale="true" :viewer="true" v-if="hotData.type == 'media' && !isMobile" />
+                    <metasWeb v-if="hotData.type == 'link'" />
+                </div>
+            </div>
         </div>
-      </div>
-    </div>
-  </teleport>
+    </teleport>
 </template>
 
 <script setup>
-import { onMounted, ref, watchEffect, computed, watch, nextTick } from "vue";
-import { Dialog } from "@/global_components";
-import metasImage from "./metas/metas-image";
-import imageView from "./metas/image-view";
-
-import metasWeb from "./metas/metas-web";
-import common from "@/utils/common";
-import { useStore } from "vuex";
-import { getApp, useApp } from "@/app";
-const isMobile = ref(false);
-const metasHeight = ref(0);
+import { reactive, defineEmits, onBeforeMount, onMounted, ref, watchEffect, computed, watch, nextTick } from 'vue'
+import { Dialog } from '@/global_components'
+import metasImage from './metas/metas-image'
+import imageView from './metas/image-view'
+import metasMedia from './metas/metas-media'
+import metasWeb from './metas/metas-web'
+import common from '@/utils/common'
+import { useStore } from 'vuex'
+import { getApp, useApp } from '@/app'
+const isMobile = ref(false)
+const metasHeight = ref(0)
 onMounted(async () => {
-  const app = await useApp();
-  isMobile.value = app.config.mobile;
-  nextTick(() => {
-    let Layer = document.getElementById("tag-info");
-    let layerHeight = Layer.getBoundingClientRect().height;
-    metasHeight.value = layerHeight * 0.85;
-  });
-});
-import { useMusicPlayer } from "@/utils/sound";
-const musicPlayer = useMusicPlayer();
-const store = useStore();
-const emit = defineEmits(["close"]);
+    const app = await useApp()
+    isMobile.value = app.config.mobile
+    nextTick(() => {
+        let Layer = document.getElementById('tag-info')
+        let layerHeight = Layer.getBoundingClientRect().height
+        metasHeight.value = layerHeight * 0.85
+    })
+})
+import { useMusicPlayer } from '@/utils/sound'
+const musicPlayer = useMusicPlayer()
+const store = useStore()
+const emit = defineEmits(['close'])
 const hotData = computed(() => {
-  let data = store.getters["tag/hotData"];
-  if ((data && data.type == "audio") || (data && data.type == "video")) {
-    // musicPlayer.pause(true)
-  }
-  return store.getters["tag/hotData"];
-});
+    let data = store.getters['tag/hotData']
+    if ((data && data.type == 'audio') || (data && data.type == 'video')) {
+        // musicPlayer.pause(true)
+    }
+    return store.getters['tag/hotData']
+})
 
 const audioInfo = computed(() => {
-  return hotData.value.media.audio;
-});
-const audio = ref(null);
+    return hotData.value.media.audio
+})
+const audio = ref(null)
 watchEffect(() => {
-  if (audio.value) {
-    audio.value.play();
-  }
-});
+    if (audio.value) {
+        audio.value.play()
+    }
+})
 
 const close = () => {
-  emit("close");
-};
-onMounted(() => {});
+    emit('close')
+}
+onMounted(() => {})
 </script>
 <style lang="scss">
 .tag-layer {
-  width: 100vw;
-  height: 100vh;
-  z-index: 10000;
-  top: 0;
-  position: fixed;
-  left: 0;
-  // padding: calc(var(--editor-head-height) + 20px) calc(var(--editor-toolbox-width) + 20px) 60px calc(var(--editor-menu-width) + 20px);
-  background-color: rgba(0, 0, 0, 0.7);
-
-  .tag-info {
-    color: #fff;
-    width: 100%;
-    height: 85%;
-    position: absolute;
-    top: 7.5%;
+    width: 100vw;
+    height: 100vh;
+    z-index: 10002;
+    top: 0;
+    position: fixed;
     left: 0;
-    .close-btn {
-      position: fixed;
-      right: 36px;
-      top: 36px;
-      font-size: 18px;
-      cursor: pointer;
-      z-index: 100;
-    }
-    .tag-metas {
-      width: 100%;
-      height: 100%;
-      position: relative;
+    // padding: calc(var(--editor-head-height) + 20px) calc(var(--editor-toolbox-width) + 20px) 60px calc(var(--editor-menu-width) + 20px);
+    background-color: rgba(0, 0, 0, 0.7);
 
-      .pic-box {
+    .tag-info {
+        color: #fff;
         width: 100%;
-        height: 100%;
-        border: none;
-
-        .image-list {
-          .image-item {
-            background-size: contain;
-          }
+        height: 85%;
+        position: absolute;
+        top: 7.5%;
+        left: 0;
+        .close-btn {
+            position: fixed;
+            right: 36px;
+            top: 36px;
+            font-size: 18px;
+            cursor: pointer;
+            z-index: 100;
+            text-shadow: 0px 0px 4px rgb(0 0 0 / 40%);
         }
-      }
-      .video-box {
-        height: auto;
-        border: none;
-        video {
-          width: 100%;
-          height: auto;
-          object-fit: contain;
+        .tag-metas {
+            width: 100%;
+            height: 100%;
+            position: relative;
+
+            .pic-box {
+                width: 100%;
+                height: 100%;
+                border: none;
+
+                .image-list {
+                    .image-item {
+                        background-size: contain;
+                    }
+                }
+            }
+            .video-box {
+                height: auto;
+                border: none;
+                video {
+                    width: 100%;
+                    height: auto;
+                    object-fit: contain;
+                }
+            }
+            .web-box {
+                // height: 500px;
+                width: 91%;
+                height: 100%;
+                border: none;
+                left: 50%;
+                transform: translateX(-50%);
+            }
         }
-      }
-      .web-box {
-        // height: 500px;
-        width: 91%;
-        height: 100%;
-        border: none;
-        left: 50%;
-        transform: translateX(-50%);
-      }
     }
-  }
 }
 [is-mobile] {
-  .tag-layer {
-    .tag-info {
-      .close-btn {
-        position: absolute;
-        right: 20px;
-        top: -30px;
-        font-size: 18px;
-        cursor: pointer;
-      }
-    }
-    &.full {
-      background-color: #141414;
-      .tag-info {
-        height: 100%;
-        top: 0;
-        .close-btn {
-          position: absolute;
-          right: 20px;
-          top: 20px;
-          font-size: 18px;
-          cursor: pointer;
+    .tag-layer {
+        .tag-info {
+            .close-btn {
+                position: absolute;
+                right: 20px;
+                top: -30px;
+                font-size: 18px;
+                cursor: pointer;
+            }
+        }
+        &.full {
+            background-color: #141414;
+            .tag-info {
+                height: 100%;
+                top: 0;
+                .close-btn {
+                    position: absolute;
+                    right: 20px;
+                    top: 20px;
+                    font-size: 18px;
+                    cursor: pointer;
+                }
+            }
         }
-      }
     }
-  }
 }
 </style>

+ 27 - 3
packages/qjkankan-kankan-view/src/store/modules/tag.js

@@ -52,6 +52,7 @@ export default {
         isFixed: state => state.isFixed,
         tags: (state, getters, rootState, rootGetters) => {
             let tags = state.tagsList
+
             if (!state.tags && tags) {
                 state.tags = []
 
@@ -59,12 +60,35 @@ export default {
                     let obj = {}
                     for (let key in tags[i]) {
                         if (key == 'media') {
-                            obj[key] = JSON.parse(JSON.stringify(tags[i][key]))
+                            if (!Array.isArray(tags[i][key])) {
+                                obj[key] = []
+
+                                for (let mediaKey in tags[i][key]) {
+                                    if (mediaKey == 'image' || mediaKey == 'video' || mediaKey == 'link') {
+                                        tags[i][key][mediaKey].forEach(element => {
+                                            element.type = mediaKey
+                                        })
+                                        obj[key] = obj[key].concat(tags[i][key][mediaKey])
+                                    } else if (mediaKey == 'audio') {
+                                        obj['bgm'] = tags[i][key][mediaKey][0]
+                                    }
+                                }
+                            } else {
+                                obj[key] = JSON.parse(JSON.stringify(tags[i][key]))
+                            }
+                            // obj[key] = JSON.parse(JSON.stringify(tags[i][key]))
                         } else {
-                            obj[key] = tags[i][key]
+                            if (key == 'visiblePanos' || key == 'position') {
+                                obj[key] = tags[i][key]
+                            } else {
+                                obj[key] = JSON.parse(JSON.stringify(tags[i][key]))
+                            }
                         }
                     }
-
+                    if (Array.isArray(obj.media) && obj.type != 'link') {
+                        //如果是新格式数据,热点类型设置为media
+                        obj.type = 'media'
+                    }
                     state.tags.push(obj)
                 }
             }

+ 4 - 4
packages/qjkankan-view/.env.testdev

@@ -1,12 +1,12 @@
 VUE_APP_STATIC_DIR=showviewer
 VUE_APP_CDN=https://ossxiaoan.4dage.com
-VUE_APP_PROXY_URL_ROOT='https://test.4dkankan.com'
-VUE_APP_RESOURCE_URL='https://test.4dkankan.com/panorama/'
-VUE_APP_PROXY_URL='https://test.4dkankan.com/qjkankan/'
+VUE_APP_PROXY_URL_ROOT='https://www.4dkankan.com'
+VUE_APP_RESOURCE_URL='https://www.4dkankan.com/panorama/'
+VUE_APP_PROXY_URL='https://www.4dkankan.com/qjkankan/'
 VUE_APP_URL_FILL=
 
 # 接口请求地址
-VUE_APP_APIS_URL=https://test.4dkankan.com/
+VUE_APP_APIS_URL=https://www.4dkankan.com/
 VUE_APP_DEBBUG_FLAG=0516-03
 VUE_APP_DEBBUG_NOTIFY=0
 VUE_APP_DEBBUG_V4=1

File diff suppressed because it is too large
+ 11975 - 9402
pnpm-lock.yaml