Browse Source

feat: 标注上传媒体

jinx 2 years ago
parent
commit
68d27060b6

File diff suppressed because it is too large
+ 12754 - 0
package-lock.json


+ 56 - 6
src/components/files/TagEditor.vue

@@ -36,6 +36,7 @@
 </template>
 <script setup>
 import { ref, inject, onMounted, onBeforeUnmount, watch } from 'vue'
+import { convertBlob2File } from '@/utils/file'
 import { http } from '@/utils/request'
 import browser from '@/utils/browser'
 import Toast from '@/components/dialog/Toast'
@@ -48,6 +49,7 @@ import { from } from 'readable-stream'
 const showTips = ref(null)
 const projectId = browser.valueFromUrl('projectId') || 1
 const notify = inject('notify')
+const tags = inject('tags')
 const height = ref(0)
 const form = ref({
     title: '',
@@ -55,6 +57,9 @@ const form = ref({
     status: '',
     members: [],
 })
+const typeList = ['image', 'video', 'audio', 'link']
+let mediaList = []
+let tag = null
 const data = ref({
     status: [
         { text: '待处理', value: 1 },
@@ -73,7 +78,7 @@ const onClose = () => {
     }
     notify.value = null
 }
-const onSubmit = () => {
+const onSubmit = async () => {
     if (!form.value.title) {
         return (showTips.value = '请输入资料名称')
     }
@@ -82,26 +87,66 @@ const onSubmit = () => {
     }
     notify.value.title = form.value.title
     notify.value.content = form.value.describe
-    let tag = JSON.stringify(notify.value, (key, value) => {
+    //提交前删除别的数据
+    typeList.forEach(item => {
+        if (notify.value.type != item) {
+            delete notify.value.media[item]
+        }
+    })
+    tag = JSON.stringify(notify.value, (key, value) => {
         if (key === 'visiblePanos') {
             return value.map(item => item.id)
         }
+
+        if (key === 'media' && notify.value.media[notify.value.type]) {
+            mediaList = notify.value.media[notify.value.type]
+        }
+
         return value
     })
-
     tag = JSON.parse(tag)
     tag.status = form.value.status
     tag.members = form.value.members.map(item => item.value)
     delete tag.__temp
-    http.post(`smart-site/marking/addOrUpdate`, {
+    await handlerUpload(tag)
+}
+const handlerUpload = async data => {
+    if (mediaList.length) {
+        for (let i = 0; i < mediaList.length; i++) {
+            if (mediaList[i].file) {
+                let res = await http.postFile(`smart-site/upload/${projectId}/${notify.value.type}/file/`, {
+                    file: mediaList[i].file,
+                })
+                if (res.success) {
+                    delete tag.media[notify.value.type][i].file
+                    delete notify.value.media[notify.value.type][i].file
+                    tag.media[notify.value.type][i].src = res.data
+                    notify.value.media[notify.value.type][i].src = res.data
+                } else if (res.code == 4008) {
+                    showTips.value = '请先登录'
+                } else {
+                    showTips.value = res.message
+                }
+            }
+        }
+
+        mediaList = []
+    }
+    console.log(tag)
+    let params = {
         projectId,
         userIds: tag.members,
         markingStatus: form.value.status,
         markingTitle: form.value.title,
         hotData: tag,
-    }).then(response => {
+    }
+    if (tag.id) {
+        params.markingId = tag.id
+    }
+    http.post(`smart-site/marking/addOrUpdate`, params).then(response => {
         if (response.success) {
             delete notify.value.__temp
+            notify.value.id = response.data
             notify.value = null
         } else if (response.code == 4008) {
             showTips.value = '请先登录'
@@ -113,7 +158,7 @@ const onSubmit = () => {
 const onResize = () => {
     height.value = window.innerHeight - 90
 }
-
+let markingId = null
 onMounted(() => {
     http.post(`smart-site/projectTeam/select`, { projectId }).then(response => {
         data.value.members = response.data.map(item => {
@@ -136,6 +181,11 @@ onMounted(() => {
                     }
                 })
             }
+            //初始化markingId
+            console.log(notify.value)
+            console.log(tags)
+            tags.value.forEach(item => {})
+            console.log(markingId)
         }
     })
 

+ 2 - 2
src/components/files/index.vue

@@ -14,7 +14,7 @@
                         <div class="title"><i></i>{{ tag.title }}</div>
                         <div class="more" @click="onShowMore(tag)">
                             <i class="iconfont icon-more"></i>
-                            <div v-if="showMoreSid == tag.sid" v-click-outside="onOutside">
+                            <div v-if="showMoreSid == tag.id" v-click-outside="onOutside">
                                 <div @click.stop="onMoreHandler('modify', tag)">编辑</div>
                                 <div @click.stop="onMoreHandler('delete', tag)">删除</div>
                             </div>
@@ -157,7 +157,7 @@ const onOutside = () => {
     }
 }
 const onShowMore = tag => {
-    showMoreSid.value = tag.sid
+    showMoreSid.value = tag.id
 }
 const onMoreHandler = (type, tag) => {
     if (type == 'modify') {

+ 3 - 1
src/components/form/Area.vue

@@ -1,7 +1,7 @@
 <template>
     <div class="control">
         <div class="component area">
-            <textarea  :maxlength="maxlength" :placeholder="placeholder" v-model="modelValue" @input="e => emits('update:modelValue', e.target.value)" ></textarea>
+            <textarea :maxlength="maxlength" :placeholder="placeholder" :value="modelValue" @input="e => emits('update:modelValue', e.target.value)"></textarea>
             <div class="maxlength" v-if="maxlength">
                 <span>{{ modelValue.length }}</span
                 >&nbsp;/&nbsp;{{ maxlength }}
@@ -10,10 +10,12 @@
     </div>
 </template>
 <script setup>
+
 const props = defineProps({
     modelValue: {
         type: String,
         require: true,
+        default: '',
     },
     maxlength: {
         type: Number,

+ 1 - 1
src/components/form/Input.vue

@@ -14,7 +14,7 @@
             </div>
         </div>
         <div class="component text" v-if="type == 'text'">
-            <input class="component text" :maxlength="maxlength" :placeholder="placeholder" v-model="modelValue" @input="e => emits('update:modelValue', e.target.value)" />
+            <input class="component text" :maxlength="maxlength" :placeholder="placeholder" :value="modelValue" @input="e => emits('update:modelValue', e.target.value)" />
             <div class="maxlength" v-if="maxlength">
                 <span>{{ modelValue.length }}</span
                 >&nbsp;/&nbsp;20

+ 43 - 9
src/components/form/medias/Audio.vue

@@ -1,8 +1,9 @@
 <template>
-    <div class="media" v-show="media">
-
+    <div class="media" v-if="media.length">
+        <i class="iconfont icon-music"></i>
+        <span class="music-name">{{ media[0].name }}</span>
     </div>
-    <div class="placeholder" @click="file.click()" v-show="media == null">
+    <div class="placeholder" @click="file.click()" v-if="!media.length">
         <div class="icon">
             <i class="iconfont icon-add"></i>
             <span>上传音频</span>
@@ -10,14 +11,17 @@
         <div class="tips">支持 mp3/wav 文件:≤ 5MB</div>
         <input ref="file" type="file" style="display: none" accept=".mp3, .wav" @change="onChange" />
     </div>
+    <div class="del-btn" v-if="notify.media?.[notify.type]?.length" @click="delMedia">
+        <i class="iconfont icon-del"></i>
+    </div>
 </template>
 <script setup>
-import { ref, onMounted } from 'vue'
+import { ref, onMounted, inject } from 'vue'
 import { checkSizeLimitFree, base64ToDataURL } from '@/utils/file'
-
+const notify = inject('notify')
 const emits = defineEmits(['tips'])
 const file = ref(null)
-const media = ref(null)
+const media = ref([])
 const onChange = e => {
     if (!e.target.files.length) {
         return
@@ -28,20 +32,41 @@ const onChange = e => {
     if (checkSizeLimitFree(file.size, 5)) {
         let reader = new FileReader()
         reader.onload = function () {
-            images.value.push(base64ToDataURL(reader.result))
+            media.value.push({ src: base64ToDataURL(reader.result), file, name: file.name })
+            notify.value.media['audio'] = media.value
         }
         reader.readAsDataURL(file)
     } else {
-        emits('tips','请上传 5MB 以内的 jpg/png 文件')
+        emits('tips', '请上传 5MB 以内的 jpg/png 文件')
     }
     e.target.value = ''
 }
 
+const delMedia = () => {
+    media.value.splice(0, 1)
+}
 onMounted(() => {
-
+    media.value = notify.value.media?.audio || []
 })
 </script>
 <style lang="scss" scoped>
+.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;
+    .iconfont {
+        font-size: 1em;
+    }
+}
 .placeholder {
     cursor: pointer;
     width: 100%;
@@ -72,6 +97,15 @@ onMounted(() => {
 .media {
     width: 100%;
     height: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    .music-name {
+        margin-left: 5px;
+    }
+    .iconfont {
+        font-size: 1em;
+    }
     .add {
         cursor: pointer;
         font-size: 12px;

+ 43 - 8
src/components/form/medias/Image.vue

@@ -2,7 +2,7 @@
     <div class="media-image" v-show="images.length">
         <div class="swiper" ref="swiper$">
             <div class="swiper-wrapper">
-                <div class="swiper-slide" v-for="(url, index) in images" :style="`background-image: url(${url})`" :key="index"></div>
+                <div class="swiper-slide" v-for="(item, index) in images" :style="`background-image: url(${item.src})`" :key="index"></div>
             </div>
             <div class="swiper-button-prev"></div>
             <div class="swiper-button-next"></div>
@@ -20,11 +20,16 @@
         <div class="tips">支持JPG、PNG等图片格式,单张不超过5MB,最多支持上传9张。</div>
         <input ref="file" type="file" style="display: none" accept="image/jpg,image/jpeg,image/png" @change="onChange" />
     </div>
+
+    <div class="del-btn" v-if="notify.media?.[notify.type]?.length" @click="delPic">
+        <i class="iconfont icon-del"></i>
+    </div>
 </template>
 <script setup>
-import { ref, onMounted } from 'vue'
-import { checkSizeLimitFree, base64ToDataURL } from '@/utils/file'
-
+import { ref, onMounted, inject } from 'vue'
+import { checkSizeLimitFree, base64ToDataURL, convertBlob2File, base64ToBlob } from '@/utils/file'
+import common from '@/utils/common'
+const notify = inject('notify')
 const emits = defineEmits(['tips'])
 
 const file = ref(null)
@@ -41,17 +46,23 @@ const onChange = e => {
     if (checkSizeLimitFree(file.size, 5)) {
         let reader = new FileReader()
         reader.onload = function () {
-            images.value.push(base64ToDataURL(reader.result))
+            images.value.push({ src: base64ToDataURL(reader.result), file })
+
+            notify.value.media['image'] = images.value
         }
         reader.readAsDataURL(file)
     } else {
-        emits('tips','请上传 5MB 以内的 jpg/png 文件')
+        emits('tips', '请上传 5MB 以内的 jpg/png 文件')
     }
     e.target.value = ''
 }
-
+const delPic = () => {
+    let index = swiper.activeIndex
+    images.value.splice(index, 1)
+}
+let swiper
 onMounted(() => {
-    let swiper = new Swiper(swiper$.value, {
+    swiper = new Swiper(swiper$.value, {
         observer: true,
         navigation: {
             prevEl: swiper$.value.querySelector('.swiper-button-prev'),
@@ -63,9 +74,33 @@ onMounted(() => {
             },
         },
     })
+    images.value = notify.value.media?.image || []
+
+    // if (!notify.value.media) {
+    //     notify.value.media = {}
+    // }
+
+    // notify.value.media['image'] = images.value
 })
 </script>
 <style lang="scss" scoped>
+.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;
+    .iconfont {
+        font-size: 1em;
+    }
+}
 .placeholder {
     cursor: pointer;
     width: 100%;

+ 9 - 3
src/components/form/medias/Link.vue

@@ -15,21 +15,27 @@
     </div>
 </template>
 <script setup>
-import { ref, onMounted } from 'vue'
+import { ref, onMounted, inject, computed } from 'vue'
 const emits = defineEmits(['tips'])
+const notify = inject('notify')
 const url = ref(null)
 const href = ref('')
-const onDelete = () =>{
+const onDelete = () => {
     url.value = null
+    notify.value.media['link'] = []
 }
 const onConfirm = () => {
     if (href.value) {
         url.value = 'https://' + href.value.replace(/http(s?):\/\//, '')
         href.value = ''
     }
+
+    notify.value.media['link'] = [{ src: url.value }]
 }
 
-onMounted(() => {})
+onMounted(() => {
+    url.value = notify.value.media?.link && notify.value.media.link[0] ? notify.value.media.link[0].src : null
+})
 </script>
 <style lang="scss" scoped>
 .placeholder {

+ 43 - 10
src/components/form/medias/Video.vue

@@ -1,8 +1,18 @@
 <template>
-    <div class="media" v-show="media">
-        <video x5-video-player-type="h5-page" controlslist="nodownload" disablepictureinpicture="" webkit-playsinline="" x-webkit-airplay="" playsinline="" controls="" autoplay="" src="blob:http://test.4dkankan.com/e9d98629-af14-4a25-9a11-56026738b12a"></video>
+    <div class="media" v-if="media.length">
+        <video
+            x5-video-player-type="h5-page"
+            controlslist="nodownload"
+            disablepictureinpicture=""
+            webkit-playsinline=""
+            x-webkit-airplay=""
+            playsinline=""
+            controls=""
+            autoplay=""
+            :src="media[0].src"
+        ></video>
     </div>
-    <div class="placeholder" @click="file.click()" v-show="media == null">
+    <div class="placeholder" @click="file.click()" v-if="!media.length">
         <div class="icon">
             <i class="iconfont icon-add"></i>
             <span>上传视频</span>
@@ -10,14 +20,17 @@
         <div class="tips">支持 mp4/mov 文件:≤ 20MB,≤ 2Mbps</div>
         <input ref="file" type="file" style="display: none" accept=".mp4, .mov" @change="onChange" />
     </div>
+    <div class="del-btn" v-if="notify.media?.[notify.type]?.length" @click="delMedia">
+        <i class="iconfont icon-del"></i>
+    </div>
 </template>
 <script setup>
-import { ref, onMounted } from 'vue'
+import { ref, onMounted, inject } from 'vue'
 import { checkSizeLimitFree, base64ToDataURL } from '@/utils/file'
-
+const notify = inject('notify')
 const emits = defineEmits(['tips'])
 const file = ref(null)
-const media = ref(null)
+const media = ref([])
 const onChange = e => {
     if (!e.target.files.length) {
         return
@@ -28,20 +41,40 @@ const onChange = e => {
     if (checkSizeLimitFree(file.size, 20)) {
         let reader = new FileReader()
         reader.onload = function () {
-            images.value.push(base64ToDataURL(reader.result))
+            media.value.push({ src: base64ToDataURL(reader.result), file })
+            notify.value.media['video'] = media.value
         }
         reader.readAsDataURL(file)
     } else {
-        emits('tips','请上传 5MB 以内的 jpg/png 文件')
+        emits('tips', '请上传 5MB 以内的 jpg/png 文件')
     }
     e.target.value = ''
 }
-
+const delMedia = () => {
+    media.value.splice(0, 1)
+}
 onMounted(() => {
-
+    media.value = notify.value.media?.video || []
 })
 </script>
 <style lang="scss" scoped>
+.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;
+    .iconfont {
+        font-size: 1em;
+    }
+}
 .placeholder {
     cursor: pointer;
     width: 100%;

+ 18 - 3
src/components/form/medias/index.vue

@@ -9,18 +9,24 @@
         <div class="control">
             <component :is="component" @tips="onTips"></component>
             <Toast v-if="tips" type="error" :content="tips" :close="() => (tips = null)" />
+        
         </div>
     </div>
 </template>
 <script setup>
-import { ref, computed } from 'vue'
+import { ref, computed, inject, onMounted } from 'vue'
 import Toast from '@/components/dialog/Toast'
 import Image from './Image.vue'
 import Video from './Video.vue'
 import Audio from './Audio.vue'
 import Link from './Link.vue'
 const tips = ref(null)
-const media = ref('image')
+const notify = inject('notify')
+// const media = ref(notify.value?.type || 'image')
+const media = computed(() => {
+    return notify.value?.type || 'image'
+})
+
 const component = computed(() => {
     switch (media.value) {
         case 'image':
@@ -33,14 +39,23 @@ const component = computed(() => {
             return Link
     }
 })
-const onTips = msg=> {
+const onTips = msg => {
     tips.value = msg
 }
 const onMediaChange = type => {
     media.value = type
+    notify.value.type = type
 }
+onMounted(() => {
+    notify.value.type = notify.value?.type || 'image'
+ if (!notify.value.media) {
+        notify.value.media = {}
+    }
+    
+})
 </script>
 <style lang="scss" scoped>
+
 .medias {
     ul,
     li {