| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415 |
- <template>
- <div
- class="o-device has-padding u-pointer"
- @click="onClick"
- >
- <div class="l-flex__none l-flex--row o-device__block o-device__header">
- <i
- class="l-flex__none o-device__status"
- :class="statusClass"
- />
- <auto-text
- class="l-flex__fill"
- :text="name"
- />
- <template v-if="isActivated && 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
- v-if="shot"
- class="l-flex__fill o-device__block o-device__preview"
- :style="styles"
- />
- <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="o-device__current"
- >
- 当前暂无节目
- </span>
- <auto-text
- v-if="next"
- class="l-flex__none o-device__next"
- :text="nextInfo"
- />
- </template>
- </div>
- <auto-text
- class="l-flex__none o-device__block o-device__footer"
- :text="address"
- />
- </div>
- </template>
- <script>
- import { getTimeline } from '@/api/calendar'
- import { parseTime } from '@/utils'
- import {
- listen,
- unlisten
- } from '@/utils/mqtt'
- import { getName } from '@/utils/cache'
- import {
- getAndCheck,
- screenshot,
- reset,
- stop
- } from '@/utils/screenshot'
- export default {
- name: 'DeviceCard',
- props: {
- device: {
- type: Object,
- default: null
- }
- },
- data () {
- return {
- isShotting: false,
- shot: null,
- timeline: [],
- loadingTimeline: false,
- current: null,
- next: null
- }
- },
- computed: {
- name () {
- return this.device.name
- },
- isActivated () {
- return this.device.activate === 2
- },
- isOnline () {
- return this.device.onlineStatus === 1
- },
- statusClass () {
- return this.isActivated
- ? this.isOnline
- ? 'u-color--success dark'
- : 'u-color--error light'
- : this.device.activate
- ? 'u-color--primary'
- : 'u-color--warning'
- },
- statusTip () {
- return this.isActivated
- ? this.isOnline
- ? '在线'
- : '离线'
- : this.device.activate
- ? '已激活'
- : '未激活'
- },
- address () {
- return `地址:${this.device.remark}`
- },
- styles () {
- return this.isActivated && this.isOnline && this.shot ? {
- backgroundImage: `url("${this.shot}")`
- } : null
- },
- nextInfo () {
- return this.next ? `下一场:${this.next.startDate} ${this.next.startTime} ${this.next.name}` : ''
- }
- },
- created () {
- if (this.isActivated) {
- listen(this.onMessage)
- this.getTimeline()
- if (this.isOnline) {
- getAndCheck(this.device, this.onScreenshotUpdate)
- } else {
- reset(this.device.id)
- }
- }
- this.$timer = -1
- },
- beforeDestroy () {
- if (this.isActivated) {
- unlisten(this.onMessage)
- if (this.isOnline) {
- stop(this.device.id)
- }
- }
- clearTimeout(this.$timer)
- },
- methods: {
- screenshot () {
- 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 {
- message = JSON.parse(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 - 1000, '{y}.{m}.{d}') : '',
- endTime: endDateTime ? parseTime(endDateTime - 1000, '{h}:{i}:{s}') : ''
- }
- },
- getTimeline () {
- this.loadingTimeline = true
- getTimeline(this.device.id, { custom: true }).then(({ data }) => {
- this.timeline = (JSON.parse(data.eventDetail) || []).map(this.createItem)
- this.checkTimeline()
- }).catch(({ isCancel }) => {
- if (!isCancel) {
- 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.current && 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 getName(item.type, item.id)
- }
- return Promise.resolve('未知')
- },
- getDetail () {
- if (this.current) {
- this.getName(this.current).then(name => {
- this.current.name = name
- this.finishTimeline()
- }, ({ isCancel }) => {
- if (!isCancel) {
- if (this.current.count == null) {
- this.current.count = 1
- } else if (this.current.count < 3) {
- this.current.count += 1
- } else {
- this.current.name = '未知'
- this.finishTimeline()
- return
- }
- this.$timer = setTimeout(this.getDetail, 2000)
- }
- })
- } else {
- this.finishTimeline()
- }
- }
- }
- }
- </script>
- <style lang="scss" scoped>
- .o-device {
- display: inline-flex;
- flex-direction: column;
- color: $black;
- line-height: 1;
- border-radius: $radius;
- background-color: #fff;
- &__block + &__block {
- margin-top: $spacing;
- }
- &__header {
- justify-self: flex-start;
- height: 24px;
- font-size: 16px;
- font-weight: bold;
- }
- &__status {
- display: inline-block;
- width: 12px;
- height: 12px;
- margin-right: 6px;
- border-radius: 50%;
- background-color: currentColor;
- }
- &__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: 2px 8px 2px 10px;
- margin-left: -10px;
- font-size: 12px;
- line-height: 1;
- border-radius: 9px 0 0 9px;
- background-color: currentColor;
- }
- &__preview {
- padding-top: 50%;
- background-position: center center;
- background-size: contain;
- background-repeat: no-repeat;
- }
- &__info {
- justify-content: center;
- height: 100px;
- }
- &__current {
- align-self: stretch;
- font-size: 20px;
- font-weight: bold;
- text-align: center;
- }
- &__time {
- margin: $spacing 0 24px;
- 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: $gray;
- font-size: 12px;
- font-weight: normal;
- transform: translate(-50%, 4px);
- }
- &__next {
- align-self: stretch;
- color: $gray;
- font-size: 12px;
- text-align: center;
- }
- &__current + &__next {
- margin-top: 24px;
- }
- &__footer {
- font-size: 12px;
- font-weight: bold;
- }
- }
- .o-shot {
- display: inline-block;
- width: 480px;
- height: 270px;
- object-fit: contain;
- }
- </style>
|