Browse Source

feat(组件): 增加playground tag

gemercheung 2 năm trước cách đây
mục cha
commit
7593cc4483

+ 1 - 1
packages/components/basic/icon/src/icon.vue

@@ -18,7 +18,7 @@ defineOptions({
 const props = defineProps(iconProps)
 
 const style = computed(() => ({
-    'font-size': normalizeUnitToStyle(props.size),
+    'font-size': normalizeUnitToStyle(props.size || 14),
     color: props.color,
 }))
 const className = computed(() => {

+ 6 - 4
playground/index.html

@@ -3,12 +3,14 @@
     <head>
         <meta charset="UTF-8" />
         <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+        <link rel="stylesheet" href="//at.alicdn.com/t/c/font_2596172_5i5zp5tvfj9.css" />
         <meta name="viewport" content="width=device-width, initial-scale=1.0" />
         <title>kankan component</title>
-        <!-- <script src="//4dkk.4dage.com/v4/sdk/4.2.2/kankan-sdk-deps.js"></script> -->
-        <!-- <script src="//4dkk.4dage.com/v4/sdk/4.2.2/kankan-sdk.js"></script> -->
-        <script src="http://localhost:3099/dist/sdk/kankan-sdk-deps.js"></script>
-        <script src="http://localhost:3099/dist/sdk/kankan-sdk.js"></script>
+        <script src="//4dkk.4dage.com/v4-test/www/sdk/kankan-sdk-deps.js?v=4.6.0-alpha.10"></script>
+        <script src="//4dkk.4dage.com/v4-test/www/sdk/kankan-sdk.js?v=4.6.0-alpha.10"></script>
+
+        <!-- <script src="http://localhost:3099/dist/sdk/kankan-sdk-deps.js"></script>
+        <script src="http://localhost:3099/dist/sdk/kankan-sdk.js"></script> -->
     </head>
     <body>
         <div id="app"></div>

+ 5 - 5
playground/src/App.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts">
 import { onMounted, inject } from 'vue'
 import { UIAudio, UIButton, UIIcon, UIInput } from '@kankan-components/components'
-import Tags from './components/tag/Tags.vue'
+import Tags from './components/tag'
 // import { buildProps } from '@kankan-components/utils';
 // import { h } from 'vue';
 // import * as KanKanSDK from '@kankan/sdk';
@@ -18,10 +18,10 @@ onMounted(async () => {
         <Tags />
     </div>
     <div id="scene-front">
-        <UIAudio src="http://samplelib.com/lib/preview/mp3/sample-3s.mp3" />
-        <UIButton>djdjddd</UIButton>
-        <UIIcon type="checkbox" :size="129" />
-        <UIInput type="radio" />
+        <!-- <UIAudio src="http://samplelib.com/lib/preview/mp3/sample-3s.mp3" /> -->
+        <!-- <UIButton>djdjddd</UIButton> -->
+        <!-- <UIIcon type="checkbox" :size="129" />
+        <UIInput type="radio" /> -->
     </div>
 </template>
 

+ 0 - 36
playground/src/components/HelloWorld.vue

@@ -1,36 +0,0 @@
-<script setup lang="ts">
-import { ref } from 'vue'
-
-defineProps<{ msg: string }>()
-
-const count = ref(0)
-</script>
-
-<template>
-    <h1>{{ msg }}</h1>
-
-    <div class="card">
-        <button type="button" @click="count++">count is {{ count }}</button>
-        <p>
-            Edit
-            <code>components/HelloWorld.vue</code> to test HMR
-        </p>
-    </div>
-
-    <p>
-        Check out
-        <a href="https://vuejs.org/guide/quick-start.html#local" target="_blank">create-vue</a>, the official Vue + Vite starter
-    </p>
-    <p>
-        Install
-        <a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
-        in your IDE for a better DX
-    </p>
-    <p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
-</template>
-
-<style scoped>
-.read-the-docs {
-    color: #888;
-}
-</style>

+ 0 - 77
playground/src/components/tag/TagView.vue

@@ -1,77 +0,0 @@
-<script setup lang="ts">
-import { type PropType } from 'vue'
-interface Tag {
-    x: Number
-    y: Number
-    sid: string
-    visible: boolean
-}
-const props = defineProps({
-    tag: {
-        type: Object as PropType<Tag>,
-        required: true,
-    },
-})
-</script>
-
-<template>
-    <div :data-tag-id="props.tag.sid" :style="{ transform: `translate(${props.tag.x}px,${props.tag.y}px)`, display: props.tag.visible ? 'block' : 'none' }">
-        <span class="tag-icon animate" style="background-image: url(http://4dkk.4dage.com/v4/sdk/4.2.2/images/tag_icon_default.svg)"></span>
-    </div>
-</template>
-<style scoped>
-div {
-    position: absolute;
-}
-</style>
-<style>
-[xui_tags_view] {
-    position: absolute;
-    width: 100%;
-    height: 100%;
-}
-[xui_tags_view] > div {
-    pointer-events: all;
-    display: none;
-    position: absolute;
-    width: 48px;
-    height: 48px;
-    margin-left: -24px;
-    margin-top: -24px;
-    z-index: 1;
-}
-
-[xui_tags_view] > div.focus {
-    z-index: 2;
-}
-[xui_tags_view] > div.fixed {
-    z-index: 3;
-}
-[xui_tags_view] > div.active {
-    z-index: 4;
-}
-
-[xui_tags_view] .tag-icon {
-    display: block;
-    width: 48px;
-    height: 48px;
-    background-size: cover;
-    cursor: pointer;
-}
-
-[xui_tags_view] .tag-icon.animate {
-    animation: tag-animate-zoom 3s -1s linear infinite;
-}
-
-@keyframes tag-animate-zoom {
-    0% {
-        transform: scale(1);
-    }
-    50% {
-        transform: scale(0.7);
-    }
-    100% {
-        transform: scale(1);
-    }
-}
-</style>

+ 6 - 0
playground/src/components/tag/index.ts

@@ -0,0 +1,6 @@
+import { withInstall } from '@kankan-components/utils'
+import tags from './src/Tags.vue'
+
+export const UITag = withInstall(tags)
+
+export default UITag

playground/src/components/tag/TagEditor.vue → playground/src/components/tag/src/TagEditor.vue


+ 152 - 0
playground/src/components/tag/src/TagView.vue

@@ -0,0 +1,152 @@
+<script setup lang="ts">
+import { type PropType, onMounted, ref, unref } from 'vue'
+import ShowTag from './showTag.vue'
+
+const props = defineProps({
+    tag: {
+        type: Object as PropType<TagContent>,
+        required: true,
+    },
+})
+
+const emit = defineEmits(['click'])
+
+const isShow = ref<boolean>(false)
+const isClick = ref<boolean>(false)
+const handleMouseEnter = (event: MouseEvent, tag: any, index?: number | undefined) => {
+    // console.log('in', event)
+    isShow.value = true
+}
+const handleMouseLeave = (event: MouseEvent, tag: any, index?: number | undefined) => {
+    // console.log('out', event)
+    isShow.value = false
+    // isClick.value = false
+}
+
+const handleTagClick = (tagId: string) => {
+    isClick.value = !isClick.value
+    emit('click', {
+        id: tagId,
+        isClick: isClick.value,
+    })
+}
+
+onMounted(() => {
+    console.log('tag', props.tag)
+})
+</script>
+
+<template>
+    <div
+        :data-tag-id="tag.sid"
+        @mouseleave.prevent="handleMouseLeave($event, tag)"
+        :style="{ transform: `translate(${props.tag.x}px,${props.tag.y}px)`, display: props.tag.visible ? 'block' : 'none' }"
+    >
+        <span
+            class="tag-icon animate"
+            @click="handleTagClick(tag.sid)"
+            @mouseenter.prevent="handleMouseEnter($event, tag)"
+            style="background-image: url(http://4dkk.4dage.com/v4/sdk/4.2.2/images/tag_icon_default.svg)"
+        ></span>
+        <div class="content">
+            <div class="trans" :class="{ active: isShow || isClick }">
+                <template v-if="isShow || isClick">
+                    <div class="arrow" :id="`arrow_${tag.sid}`">
+                        <!-- <ui-icon @click.stop="closeTag" v-if="getApp().config.mobile" type="close"></ui-icon> -->
+                    </div>
+                    <ShowTag :id="tag.sid" :title="tag.title" :type="tag.type" :content="tag.content" :media="tag.media" />
+                    <!-- <TagInfo v-if="true" /> -->
+                    <!-- <ShowTag @click.stop="" v-if="!isEdit && hotData" @open="openInfo" /> -->
+                </template>
+            </div>
+        </div>
+    </div>
+</template>
+<style scoped>
+/* div {
+    position: absolute;
+} */
+</style>
+<style>
+[xui_tags_view] {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+}
+[xui_tags_view] > div {
+    pointer-events: all;
+    display: none;
+    position: absolute;
+    width: 48px;
+    height: 48px;
+    margin-left: -24px;
+    margin-top: -24px;
+    z-index: 1;
+}
+
+[xui_tags_view] > div.focus {
+    z-index: 2;
+}
+[xui_tags_view] > div.fixed {
+    z-index: 3;
+}
+[xui_tags_view] > div.active {
+    z-index: 4;
+}
+
+[xui_tags_view] .tag-icon {
+    display: block;
+    width: 48px;
+    height: 48px;
+    background-size: cover;
+    cursor: pointer;
+}
+
+[xui_tags_view] .tag-icon.animate {
+    animation: tag-animate-zoom 3s -1s linear infinite;
+}
+
+@keyframes tag-animate-zoom {
+    0% {
+        transform: scale(1);
+    }
+    50% {
+        transform: scale(0.7);
+    }
+    100% {
+        transform: scale(1);
+    }
+}
+
+[xui_tags_view] .content .trans {
+    padding: 0 40px 0 0;
+    position: absolute;
+    right: 35px;
+    top: 50%;
+    min-width: 420px;
+    min-height: 60px;
+    z-index: 1000;
+    transform: translateY(-50%) scale(0);
+    transform-origin: center right;
+    transition: all 0.3s cubic-bezier(0.35, 0.32, 0.65, 0.63);
+    pointer-events: none;
+    /* background-color: rgba(0, 0, 0, 0.8); */
+}
+
+[xui_tags_view] .content .trans .arrow {
+    pointer-events: auto;
+    content: '';
+    position: absolute;
+    top: 50%;
+    right: 0;
+    width: 0;
+    height: 0;
+    border-top: 15px solid transparent;
+    border-bottom: 15px solid transparent;
+    border-left: 40px solid rgba(27, 27, 28, 0.8);
+    transform: translateY(-50%);
+}
+[xui_tags_view] .content .trans.active {
+    transform: translateY(-50%) scale(1);
+}
+</style>

+ 9 - 2
playground/src/components/tag/Tags.vue

@@ -1,19 +1,26 @@
 <script setup lang="ts">
-import { Ref, ref, inject } from 'vue'
+import { Ref, ref, inject, onMounted } from 'vue'
 import TagView from './TagView.vue'
 
 const __sdk: any = inject('__sdk')
+
 const tags: Ref<Array<any>> = ref([])
+
 __sdk.TagManager.on('loaded', (data: any) => __sdk.TagManager.load((tags.value = data) && tags.value))
+
+const handleTagview = ({ tagId: string }) => {
+    console.log('tagId', tagId)
+}
 </script>
 
 <template>
     <Teleport to=".kankan-plugins" v-if="tags.length">
         <div xui_tags_view>
-            <TagView v-for="item in tags" :tag="item" />
+            <TagView v-for="item in tags" :tag="item" @click="handleTagview" />
         </div>
     </Teleport>
 </template>
+
 <style scoped>
 [xui_tags_view] {
     position: absolute;

+ 5 - 0
playground/src/components/tag/src/metas/demo.vue

@@ -0,0 +1,5 @@
+<template>
+    <div>test</div>
+</template>
+<script lang="ts" setup></script>
+<style></style>

+ 414 - 0
playground/src/components/tag/src/metas/metasImage.vue

@@ -0,0 +1,414 @@
+<!--  -->
+<template>
+    <!-- <div v-if="imageList.length > 0 && type == 'IMAGE'" class="pic-box"> -->
+    <div class="pic-box" :class="{ show: viewer }" :style="metasHeight ? `height:${metasHeight}px;` : ''">
+        <div>
+            <div class="ctrl-btn left-btn" v-if="currentIndex != 0" @click.stop="changeImage('pre')">
+                <ui-icon type="left"></ui-icon>
+            </div>
+            <div class="ctrl-btn right-btn" v-if="currentIndex < data.length - 1" @click.stop="changeImage('next')">
+                <ui-icon type="right"></ui-icon>
+            </div>
+        </div>
+        <div class="over-box">
+            <div class="image-list" :style="`transform:translateX(${-100 * currentIndex}%);`">
+                <!-- :style="`transform:translateX(${100 * index}%);background-image:url(${common.changeUrl(i.src)});`" -->
+                <!-- v-for="(i, index) in imageList" -->
+                <div class="image-item" :style="`transform:translateX(${100 * index}%);background-image:url(${changeUrl(i.src)});`" v-for="(i, index) in data"></div>
+                <!-- <div v-else :style="`transform:translateX(${100 * index}%);`" class="image-item" v-for="(i, index) in imageList">
+                    <img @error="filesError(index)" :src="common.changeUrl(i.src)" alt="" />
+                </div> -->
+            </div>
+            <ui-icon class="loading-icon" type="_loading_"></ui-icon>
+
+            <!-- <div class="del-btn">
+                <ui-icon type="del"></ui-icon>
+            </div> -->
+        </div>
+        <div class="continue">
+            <!-- <ui-input
+                v-if="imageList.length < customer[type].maxNum && isEdit"
+                type="file"
+                :placeholder="customer[type].uploadPlace"
+                :disable="customer[type].upload"
+                :scale="customer[type].scale"
+                :accept="customer[type].accept"
+                :multiple="customer[type].multiple"
+                :maxSize="customer[type].maxSize"
+                :maxLen="customer[type].maxNum"
+                :othPlaceholder="customer[type].othPlaceholder"
+                @update:modelValue="data => hanlderFiles(data)"
+            >
+                <template v-slot:replace>
+                    <span class="continue-tips">{{ $t('tag.toolbox.continueAdd') }}</span>
+                    <span class="edit-pic-num">
+                        <span class="cur">{{ imageList.length }}</span>
+                        <span> / {{ customer[type].maxNum }}</span>
+                    </span>
+                </template>
+            </ui-input> -->
+            <!-- <span v-if="isEdit" class="pic-num">
+                <span class="cur">{{ imageList.length }}</span>
+                <span> / {{ custom[type].maxNum }}</span>
+            </span> -->
+            <span class="pic-num">
+                <span class="cur">{{ currentIndex + 1 }}</span>
+                <span><span>&nbsp;</span>/<span>&nbsp;</span></span>
+                <span>{{ data.length }}</span>
+            </span>
+        </div>
+        <!-- 移动端缩放 -->
+    </div>
+</template>
+
+<script lang="ts">
+import { ref, defineEmits, defineComponent, PropType, inject } from 'vue'
+import { buildProps } from '@kankan-components/utils'
+import { UIIcon } from '@kankan-components/components'
+const emit = defineEmits(['close'])
+
+const currentIndex = ref(0)
+
+const props = buildProps({
+    metasHeight: {
+        type: Number,
+        default: null,
+    },
+    data: {
+        type: Array as PropType<SourceType[]>,
+        default: [],
+    },
+    viewer: {
+        type: Boolean,
+        default: false,
+    },
+    scale: {
+        type: Boolean,
+        default: false,
+    },
+})
+
+export default defineComponent({
+    name: 'metaImage',
+    props: props,
+    components: {
+        'ui-icon': UIIcon,
+    },
+    setup(props) {
+        const __sdk: any = inject('__sdk')
+        function changeUrl(name: string, now?: string) {
+            if (name.indexOf('http') != -1) {
+                return name
+            } else {
+                if ((typeof name === 'string' && name.substring(0, 4) == 'blob') || (typeof name === 'string' && name.substring(0, 10) == 'data:image')) {
+                    return name
+                } else {
+                    return __sdk.resource.getUserResourceURL(name, false, now)
+                }
+            }
+        }
+        const changeImage = (type: 'pre' | 'next') => {
+            if (type == 'pre') {
+                currentIndex.value--
+            } else {
+                currentIndex.value++
+            }
+        }
+
+        return {
+            changeUrl,
+            changeImage,
+            currentIndex,
+        }
+    },
+})
+</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;
+        }
+    }
+    .image-list {
+        width: 100%;
+        height: 100%;
+        position: relative;
+        transition: all 0.3s linear;
+        .image-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;
+            img {
+                height: 100%;
+                width: auto;
+            }
+        }
+    }
+    &.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>

+ 53 - 0
playground/src/components/tag/src/props.ts

@@ -0,0 +1,53 @@
+import { buildProps } from '@kankan-components/utils'
+// import type { ExtractPropTypes } from 'vue'
+// import type bubble from './bubble.vue'
+
+// isPlay: boolean
+// flying: boolean
+// leaveId: string
+// isEdit: boolean
+// isFixed: boolean
+// isClick: boolean
+// enterVisible: boolean
+// editPosition: boolean
+// toggleIndex: number
+// flyClose?: boolean
+
+export const tagStatusProps = buildProps({
+    isPlay: {
+        type: Boolean,
+        default: false,
+    },
+    flying: {
+        type: Boolean,
+        default: false,
+    },
+    leaveId: {
+        type: String,
+        default: '',
+    },
+    isEdit: {
+        type: Boolean,
+        default: false,
+    },
+    isFixed: {
+        type: Boolean,
+        default: false,
+    },
+    enterVisible: {
+        type: Boolean,
+        default: false,
+    },
+    editPosition: {
+        type: Boolean,
+        default: false,
+    },
+    toggleIndex: {
+        type: Number,
+        default: 0,
+    },
+    flyClose: {
+        type: Boolean,
+        default: false,
+    },
+})

+ 215 - 0
playground/src/components/tag/src/showTag.vue

@@ -0,0 +1,215 @@
+<template>
+    <div class="show-tag" :id="`tagBox_${id}`">
+        <div class="tag-title">
+            <h2>
+                {{ title }}
+            </h2>
+        </div>
+        <div class="desc">
+            <div class="text" v-html="content"></div>
+        </div>
+        <div class="tag-metas">
+            <metasImage v-if="type == 'image'" :data="media.image" />
+            <!-- <metasImage v-if="hotData.type == 'image'" />
+            <metasVideo v-if="hotData.type == 'video'" />
+            <metasWeb v-if="hotData.type == 'link'" /> -->
+        </div>
+        <div class="edit-btn">
+            <!-- <span @click="edit()"><ui-icon type="edit"></ui-icon> {{ $t('common.revise') }}</span> -->
+        </div>
+    </div>
+</template>
+<script lang="ts">
+import { type PropType, defineComponent } from 'vue'
+import { buildProps, definePropType } from '@kankan-components/utils'
+import MetasImage from './metas/metasImage.vue'
+// import demo from './metas/demo.vue'
+
+const props = buildProps({
+    id: {
+        type: String,
+        default: '',
+        required: true,
+    },
+    title: {
+        type: String,
+        default: '',
+        required: true,
+    },
+    type: {
+        type: String as PropType<TagType>,
+        default: '',
+        required: true,
+    },
+    media: {
+        type: Object as PropType<TagContent['media']>,
+        default: null,
+    },
+    content: {
+        type: String,
+        default: '',
+        required: true,
+    },
+})
+
+export default defineComponent({
+    name: 'UIShowTag',
+    props: props,
+    components: { MetasImage },
+    setup(props) {},
+})
+</script>
+
+<style lang="scss" scoped>
+.show-tag {
+    pointer-events: auto;
+    background: rgba(27, 27, 28, 0.8);
+    border-radius: 4px;
+    // border: 1px solid #000000;
+    // backdrop-filter: blur(4px);
+    min-width: 400px;
+    // min-height: 100px;
+    padding: 30px 20px;
+    max-height: 50vh;
+    overflow-y: auto;
+    .edit-btn {
+        margin-top: 20px;
+        text-align: right;
+        span {
+            font-size: 14px;
+            color: var(--editor-font-color);
+            cursor: pointer;
+            &:hover {
+                color: #fff;
+            }
+        }
+    }
+    .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-top: 20px;
+        &.nocursor {
+            cursor: auto;
+        }
+        &.mask {
+            &::after {
+                content: '';
+                position: absolute;
+                top: 0;
+                left: 0;
+                width: 100%;
+                height: 100%;
+                z-index: 100;
+            }
+        }
+    }
+    .tag-title {
+        word-break: break-all;
+        h2 {
+            font-size: 20px;
+            // margin-bottom: 10px;
+            line-height: 30px;
+            color: #ffffff;
+            position: relative;
+            .ui-audio {
+                float: right;
+                &.audio {
+                    display: inline-block;
+                    cursor: pointer;
+                }
+            }
+        }
+    }
+    .desc {
+        margin-top: 10px;
+        .text {
+            font-size: 14px;
+            color: #999999;
+            line-height: 20px;
+            text-align: justify;
+            word-break: break-all;
+        }
+    }
+}
+[is-mobile] {
+    .show-tag {
+        pointer-events: auto;
+        background: rgba(27, 27, 28, 0.8);
+        border-radius: 0.0533rem;
+        // border: 1px solid #000000;
+        // backdrop-filter: blur(0.0533rem);
+        min-width: 7.4667rem;
+        // min-height: 4rem;
+        padding: 0.4rem 0.2667rem;
+
+        .edit-btn {
+            margin-top: 0.2667rem;
+            text-align: right;
+            span {
+                font-size: 0.1867rem;
+                color: var(--editor-font-color);
+                cursor: pointer;
+                &:hover {
+                    color: #fff;
+                }
+            }
+        }
+        .tag-metas {
+            width: 100%;
+            height: 4.2667rem;
+            background: rgba(255, 255, 255, 0.1);
+            border-radius: 0.0533rem;
+            overflow: hidden;
+            position: relative;
+            cursor: -webkit-zoom-in;
+            margin-top: 0.4rem;
+            &.mask {
+                &::after {
+                    content: '';
+                    position: absolute;
+                    top: 0;
+                    left: 0;
+                    width: 100%;
+                    height: 100%;
+                    z-index: 100;
+                }
+            }
+        }
+        .tag-title {
+            h2 {
+                font-size: 0.5333rem;
+                line-height: 0.8rem;
+                color: #ffffff;
+                position: relative;
+                .ui-audio {
+                    float: right;
+                    &.audio {
+                        display: inline-block;
+                        cursor: pointer;
+                    }
+                }
+            }
+        }
+        .desc {
+            margin-bottom: 0.2933rem;
+
+            .text {
+                font-size: 0.3733rem;
+                color: #999999;
+                line-height: 0.2533rem;
+                text-align: justify;
+                line-height: 0.5333rem;
+
+                p {
+                    line-height: 0.5333rem;
+                }
+            }
+        }
+    }
+}
+</style>

+ 43 - 0
playground/src/components/tag/tag.d.ts

@@ -0,0 +1,43 @@
+
+type TagType = 'image' | 'video'
+
+type SourceType = {
+    src: string
+}
+
+interface GlobalTag {
+    isPlay: boolean
+    flying: boolean
+    leaveId: string
+    isEdit: boolean
+    isFixed: boolean
+    isClick: boolean
+    enterVisible: boolean
+    editPosition: boolean
+    toggleIndex: number
+    flyClose?: boolean
+}
+
+interface TagContent {
+    content: string
+    createTime: EpochTimeStamp
+    floorIndex: number
+    icon: string
+    panoId: string
+    position: {
+        x: number,
+        y: number,
+        z: number
+    }
+    media: {
+        image?: SourceType[],
+        video?: SourceType[],
+    }
+    sid: string
+    title: string
+    type: TagType
+    visible: boolean
+    x: number
+    y: number
+    visiblePanos?: any[]
+}

+ 3 - 2
playground/src/sdk.ts

@@ -2,8 +2,9 @@ import { App } from 'vue'
 
 const __win = window as any
 const __sdk = (__win.__sdk = new __win.KanKan({
-    num: 'KJ-JYo2ZZyKKJ',
-    server: 'https://www.4dkankan.com/',
+    num: 'KJ-t-wOXfx2SDFy',
+    // server: 'https://test.4dkankan.com',
+    server: '/demoServer',
 }))
 export { __sdk }
 

+ 10 - 0
playground/vite.config.ts

@@ -4,7 +4,17 @@ import vueJsx from '@vitejs/plugin-vue-jsx'
 import VueMacros from 'unplugin-vue-macros/vite'
 // https://vitejs.dev/config/
 export default defineConfig({
+    server: {
+        proxy: {
+            '/demoServer': {
+                target: 'https://test.4dkankan.com',
+                changeOrigin: true,
+                rewrite: (path) => path.replace(/^\/demoServer/, ''),
+            },
+        },
+    },
     plugins: [
+        //@ts-ignore
         VueMacros({
             setupComponent: false,
             setupSFC: false,