ソースを参照

perf: ux

1. program editing and preview
2. support the search of resolved media sources
Casper Dai 3 年 前
コミット
4529d7a4ca

+ 36 - 22
src/components/table/mixins/table.js

@@ -76,30 +76,34 @@ export default {
   },
   watch: {
     proxy () {
-      this.onPagination()
-    }
-  },
-  created () {
-    this.customOptions = this.createOptions(this.schema.condition)
+      this.queueStatus()
+    },
+    schema: {
+      handler () {
+        this.customOptions = this.createOptions(this.schema.condition)
 
-    this.buttons = this.schema.buttons?.map(({ type, ...item }) => {
-      return {
-        ...ButtonConfig[type],
-        ...item
-      }
-    })
+        this.buttons = this.schema.buttons?.map(({ type, ...item }) => {
+          return {
+            ...ButtonConfig[type],
+            ...item
+          }
+        })
 
-    const filterMap = {}
-    this.filters = this.schema.filters?.map(filter => {
-      filterMap[filter.key] = filter
-      return {
-        ...filter,
-        loading: false,
-        loaded: !!filter.remote
-      }
-    })
-    this.filterMap = filterMap
-    this.onPagination()
+        const filterMap = {}
+        this.filters = this.schema.filters?.map(filter => {
+          filterMap[filter.key] = filter
+          return {
+            ...filter,
+            loading: false,
+            loaded: !!filter.remote
+          }
+        })
+        this.filterMap = filterMap
+
+        this.queueStatus()
+      },
+      immediate: true
+    }
   },
   activated () {
     if (this.schema.keepalive) {
@@ -107,6 +111,16 @@ export default {
     }
   },
   methods: {
+    queueStatus () {
+      if (!this.$flushing) {
+        this.$flushing = true
+        this.$nextTick(this.flushStatus)
+      }
+    },
+    flushStatus () {
+      this.$flushing = false
+      this.onPagination()
+    },
     createOptions (condition) {
       return {
         loading: false,

+ 3 - 2
src/mixins/dv.js

@@ -10,10 +10,11 @@ export default {
     }
   },
   render (h) {
-    return h(this.canEdit ? 'Designer' : 'Viewer', {
+    const canEdit = this.canEdit
+    return h(canEdit ? 'Designer' : 'Viewer', {
       props: {
         ...this.$attrs,
-        status: this.canEdit ? State.READY : State.RESOLVED
+        status: canEdit ? State.READY : State.RESOLVED
       }
     })
   }

+ 36 - 6
src/views/bigscreen/ast/Designer.vue

@@ -168,8 +168,10 @@
                 :node="item"
                 :root="node"
                 @focus="onWidgetFocus(index)"
+                @will-move="onWidgetWillMove"
                 @blur="onWidgetBlur"
                 @menu="onWidgetMenu($event, index)"
+                @dblclick.native="onWidgetDblclick"
               />
             </div>
           </div>
@@ -513,13 +515,18 @@ export default {
         node.top = top
         node.left = left
         this.node.widgets.push(node)
+        this.$nextTick(() => {
+          this.selectedWidgetIndex = this.widgets.length - 1
+        })
       }
     },
     onWidgetFocus (index) {
       this.selectedWidgetIndex = index
-      this.grid = true
       this.visibleContentMenu = false
     },
+    onWidgetWillMove () {
+      this.grid = true
+    },
     onWidgetBlur () {
       this.grid = false
     },
@@ -527,6 +534,12 @@ export default {
       this.selectedWidgetIndex = index
       this.onRightClick(evt)
     },
+    onWidgetDblclick () {
+      const attr = this.dynamicOptions[0]?.list.find(({ type }) => type === 'data')
+      if (attr) {
+        this.onEditData(attr)
+      }
+    },
     onLayerClick (evt, index) {
       this.selectedWidgetIndex = this.widgets.length - 1 - index
       this.onRightClick(evt)
@@ -552,11 +565,20 @@ export default {
     // 删除
     deleteLayer () {
       this.widgets.splice(this.selectedWidgetIndex, 1)
-      this.selectedWidgetIndex = -1
+      this.$nextTick(() => {
+        this.selectedWidgetIndex = -1
+      })
     },
     // 复制
     copyLayer () {
       this.widgets.splice(this.selectedWidgetIndex + 1, 0, copy(this.widget))
+      this.$message({
+        type: 'success',
+        message: '复制成功,已为您选择复制后的组件'
+      })
+      this.$nextTick(() => {
+        this.selectedWidgetIndex += 1
+      })
     },
     // 置顶
     topLayer () {
@@ -565,7 +587,9 @@ export default {
           this.selectedWidgetIndex,
           1
         )[0])
-        this.selectedWidgetIndex = this.widgets.length - 1
+        this.$nextTick(() => {
+          this.selectedWidgetIndex = this.widgets.length - 1
+        })
       }
     },
     // 置底
@@ -575,7 +599,9 @@ export default {
           this.selectedWidgetIndex,
           1
         )[0])
-        this.selectedWidgetIndex = 0
+        this.$nextTick(() => {
+          this.selectedWidgetIndex = 0
+        })
       }
     },
     // 上移一层
@@ -586,7 +612,9 @@ export default {
           1,
           this.widgets[this.selectedWidgetIndex]
         )[0]
-        this.selectedWidgetIndex += 1
+        this.$nextTick(() => {
+          this.selectedWidgetIndex += 1
+        })
       }
     },
     // 下移一层
