Parcourir la source

feat: perfect dashboard

support the viewing of scheduling information and solve the same message refresh problem
Casper Dai il y a 3 ans
Parent
commit
03ffed3e02

+ 7 - 0
src/api/calendar.js

@@ -166,3 +166,10 @@ export function rejectRelease ({ id, name }, remark) {
     data: { remark }
   }, name)
 }
+
+export function getTimeline (deviceId) {
+  return request({
+    url: `/content/deviceCalender/${deviceId}`,
+    method: 'get'
+  })
+}

+ 5 - 1
src/components/AutoText/index.vue

@@ -20,7 +20,8 @@ export default {
     text: {
       type: String,
       default: ''
-    }
+    },
+    tag: null
   },
   data () {
     return {
@@ -30,6 +31,9 @@ export default {
   watch: {
     text () {
       this.$nextTick(this.checkSize)
+    },
+    tag () {
+      this.$nextTick(this.checkSize)
     }
   },
   mounted () {

+ 4 - 0
src/scss/bem/_layout.scss

@@ -10,6 +10,10 @@
 .l-flex--col {
   display: flex;
   flex-direction: column;
+
+  &.center {
+    align-items: center;
+  }
 }
 
 .l-flex__auto {

+ 150 - 35
src/views/dashboard/components/Device.vue

@@ -4,7 +4,7 @@
     :class="{ 'u-pointer': isOnline }"
     @click="askStatus"
   >
-    <div class="l-flex__none l-flex--row o-device__header">
+    <div class="l-flex__none l-flex--row o-device__block o-device__header">
       <i
         class="l-flex__none o-device__status dark"
         :class="statusClass"
@@ -33,34 +33,50 @@
     </div>
     <div
       v-if="shot"
-      class="l-flex__fill o-device__preview"
+      class="l-flex__fill o-device__block o-device__preview"
       :style="styles"
     />
-    <!-- <div class="l-flex__fill l-flex--col u-text-center">
-      <template v-if="online">
-        <auto-text
-          class="l-flex__none o-device__current"
-          text="深圳市宝安区海天路15号卓越宝中时代广场"
-        />
-        <div class="l-flex__fill o-device__time">
-          <span class="o-device__hms">
-            10:00
-            <span class="o-device__ymd">2021.10.27</span>
-          </span>
-          <span class="o-device__line" />
-          <span class="o-device__hms">
-            10:00
-            <span class="o-device__ymd">2021.10.27</span>
-          </span>
-        </div>
+    <div
+      v-if="isActivated"
+      class="l-flex__auto l-flex--col center o-device__block o-device__info"
+    >
+      <i
+        v-if="loadingTimeline"
+        class="l-flex__none el-icon-loading"
+      />
+      <template v-else>
+        <template v-if="current">
+          <auto-text
+            class="l-flex__none o-device__current"
+            :text="current.name"
+          />
+          <div class="l-flex__auto l-flex--row o-device__time">
+            <span class="o-device__hms">
+              {{ current.startTime }}
+              <span class="o-device__ymd">{{ current.startDate }}</span>
+            </span>
+            <span class="o-device__line" />
+            <span class="o-device__hms">
+              {{ current.endTime }}
+              <span class="o-device__ymd">{{ current.endDate }}</span>
+            </span>
+          </div>
+        </template>
+        <span
+          v-else
+          class="u-bold"
+        >
+          当前暂无节目
+        </span>
         <auto-text
+          v-if="next"
           class="l-flex__none o-device__next"
-          text="下一场:深圳市宝安区海天路15号卓越宝中时代广场"
+          :text="nextInfo"
         />
       </template>
-    </div> -->
+    </div>
     <auto-text
-      class="l-flex__none o-device__footer"
+      class="l-flex__none o-device__block o-device__footer"
       :text="address"
     />
     <el-dialog
@@ -100,6 +116,12 @@
 </template>
 
 <script>
+import { getProgram } from '@/api/program'
+import {
+  getSchedule,
+  getTimeline
+} from '@/api/calendar'
+import { parseTime } from '@/utils'
 import {
   listen,
   unlisten,
@@ -124,7 +146,11 @@ export default {
       loading: false,
       message: null,
       isShotting: false,
-      shot: null
+      shot: null,
+      timeline: [],
+      loadingTimeline: false,
+      current: null,
+      next: null
     }
   },
   computed: {
@@ -165,27 +191,32 @@ export default {
           : '未激活'
     },
     address () {
-      // return `位置:${this.device.address}`
       return `备注:${this.device.remark}`
     },
     styles () {
       return this.isOnline && this.shot ? {
         backgroundImage: `url("${this.shot}"`
       } : null
+    },
+    nextInfo () {
+      return this.next ? `下一场:${this.next.startDate} ${this.next.startTime} ${this.next.name}` : ''
     }
   },
   mounted () {
-    if (this.isOnline) {
+    if (this.isActivated) {
       listen(this.onMessage)
+      this.getTimeline()
     }
+    this.$timer = -1
   },
   beforeDestroy () {
-    if (this.isOnline) {
+    if (this.isActivated) {
       unlisten(this.onMessage)
       if (this.show) {
         this.handleClose()
       }
     }
+    clearTimeout(this.$timer)
   },
   methods: {
     askStatus () {
@@ -201,15 +232,18 @@ export default {
     },
     onMessage (topic, message) {
       if (message) {
-        const result = new RegExp(`${this.device.productId}/${this.device.id}/(.+)/reply`).exec(topic)
+        const result = new RegExp(`${this.device.productId}/${this.device.id}/(.+)`).exec(topic)
         if (result) {
           switch (result[1]) {
-            case 'status':
+            case 'status/reply':
               this.onAskReply(message)
               break
-            case 'screenshot':
+            case 'screenshot/reply':
               this.onScreenshotReply(message)
               break
+            case 'calendar/update':
+              this.onCalendarUpdate(message)
+              break
             default:
               break
           }
@@ -280,6 +314,81 @@ export default {
     },
     onRestartReply () {
       this.loading = false
+    },
+    onCalendarUpdate (message) {
+      clearTimeout(this.$timer)
+      try {
+        message = JSON.parse(message)
+        console.log(message)
+        this.timeline = (message.eventDetail || []).map(this.createItem)
+        this.checkTimeline()
+      } catch {
+        this.getTimeline()
+      }
+    },
+    createItem ({ programCalendarId, type, startTimestamp, endTimestamp }) {
+      const startDateTime = new Date(Number(startTimestamp))
+      const endDateTime = endTimestamp ? new Date(Number(endTimestamp)) : null
+      return {
+        type, startDateTime, endDateTime,
+        id: programCalendarId,
+        name: null,
+        startDate: parseTime(startDateTime, '{y}.{m}.{d}'),
+        startTime: parseTime(startDateTime, '{h}:{i}:{s}'),
+        endDate: endDateTime ? parseTime(endDateTime, '{y}.{m}.{d}') : '',
+        endTime: endDateTime ? parseTime(endDateTime, '{h}:{i}:{s}') : ''
+      }
+    },
+    getTimeline () {
+      this.loadingTimeline = true
+      getTimeline(this.device.id).then(({ data }) => {
+        this.timeline = (JSON.parse(data.eventDetail) || []).map(this.createItem)
+        this.checkTimeline()
+      }).catch(() => {
+        this.$timer = setTimeout(this.getTimeline, 2000)
+      })
+    },
+    checkTimeline () {
+      this.loadingTimeline = true
+      const now = Date.now()
+      const current = this.timeline.findIndex(({ startDateTime, endDateTime }) => {
+        return now >= startDateTime && (!endDateTime || now <= endDateTime)
+      })
+      this.current = this.timeline[current]
+      this.next = this.timeline[current + 1]
+      this.next && this.getName(this.next).then(name => {
+        this.next.name = name
+      })
+      this.getDetail()
+    },
+    finishTimeline () {
+      this.loadingTimeline = false
+      const time = this.current ? this.current.endDateTime : this.next ? this.next.startDateTime : null
+      clearTimeout(this.$timer)
+      if (time) {
+        this.$timer = setTimeout(this.checkTimeline, time - Date.now())
+      }
+    },
+    getName (item) {
+      if (item.id) {
+        if (item.name) {
+          return Promise.resolve(item.name)
+        }
+        return (item.type === 1 ? getProgram : getSchedule)(item.id).then(({ name }) => name)
+      }
+      return Promise.resolve('未知')
+    },
+    getDetail () {
+      if (this.current) {
+        this.getName(this.current).then(name => {
+          this.current.name = name
+          this.finishTimeline()
+        }).catch(() => {
+          this.$timer = setTimeout(this.getDetail, 2000)
+        })
+      } else {
+        this.finishTimeline()
+      }
     }
   }
 }
@@ -296,6 +405,10 @@ export default {
   background-color: #fff;
   background-size: contain;
 
+  &__block + &__block {
+    margin-top: $spacing;
+  }
+
   &__header {
     justify-self: flex-start;
     height: 24px;
@@ -346,7 +459,12 @@ export default {
 
   &__preview {
     padding-top: 50%;
-    margin: $spacing 0;
+    background-position: center center;
+    background-size: contain;
+    background-repeat: no-repeat;
+  }
+
+  &__info {
     background-position: center center;
     background-size: contain;
     background-repeat: no-repeat;
@@ -358,7 +476,7 @@ export default {
   }
 
   &__time {
-    margin-top: $spacing;
+    margin: $spacing 0 24px;
     font-size: 20px;
   }
 
@@ -385,6 +503,7 @@ export default {
   }
 
   &__next {
+    margin-top: $spacing;
     color: $gray;
     font-size: 12px;
   }
@@ -393,10 +512,6 @@ export default {
     font-size: 12px;
     font-weight: bold;
   }
-
-  &__header + &__footer {
-    margin-top: $spacing;
-  }
 }
 
 .o-shot {

+ 14 - 2
src/views/dashboard/index.vue

@@ -147,6 +147,7 @@ export default {
     subscribe([
       '+/+/online',
       '+/+/offline',
+      '+/+/calendar/update',
       '+/+/status/reply',
       '+/+/screenshot/reply'
     ], this.onMessage)
@@ -155,14 +156,25 @@ export default {
     unsubscribe([
       '+/+/online',
       '+/+/offline',
+      '+/+/calendar/update',
       '+/+/status/reply',
       '+/+/screenshot/reply'
     ], this.onMessage)
   },
   methods: {
     onMessage (topic) {
-      const result = /^(\d+)\/\d+\/(online|offline)$/.exec(topic)
-      if (result && (!this.product || this.product === result[1])) {
+      const result = /^(\d+)\/(\d+)\/(online|offline)$/.exec(topic)
+      if (result) {
+        if (this.product === result[1] && this.deviceOptions.loaded) {
+          const deviceId = result[2]
+          const device = this.deviceOptions.find(({ id }) => id === deviceId)
+          if (device && device.activate === 2) {
+            const status = result[3]
+            if (status === 'online' && device.onlineStatus === 1 || status === 'offline' && device.onlineStatus !== 1) {
+              return
+            }
+          }
+        }
         this.refreshDevices(true)
       }
     },

+ 91 - 71
src/views/schedule/timeline/index.vue

@@ -30,15 +30,15 @@
           />
         </el-select>
         <el-date-picker
-          v-model="deviceOptions.params.time"
+          v-model="timestamp"
           class="l-flex__none c-sibling-item u-pointer"
           type="date"
           placeholder="选择日期"
-          value-format="yyyy-MM-dd"
+          value-format="timestamp"
           :picker-options="pickerOptions"
           :editable="false"
           :clearable="false"
-          @change="search"
+          @change="onTimeChange"
         />
         <search-input
           v-model.trim="deviceOptions.params.name"
@@ -97,13 +97,13 @@
                 <el-link
                   class="u-pointer"
                   type="warning"
-                  @click.stop="getPrograms(item)"
+                  @click.stop="getTimeline(item)"
                 >
                   出错了,点击重试
                 </el-link>
               </div>
             </template>
-            <template v-else>
+            <template v-else-if="item.options.list.length">
               <div
                 v-for="(target, index) in item.options.list"
                 :key="index"
@@ -115,12 +115,28 @@
                 <template v-if="target.id">
                   <i class="l-flex__none c-program__img o-program" />
                   <div class="l-flex__auto">
-                    <div class="c-program__time u-ellipsis">{{ target.time }}</div>
-                    <div class="c-program__name u-ellipsis">{{ target.name }}</div>
+                    <auto-text
+                      class="c-program__time"
+                      :text="target.time"
+                      :tag="target.style.width"
+                    />
+                    <auto-text
+                      class="c-program__name"
+                      :text="target.name"
+                      :tag="target.style.width"
+                    />
+                    <!-- <div class="c-program__time u-ellipsis">{{ target.time }}</div>
+                    <div class="c-program__name u-ellipsis">{{ target.name }}</div> -->
                   </div>
                 </template>
               </div>
             </template>
+            <div
+              v-else
+              class="has-padding--h"
+            >
+              当前时段暂无节目
+            </div>
           </div>
         </div>
         <div
@@ -154,14 +170,17 @@ import {
   getProducts,
   getDevices
 } from '@/api/device'
+import { getTimeline } from '@/api/calendar'
 import {
   createListOptions,
   parseTime
 } from '@/utils'
+import AutoText from '@/components/AutoText'
 
 export default {
   name: 'ScheduleTimeline',
   components: {
+    AutoText
   },
   data () {
     return {
@@ -173,7 +192,6 @@ export default {
       firstLoadSize: 999,
       deviceOptions: createListOptions({
         productId: '',
-        time: parseTime(new Date(), '{y}-{m}-{d}'),
         name: '',
         pageSize: 4
       }),
@@ -184,6 +202,7 @@ export default {
         canPresent: false,
         canNext: false
       },
+      timestamp: 0,
       device: null,
       program: null
     }
@@ -216,7 +235,7 @@ export default {
   },
   created () {
     this.$timer = -1
-    this.initTimes(new Date())
+    this.initTimes(new Date(parseTime(new Date(), '{y}/{m}/{d}')).getTime())
     this.getProducts()
     this.getDevices()
   },
@@ -230,17 +249,16 @@ export default {
       const max = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 30)
       return date < min || date > max
     },
-    initTimes (date) {
-      this.$date = date
-      if (parseTime(new Date(), '{y}-{m}-{d}') === parseTime(date, '{y}-{m}-{d}')) {
+    initTimes (timestamp) {
+      this.timestamp = timestamp
+      if (parseTime(new Date(), '{y}-{m}-{d}') === parseTime(timestamp, '{y}-{m}-{d}')) {
         this.controlOptions.start = Math.min(20, new Date().getHours())
       } else {
         this.controlOptions.start = 0
       }
-      this._createTimes()
-      this._checkTime()
+      this._refreshTimes()
     },
-    _createTimes () {
+    _refreshTimes () {
       const start = this.controlOptions.start
       const times = []
       for (let i = 0; i < 5; i++) {
@@ -249,8 +267,11 @@ export default {
       this.controlOptions.times = times
       this.controlOptions.canPresent = start > 0
       this.controlOptions.canNext = start < 20
-      this.$startDateTime = new Date(this.$date.getFullYear(), this.$date.getMonth(), this.$date.getDate(), start)
-      this.$endDateTime = new Date(this.$date.getFullYear(), this.$date.getMonth(), this.$date.getDate(), start + 4)
+      this.$startDateTime = this.timestamp + start * 3600000
+      this.$endDateTime = this.timestamp + (start + 4) * 3600000
+
+      this._calc()
+      this.$timer = setInterval(this._calc, 1000)
     },
     _calc () {
       const now = Date.now()
@@ -262,21 +283,18 @@ export default {
         }
       }
     },
-    offsetTime (offset) {
-      this.controlOptions.start += offset
-      this._createTimes()
-      this._checkTime()
+    _refreshTimeline () {
       this.deviceOptions.list.forEach(device => {
         this._calcPrograms(device.options)
       })
-      if (this.programId && (this.program.startDateTime >= this.$endDateTime || this.program.endDateTime <= this.$startDateTime)) {
+      if (this.programId && (this.program.startDateTime >= this.$endDateTime || this.program.endDateTime && this.program.endDateTime <= this.$startDateTime)) {
         this.program = null
       }
     },
-    _checkTime () {
-      clearInterval(this.$timer)
-      this._calc()
-      this.$timer = setInterval(this._calc, 1000)
+    offsetTime (offset) {
+      this.controlOptions.start += offset
+      this._refreshTimes()
+      this._refreshTimeline()
     },
     _getProducts () {
       this.fetching = true
@@ -328,7 +346,7 @@ export default {
       getDevices(options.params).then(({ data, totalCount }) => {
         options.list = data.map(this.transform)
         options.totalCount = totalCount
-        options.list.forEach(this.getPrograms)
+        options.list.forEach(this.getTimeline)
       }, () => {
         options.error = true
         options.list = []
@@ -336,40 +354,29 @@ export default {
         options.loading = false
       })
     },
-    getPrograms (device) {
+    getTimeline (device) {
       const options = device.options
       options.error = false
       options.loading = true
-      new Promise((resolve, reject) => {
-        Math.random() > 0.7 ? reject() : setTimeout(() => resolve({
-          data: this.createPrograms()
-        }), 2000)
-      }).finally(() => {
+      getTimeline(device.id).finally(() => {
         options.loading = false
       }).then(({ data }) => {
-        options.programs = data
+        options.programs = (JSON.parse(data.eventDetail) || []).map(this.createProgram)
         this._calcPrograms(options)
       }, () => {
         options.error = true
         options.list = []
       })
     },
-    createPrograms () {
-      const programs = []
-      let startDateTime = new Date(Date.now() + ((0.5 - Math.random()) * 30 | 0) * 60000)
-      let endDateTime
-      for (let i = 0; i < 4; i++) {
-        endDateTime = new Date(startDateTime.getTime() + (30 + Math.random() * 180 | 0) * 60000)
-        programs.push({
-          id: Math.random().toString().slice(2),
-          name: startDateTime,
-          time: `${parseTime(startDateTime, '{h}:{i}')}-${parseTime(endDateTime, '{h}:{i}')}`,
-          startDateTime,
-          endDateTime
-        })
-        startDateTime = new Date(endDateTime.getTime() + (Math.random() * 60 | 0) * 60000)
+    createProgram ({ programCalendarId, type, startTimestamp, endTimestamp }) {
+      const startDateTime = new Date(Number(startTimestamp))
+      const endDateTime = endTimestamp ? new Date(Number(endTimestamp)) : null
+      return {
+        type, startDateTime, endDateTime,
+        id: programCalendarId || Math.random(),
+        name: `${parseTime(startDateTime, '{h}:{i}:{s}')}-${endDateTime ? parseTime(endDateTime, '{h}:{i}:{s}') : ''}`,
+        time: `${parseTime(startDateTime, '{y}.{m}.{d} {h}:{i}:{s}')} - ${endDateTime ? parseTime(endDateTime, '{y}.{m}.{d} {h}:{i}:{s}') : '未知'}`
       }
-      return programs
     },
     _calcPrograms (options) {
       if (options.loading || options.error) {
@@ -382,33 +389,43 @@ export default {
       let comparison = this.$startDateTime
       let i = 0
       while (remaining && i < programs.length) {
-        const { startDateTime, endDateTime } = programs[i]
-        if (startDateTime >= this.$endDateTime) {
-          break
+        const program = programs[i]
+        const { startDateTime, endDateTime } = program
+        i += 1
+        if (startDateTime >= this.$endDateTime || endDateTime && endDateTime <= this.$startDateTime) {
+          continue
         }
-        if (endDateTime > this.$startDateTime) {
-          if (startDateTime > comparison) {
-            arr.push({
-              style: { width: `${(startDateTime - comparison) / total}%` }
-            })
-            remaining -= startDateTime - comparison
-            comparison = startDateTime
-          }
-          const duration = Math.min(remaining, endDateTime - comparison)
+        if (startDateTime > comparison) {
           arr.push({
-            ...programs[i],
-            style: { width: `${duration / total}%` }
+            style: { width: `${(startDateTime - comparison) / total}%` }
           })
-          remaining -= duration
-          comparison = endDateTime
+          remaining -= startDateTime - comparison
+          comparison = startDateTime
         }
-        i += 1
+        if (!endDateTime) {
+          arr.push({
+            ...program,
+            style: { width: `${remaining / total}%` }
+          })
+          break
+        }
+        const duration = Math.min(remaining, endDateTime - comparison)
+        arr.push({
+          ...program,
+          style: { width: `${duration / total}%` }
+        })
+        remaining -= duration
+        comparison = endDateTime
       }
       options.list = arr
     },
     chooseProgram (device, program) {
       this.device = device
       this.program = program
+    },
+    onTimeChange (val) {
+      this.initTimes(val)
+      this._refreshTimeline()
     }
   }
 }
@@ -474,7 +491,7 @@ export default {
   }
 
   &__row.selected &__left::after {
-    content: '';
+    content: "";
     position: absolute;
     top: 0;
     left: 0;
@@ -529,7 +546,6 @@ export default {
     right: 0;
     bottom: 0;
     pointer-events: none;
-
   }
 
   &__mask {
@@ -539,10 +555,14 @@ export default {
     bottom: 0;
     color: #e51414;
     border-right: 1px solid currentColor;
-    background-image: linear-gradient(-90deg, rgba(#ff8a8a, .33) 0%, rgba(#fff, 0) 100%);
+    background-image: linear-gradient(
+      -90deg,
+      rgba(#ff8a8a, 0.33) 0%,
+      rgba(#fff, 0) 100%
+    );
 
     &::after {
-      content: '';
+      content: "";
       position: absolute;
       top: 0;
       right: -5px;
@@ -583,6 +603,6 @@ export default {
 }
 
 .o-program {
-  background: url('~@/assets/program_bg.png') 0 0 / 100% 100% no-repeat;
+  background: url("~@/assets/program_bg.png") 0 0 / 100% 100% no-repeat;
 }
 </style>