| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- <template>
- <div
- class="o-device u-pointer"
- @click="onClick"
- >
- <div class="l-flex__none l-flex--row o-device__header">
- <auto-text
- class="l-flex__fill"
- :text="name"
- />
- <template v-if="isOnline">
- <i
- v-if="isShotting"
- class="l-flex__none el-icon-loading"
- @click.stop
- />
- <i
- v-else
- class="l-flex__none o-device__shot"
- @click.stop="screenshot"
- />
- </template>
- <div
- class="l-flex__none o-device__tip"
- :class="statusClass"
- >
- <span class="u-color--white">{{ statusTip }}</span>
- </div>
- </div>
- <div
- class="l-flex__fill o-device__preview u-size--contain"
- :class="{ offline: !isOnline, empty: !shot }"
- :style="styles"
- />
- <div class="l-flex__auto l-flex--col center o-device__info">
- <template v-if="isOnline">
- <i
- v-if="loadingTimeline"
- class="l-flex__none el-icon-loading"
- />
- <template v-else-if="current">
- <auto-text
- class="l-flex__none o-device__current"
- :text="current.target.name"
- />
- <div class="l-flex__none l-flex--row o-device__time">
- <span class="o-device__hms">
- {{ current.startTime }}
- <span class="o-device__ymd">{{ current.startDate }}</span>
- </span>
- <template v-if="current.endDate">
- <span class="o-device__line" />
- <span class="o-device__hms">
- {{ current.endTime }}
- <span class="o-device__ymd">{{ current.endDate }}</span>
- </span>
- </template>
- </div>
- </template>
- <span
- v-else
- class="l-flex__auto l-flex--row center o-device__current"
- >
- 当前暂无节目
- </span>
- </template>
- <template v-else>
- <span class="l-flex__auto l-flex--row center o-device__current">
- 当前设备离线了
- </span>
- </template>
- </div>
- <div class="l-flex__none o-device__next">
- <auto-text
- v-if="next"
- :text="nextInfo"
- />
- </div>
- <div class="l-flex__none l-flex--row o-device__footer">
- <i class="l-flex__none o-device__icon el-icon-location-outline" />
- <auto-text
- class="l-flex__auto"
- :text="address"
- />
- </div>
- </div>
- </template>
- <script>
- import { getTimeline } from '@/api/device'
- import {
- listen,
- unlisten
- } from '@/utils/mqtt'
- import { ScreenshotCache } from '@/utils/cache'
- import {
- isHit,
- getNearestHitDate,
- getFinishDate,
- getStartDate,
- toDate
- } from '@/utils/event'
- import { parseTime } from '@/utils'
- export default {
- name: 'DeviceCard',
- props: {
- device: {
- type: Object,
- required: true
- }
- },
- data () {
- return {
- isShotting: false,
- shot: null,
- loadingTimeline: false,
- current: null,
- next: null
- }
- },
- computed: {
- name () {
- return this.device.name
- },
- address () {
- return `${this.device.remark}`
- },
- isOnline () {
- return this.device.onlineStatus === 1
- },
- statusClass () {
- return this.isOnline ? 'u-color--success dark' : 'u-color--error dark'
- },
- statusTip () {
- return this.isOnline ? '在线' : '离线'
- },
- styles () {
- return this.isOnline && this.shot ? { backgroundImage: `url("${this.shot}")` } : null
- },
- nextInfo () {
- return this.next ? `下一场:${this.next.startDate} ${this.next.startTime} ${this.next.target.name}` : ''
- }
- },
- created () {
- if (this.isOnline) {
- this.getTimeline()
- listen(this.onMessage)
- ScreenshotCache.watch(this.device, this.onScreenshotUpdate)
- } else {
- ScreenshotCache.remove(this.device.id)
- }
- },
- beforeDestroy () {
- if (this.isOnline) {
- unlisten(this.onMessage)
- ScreenshotCache.unwatch(this.device.id)
- clearTimeout(this.$timer)
- this.$timer = -1
- }
- },
- methods: {
- screenshot () {
- ScreenshotCache.screenshot(this.device.id)
- },
- onScreenshotUpdate ({ waiting, base64 }) {
- this.isShotting = waiting
- this.shot = waiting ? null : base64
- },
- onClick () {
- this.$router.push({
- name: 'device-detail',
- 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
- }
- }
- }
- },
- onCalendarUpdate (message) {
- clearTimeout(this.$timer)
- try {
- this.onUpdate(JSON.parse(message).eventDetail || [])
- } catch {
- this.getTimeline()
- }
- },
- onUpdate (events) {
- if (this.$timer === -1) {
- return
- }
- if (events.length) {
- const now = Date.now()
- this.timeline = events.filter(({ until }) => !until || now < new Date(until)).sort((a, b) => {
- if (b.priority === a.priority) {
- return toDate(a.start) - toDate(b.start)
- }
- return b.priority - a.priority
- })
- this.checkTimeline()
- } else {
- clearTimeout(this.$timer)
- this.timeline = null
- this.current = null
- this.next = null
- this.loadingTimeline = false
- }
- },
- getTimeline () {
- 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)
- }
- })
- },
- checkTimeline (target) {
- this.loadingTimeline = false
- const now = new Date()
- let current = target
- let currentEndDate = null
- let next = null
- if (!current) {
- for (let i = 0; i < this.timeline.length; i++) {
- const event = this.timeline[i]
- if (!current && isHit(event, now)) {
- current = event
- break
- }
- }
- }
- currentEndDate = current && getFinishDate(current, now)
- let nextStartDate = null
- for (let i = 0; i < this.timeline.length; i++) {
- const event = this.timeline[i]
- if (!current || currentEndDate || event.priority > current.priority) {
- const rangeStart = !current || event.priority > current.priority ? now : currentEndDate || now
- if (!nextStartDate || rangeStart < nextStartDate) {
- const hit = getNearestHitDate(event, rangeStart, nextStartDate)
- if (hit && (!next || hit < nextStartDate)) {
- next = event
- nextStartDate = hit
- }
- }
- }
- }
- console.log('current', current, currentEndDate)
- console.log('next', next, nextStartDate)
- if (nextStartDate && (!currentEndDate || currentEndDate > nextStartDate)) {
- currentEndDate = nextStartDate
- }
- this.current = current && this.getEventInfo(current, getStartDate(current, now), currentEndDate)
- this.next = next && this.getEventInfo(next, nextStartDate)
- clearTimeout(this.$timer)
- if (currentEndDate) {
- 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)
- }
- }
- },
- getEventInfo (event, startDate, endDate) {
- return {
- target: event.target,
- startDate: parseTime(startDate, '{y}.{m}.{d}'),
- startTime: parseTime(startDate, '{h}:{i}:{s}'),
- endDate: endDate ? parseTime(endDate, '{y}.{m}.{d}') : '',
- endTime: endDate ? parseTime(endDate, '{h}:{i}:{s}') : ''
- }
- }
- }
- }
- </script>
- <style lang="scss" scoped>
- .o-device {
- display: inline-flex;
- flex-direction: column;
- padding: 12px $spacing;
- color: $black;
- line-height: 1;
- border-radius: $radius;
- background-color: #fff;
- &__header {
- justify-self: flex-start;
- height: 24px;
- font-size: 16px;
- font-weight: bold;
- }
- &__shot {
- display: inline-block;
- width: 24px;
- height: 24px;
- background: url("~@/assets/icon_screenshot.png") 0 0 / 100% 100% no-repeat;
- }
- &__tip {
- display: inline-block;
- position: relative;
- left: 16px;
- padding: 4px 8px 4px 10px;
- margin-left: -4px;
- font-size: 12px;
- line-height: 1;
- border-radius: 4px 0 0 4px;
- background-color: currentColor;
- }
- &__preview {
- margin-top: 12px;
- padding-top: 56.25%;
- border-radius: 4px;
- &.empty {
- background: url("~@/assets/image_no_program.svg") 0 0 / 100% 100%
- no-repeat;
- }
- &.offline {
- background: url("~@/assets/image_offline.svg") 0 0 / 100% 100% no-repeat;
- }
- }
- &__info {
- margin-top: 12px;
- height: 88px;
- }
- &__current {
- align-self: stretch;
- font-size: 20px;
- font-weight: bold;
- text-align: center;
- }
- &__time {
- margin: $spacing 0 32px;
- font-size: 20px;
- }
- &__line {
- display: inline-block;
- width: 20px;
- margin: 0 10px;
- border-bottom: 1px solid currentColor;
- }
- &__hms {
- position: relative;
- font-weight: bold;
- }
- &__ymd {
- position: absolute;
- top: 100%;
- left: 50%;
- color: $info;
- font-size: 12px;
- font-weight: normal;
- transform: translate(-50%, 4px);
- }
- &__next {
- align-self: stretch;
- height: 16px;
- color: $info;
- font-size: 12px;
- line-height: 16px;
- text-align: center;
- }
- &__footer {
- margin-top: 10px;
- color: $blue;
- font-size: 12px;
- font-weight: bold;
- .o-device__icon {
- margin-right: 6px;
- font-size: 16px;
- }
- }
- }
- </style>
|