@@ -597,7 +625,9 @@ export default {
           1,
           this.widgets[this.selectedWidgetIndex]
         )[0]
-        this.selectedWidgetIndex -= 1
+        this.$nextTick(() => {
+          this.selectedWidgetIndex -= 1
+        })
       }
     },
     changeAttr (value) {

+ 32 - 5
src/views/bigscreen/ast/Viewer.vue

@@ -56,7 +56,7 @@
               />
             </div>
             <div
-              v-if="node"
+              v-if="hasRootAssets"
               ref="rootElement"
               key="root"
               class="o-layer"
@@ -90,6 +90,7 @@
           <widget
             v-for="item in widgets"
             :key="item.id"
+            :class="{ 'breathe-inset': item.id === selectedWidgetId }"
             :node="item"
             @click.native="onWidgetClick(item, $event)"
           />
@@ -132,6 +133,13 @@ export default {
     },
     selectedWidgetId () {
       return this.selectedWidget?.id
+    },
+    hasRootAssets () {
+      if (this.node) {
+        const { backgroundImage, bgm } = this.node
+        return backgroundImage.length > 0 || bgm.length > 0
+      }
+      return false
     }
   },
   watch: {
@@ -139,7 +147,7 @@ export default {
       this.node && setTimeout(() => {
         if (this.selectedWidget) {
           this.$refs.widgetElements?.find(element => element.classList.contains('active'))?.scrollIntoView({ behavior: 'smooth', block: 'start' })
-        } else {
+        } else if (this.hasRootAssets) {
           this.$refs.rootElement.scrollIntoView({ behavior: 'smooth', block: 'start' })
         }
       })
@@ -157,9 +165,9 @@ export default {
       }
     },
     onWidgetClick (widget, evt) {
-      if (!this.selectedWidget || this.selectedWidget.id !== widget.id) {
-        if (this.hasAssets(widget)) {
-          evt.stopPropagation()
+      if (this.hasAssets(widget)) {
+        evt.stopPropagation()
+        if (!this.selectedWidget || this.selectedWidget.id !== widget.id) {
           this.selectedWidget = widget
         }
       }
@@ -305,4 +313,23 @@ export default {
     background-color: $blue;
   }
 }
+
+.breathe-inset::after {
+  content: "";
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  animation: breathe-inset 1s ease-in-out infinite alternate;
+}
+
+@keyframes breathe-inset {
+  0% {
+    box-shadow: inset 0 0 20px 30px rgba($blue, 0.5);
+  }
+  100% {
+    box-shadow: inset 0 0 60px 30px $blue;
+  }
+}
 </style>

+ 9 - 0
src/views/bigscreen/ast/components/DynamicItem.vue

@@ -32,6 +32,7 @@
       type="textarea"
       size="mini"
       placeholder="请填写内容"
+      :rows="5"
       @keydown.enter.native="onKeyEnter($event)"
       @change="onTextAreaChange"
     />
@@ -225,7 +226,12 @@ export default {
   watch: {
     node: {
       handler () {
+        if (this.$valWatch) {
+          this.$valWatch()
+          this.$valWatch = null
+        }
         if (this.isTextArea) {
+          this.$valWatch = this.$watch(`node.${[this.attrKey]}`, this.onValChanged)
           this.inputValue = this.node[this.attrKey]
         }
       },
@@ -262,6 +268,9 @@ export default {
     onTextAreaChange () {
       this.node[this.attrKey] = this.inputValue
       this.onChange()
+    },
+    onValChanged () {
+      this.inputValue = this.node[this.attrKey]
     }
   }
 }

+ 22 - 14
src/views/bigscreen/ast/core/components/Draggable.vue

@@ -5,11 +5,11 @@
     @mousedown.stop="handleMouseDown"
     @contextmenu.prevent
   >
-    <el-input
+    <!-- <el-input
       ref="input"
       v-model="value"
       :class="b('focus')"
-    />
+    /> -->
     <div
       ref="wrapper"
       :class="b('wrapper')"
@@ -219,13 +219,13 @@ export default {
     }
   },
   watch: {
-    active (val) {
-      if (val) {
-        this.handleKeydown()
-      } else {
-        document.onkeydown = this.keyDown
-      }
-    },
+    // active (val) {
+    //   if (val) {
+    //     this.handleKeydown()
+    //   } else {
+    //     document.onkeydown = this.keyDown
+    //   }
+    // },
     top (val) {
       this.baseTop = getFixed(val)
     },
@@ -289,7 +289,7 @@ export default {
       this.baseHeight = getFixed(this.height) || this.children.offsetHeight
       this.baseLeft = getFixed(this.left)
       this.baseTop = getFixed(this.top)
-      this.keyDown = document.onkeydown
+      // this.keyDown = document.onkeydown
     },
     setMove (left, top) {
       this.$emit('move', {
@@ -309,6 +309,7 @@ export default {
     },
     handleClear () {
       document.onmouseup = () => {
+        clearTimeout(this.$timer)
         document.onmousemove = null
         document.onmouseup = null
         this.onMouseUp()
@@ -398,9 +399,9 @@ export default {
       if (this.disabled || this.lock) {
         return
       }
-      this.$nextTick(() => {
-        this.$refs.input.focus()
-      })
+      // this.$nextTick(() => {
+      //   this.$refs.input.focus()
+      // })
       this.active = true
       if (e.button !== 0) {
         this.$emit('menu', e)
@@ -412,10 +413,17 @@ export default {
         width: this.baseWidth,
         height: this.baseHeight
       })
-      this.moveActive = true
+      this.$timer = setTimeout(() => {
+        this.moveActive = true
+        this.$emit('will-move')
+      }, 100)
       let disX = e.clientX
       let disY = e.clientY
       document.onmousemove = (e) => {
+        if (!this.moveActive) {
+          this.moveActive = true
+          this.$emit('will-move')
+        }
         const left = e.clientX - disX
         const top = e.clientY - disY
         disX = e.clientX

+ 32 - 3
src/views/bigscreen/ast/core/widget/CText.vue

@@ -1,10 +1,13 @@
 <template>
   <div
+    ref="text"
     class="c-text"
     :style="styles"
-  >
-    {{ node.text }}
-  </div>
+    :contenteditable="contenteditable"
+    @focus="onFocus"
+    @blur="onBlur"
+    v-text="node.text"
+  />
 </template>
 
 <script>
@@ -16,6 +19,10 @@ export default {
     node: {
       type: Object,
       default: null
+    },
+    editable: {
+      type: [Boolean, String],
+      default: false
     }
   },
   computed: {
@@ -38,6 +45,28 @@ export default {
         'text-align': textAlign,
         'background-color': backgroundColor
       }
+    },
+    contenteditable () {
+      return this.editable ? 'plaintext-only' : false
+    }
+  },
+  methods: {
+    onFocus () {
+      console.log('onFocus')
+      document.addEventListener('keydown', this.onKeydown)
+    },
+    onBlur () {
+      console.log('onBlur')
+      this.node.text = this.$refs.text.innerText
+      this.$nextTick(() => {
+        this.$refs.text.scrollTop = 0
+      })
+      document.removeEventListener('keydown', this.onKeydown)
+    },
+    onKeydown (e) {
+      if (e.keyCode === 27) {
+        this.$refs.text.blur()
+      }
     }
   }
 }

+ 1 - 0
src/views/bigscreen/ast/core/widget/Widget.vue

@@ -12,6 +12,7 @@
     <component
       :is="widgetType"
       :node="node"
+      editable
     />
   </draggable>
 </template>

+ 6 - 3
src/views/platform/media/mixin.js

@@ -28,19 +28,19 @@ export default {
       [AssetType.IMAGE]: {
         [State.READY]: createListOptions({ type: AssetType.IMAGE, status: State.READY }),
         [State.SUBMITTED]: createListOptions({ type: AssetType.IMAGE, status: State.SUBMITTED }),
-        [State.RESOLVED]: createListOptions({ type: AssetType.IMAGE, status: State.RESOLVED }),
+        [State.RESOLVED]: createListOptions({ type: AssetType.IMAGE, status: State.RESOLVED, originalName: '' }),
         [State.REJECTED]: createListOptions({ type: AssetType.IMAGE, status: State.REJECTED })
       },
       [AssetType.VIDEO]: {
         [State.READY]: createListOptions({ type: AssetType.VIDEO, status: State.READY }),
         [State.SUBMITTED]: createListOptions({ type: AssetType.VIDEO, status: State.SUBMITTED }),
-        [State.RESOLVED]: createListOptions({ type: AssetType.VIDEO, status: State.RESOLVED }),
+        [State.RESOLVED]: createListOptions({ type: AssetType.VIDEO, status: State.RESOLVED, originalName: '' }),
         [State.REJECTED]: createListOptions({ type: AssetType.VIDEO, status: State.REJECTED })
       },
       [AssetType.AUDIO]: {
         [State.READY]: createListOptions({ type: AssetType.AUDIO, status: State.READY }),
         [State.SUBMITTED]: createListOptions({ type: AssetType.AUDIO, status: State.SUBMITTED }),
-        [State.RESOLVED]: createListOptions({ type: AssetType.AUDIO, status: State.RESOLVED }),
+        [State.RESOLVED]: createListOptions({ type: AssetType.AUDIO, status: State.RESOLVED, originalName: '' }),
         [State.REJECTED]: createListOptions({ type: AssetType.AUDIO, status: State.REJECTED })
       }
     }
@@ -50,6 +50,9 @@ export default {
       return {
         list: getAssets,
         transform: this.transform,
+        filters: this.active === `${State.RESOLVED}` ? [
+          { key: 'originalName', type: 'search', placeholder: '媒资名称' }
+        ] : [],
         cols: [
           { prop: 'file', label: '文件', type: 'asset', on: this.onViewAsset },
           { prop: 'name', 'min-width': 120, render: this.active === `${State.READY}` ? (data, h) => {