فهرست منبع

perf(ux): autoImage component

Casper Dai 3 سال پیش
والد
کامیت
43892b6b33
37فایلهای تغییر یافته به همراه415 افزوده شده و 332 حذف شده
  1. 161 0
      src/components/AutoImage/index.vue
  2. 2 4
      src/components/Schedule/ScheduleCalendar/ScheduleCalendarMonth.vue
  3. 1 2
      src/components/Schedule/ScheduleCalendar/ScheduleCalendarWeek.vue
  4. 22 5
      src/components/dialog/PreviewDialog/index.vue
  5. 1 1
      src/components/table/GridTable/index.vue
  6. 6 5
      src/components/table/Table/Column.vue
  7. 1 1
      src/components/table/Table/index.vue
  8. 1 4
      src/components/table/mixins/table.js
  9. 11 0
      src/icons/svg/image-broken.svg
  10. 0 0
      src/icons/svg/video-broken.svg
  11. 1 11
      src/scss/bem/_component.scss
  12. 8 0
      src/scss/bem/_ishas.scss
  13. 29 14
      src/scss/bem/_object.scss
  14. 10 0
      src/scss/bem/_utility.scss
  15. 32 46
      src/views/bigscreen/ast/Designer.vue
  16. 4 0
      src/views/bigscreen/ast/Viewer.vue
  17. 10 23
      src/views/bigscreen/ast/components/Card.vue
  18. 5 21
      src/views/bigscreen/ast/components/WidgetShortcut.vue
  19. 4 12
      src/views/bigscreen/ast/core/widget/CText.vue
  20. 1 4
      src/views/dashboard/components/Card.vue
  21. 1 4
      src/views/dashboard/components/Device.vue
  22. 10 13
      src/views/device/detail/components/DeviceInfo.vue
  23. 3 3
      src/views/device/detail/components/DeviceInvoke/DeviceNetwork/index.vue
  24. 3 3
      src/views/device/detail/components/DeviceInvoke/DeviceReboot.vue
  25. 3 3
      src/views/device/detail/components/DeviceInvoke/DeviceRestore.vue
  26. 3 3
      src/views/device/detail/components/DeviceInvoke/DeviceSwitch.vue
  27. 3 3
      src/views/device/detail/components/DeviceInvoke/DeviceTime.vue
  28. 3 3
      src/views/device/detail/components/DeviceInvoke/ScreenLight.vue
  29. 3 3
      src/views/device/detail/components/DeviceInvoke/ScreenSwitch.vue
  30. 3 3
      src/views/device/detail/components/DeviceInvoke/ScreenVolume.vue
  31. 0 16
      src/views/device/detail/components/DeviceInvoke/index.vue
  32. 11 5
      src/views/device/detail/components/DeviceRuntime/Download.vue
  33. 11 10
      src/views/device/detail/components/DeviceRuntime/Running.vue
  34. 10 15
      src/views/device/detail/components/DeviceRuntime/ScreenShot.vue
  35. 0 51
      src/views/device/detail/components/DeviceRuntime/index.vue
  36. 16 41
      src/views/device/detail/components/external/Sensors/Sensor.vue
  37. 22 0
      src/views/device/detail/index.vue

+ 161 - 0
src/components/AutoImage/index.vue

