Quellcode durchsuchen

feat(dashboard): use time range to obtain device scheduling

Casper Dai vor 2 Jahren
Ursprung
Commit
94ba0d5169

+ 12 - 1
src/utils/cache/screenshot.js

@@ -25,6 +25,13 @@ export function watch ({ productId, id }, cb, expired) {
   }
 }
 
+export function check (deviceId, expired) {
+  const inst = cache.get(deviceId)
+  if (retry(inst, expired)) {
+    screenshot(deviceId, true)
+  }
+}
+
 function retry (inst, expired = 30000) {
   if (!inst) {
     return false
@@ -47,7 +54,7 @@ export function unwatch (deviceId) {
 
 export function screenshot (deviceId, silence) {
   const inst = cache.get(deviceId)
-  if (inst) {
+  if (inst && !inst.waiting) {
     inst.waiting = true
     inst.timestamp = Date.now()
     inst.base64 = null
@@ -117,3 +124,7 @@ export function remove (deviceId) {
     emit(inst)
   }
 }
+
+export function clear () {
+  cache.clear()
+}

+ 56 - 4
src/views/dashboard/Dashboard.vue

@@ -88,11 +88,16 @@
         count="100"
       />
     </div>
-    <div class="l-flex__auto l-grid--info c-sibling-item--v far u-overflow-y--auto">
+    <div
+      ref="deviceContainer"
+      class="l-flex__auto l-grid--info c-sibling-item--v far u-overflow-y--auto"
+    >
       <device
         v-for="item in deviceOptions.list"
         :key="item.id"
         :device="item"
+        :observer="observerDom"
+        :flag="item.flag"
       />
     </div>
     <div
@@ -114,6 +119,7 @@ import {
   subscribe,
   unsubscribe
 } from '@/utils/mqtt'
+import { ScreenshotCache } from '@/utils/cache'
 import {
   getDevicesByQuery,
   getDeviceStatisticsByPath
@@ -147,6 +153,7 @@ export default {
     ], this.onMessage)
   },
   beforeDestroy () {
+    ScreenshotCache.clear()
     clearTimeout(this.$timer)
     this.monitor = { loading: true }
     unsubscribe([
@@ -154,20 +161,58 @@ export default {
       '+/+/offline',
       '+/+/calendar/update'
     ], this.onMessage)
+    if (this.$observer) {
+      this.$observer.disconnect()
+      this.$observer = null
+    }
   },
   methods: {
+    observerDom (el) {
+      if (!this.$observer) {
+        this.$observer = new IntersectionObserver(entries => {
+          console.log('observer')
+          entries.forEach(entry => {
+            const inst = entry.target.__vue__
+            if (inst && inst.intoView && inst.outView) {
+              if (entry.isIntersecting) {
+                inst.intoView()
+              } else {
+                inst.outView()
+              }
+            } else {
+              const device = this.deviceOptions.map?.[entry.target.dataset.id]
+              if (device) {
+                if (entry.isIntersecting) {
+                  !device.flag && (device.flag = Date.now())
+                } else {
+                  device.flag && (device.flag = 0)
+                }
+              }
+            }
+          })
+        }, {
+          root: this.$refs.deviceContainer,
+          threshold: [0.25]
+        })
+      }
+      this.$observer.observe(el)
+    },
     onMessage (topic) {
       if (!this.deviceOptions.loaded) {
         return
       }
-      const result = /^\d+\/(\d+)\/(online|offline)$/.exec(topic)
+      const result = /^\d+\/(\d+)\/(online|offline|calendar\/update)$/.exec(topic)
       if (!result) {
         return
       }
       const deviceId = result[1]
       const status = result[2]
-      const device = this.deviceOptions.list.find(({ id }) => id === deviceId)
+      const device = this.deviceOptions.map?.[deviceId]
       if (device) {
+        if (status === 'calendar/update') {
+          device.flag = -Date.now()
+          return
+        }
         const onlineStatus = device.onlineStatus === 1 ? 'online' : 'offline'
         if (status === onlineStatus) {
           return
@@ -206,6 +251,7 @@ export default {
       this.monitor = monitor
       clearTimeout(this.$timer)
       this.deviceOptions = { loaded: false }
+      this.$observer?.disconnect()
       getDeviceStatisticsByPath(this.group.path).then(({ data }) => {
         const { deactivatedTotal, notConnectedTotal, offLineTotal, onLineTotal, total } = data
         monitor.total = total
@@ -239,8 +285,14 @@ export default {
         org: this.group.path
       }, { custom: true }).then(
         ({ data }) => {
-          options.list = data.sort(this.sort)
+          const map = {}
+          options.list = data.sort(this.sort).map(device => {
+            map[device.id] = device
+            device.flag = 0
+            return device
+          })
           options.loaded = true
+          options.map = map
         },
         ({ isCancel }) => {
           if (!isCancel && !this.monitor.loading) {

+ 151 - 80
src/views/dashboard/components/Device.vue

@@ -1,6 +1,7 @@
 <template>
   <div
     class="o-device u-pointer"
+    :data-id="device.id"
     @click="onClick"
   >
     <div class="l-flex__none l-flex--row c-sibling-item--v o-device__header u-font-size u-bold">
@@ -46,13 +47,13 @@
           <div class="l-flex__none l-flex--row c-sibling-item--v">
             <div class="u-relative">
               <span class="u-font-size--md u-bold">{{ current.startTime }}</span>
-              <span class="o-device__ymd u-color--info light u-font-size--xs">{{ current.startDate }}</span>
+              <span class="o-device__ymd u-color--black light u-font-size--xs">{{ current.startDate }}</span>
             </div>
             <template v-if="current.endDate">
               <span class="o-device__line" />
               <div class="u-relative">
                 <span class="u-font-size--md u-bold">{{ current.endTime }}</span>
-                <span class="o-device__ymd u-color--info light u-font-size--xs">{{ current.endDate }}</span>
+                <span class="o-device__ymd u-color--black light u-font-size--xs">{{ current.endDate }}</span>
               </div>
             </template>
           </div>
@@ -71,7 +72,7 @@
         {{ lastOnline }}
       </span>
     </div>
-    <div class="l-flex__none l-flex--row c-sibling-item--v o-device__next u-color--info light u-font-size--xs u-text--center">
+    <div class="l-flex__none l-flex--row c-sibling-item--v o-device__next u-color--black light u-font-size--xs u-text--center">
       <auto-text
         v-if="next"
         class="l-flex__fill"
@@ -89,14 +90,14 @@
 </template>
 
 <script>
-import { getTimeline } from '@/api/device'
-import { EventPriorityInfo } from '@/constant'
 import {
-  listen,
-  unlisten
-} from '@/utils/mqtt'
+  EventPriority,
+  EventPriorityInfo
+} from '@/constant'
+import { getTimelineByRange } from '@/api/device'
 import { ScreenshotCache } from '@/utils/cache'
 import {
+  ONE_DAY,
   isHit,
   getNearestHitDate,
   getFinishDate,
@@ -111,6 +112,14 @@ export default {
     device: {
       type: Object,
       required: true
+    },
+    observer: {
+      type: Function,
+      required: true
+    },
+    flag: {
+      type: Number,
+      required: true
     }
   },
   data () {
@@ -150,26 +159,76 @@ export default {
       return this.next ? `下一场:${this.next.startDate} ${this.next.startTime} ${this.next.name}` : ''
     }
   },
+  watch: {
+    flag () {
+      this.checkStatus()
+    }
+  },
   created () {
+    this.$timer = -1
+    this.$refreshTimer = -1
+  },
+  mounted () {
     if (this.isOnline) {
-      this.getTimeline()
-      listen(this.onMessage)
-      ScreenshotCache.watch(this.device, this.onScreenshotUpdate)
+      this.observer(this.$el)
     } else {
       ScreenshotCache.remove(this.device.id)
     }
   },
   beforeDestroy () {
-    if (this.isOnline) {
-      unlisten(this.onMessage)
+    if (this.$firstView) {
       ScreenshotCache.unwatch(this.device.id)
+      clearTimeout(this.$refreshTimer)
       clearTimeout(this.$timer)
       this.$timer = -1
     }
   },
   methods: {
+    intoView () {
+      if (!this.isOnline) {
+        return
+      }
+      if (!this.$firstView) {
+        ScreenshotCache.watch(this.device, this.onScreenshotUpdate)
+        this.$firstView = true
+      }
+      console.log(this.device.name, 'intoView')
+      this.$timer = null
+      if (!this.loadingTimeline) {
+        if (this.timeline && this.$maxDate - Date.now() > 300000) {
+          this.onUpdate()
+        } else {
+          this.getTimeline()
+        }
+      }
+    },
+    outView () {
+      if (!this.isOnline) {
+        return
+      }
+      console.log(this.device.name, 'outView')
+      clearTimeout(this.$refreshTimer)
+      clearTimeout(this.$timer)
+      this.$timer = -1
+    },
+    checkStatus () {
+      if (!this.isOnline) {
+        return
+      }
+      if (this.flag) {
+        if (this.flag > 0) {
+          this.intoView()
+        } else if (this.$timer === -1) {
+          this.timeline = null
+        } else {
+          this.getTimeline()
+        }
+      } else {
+        this.outView()
+      }
+    },
     screenshot () {
-      ScreenshotCache.screenshot(this.device.id)
+      ScreenshotCache.screenshot(this.device.id, true)
     },
     onScreenshotUpdate ({ waiting, base64 }) {
       this.isShotting = waiting
@@ -181,72 +240,79 @@ export default {
         params: { id: this.device.id }
       })
     },
-    onMessage (topic, message) {
-      if (message) {
-        const result = new RegExp(`${this.device.productId}/${this.device.id}/(.+)`).exec(topic)
-        if (result) {
-          switch (result[1]) {
-            case 'calendar/update':
-              this.onCalendarUpdate(message)
-              break
-            default:
-              break
+    getTimeline () {
+      clearTimeout(this.$refreshTimer)
+      clearTimeout(this.$timer)
+      this.loadingTimeline = true
+      this.timeline = null
+      this.current = null
+      this.next = null
+      const { from, to } = this.getTimeRange()
+      console.log(this.device.id, from, to)
+      this.$maxDate = toDate(to)
+      getTimelineByRange(this.device.id, from, to, { custom: true }).then(
+        events => {
+          const now = Date.now()
+          console.log(events)
+          this.timeline = events.filter((event, index) => {
+            const { priority, until } = event
+            const allow = priority > EventPriority.SHIM && (!until || now < toDate(until))
+            if (allow) {
+              event.key = index
+            }
+            return allow
+          }).sort((a, b) => {
+            if (b.priority === a.priority) {
+              return toDate(a.start) - toDate(b.start)
+            }
+            return b.priority - a.priority
+          })
+          this.onUpdate()
+          this.loadingTimeline = false
+        },
+        ({ isCancel }) => {
+          if (!isCancel && this.$timer !== -1) {
+            this.$timer = setTimeout(this.getTimeline, 2000)
+          } else {
+            this.loadingTimeline = false
           }
         }
-      }
+      )
     },
-    onCalendarUpdate (message) {
-      try {
-        this.onUpdate(JSON.parse(message).eventDetail || [])
-      } catch {
-        this.getTimeline()
+    getTimeRange () {
+      const startDateTime = parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:00')
+      const endDate = toDate(startDateTime)
+      endDate.setHours(endDate.getHours() + (new Date().getMinutes() >= 50 ? 2 : 1))
+      endDate.setMinutes(0)
+      return {
+        from: startDateTime,
+        to: parseTime(endDate, '{y}-{m}-{d} {h}:00:00')
       }
     },
-    onUpdate (events) {
+    onUpdate () {
       if (this.$timer === -1) {
         return
       }
-      clearTimeout(this.$timer)
-      if (events.length) {
-        const now = Date.now()
-        this.timeline = events.filter((event, index) => {
-          const { until } = event
-          const allow = !until || now < toDate(until)
-          if (allow) {
-            event.key = index
-          }
-          return allow
-        }).sort((a, b) => {
-          if (b.priority === a.priority) {
-            return toDate(a.start) - toDate(b.start)
-          }
-          return b.priority - a.priority
-        })
+      ScreenshotCache.check(60000)
+      const pointerDate = this.current?.pointerDate || this.next?.pointerDate
+      if (!pointerDate || pointerDate <= Date.now()) {
+        console.log('onUpdate', pointerDate, 'refresh')
         this.checkTimeline()
       } else {
-        this.timeline = null
-        this.current = null
-        this.next = null
-        this.loadingTimeline = false
+        console.log('onUpdate', pointerDate, 'use cache')
+        this.delayCheck(pointerDate, this.current?.event)
       }
-    },
-    getTimeline () {
-      clearTimeout(this.$timer)
-      this.loadingTimeline = true
-      getTimeline(this.device.id, { custom: true }).then(this.onUpdate).catch(({ isCancel }) => {
-        if (!isCancel && this.$timer !== -1) {
-          this.$timer = setTimeout(this.getTimeline, 2000)
-        }
-      })
+      clearTimeout(this.$refreshTimer)
+      // 避免多设备同时请求
+      this.$refreshTimer = setTimeout(this.getTimeline, this.$maxDate - Date.now() - 300000 + (Math.random() * 120 | 0) * 1000)
     },
     checkTimeline (target) {
-      this.loadingTimeline = false
       const now = new Date()
       let current = target
       if (!current) {
         for (let i = 0; i < this.timeline.length; i++) {
           const event = this.timeline[i]
-          if (!current && isHit(event, now)) {
+          if (isHit(event, now)) {
             current = event
             break
           }
@@ -268,34 +334,39 @@ export default {
           }
         }
       }
-      console.log('current', current, currentEndDate)
-      console.log('next', next, nextStartDate)
+      console.log(this.device.id, this.device.name)
+      console.log('current', current, 'next', next)
       if (nextStartDate && (!currentEndDate || currentEndDate > nextStartDate)) {
         currentEndDate = nextStartDate
       }
-      this.current = current && this.getEventInfo(current, getStartDate(current, now), currentEndDate)
-      this.next = next && this.getEventInfo(next, nextStartDate)
+      this.current = current && this.getEventInfo(current, getStartDate(current, now), currentEndDate, currentEndDate)
+      this.next = next && this.getEventInfo(next, nextStartDate, null, nextStartDate)
 
       clearTimeout(this.$timer)
       if (currentEndDate) {
         const minUntil = currentEndDate.getTime()
-        this.timeline = this.timeline.filter(({ until }) => !until || toDate(until).getTime() > minUntil)
-
-        const delay = currentEndDate - now
-        // delay有最大限制2^31-1,超过后会直接执行
-        // 当前限制最多1天
-        if (delay > 86400000) {
-          this.$timer = setTimeout(this.checkTimeline, 86400000, current)
-        } else {
-          this.$timer = setTimeout(this.checkTimeline, delay, next)
-        }
+        const minPriority = current ? current.priority : -1
+        const now = Date.now()
+        this.timeline = this.timeline.filter(({ priority, until }) => !until || now < toDate(until) && (toDate(until) >= minUntil || priority > minPriority))
+        this.delayCheck(currentEndDate, current)
+      }
+    },
+    delayCheck (pointerDate, current) {
+      const delay = pointerDate - Date.now()
+      // delay有最大限制2^31-1,超过后会直接执行
+      // 当前限制最多1天
+      if (delay >= ONE_DAY) {
+        this.$timer = setTimeout(this.checkTimeline, ONE_DAY, current)
+      } else {
+        this.$timer = setTimeout(this.checkTimeline, delay)
       }
     },
-    getEventInfo (event, startDate, endDate) {
-      const target = event.target
+    getEventInfo (event, startDate, endDate, pointerDate) {
+      const { priority, target } = event
       return {
-        target,
-        name: target.name || EventPriorityInfo[event.priority],
+        event,
+        pointerDate,
+        name: target.name || EventPriorityInfo[priority],
         startDate: parseTime(startDate, '{y}.{m}.{d}'),
         startTime: parseTime(startDate, '{h}:{i}:{s}'),
         endDate: endDate ? parseTime(endDate, '{y}.{m}.{d}') : '',