Explorar o código

feat: support PPT and PDF

Casper Dai %!s(int64=3) %!d(string=hai) anos
pai
achega
73002ad456

BIN=BIN
keycloak-theme/login.zip


+ 1 - 1
src/api/base.js

@@ -28,7 +28,7 @@ const Status = {
   [State.REJECTED]: [3, 4, 5],
   [State.REVIEW]: [1],
   [State.AVAILABLE]: [0, 1, 2],
-  [State.AVAILABLE_ASSET]: [0, 1]
+  [State.AVAILABLE_ASSET]: [-1, 0, 1]
 }
 export function addStatusScope ({ status, ...data }) {
   switch (status) {

+ 41 - 37
src/components/table/Table/Column.vue

@@ -1,6 +1,9 @@
 <script>
 import { getThumbnailUrl } from '@/api/asset'
-import { AssetType } from '@/constant'
+import {
+  State,
+  AssetType
+} from '@/constant'
 
 export default {
   name: 'SchemaTableColumn',
@@ -67,43 +70,44 @@ export default {
     },
     renderAsset ({ row: data }) {
       const value = this.getRenderValue(data)
-      if (value) {
-        const { on } = this.schema
-        if (value.thumbnail) {
-          return this.$createElement('auto-image', {
-            staticClass: `o-thumbnail${on ? ' u-pointer' : ''}`,
-            props: {
-              src: getThumbnailUrl(value.thumbnail, '64,fit'),
-              broken: value.type === AssetType.VIDEO ? 'video-broken' : 'image-broken'
-            },
-            nativeOn: on && { click: $event => {
-              $event.stopPropagation()
-              on(value, data)
-            } }
-          })
-        }
-        switch (value.type) {
-          case AssetType.VIDEO:
-          case AssetType.AUDIO:
-            return this.$createElement('SvgIcon', {
-              staticClass: `o-thumbnail${on ? ' u-pointer' : ''}`,
-              props: {
-                'icon-class': value.type === AssetType.VIDEO
-                  ? value.thumbnail === ''
-                    ? 'video-broken'
-                    : 'video-thumb'
-                  : 'audio-thumb'
-              },
-              on: on && { click: $event => {
-                $event.stopPropagation()
-                on(value, data)
-              } }
-            })
-          default:
-            break
-        }
+      if (!value || value.status === State.DRAFT) {
+        return '-'
       }
-      return '-'
+      const { on } = this.schema
+      if (value.thumbnail) {
+        return this.$createElement('auto-image', {
+          staticClass: `o-thumbnail${on ? ' u-pointer' : ''}`,
+          props: {
+            src: getThumbnailUrl(value.thumbnail, '64,fit'),
+            broken: value.type === AssetType.VIDEO ? 'video-broken' : 'image-broken'
+          },
+          nativeOn: on && { click: $event => {
+            $event.stopPropagation()
+            on(value, data)
+          } }
+        })
+      }
+      let svgIcon = 'video-thumb'
+      switch (value.type) {
+        case AssetType.VIDEO:
+          if (value.thumbnail === '') {
+            svgIcon = 'video-broken'
+          }
+          break
+        case AssetType.AUDIO:
+          svgIcon = 'audio-thumb'
+          break
+        default:
+          break
+      }
+      return this.$createElement('SvgIcon', {
+        staticClass: `o-thumbnail${on ? ' u-pointer' : ''}`,
+        props: { 'icon-class': svgIcon },
+        on: on && { click: $event => {
+          $event.stopPropagation()
+          on(value, data)
+        } }
+      })
     },
     renderChildren (data) {
       const { type, render } = this.schema

+ 3 - 1
src/constant.js

@@ -10,7 +10,9 @@ export const GATEWAY_CAMERA = `${GATEWAY_WS}${process.env.VUE_APP_CAMERA_PROXY}`
 export const AssetType = {
   IMAGE: 1,
   VIDEO: 2,
-  AUDIO: 3
+  AUDIO: 3,
+  PPT: 4,
+  PDF: 5
 }
 
 export const Role = {

+ 15 - 1
src/icons/svg/pdf.svg

@@ -1 +1,15 @@
-<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><path d="M869.073 277.307H657.111V65.344l211.962 211.963zm-238.232 26.27V65.344l-476.498-.054v416.957h714.73v-178.67H630.841zm-335.836 360.57c-5.07-3.064-10.944-5.133-17.61-6.201-6.67-1.064-13.603-1.6-20.81-1.6h-48.821v85.641h48.822c7.206 0 14.14-.532 20.81-1.6 6.665-1.065 12.54-3.133 17.609-6.202 5.064-3.063 9.134-7.406 12.208-13.007 3.065-5.602 4.6-12.937 4.6-22.011 0-9.07-1.535-16.408-4.6-22.01-3.074-5.603-7.144-9.94-12.208-13.01zM35.82 541.805v416.904h952.358V541.805H35.821zm331.421 191.179c-3.6 11.071-9.343 20.879-17.209 29.413-7.874 8.542-18.078 15.408-30.617 20.61-12.544 5.206-27.747 7.807-45.621 7.807h-66.036v102.45h-62.831V607.517h128.867c17.874 0 33.077 2.6 45.62 7.802 12.541 5.207 22.745 12.076 30.618 20.615 7.866 8.538 13.604 18.277 17.21 29.212 3.6 10.943 5.401 22.278 5.401 34.018 0 11.477-1.8 22.752-5.402 33.819zM644.9 806.417c-5.343 17.61-13.408 32.818-24.212 45.627-10.807 12.803-24.283 22.879-40.423 30.213-16.146 7.343-35.155 11.007-57.03 11.007h-123.26V607.518h123.26c18.41 0 35.552 2.941 51.428 8.808 15.873 5.869 29.618 14.671 41.22 26.412 11.608 11.744 20.674 26.411 27.217 44.02 6.535 17.61 9.803 38.288 9.803 62.035 0 20.81-2.67 40.02-8.003 57.624zm245.362-146.07h-138.07v66.03h119.66v48.829h-119.66v118.058h-62.83V607.518h200.9v52.829h-.001zm-318.2 25.611c-6.402-8.266-14.877-14.604-25.412-19.01-10.544-4.402-23.551-6.602-39.019-6.602h-44.825v180.088h56.029c9.07 0 17.872-1.463 26.415-4.401 8.535-2.932 16.14-7.802 22.812-14.609 6.665-6.8 12.007-15.667 16.007-26.61 4.003-10.94 6.003-24.275 6.003-40.021 0-14.408-1.4-27.416-4.202-39.019-2.8-11.607-7.406-21.542-13.808-29.816zm0 0"/></svg>
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 34 34" style="enable-background:new 0 0 34 34;" xml:space="preserve">
+<path d="M24.4,3H8C5.2,3,3,5.2,3,8v18c0,2.8,2.2,5,5,5h18c2.8,0,5-2.2,5-5V9.6L24.4,3z M25,6.4L27.6,9H26c-0.6,0-1-0.4-1-1V6.4z
+	 M29,26c0,1.7-1.3,3-3,3H8c-1.7,0-3-1.3-3-3V8c0-1.7,1.3-3,3-3h15v3c0,1.7,1.3,3,3,3h3V26z"/>
+<path d="M10,26.1c-0.7,0-1.4-0.3-1.8-1c-0.2-0.5-0.4-1.5,1-2.8c1-1,3.3-1.9,5.7-2.4c0.5-1.2,1-2.4,1.2-3.7c-0.9-1.4-1.7-2.9-2.4-4.4
+	c-0.4-1-0.3-2.1,0.3-3c0.6-0.7,1.5-1,2.4-0.9c1.1,0.3,2,1.1,2.2,2.2c0.2,1.9,0.1,3.9-0.4,5.8c0.7,1.1,1.4,2.2,2.2,3.2
+	c1.5-0.2,3,0,4.4,0.5c2.2,1.2,2.4,2.7,1.9,3.7c-0.6,1.2-2.6,1.8-4.7,0.6c-1-0.8-1.9-1.7-2.6-2.7c-1,0.1-2.2,0.2-3.3,0.5
+	c-0.2,0.3-0.4,0.7-0.6,1c-1.2,2-3.2,3.3-5.5,3.5L10,26.1z M13.4,22.3c-1,0.3-1.9,0.7-2.8,1.4c-0.1,0.1-0.2,0.3-0.3,0.4
+	C11.5,23.9,12.6,23.3,13.4,22.3L13.4,22.3z M22,21c0.3,0.4,0.7,0.8,1.1,1.1c0.5,0.3,1.1,0.4,1.7,0.3c0.1,0,0.2-0.1,0.2-0.1
+	c0-0.1-0.2-0.5-1.1-1C23.4,21.1,22.7,21,22,21z M17.6,18.4c-0.1,0.3-0.2,0.6-0.3,1l0.9-0.1L17.6,18.4z M16,9.8
+	c-0.1,0-0.3,0.1-0.4,0.2c-0.2,0.3-0.2,0.7,0,1c0.3,0.8,0.7,1.5,1.1,2.2c0.1-0.8,0.1-1.7,0.1-2.5C16.6,10.2,16.4,9.8,16,9.8L16,9.8z"
+	/>
+</svg>

+ 9 - 0
src/icons/svg/ppt.svg

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 34 34" style="enable-background:new 0 0 34 34;" xml:space="preserve">
+<path d="M24.4,3H8C5.2,3,3,5.2,3,8v18c0,2.8,2.2,5,5,5h18c2.8,0,5-2.2,5-5V9.6L24.4,3z M25,6.4L27.6,9H26c-0.6,0-1-0.4-1-1V6.4z
+	 M29,26c0,1.7-1.3,3-3,3H8c-1.7,0-3-1.3-3-3V8c0-1.7,1.3-3,3-3h15v3c0,1.7,1.3,3,3,3h3V26z"/>
+<path d="M19,11h-6v12c0,0.6,0.4,1,1,1s1-0.4,1-1v-4h4c2.2,0,4-1.8,4-4S21.2,11,19,11z M19,17h-4v-4h4c1.1,0,2,0.9,2,2S20.1,17,19,17
+	z"/>
+</svg>

+ 1 - 1
src/layout/components/Navbar/UploadDashboard/index.vue

@@ -66,7 +66,7 @@ export default {
   },
   computed: {
     accept () {
-      return '.png,.jpg,.jpeg,.gif,.mp4,audio/mpeg'
+      return '.png,.jpg,.jpeg,.gif,.mp4,audio/mpeg,.ppt,.pptx,application/pdf'
     }
   },
   created () {

+ 5 - 1
src/utils/upload.js

@@ -205,10 +205,14 @@ function getType ({ name, type }) {
   switch (true) {
     case /png|jpg|jpeg|gif/i.test(type):
       return AssetType.IMAGE
-    case /mp4/.test(type):
+    case /mp4/i.test(type):
       return AssetType.VIDEO
     case /audio\/mpeg/.test(type):
       return AssetType.AUDIO
+    case /application\/(vnd.ms-powerpoint|vnd.openxmlformats-officedocument.presentationml.presentation)/.test(type):
+      return AssetType.PPT
+    case /application\/pdf/.test(type):
+      return AssetType.PDF
     default:
       Message({
         type: 'warning',

+ 44 - 69
src/views/platform/media/MediaDesigner.vue

@@ -1,74 +1,31 @@
 <template>
-  <wrapper
-    :vertical="false"
-    fill
-    margin
-    padding
-    background
-  >
-    <div class="l-flex__none c-media-type">
-      <div
-        class="c-media-type__item section u-pointer"
-        :class="{ active: isImage }"
-        @click="to('IMAGE')"
-      >
-        <svg-icon
-          class="c-media-type__icon"
-          icon-class="image"
-        />
-        图片
-      </div>
-      <div
-        class="c-media-type__item section u-pointer"
-        :class="{ active: isVideo }"
-        @click="to('VIDEO')"
-      >
-        <svg-icon
-          class="c-media-type__icon"
-          icon-class="video"
-        />
-        视频
-      </div>
-      <div
-        class="c-media-type__item section u-pointer"
-        :class="{ active: isAudio }"
-        @click="to('AUDIO')"
-      >
-        <svg-icon
-          class="c-media-type__icon"
-          icon-class="audio"
-        />
-        音频
-      </div>
-    </div>
-    <div class="l-flex__auto l-flex--col">
-      <el-tabs
-        v-model="active"
-        class="c-tabs has-bottom-padding"
-      >
-        <el-tab-pane
-          label="待审核"
-          name="101"
-        />
-        <el-tab-pane
-          label="已审核"
-          name="2"
-        />
-        <el-tab-pane
-          label="驳回"
-          name="3"
-        />
-      </el-tabs>
-      <schema-table
-        v-if="type"
-        ref="table"
-        :schema="schema"
-        :proxy.sync="currOptions"
-        :header-cell-class-name="adjustHeader"
+  <div class="l-flex__auto l-flex--col">
+    <el-tabs
+      :value="active"
+      class="c-tabs has-bottom-padding"
+      @tab-click="onTabClick"
+    >
+      <el-tab-pane
+        label="待审核"
+        name="101"
       />
-    </div>
-    <preview-dialog ref="previewDialog" />
-  </wrapper>
+      <el-tab-pane
+        label="已审核"
+        name="2"
+      />
+      <el-tab-pane
+        label="驳回"
+        name="3"
+      />
+    </el-tabs>
+    <schema-table
+      v-if="isVaild"
+      ref="table"
+      :schema="schema"
+      :proxy.sync="currOptions"
+      :header-cell-class-name="adjustHeader"
+    />
+  </div>
 </template>
 
 <script>
@@ -82,6 +39,16 @@ import mixin from './mixin'
 export default {
   name: 'MediaDesigner',
   mixins: [mixin],
+  data () {
+    return {
+      active: `${State.AVAILABLE_ASSET}`
+    }
+  },
+  computed: {
+    isVaild () {
+      return this.type && this.active
+    }
+  },
   created () {
     addListener('uploaded', this.onUploaded)
   },
@@ -93,6 +60,14 @@ export default {
       if (this.type === type && Number(this.active) === State.AVAILABLE_ASSET) {
         this.$refs.table.pageTo(1)
       }
+    },
+    onTabClick ({ name: active }) {
+      if (this.active !== active) {
+        this.active = null
+        this.$nextTick(() => {
+          this.active = active
+        })
+      }
     }
   }
 }

+ 15 - 53
src/views/platform/media/MediaViewer.vue

@@ -1,63 +1,25 @@
 <template>
-  <wrapper
-    :vertical="false"
-    fill
-    margin
-    padding
-    background
-  >
-    <div class="l-flex__none c-media-type">
-      <div
-        class="c-media-type__item section u-pointer"
-        :class="{ active: isImage }"
-        @click="to('IMAGE')"
-      >
-        <svg-icon
-          class="c-media-type__icon"
-          icon-class="image"
-        />
-        图片
-      </div>
-      <div
-        class="c-media-type__item section u-pointer"
-        :class="{ active: isVideo }"
-        @click="to('VIDEO')"
-      >
-        <svg-icon
-          class="c-media-type__icon"
-          icon-class="video"
-        />
-        视频
-      </div>
-      <div
-        class="c-media-type__item section u-pointer"
-        :class="{ active: isAudio }"
-        @click="to('AUDIO')"
-      >
-        <svg-icon
-          class="c-media-type__icon"
-          icon-class="audio"
-        />
-        音频
-      </div>
-    </div>
-    <schema-table
-      v-if="type"
-      ref="table"
-      class="has-left-padding"
-      :schema="schema"
-      :proxy.sync="currOptions"
-      :header-cell-class-name="adjustHeader"
-    />
-    <preview-dialog ref="previewDialog" />
-  </wrapper>
+  <schema-table
+    v-if="type"
+    ref="table"
+    class="has-left-padding"
+    :schema="schema"
+    :proxy.sync="currOptions"
+    :header-cell-class-name="adjustHeader"
+  />
 </template>
 
 <script>
+import { State } from '@/constant'
 import mixin from './mixin'
 
 export default {
   name: 'MediaViewer',
-  mixins: [mixin]
+  mixins: [mixin],
+  data () {
+    return {
+      active: `${State.RESOLVED}`
+    }
+  }
 }
 </script>

+ 118 - 2
src/views/platform/media/index.vue

@@ -1,5 +1,82 @@
+<template>
+  <wrapper
+    :vertical="false"
+    fill
+    margin
+    padding
+    background
+  >
+    <div class="l-flex__none c-media-type">
+      <div
+        class="c-media-type__item section u-pointer"
+        :class="{ active: isImage }"
+        @click="to('IMAGE')"
+      >
+        <svg-icon
+          class="c-media-type__icon"
+          icon-class="image"
+        />
+        图片
+      </div>
+      <div
+        class="c-media-type__item section u-pointer"
+        :class="{ active: isVideo }"
+        @click="to('VIDEO')"
+      >
+        <svg-icon
+          class="c-media-type__icon"
+          icon-class="video"
+        />
+        视频
+      </div>
+      <div
+        class="c-media-type__item section u-pointer"
+        :class="{ active: isAudio }"
+        @click="to('AUDIO')"
+      >
+        <svg-icon
+          class="c-media-type__icon"
+          icon-class="audio"
+        />
+        音频
+      </div>
+      <div
+        class="c-media-type__item section u-pointer"
+        :class="{ active: isPPT }"
+        @click="to('PPT')"
+      >
+        <svg-icon
+          class="c-media-type__icon"
+          icon-class="ppt"
+        />
+        PPT
+      </div>
+      <div
+        class="c-media-type__item section u-pointer"
+        :class="{ active: isPDF }"
+        @click="to('PDF')"
+      >
+        <svg-icon
+          class="c-media-type__icon"
+          icon-class="pdf"
+        />
+        PDF
+      </div>
+    </div>
+    <component
+      :is="activeComponent"
+      :type="type"
+      @view="onView"
+    />
+    <preview-dialog ref="previewDialog" />
+  </wrapper>
+</template>
+
 <script>
-import dvMixin from '@/mixins/dv'
+import {
+  Access,
+  AssetType
+} from '@/constant'
 import Designer from './MediaDesigner'
 import Viewer from './MediaViewer'
 
@@ -9,7 +86,46 @@ export default {
     Designer,
     Viewer
   },
-  mixins: [dvMixin]
+  data () {
+    return {
+      type: AssetType.IMAGE
+    }
+  },
+  computed: {
+    activeComponent () {
+      return this.accessSet.has(Access.MANAGE_CALENDAR)
+        ? 'Designer'
+        : 'Viewer'
+    },
+    isImage () {
+      return this.type === AssetType.IMAGE
+    },
+    isVideo () {
+      return this.type === AssetType.VIDEO
+    },
+    isAudio () {
+      return this.type === AssetType.AUDIO
+    },
+    isPPT () {
+      return this.type === AssetType.PPT
+    },
+    isPDF () {
+      return this.type === AssetType.PDF
+    }
+  },
+  methods: {
+    to (type) {
+      if (this.type !== AssetType[type]) {
+        this.type = null
+        this.$nextTick(() => {
+          this.type = AssetType[type]
+        })
+      }
+    },
+    onView (asset) {
+      this.$refs.previewDialog.show(asset)
+    }
+  }
 }
 </script>
 

+ 29 - 15
src/views/platform/media/mixin.js

@@ -15,15 +15,13 @@ import {
 
 export default {
   props: {
-    status: {
+    type: {
       type: Number,
-      default: State.AVAILABLE_ASSET
+      default: AssetType.IMAGE
     }
   },
   data () {
     return {
-      type: AssetType.IMAGE,
-      active: `${this.status}`,
       [AssetType.IMAGE]: {
         [State.AVAILABLE_ASSET]: createListOptions({ type: AssetType.IMAGE, status: State.AVAILABLE_ASSET }),
         [State.RESOLVED]: createListOptions({ type: AssetType.IMAGE, status: State.RESOLVED, originalName: '' }),
@@ -38,6 +36,16 @@ export default {
         [State.AVAILABLE_ASSET]: createListOptions({ type: AssetType.AUDIO, status: State.AVAILABLE_ASSET }),
         [State.RESOLVED]: createListOptions({ type: AssetType.AUDIO, status: State.RESOLVED, originalName: '' }),
         [State.REJECTED]: createListOptions({ type: AssetType.AUDIO, status: State.REJECTED })
+      },
+      [AssetType.PPT]: {
+        [State.AVAILABLE_ASSET]: createListOptions({ type: AssetType.PPT, status: State.AVAILABLE_ASSET }),
+        [State.RESOLVED]: createListOptions({ type: AssetType.AUDIO, status: State.RESOLVED, originalName: '' }),
+        [State.REJECTED]: createListOptions({ type: AssetType.AUDIO, status: State.REJECTED })
+      },
+      [AssetType.PDF]: {
+        [State.AVAILABLE_ASSET]: createListOptions({ type: AssetType.PDF, status: State.AVAILABLE_ASSET }),
+        [State.RESOLVED]: createListOptions({ type: AssetType.AUDIO, status: State.RESOLVED, originalName: '' }),
+        [State.REJECTED]: createListOptions({ type: AssetType.AUDIO, status: State.REJECTED })
       }
     }
   },
@@ -69,7 +77,7 @@ export default {
               : data.name
             : null },
           this.active === `${State.REJECTED}` ? { prop: 'remark', label: '驳回原因' } : null,
-          this.isImage ? null : { prop: 'duration', label: '时长' },
+          this.isVideo || this.isAudio ? { prop: 'duration', label: '时长' } : null,
           { prop: 'size', label: '文件大小' },
           { prop: 'createTime', label: '上传时间' },
           { prop: 'ai', label: 'AI审核', type: 'tag', width: 100 },
@@ -96,6 +104,12 @@ export default {
     },
     isAudio () {
       return this.type === AssetType.AUDIO
+    },
+    isPPT () {
+      return this.type === AssetType.PPT
+    },
+    isPDF () {
+      return this.type === AssetType.PDF
     }
   },
   methods: {
@@ -107,7 +121,8 @@ export default {
       asset.file = {
         type: asset.type,
         url: asset.keyName,
-        thumbnail: asset.thumbnail
+        thumbnail: asset.thumbnail,
+        status: asset.status
       }
       asset.duration = parseDuration(asset.duration)
       asset.size = parseByte(asset.size)
@@ -160,19 +175,18 @@ export default {
           return null
       }
     },
-    to (type) {
-      if (this.type !== AssetType[type]) {
-        this.type = null
-        this.$nextTick(() => {
-          this.type = AssetType[type]
-        })
-      }
-    },
     onView ({ file }) {
       this.onViewAsset(file)
     },
     onViewAsset (asset) {
-      this.$refs.previewDialog.show(asset)
+      if (asset.status === State.DRAFT) {
+        this.$message({
+          type: 'warning',
+          message: '文件正在解析中,请稍后再试'
+        })
+        return
+      }
+      this.$emit('view', asset)
     },
     onEdit (asset) {
       if (!asset.name) {