@@ -0,0 +1,161 @@
+<script>
+export default {
+  name: 'AutoImage',
+  props: {
+    src: {
+      type: String,
+      default: null
+    },
+    placeholder: {
+      type: String,
+      default: null
+    },
+    broken: {
+      type: String,
+      default: 'image-broken'
+    },
+    retry: {
+      type: [Boolean, String],
+      default: false
+    }
+  },
+  data () {
+    return {
+      status: 0
+    }
+  },
+  watch: {
+    src: {
+      handler (val) {
+        if (val) {
+          this.load()
+        } else {
+          this.setStatus()
+        }
+      },
+      immediate: true
+    }
+  },
+  beforeDestroy () {
+    this.setStatus()
+  },
+  methods: {
+    load () {
+      this.setStatus(1)
+      const img = new Image()
+      img.onload = () => {
+        this.setStatus(2)
+      }
+      img.onerror = () => {
+        this.setStatus(3)
+      }
+      this.$img = img
+      img.src = this.src
+    },
+    setStatus (status = 0) {
+      if (this.$img) {
+        this.$img.onload = this.$img.onerror = null
+        this.$img = null
+      }
+      this.status = status
+    },
+    createPlacehoder (h, val) {
+      return h('div', {
+        staticClass: 'l-flex--row center o-error-image'
+      }, [
+        this.createSvgIcon(h, val)
+      ])
+    },
+    createSvgIcon (h, icon) {
+      return h('svg-icon', {
+        staticClass: 'o-error-image__img',
+        props: {
+          'icon-class': icon
+        }
+      })
+    }
+  },
+  render (h) {
+    switch (this.status) {
+      case 1:
+        return h('div', {
+          staticClass: 'l-flex--row center o-error-image'
+        }, [
+          h('i', {
+            staticClass: 'el-icon-loading'
+          })
+        ])
+      case 2:
+        return h('img', {
+          staticClass: 'o-image',
+          attrs: {
+            src: this.src
+          }
+        })
+      case 3:
+        return this.retry
+          ? h('div', {
+            staticClass: 'o-error-image retry',
+            on: {
+              click: $event => {
+                $event.stopPropagation()
+                this.load()
+              }
+            }
+          }, [
+            this.createSvgIcon(h, this.broken),
+            h('i', {
+              staticClass: 'o-error-image__refresh el-icon-refresh'
+            })
+          ])
+          : this.createPlacehoder(h, this.broken)
+      default:
+        return this.placeholder ? this.createPlacehoder(h, this.placeholder) : null
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.o-image {
+  object-fit: contain;
+}
+
+.o-error-image {
+  position: relative;
+  overflow: hidden;
+
+  &__img {
+    width: 100%;
+    height: 100%;
+  }
+
+  &.retry:hover {
+    border-radius: $radius;
+    cursor: pointer;
+
+    &::before {
+      content: "";
+      position: absolute;
+      top: 0;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      background-color: rgba(#000, 0.5);
+    }
+
+    .o-error-image__refresh {
+      display: inline-block;
+    }
+  }
+
+  &__refresh {
+    display: none;
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    color: #fff;
+    transform: translate(-50%, -50%);
+  }
+}
+</style>

+ 2 - 4
src/components/Schedule/ScheduleCalendar/ScheduleCalendarMonth.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="l-flex--col c-month-calendar">
-    <div class="l-flex__none l-flex c-month-calendar__header">
+    <div class="l-flex__none l-flex c-month-calendar__header has-border">
       <div class="l-flex__auto u-readonly">周日</div>
       <div class="l-flex__auto u-readonly">周一</div>
       <div class="l-flex__auto u-readonly">周二</div>
@@ -9,7 +9,7 @@
       <div class="l-flex__auto u-readonly">周五</div>
       <div class="l-flex__auto u-readonly">周六</div>
     </div>
-    <div class="c-month-calendar__content">
+    <div class="c-month-calendar__content has-border">
       <div
         v-for="(week, weekIndex) in weeks"
         :key="weekIndex"
@@ -104,12 +104,10 @@ export default {
     padding: 10px 0;
     font-weight: bold;
     text-align: center;
-    border: 1px solid $info;
   }
 
   &__content {
     min-height: 0;
-    border: 1px solid $info;
     border-top: none;
     overflow-y: auto;
   }

+ 1 - 2
src/components/Schedule/ScheduleCalendar/ScheduleCalendarWeek.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="l-flex--col c-week-calendar">
+  <div class="l-flex--col c-week-calendar has-border">
     <div class="l-flex__none l-flex c-week-calendar__header">
       <div class="l-flex__none o-time header" />
       <div class="l-flex__fill l-flex">
@@ -220,7 +220,6 @@ export default {
   position: relative;
   color: $black;
   font-size: 16px;
-  border: 1px solid $info;
 
   &__header {
     padding: 10px 0;

+ 22 - 5
src/components/dialog/PreviewDialog/index.vue

@@ -6,14 +6,15 @@
     v-on="$listeners"
   >
     <template v-if="showDetail">
-      <img
+      <auto-image
         v-if="isImage"
-        class="c-preview__img"
+        class="o-image--preview"
         :src="assetUrl"
-      >
+        retry
+      />
       <video
         v-if="isVideo"
-        class="c-preview__video"
+        class="o-video--preview"
         :src="assetUrl"
         autoplay
         controls
@@ -36,7 +37,6 @@ import { AssetType } from '@/constant'
 
 export default {
   name: 'PreviewDialog',
-  components: {},
   data () {
     return {
       showDetail: false,
@@ -65,3 +65,20 @@ export default {
   }
 }
 </script>
+
+<style lang="scss" scoped>
+.o-image--preview {
+  min-width: 96px;
+  max-width: 100%;
+  min-height: 96px;
+  max-height: 80vh;
+  color: #fff;
+  font-size: 32px;
+  line-height: 0;
+}
+
+.o-video--preview {
+  width: 100%;
+  max-height: 80vh;
+}
+</style>

+ 1 - 1
src/components/table/GridTable/index.vue

@@ -14,7 +14,7 @@
         />
         <template v-if="buttons">
           <button
-            v-for="(button, index) in filteredButtons"
+            v-for="(button, index) in buttons"
             :key="index"
             class="o-button"
             @click="onClickButton(button)"

+ 6 - 5
src/components/table/Table/Column.vue

@@ -68,12 +68,13 @@ export default {
       if (value) {
         const { on } = this.schema
         if (value.thumbnail) {
-          return this.$createElement('i', {
+          return this.$createElement('auto-image', {
             staticClass: `o-thumbnail${on ? ' u-pointer' : ''}`,
-            staticStyle: {
-              'background-image': `url('${getThumbnailUrl(value.thumbnail, '64,fit')}')`
+            props: {
+              src: getThumbnailUrl(value.thumbnail, '64,fit'),
+              broken: value.type === AssetType.VIDEO ? 'video-broken' : 'image-broken'
             },
-            on: on && { click: $event => {
+            nativeOn: on && { click: $event => {
               $event.stopPropagation()
               on(value, data)
             } }
@@ -87,7 +88,7 @@ export default {
               props: {
                 'icon-class': value.type === AssetType.VIDEO
                   ? value.thumbnail === ''
-                    ? 'video-fail'
+                    ? 'video-broken'
                     : 'video-thumb'
                   : 'audio-thumb'
               },

+ 1 - 1
src/components/table/Table/index.vue

@@ -15,7 +15,7 @@
         />
         <template v-if="buttons">
           <button
-            v-for="(button, index) in filteredButtons"
+            v-for="(button, index) in buttons"
             :key="index"
             class="o-button"
             @click="onClickButton(button)"

+ 1 - 4
src/components/table/mixins/table.js

@@ -48,10 +48,7 @@ export default {
   },
   computed: {
     hasHeader () {
-      return this.$scopedSlots.header || this.buttons || this.filters
-    },
-    filteredButtons () {
-      return this.buttons.filter(({ render }) => !render || render())
+      return this.$scopedSlots.header || this.buttons?.length || this.filters?.length
     },
     tableData () {
       const { transformData } = this.schema

+ 11 - 0
src/icons/svg/image-broken.svg

@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="64" height="40" viewBox="0 0 64 40">
+  <defs>
+    <style>
+      .cls-1 {
+        fill: #d5d9e4;
+        fill-rule: evenodd;
+      }
+    </style>
+  </defs>
+  <path id="icon_image_fail_small" class="cls-1" d="M49,22l-9.018,8.789L34,21l4-9L35,6l6-6H64V37Zm2-12a3,3,0,1,0,3,3A3,3,0,0,0,51,10ZM28.814,19.168l-5.53-5.53L0,36.922V0H31L28,6l4,6Zm-5.53-2.7,7.8,7.8L45,39l-3.916-6.409L49,25,64,40H0V39.751Z"/>
+</svg>

+ 0 - 0
src/icons/svg/video-fail.svg → src/icons/svg/video-broken.svg


+ 1 - 11
src/scss/bem/_component.scss

@@ -345,19 +345,9 @@
     padding: 0;
     text-align: center;
   }
-
-  &__img {
-    max-width: 100%;
-    max-height: 80vh;
-  }
-
-  &__video {
-    width: 100%;
-    max-height: 80vh;
-  }
 }
 
-.el-table__body .c-thumbnail-col .cell {
+.c-thumbnail-col .cell {
   display: flex;
   justify-content: center;
   align-items: center;

+ 8 - 0
src/scss/bem/_ishas.scss

@@ -32,3 +32,11 @@
 .is-rotate {
   transform: rotate(180deg);
 }
+
+.has-border {
+  border: 1px solid $info;
+
+  &.radius {
+    border-radius: 2px;
+  }
+}

+ 29 - 14
src/scss/bem/_object.scss

@@ -73,16 +73,6 @@
   }
 }
 
-.o-icon--active {
-  &:hover {
-    color: #5cb6ff;
-  }
-
-  &:active {
-    color: $blue;
-  }
-}
-
 .o-tag {
   min-width: 80px;
   text-align: center;
@@ -111,12 +101,11 @@
 }
 
 .o-thumbnail {
-  display: inline-block;
+  display: inline-flex;
   width: 64px !important;
   height: 40px !important;
-  background-position: center;
-  background-size: contain;
-  background-repeat: no-repeat;
+  color: $blue;
+  font-size: 20px;
 }
 
 .o-select {
@@ -224,3 +213,29 @@
     line-height: 1;
   }
 }
+
+.o-icon {
+  display: inline-flex;
+  width: 64px;
+  height: 64px;
+
+  &.medium {
+    width: 32px;
+    height: 32px;
+  }
+
+  &.mini {
+    width: 24px;
+    height: 24px;
+  }
+}
+
+.o-icon--active {
+  &:hover {
+    color: $primary  !important;
+  }
+
+  &:active {
+    color: $blue  !important;
+  }
+}

+ 10 - 0
src/scss/bem/_utility.scss

@@ -22,6 +22,10 @@
   overflow-y: auto;
 }
 
+.u-color--blue {
+  color: $blue;
+}
+
 .u-color--primary {
   color: $primary;
 }
@@ -75,3 +79,9 @@
     align-self: center;
   }
 }
+
+.u-size--contain {
+  background-position: center center;
+  background-size: contain;
+  background-repeat: no-repeat;
+}

+ 32 - 46
src/views/bigscreen/ast/Designer.vue

@@ -19,22 +19,22 @@
         @click="onBack"
       />
       <span class="c-designer__name u-bold u-ellipsis">{{ program.name }}</span>
-      <span>{{ program.resolutionRatio }}</span>
-      <div class="l-flex__fill c-sibling-item" />
+      <span class="c-sibling-item">{{ program.resolutionRatio }}</span>
       <div
         v-if="hasNext"
-        class="l-flex--row center c-sibling-item c-designer__shortcut u-pointer"
+        class="c-sibling-item c-designer__shortcut u-pointer"
         @click="switchBgm"
       >
         <i class="o-next" />
       </div>
       <div
         v-if="hasAudio"
-        class="l-flex--row center c-sibling-item c-designer__shortcut u-pointer"
+        class="c-sibling-item c-designer__shortcut u-pointer"
         @click="toggleMute"
       >
         <volume :muted="muted" />
       </div>
+      <div class="l-flex__fill c-sibling-item" />
       <button
         v-if="hasWidgets"
         class="c-sibling-item o-button mini"
@@ -244,7 +244,7 @@
             class="c-card__tag"
           >{{ source.tag }}</div>
           <i
-            class="c-card__icon el-icon-delete"
+            class="c-card__icon el-icon-delete o-icon--active"
             @mousedown.stop
             @pointerdown.stop
             @click.stop="onDelAsset(index)"
@@ -315,7 +315,7 @@
                   class="c-card__checkbox"
                 />
                 <i
-                  class="el-icon-video-play c-card__type u-pointer"
+                  class="c-card__type el-icon-video-play o-icon--active u-pointer"
                   @click.stop="onView(item)"
                 />
               </card>
@@ -409,7 +409,7 @@ export default {
         condition: { originalName: '', type: this.assetType[0] },
         list: this.getAssets,
         transform: this.transformAsset,
-        filters: (this.assetType.length ? [
+        filters: (this.assetType.length > 1 ? [
           {
             key: 'type', type: 'select', options: this.assetType.map(type => {
               return {
@@ -759,12 +759,7 @@ export default {
       this.muted = this.$muted
     },
     onDelAsset (index) {
-      this.$confirm(
-        '移除该数据?',
-        { type: 'warning' }
-      ).then(() => {
-        this.sources.splice(index, 1)
-      })
+      this.sources.splice(index, 1)
     },
     onSourceDragStart () {
       this.dragging = true
@@ -1085,22 +1080,15 @@ $border: #242835;
   }
 }
 
-.c-grid.dragging .c-card .c-card__icon {
+.c-info-grid.dragging .c-card .c-card__icon {
   display: none;
 }
 
 .c-card {
-  &:hover &__icon {
-    display: block;
-    padding: 6px;
-    color: #fff;
-    border-radius: 50%;
-    background-color: rgba(#000, 0.5);
-    cursor: pointer;
-  }
+  color: $info;
 
-  &.sortable-chosen &__icon {
-    display: none;
+  &:hover &__icon {
+    display: inline-block;
   }
 
   &__icon {
@@ -1108,23 +1096,22 @@ $border: #242835;
     position: absolute;
     top: 0;
     right: 0;
+    padding: 6px;
+    font-size: 16px;
+    border-radius: 50%;
+    background-color: rgba(#000, 0.4);
     z-index: 9;
   }
 
-  &__type {
+  &__tag {
     position: absolute;
-    top: 50%;
-    left: 50%;
-    padding: 2px;
-    color: $gray;
-    font-size: 24px;
-    border-radius: 50%;
-    background-color: rgba(0, 0, 0, 0.5);
-    transform: translate(-50%, -50%);
-
-    &:hover {
-      color: $primary;
-    }
+    top: 0;
+    left: 0;
+    padding: 4px 6px;
+    font-size: 12px;
+    line-height: 1;
+    border-bottom-right-radius: $radius;
+    background-color: rgba(#000, 0.4);
   }
 
   &__checkbox {
@@ -1135,16 +1122,15 @@ $border: #242835;
     z-index: 9;
   }
 
-  &__tag {
+  &__type {
     position: absolute;
-    top: 0;
-    left: 0;
-    padding: 4px 6px;
-    color: #fff;
-    font-size: 12px;
-    line-height: 1;
-    border-bottom-right-radius: $radius;
-    background-color: rgba(#000, 0.5);
+    top: 50%;
+    left: 50%;
+    padding: 2px;
+    font-size: 24px;
+    border-radius: 50%;
+    background-color: rgba(#000, 0.4);
+    transform: translate(-50%, -50%);
   }
 }
 

+ 4 - 0
src/views/bigscreen/ast/Viewer.vue

@@ -226,6 +226,10 @@ export default {
     &:hover {
       background-color: $gray--light;
     }
+
+    &:active {
+      background-color: $gray;
+    }
   }
 
   &__name {

+ 10 - 23
src/views/bigscreen/ast/components/Card.vue

@@ -4,20 +4,13 @@
     @click="onClick"
     @dblclick="onDblclick"
   >
-    <div class="c-card__content">
-      <i
-        v-if="source.thumbnailUrl"
-        class="c-card__image"
-        :style="{ 'background-image': `url('${source.thumbnailUrl}')` }"
-      />
-      <svg-icon
-        v-else
-        class="c-card__image"
-        :icon-class="source.icon"
-      />
-      <div class="c-card__name u-ellipsis">{{ source.name }}</div>
-      <slot />
-    </div>
+    <auto-image
+      class="c-card__image"
+      :placeholder="source.icon"
+      :src="source.thumbnailUrl"
+    />
+    <div class="c-card__name u-ellipsis">{{ source.name }}</div>
+    <slot />
   </div>
 </template>
 
@@ -45,20 +38,16 @@ export default {
 .c-card {
   display: inline-block;
   position: relative;
+  padding-top: 60%;
+  color: $info;
   background-color: rgba(0, 0, 0, 0.8);
 
-  &__content {
-    position: relative;
-    padding-top: 60%;
-  }
-
   &__name {
     position: absolute;
     left: 0;
     right: 0;
     bottom: 0;
     padding: 4px 6px;
-    color: $gray;
     font-size: 14px;
     text-align: center;
     background-color: rgba(0, 0, 0, 0.6);
@@ -70,9 +59,7 @@ export default {
     left: 0;
     width: 100%;
     height: 100%;
-    background-position: center;
-    background-size: contain;
-    background-repeat: no-repeat;
+    font-size: 20px;
   }
 }
 </style>

+ 5 - 21
src/views/bigscreen/ast/components/WidgetShortcut.vue

@@ -6,7 +6,7 @@
     >
       <div
         v-if="customStyle"
-        class="o-widget-shortcut__thumbnail"
+        class="o-widget-shortcut__thumbnail u-size--contain"
         :style="customStyle"
       />
       <svg-icon
@@ -31,18 +31,13 @@
         class="o-widget-shortcut"
       >
         <div
-          class="o-widget-shortcut__content thumbnail radius"
+          class="o-widget-shortcut__content thumbnail"
           @click="onView(source)"
         >
-          <i
-            v-if="source.thumbnail"
+          <auto-image
             class="o-widget-shortcut__thumbnail"
-            :style="{ 'background-image': `url('${source.thumbnail}')` }"
-          />
-          <svg-icon
-            v-else
-            class="o-widget-shortcut__thumbnail"
-            :icon-class="source.icon"
+            :placeholder="source.icon"
+            :src="source.thumbnail"
           />
           <div
             v-if="needTag"
@@ -206,9 +201,6 @@ export default {
     position: relative;
     padding-top: 40%;
     background-color: rgba(#000, 0.05);
-    background-position: center;
-    background-size: contain;
-    background-repeat: no-repeat;
 
     &.thumbnail {
       padding-top: 60%;
@@ -218,11 +210,6 @@ export default {
         color: #fff !important;
       }
     }
-
-    &.radius {
-      border-radius: $radius;
-      overflow: hidden;
-    }
   }
 
   &__logo {
@@ -245,9 +232,6 @@ export default {
     left: 0;
     width: 100%;
     height: 100%;
-    background-position: center;
-    background-size: contain;
-    background-repeat: no-repeat;
   }
 
   &__assets {

+ 4 - 12
src/views/bigscreen/ast/core/widget/CText.vue

@@ -4,7 +4,6 @@
     class="c-text"
     :style="styles"
     :contenteditable="contenteditable"
-    @focus="onFocus"
     @blur="onBlur"
     v-text="node.text"
   />
@@ -51,22 +50,11 @@ export default {
     }
   },
   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()
-      }
     }
   }
 }
@@ -79,5 +67,9 @@ export default {
   word-wrap: break-word;
   white-space: pre-wrap;
   overflow: hidden;
+
+  &[contenteditable="plaintext-only"] {
+    cursor: text;
+  }
 }
 </style>

+ 1 - 4
src/views/dashboard/components/Card.vue

@@ -5,7 +5,7 @@
   >
     <div class="o-card__title">
       <i
-        class="l-flex__none o-card__icon"
+        class="l-flex__none o-card__icon has-bg"
         :class="type"
       />
       <div class="l-flex__fill u-text-center">
@@ -104,9 +104,6 @@ export default {
     width: 36px;
     height: 36px;
     margin-right: $spacing;
-    background-position: 0 0;
-    background-size: 100% 100%;
-    background-repeat: no-repeat;
 
     &.info {
       background-image: url("~@/assets/icon_info.png");

+ 1 - 4
src/views/dashboard/components/Device.vue

@@ -33,7 +33,7 @@
     </div>
     <div
       v-if="shot"
-      class="l-flex__fill o-device__block o-device__preview"
+      class="l-flex__fill o-device__block o-device__preview u-size--contain"
       :style="styles"
     />
     <div
@@ -356,9 +356,6 @@ export default {
 
   &__preview {
     padding-top: 50%;
-    background-position: center center;
-    background-size: contain;
-    background-repeat: no-repeat;
   }
 
   &__info {

+ 10 - 13
src/views/device/detail/components/DeviceInfo.vue

@@ -37,8 +37,16 @@
         <div class="l-flex__fill c-info__value">{{ device.resolutionRatio }}</div>
       </div>
       <div class="l-flex--row l-flex__fill c-info__item">
-        <div class="l-flex__none c-info__title">创建时间</div>
-        <div class="l-flex__fill c-info__value">{{ device.createTime }}</div>
+        <template v-if="__STAGING__">
+          <div class="l-flex__none c-info__title">内容保护</div>
+          <div class="l-flex__fill c-info__value primary">
+            <content-protection :device="device" />
+          </div>
+        </template>
+        <template v-else>
+          <div class="l-flex__none c-info__title">创建时间</div>
+          <div class="l-flex__fill c-info__value">{{ device.createTime }}</div>
+        </template>
       </div>
     </div>
     <div class="l-flex--row c-info__block">
@@ -47,17 +55,6 @@
         <div class="l-flex__fill c-info__value primary">{{ device.remark }}</div>
       </div>
     </div>
-    <div
-      v-if="__STAGING__"
-      class="l-flex--row c-info__block"
-    >
-      <div class="l-flex--row l-flex__fill c-info__item">
-        <div class="l-flex__none c-info__title">内容保护</div>
-        <div class="l-flex__fill c-info__value primary">
-          <content-protection :device="device" />
-        </div>
-      </div>
-    </div>
     <confirm-dialog
       ref="editDialog"
       title="编辑"

+ 3 - 3
src/views/device/detail/components/DeviceInvoke/DeviceNetwork/index.vue

@@ -1,7 +1,7 @@
 <template>
-  <div class="l-flex--col center o-invoke has-padding">
+  <div class="l-flex--col center has-border radius has-padding">
     <i
-      class="o-invoke__icon u-pointer"
+      class="o-icon has-bg u-pointer"
       @click="invoke"
     />
     <div class="has-padding u-color--black u-bold">网络设置</div>
@@ -379,7 +379,7 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.o-invoke__icon {
+.o-icon {
   background-image: url("~@/assets/icon_screen_network.png");
 }
 </style>

+ 3 - 3
src/views/device/detail/components/DeviceInvoke/DeviceReboot.vue

@@ -1,10 +1,10 @@
 <template>
   <div
     v-loading="rebooting"
-    class="l-flex--col center o-invoke has-padding"
+    class="l-flex--col center has-border radius has-padding"
   >
     <i
-      class="o-invoke__icon u-pointer"
+      class="o-icon has-bg u-pointer"
       @click="invoke"
     />
     <div class="has-padding u-color--black u-bold">重启设备</div>
@@ -62,7 +62,7 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.o-invoke__icon {
+.o-icon {
   background-image: url("~@/assets/icon_device_reboot.png");
 }
 </style>

+ 3 - 3
src/views/device/detail/components/DeviceInvoke/DeviceRestore.vue

@@ -1,7 +1,7 @@
 <template>
-  <div class="l-flex--col center o-invoke has-padding">
+  <div class="l-flex--col center has-border radius has-padding">
     <i
-      class="o-invoke__icon u-pointer"
+      class="o-icon has-bg u-pointer"
       @click="invoke"
     />
     <div class="has-padding u-color--black u-bold">恢复出厂</div>
@@ -41,7 +41,7 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.o-invoke__icon {
+.o-icon {
   background-image: url("~@/assets/icon_device_restore.png");
 }
 </style>

+ 3 - 3
src/views/device/detail/components/DeviceInvoke/DeviceSwitch.vue

@@ -1,7 +1,7 @@
 <template>
-  <div class="l-flex--col center o-invoke has-padding">
+  <div class="l-flex--col center has-border radius has-padding">
     <i
-      class="o-invoke__icon u-pointer"
+      class="o-icon has-bg u-pointer"
       @click="invoke"
     />
     <div class="has-padding u-color--black u-bold">开关设备</div>
@@ -67,7 +67,7 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.o-invoke__icon {
+.o-icon {
   background-image: url("~@/assets/icon_device_switch.png");
 }
 </style>

+ 3 - 3
src/views/device/detail/components/DeviceInvoke/DeviceTime.vue

@@ -1,7 +1,7 @@
 <template>
-  <div class="l-flex--col center o-invoke has-padding">
+  <div class="l-flex--col center has-border radius has-padding">
     <i
-      class="o-invoke__icon u-pointer"
+      class="o-icon has-bg u-pointer"
       @click="invoke"
     />
     <div class="has-padding u-color--black u-bold">时间校对</div>
@@ -22,7 +22,7 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.o-invoke__icon {
+.o-icon {
   background-image: url("~@/assets/icon_device_time.png");
 }
 </style>

+ 3 - 3
src/views/device/detail/components/DeviceInvoke/ScreenLight.vue

@@ -1,7 +1,7 @@
 <template>
-  <div class="l-flex--col center o-invoke has-padding ">
+  <div class="l-flex--col center has-border radius has-padding ">
     <i
-      class="o-invoke__icon u-pointer"
+      class="o-icon has-bg u-pointer"
       @click="invoke"
     />
     <div class="has-padding u-color--black u-bold">{{ type }}</div>
@@ -85,7 +85,7 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.o-invoke__icon {
+.o-icon {
   background-image: url("~@/assets/icon_screen_light.png");
 }
 </style>

+ 3 - 3
src/views/device/detail/components/DeviceInvoke/ScreenSwitch.vue

@@ -1,7 +1,7 @@
 <template>
-  <div class="l-flex--col center o-invoke has-padding">
+  <div class="l-flex--col center has-border radius has-padding">
     <i
-      class="o-invoke__icon u-pointer"
+      class="o-icon has-bg u-pointer"
       @click="invoke"
     />
     <div class="has-padding u-color--black u-bold">开关大屏</div>
@@ -67,7 +67,7 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.o-invoke__icon {
+.o-icon {
   background-image: url("~@/assets/icon_screen_switch.png");
 }
 </style>

+ 3 - 3
src/views/device/detail/components/DeviceInvoke/ScreenVolume.vue

@@ -1,7 +1,7 @@
 <template>
-  <div class="l-flex--col center o-invoke has-padding ">
+  <div class="l-flex--col center has-border radius has-padding ">
     <i
-      class="o-invoke__icon u-pointer"
+      class="o-icon has-bg u-pointer"
       @click="invoke"
     />
     <div class="has-padding u-color--black u-bold">{{ type }}</div>
@@ -85,7 +85,7 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.o-invoke__icon {
+.o-icon {
   background-image: url("~@/assets/icon_screen_volume.png");
 }
 </style>

+ 0 - 16
src/views/device/detail/components/DeviceInvoke/index.vue

@@ -40,19 +40,3 @@ export default {
   }
 }
 </script>
-
-<style lang="scss">
-.o-invoke {
-  border: 1px solid $info;
-  border-radius: 2px;
-
-  &__icon {
-    display: inline-block;
-    width: 64px;
-    height: 64px;
-    background-position: 0 0;
-    background-size: 100% 100%;
-    background-repeat: no-repeat;
-  }
-}
-</style>

+ 11 - 5
src/views/device/detail/components/DeviceRuntime/Download.vue

@@ -1,18 +1,18 @@
 <template>
-  <div class="l-flex--col c-runtime">
+  <div class="l-flex--col o-device-grid-item has-border radius">
     <div class="l-flex--row l-flex__none has-bottom-padding">
-      <i class="l-flex__none c-runtime__icon download" />
-      <span class="l-flex__fill c-runtime__title u-ellipsis">文件下载</span>
+      <i class="l-flex__none o-device-grid-item__icon o-icon medium has-bg" />
+      <span class="l-flex__fill u-color--info u-ellipsis">文件下载</span>
       <i
         v-if="count"
-        class="l-flex__none c-runtime__list el-icon-s-operation u-pointer"
+        class="l-flex__none o-device-grid-item__large u-color--blue el-icon-s-operation u-pointer"
         @click="showDownload"
       />
     </div>
     <div class="l-flex__fill u-text-center">
       <div class="has-bottom-padding">当前已下载文件</div>
       <div class="u-color--black">
-        <span class="large">{{ downloadCount }}</span> 个
+        <span class="o-device-grid-item__largest">{{ downloadCount }}</span> 个
       </div>
     </div>
     <div
@@ -70,3 +70,9 @@ export default {
   }
 }
 </script>
+
+<style lang="scss" scoped>
+.o-device-grid-item__icon {
+  background-image: url("~@/assets/icon_download.png");
+}
+</style>

+ 11 - 10
src/views/device/detail/components/DeviceRuntime/Running.vue

@@ -1,8 +1,8 @@
 <template>
-  <div class="l-flex--col c-runtime">
+  <div class="l-flex--col o-device-grid-item has-border radius">
     <div class="l-flex__none l-flex--row">
-      <i class="l-flex__none c-runtime__icon running" />
-      <span class="l-flex__fill c-runtime__title u-ellipsis">状态</span>
+      <i class="l-flex__none o-device-grid-item__icon o-icon medium has-bg" />
+      <span class="l-flex__fill u-color--info u-ellipsis">状态</span>
       <template v-if="online">
         <i
           v-if="asking"
@@ -10,14 +10,14 @@
         />
         <i
           v-else
-          class="l-flex__none o-invoke u-pointer"
+          class="l-flex__none o-icon mini has-bg u-pointer"
           @click="invoke"
         />
       </template>
     </div>
     <div
       v-if="!asking"
-      class="l-flex__fill l-flex--row center medium has-bottom-padding u-color--black u-bold"
+      class="l-flex__fill l-flex--row center o-device-grid-item__large has-bottom-padding u-color--black u-bold"
     >
       {{ online ? status : '离线' }}
     </div>
@@ -91,10 +91,11 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.o-invoke {
-  display: inline-block;
-  width: 24px;
-  height: 24px;
-  background: url("~@/assets/icon_screenshot.png") 0 0 / 100% 100% no-repeat;
+.o-icon {
+  background-image: url("~@/assets/icon_screenshot.png");
+
+  &.o-device-grid-item__icon {
+    background-image: url("~@/assets/icon_condition.png");
+  }
 }
 </style>

+ 10 - 15
src/views/device/detail/components/DeviceRuntime/ScreenShot.vue

@@ -1,20 +1,20 @@
 <template>
-  <div class="l-flex--col c-runtime">
+  <div class="l-flex--col o-device-grid-item has-border radius">
     <div class="l-flex__none l-flex--row has-bottom-padding">
-      <i class="l-flex__none c-runtime__icon screenshot" />
-      <span class="l-flex__fill c-runtime__title u-ellipsis">截屏</span>
+      <i class="l-flex__none o-device-grid-item__icon o-icon medium has-bg" />
+      <span class="l-flex__fill u-color--info u-ellipsis">截屏</span>
       <i
         v-if="asking"
         class="l-flex__none el-icon-loading u-color--black"
       />
       <i
         v-else
-        class="l-flex__none o-invoke u-pointer"
+        class="l-flex__none o-icon mini has-bg u-pointer"
         @click="invoke"
       />
     </div>
     <div
-      class="l-flex__fill o-preview"
+      class="l-flex__fill o-preview u-size--contain"
       :style="styles"
     />
   </div>
@@ -61,16 +61,11 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.o-invoke {
-  display: inline-block;
-  width: 24px;
-  height: 24px;
-  background: url("~@/assets/icon_screenshot.png") 0 0 / 100% 100% no-repeat;
-}
+.o-icon {
+  background-image: url("~@/assets/icon_screenshot.png");
 
-.o-preview {
-  background-position: center center;
-  background-size: contain;
-  background-repeat: no-repeat;
+  &.o-device-grid-item__icon {
+    background-image: url("~@/assets/icon_image.png");
+  }
 }
 </style>

+ 0 - 51
src/views/device/detail/components/DeviceRuntime/index.vue

@@ -22,54 +22,3 @@ export default {
   }
 }
 </script>
-
-<style lang="scss">
-.c-runtime {
-  height: 180px;
-  padding: 12px 16px 16px;
-  color: $info;
-  font-size: 14px;
-  line-height: 1;
-  border: 1px solid $info;
-  border-radius: 2px;
-
-  .medium {
-    font-size: 18px;
-  }
-
-  .large {
-    font-size: 32px;
-  }
-
-  &__icon {
-    display: inline-block;
-    width: 32px;
-    height: 32px;
-    margin-right: 8px;
-    background-position: 0 0;
-    background-size: 100% 100%;
-    background-repeat: no-repeat;
-
-    &.running {
-      background-image: url("~@/assets/icon_condition.png");
-    }
-
-    &.screenshot {
-      background-image: url("~@/assets/icon_image.png");
-    }
-
-    &.download {
-      background-image: url("~@/assets/icon_download.png");
-    }
-  }
-
-  &__title {
-    color: $info--dark;
-  }
-
-  &__list {
-    color: $blue;
-    font-size: 18px;
-  }
-}
-</style>

+ 16 - 41
src/views/device/detail/components/external/Sensors/Sensor.vue

@@ -1,14 +1,14 @@
 <template>
-  <div class="l-flex--col o-sensor">
+  <div class="l-flex--col o-device-grid-item o-sensor has-border radius">
     <div class="l-flex--row l-flex__none">
       <i
-        class="l-flex__none o-sensor__icon"
+        class="l-flex__none o-device-grid-item__icon o-icon has-bg"
         :class="type"
       />
-      <span class="l-flex__fill o-sensor__title u-ellipsis">{{ title }}</span>
+      <span class="l-flex__fill u-color--info u-ellipsis">{{ title }}</span>
       <i
         v-if="enough"
-        class="l-flex__none o-sensor__list el-icon-s-operation u-pointer"
+        class="l-flex__none o-device-grid-item__large u-color--blue el-icon-s-operation u-pointer"
         @click="show = true"
       />
     </div>
@@ -132,50 +132,25 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.o-sensor {
-  height: 180px;
-  padding: 12px 16px 16px;
-  color: $info;
-  font-size: 14px;
-  line-height: 1;
-  border: 1px solid $info;
-  border-radius: 2px;
-
-  &__icon {
-    display: inline-block;
-    width: 32px;
-    height: 32px;
-    margin-right: 8px;
-    background-position: 0 0;
-    background-size: 100% 100%;
-    background-repeat: no-repeat;
-
-    &.temperature {
-      background-image: url("~@/assets/icon_temperature.png");
-    }
-
-    &.smoke {
-      background-image: url("~@/assets/icon_smoke.png");
-    }
-
-    &.flooding {
-      background-image: url("~@/assets/icon_flooding.png");
-    }
+.o-device-grid-item__icon {
+  &.temperature {
+    background-image: url("~@/assets/icon_temperature.png");
+  }
 
-    &.light {
-      background-image: url("~@/assets/icon_light.png");
-    }
+  &.smoke {
+    background-image: url("~@/assets/icon_smoke.png");
   }
 
-  &__title {
-    color: $info--dark;
+  &.flooding {
+    background-image: url("~@/assets/icon_flooding.png");
   }
 
-  &__list {
-    color: $blue;
-    font-size: 18px;
+  &.light {
+    background-image: url("~@/assets/icon_light.png");
   }
+}
 
+.o-sensor {
   &__current {
     margin-top: 6px;
     color: $info;

+ 22 - 0
src/views/device/detail/index.vue

@@ -223,3 +223,25 @@ export default {
   }
 }
 </style>
+
+<style lang="scss">
+.o-device-grid-item {
+  height: 180px;
+  padding: 12px 16px 16px;
+  color: $info;
+  font-size: 14px;
+  line-height: 1;
+
+  &__large {
+    font-size: 18px;
+  }
+
+  &__largest {
+    font-size: 32px;
+  }
+
+  &__icon {
+    margin-right: 8px;
+  }
+}
+</style>