|
|
@@ -0,0 +1,405 @@
|
|
|
+<template>
|
|
|
+ <wrapper
|
|
|
+ fill
|
|
|
+ margin
|
|
|
+ padding
|
|
|
+ background
|
|
|
+ horizontal
|
|
|
+ >
|
|
|
+ <device-tree
|
|
|
+ ref="tree"
|
|
|
+ class="c-sibling-item c-sidebar u-width--md"
|
|
|
+ shrink
|
|
|
+ checkbox
|
|
|
+ check-on-click-node
|
|
|
+ @change="onChange"
|
|
|
+ />
|
|
|
+ <div class="l-flex__fill l-flex c-sibling-item far c-step">
|
|
|
+ <div class="l-flex__fill l-flex--col c-step__column">
|
|
|
+ <div class="l-flex__none l-flex--row c-sibling-item--v u-height u-font-size--sm u-bold">
|
|
|
+ 实时画面
|
|
|
+ </div>
|
|
|
+ <template v-if="hasData">
|
|
|
+ <div class="l-flex__fill c-sibling-item--v u-overflow-y--auto">
|
|
|
+ <draggable
|
|
|
+ class="c-record-grid"
|
|
|
+ handle=".o-video"
|
|
|
+ animation="300"
|
|
|
+ >
|
|
|
+ <device-player
|
|
|
+ v-for="item in devices"
|
|
|
+ :key="item.id"
|
|
|
+ :device="item"
|
|
|
+ controls
|
|
|
+ autoplay
|
|
|
+ retry
|
|
|
+ keep
|
|
|
+ check-online
|
|
|
+ @click="onChoose"
|
|
|
+ />
|
|
|
+ </draggable>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <el-empty description="请选择设备" />
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ <div class="l-flex__none l-flex--col c-step__column u-width--xl">
|
|
|
+ <div class="l-flex__none l-flex--row c-sibling-item--v">
|
|
|
+ <div
|
|
|
+ class="l-flex__fill c-sibling-item o-tip u-font-size"
|
|
|
+ :class="{ active: isSelected }"
|
|
|
+ >
|
|
|
+ <span class="u-ellipsis">
|
|
|
+ {{ tip }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="l-flex__none l-flex--row c-sibling-item near">
|
|
|
+ <div class="l-flex__none c-sibling-item u-font-size--sm u-bold">
|
|
|
+ 优先级
|
|
|
+ </div>
|
|
|
+ <el-select
|
|
|
+ v-model="priority"
|
|
|
+ class="c-sibling-item u-width--xs"
|
|
|
+ size="small"
|
|
|
+ @change="onConditionChange"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="option in priorityOptions"
|
|
|
+ :key="option.value"
|
|
|
+ :label="option.label"
|
|
|
+ :value="option.value"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="l-flex__none l-flex--row c-sibling-item--v">
|
|
|
+ <div class="l-flex__none c-sibling-item u-font-size--sm u-bold">
|
|
|
+ 时间范围
|
|
|
+ </div>
|
|
|
+ <el-date-picker
|
|
|
+ v-model="dateRange"
|
|
|
+ class="c-sibling-item near"
|
|
|
+ type="datetimerange"
|
|
|
+ size="mini"
|
|
|
+ range-separator="至"
|
|
|
+ start-placeholder="开始时间"
|
|
|
+ end-placeholder="结束时间"
|
|
|
+ value-format="timestamp"
|
|
|
+ :default-time="['00:00:00', '23:59:59']"
|
|
|
+ :clearable="false"
|
|
|
+ @change="onConditionChange"
|
|
|
+ />
|
|
|
+ <i
|
|
|
+ class="c-sibling-item near el-icon-refresh u-font-size--md has-active"
|
|
|
+ @click="onConditionChange"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ ref="container"
|
|
|
+ class="l-flex__fill c-sibling-item--v u-overflow-y--auto"
|
|
|
+ @scroll="checkScroll"
|
|
|
+ >
|
|
|
+ <el-table
|
|
|
+ :data="options.events"
|
|
|
+ class="c-sibling-item--v"
|
|
|
+ size="small"
|
|
|
+ >
|
|
|
+ <template #empty>
|
|
|
+ <el-empty v-if="!options.loading" />
|
|
|
+ <div
|
|
|
+ v-else
|
|
|
+ class="u-text--center"
|
|
|
+ >
|
|
|
+ <i class="el-icon-loading u-font-size" />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <el-table-column
|
|
|
+ width="80"
|
|
|
+ prop="time"
|
|
|
+ label="播出时间"
|
|
|
+ />
|
|
|
+ <el-table-column
|
|
|
+ show-overflow-tooltip
|
|
|
+ prop="originalName"
|
|
|
+ label="素材名称"
|
|
|
+ min-width="120"
|
|
|
+ />
|
|
|
+ <el-table-column
|
|
|
+ show-overflow-tooltip
|
|
|
+ prop="name"
|
|
|
+ label="编单名称"
|
|
|
+ />
|
|
|
+ <!-- <el-table-column
|
|
|
+ width="64"
|
|
|
+ prop="priority"
|
|
|
+ label="优先级"
|
|
|
+ align="center"
|
|
|
+ /> -->
|
|
|
+ <el-table-column
|
|
|
+ width="64"
|
|
|
+ prop="status"
|
|
|
+ label="状态"
|
|
|
+ align="center"
|
|
|
+ >
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <span :class="scope.row.color">
|
|
|
+ {{ scope.row.label }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ <div
|
|
|
+ v-if="options.loaded && options.loading"
|
|
|
+ class="c-sibling-item--v u-text--center"
|
|
|
+ >
|
|
|
+ <i class="el-icon-loading" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </wrapper>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import {
|
|
|
+ EventPriority,
|
|
|
+ EventPriorityInfo
|
|
|
+} from '@/constant.js'
|
|
|
+import { parseTime } from '@/utils'
|
|
|
+import { getPlaylist } from '@/api/device.js'
|
|
|
+import Draggable from 'vuedraggable'
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'DeviceRecord',
|
|
|
+ components: {
|
|
|
+ Draggable
|
|
|
+ },
|
|
|
+ data () {
|
|
|
+ const now = Date.now()
|
|
|
+
|
|
|
+ return {
|
|
|
+ priority: EventPriority.INSERTED,
|
|
|
+ dateRange: [new Date(parseTime(now, '{y}/{m}/{d} 00:00:00')).getTime(), new Date(parseTime(now, '{y}/{m}/{d} 23:59:59')).getTime()],
|
|
|
+ priorityOptions: [
|
|
|
+ {
|
|
|
+ value: EventPriority.SCHEDULING,
|
|
|
+ label: EventPriorityInfo[EventPriority.SCHEDULING]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: EventPriority.INSERTED,
|
|
|
+ label: EventPriorityInfo[EventPriority.INSERTED]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: EventPriority.EMBEDDED,
|
|
|
+ label: EventPriorityInfo[EventPriority.EMBEDDED]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: EventPriority.EMERGENT,
|
|
|
+ label: EventPriorityInfo[EventPriority.EMERGENT]
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ devices: [],
|
|
|
+ options: {
|
|
|
+ id: null,
|
|
|
+ name: null,
|
|
|
+ loading: false,
|
|
|
+ loaded: false,
|
|
|
+ more: true,
|
|
|
+ events: [],
|
|
|
+ pageIndex: 1,
|
|
|
+ pageSize: 100
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ hasData () {
|
|
|
+ return this.devices.length > 0
|
|
|
+ },
|
|
|
+ isSelected () {
|
|
|
+ return !!this.options.id
|
|
|
+ },
|
|
|
+ tip () {
|
|
|
+ return this.isSelected ? this.options.name : '播放清单'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ checkScroll () {
|
|
|
+ if (this.options.loading || !this.options.more) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const container = this.$refs.container
|
|
|
+ const scrollHeight = container.scrollHeight
|
|
|
+ const scrollTop = container.scrollTop + container.clientHeight
|
|
|
+ if (scrollHeight - scrollTop <= 50) {
|
|
|
+ this.loadMore()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ loadMore () {
|
|
|
+ if (this.options.loading) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const options = this.options
|
|
|
+ options.loading = true
|
|
|
+ getPlaylist({
|
|
|
+ deviceIdList: [options.id],
|
|
|
+ start: this.dateRange[0],
|
|
|
+ util: this.dateRange[1],
|
|
|
+ priority: this.priority,
|
|
|
+ pageSize: options.pageSize,
|
|
|
+ pageIndex: options.pageIndex
|
|
|
+ }).then(({ data, totalCount }) => {
|
|
|
+ options.events = Object.freeze([...options.events, ...data].map(this.transformEvent))
|
|
|
+ options.more = totalCount > options.pageIndex * options.pageSize
|
|
|
+ options.loaded = true
|
|
|
+ options.pageIndex += 1
|
|
|
+ }).finally(() => {
|
|
|
+ options.loading = false
|
|
|
+ })
|
|
|
+ },
|
|
|
+ transformEvent (event) {
|
|
|
+ const { start, until, time } = event
|
|
|
+ const status = this.checkTime(Date.now(), start, until)
|
|
|
+ return {
|
|
|
+ ...event,
|
|
|
+ time: time || this.timestampToDatetime(event.start),
|
|
|
+ status,
|
|
|
+ ...this.getStatusInfo(status)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ getStatusInfo (status) {
|
|
|
+ switch (status) {
|
|
|
+ case -1:
|
|
|
+ return {
|
|
|
+ label: '已播',
|
|
|
+ color: 'u-color--success'
|
|
|
+ }
|
|
|
+ case 0:
|
|
|
+ return {
|
|
|
+ label: '在播',
|
|
|
+ color: 'u-color--primary'
|
|
|
+ }
|
|
|
+ case 1:
|
|
|
+ return {
|
|
|
+ label: '未播',
|
|
|
+ color: 'u-color--error'
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ return {}
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onChoose (device) {
|
|
|
+ if (!device) {
|
|
|
+ this.currentId = null
|
|
|
+ this.options = {
|
|
|
+ id: null,
|
|
|
+ name: null,
|
|
|
+ loading: false,
|
|
|
+ loaded: false,
|
|
|
+ more: false,
|
|
|
+ events: [],
|
|
|
+ pageIndex: 1,
|
|
|
+ pageSize: 100
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const { id, name } = device
|
|
|
+ if (this.options.id === id) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.options = {
|
|
|
+ id,
|
|
|
+ name,
|
|
|
+ loading: false,
|
|
|
+ loaded: false,
|
|
|
+ more: true,
|
|
|
+ events: [],
|
|
|
+ pageIndex: 1,
|
|
|
+ pageSize: 100
|
|
|
+ }
|
|
|
+ this.loadMore()
|
|
|
+ },
|
|
|
+ checkTime (time, start, end) {
|
|
|
+ if (time < start) {
|
|
|
+ return 1
|
|
|
+ }
|
|
|
+ if (time >= start && time <= end) {
|
|
|
+ return 0
|
|
|
+ }
|
|
|
+ return -1
|
|
|
+ },
|
|
|
+ formatTime (date) {
|
|
|
+ return date.toISOString().slice(0, 19).replace('T', ' ')
|
|
|
+ },
|
|
|
+ timestampToDatetime (timestamp) {
|
|
|
+ const date = new Date(parseInt(timestamp))
|
|
|
+ return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })
|
|
|
+ },
|
|
|
+ onChange (devices) {
|
|
|
+ const isEmpty = this.devices.length === 0
|
|
|
+ if (isEmpty && devices.length === 1 || devices.length && !this.options.id) {
|
|
|
+ this.onChoose(devices?.[0])
|
|
|
+ }
|
|
|
+ this.devices = devices
|
|
|
+ },
|
|
|
+ onConditionChange () {
|
|
|
+ const { id, name } = this.options
|
|
|
+ if (id) {
|
|
|
+ this.options = {
|
|
|
+ id,
|
|
|
+ name,
|
|
|
+ loading: false,
|
|
|
+ loaded: false,
|
|
|
+ more: true,
|
|
|
+ events: [],
|
|
|
+ pageIndex: 1,
|
|
|
+ pageSize: 100
|
|
|
+ }
|
|
|
+ this.loadMore()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.c-record-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
|
|
+ gap: $spacing--3xs;
|
|
|
+}
|
|
|
+
|
|
|
+.played {
|
|
|
+ color: green;
|
|
|
+}
|
|
|
+
|
|
|
+.playing {
|
|
|
+ color: blue;
|
|
|
+}
|
|
|
+
|
|
|
+.not-played {
|
|
|
+ color: red;
|
|
|
+}
|
|
|
+
|
|
|
+.o-tip {
|
|
|
+ display: inline-flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ min-width: 60px;
|
|
|
+ height: $height--sm;
|
|
|
+ padding: 0 $padding;
|
|
|
+ color: #fff;
|
|
|
+ font-size: $font-size--sm;
|
|
|
+ line-height: 1;
|
|
|
+ border: none;
|
|
|
+ border-radius: $radius--sm;
|
|
|
+ color: $gray--dark;
|
|
|
+ background-color: $gray--light;
|
|
|
+
|
|
|
+ &.active {
|
|
|
+ color: $primary;
|
|
|
+ background-color: $blue--light;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|
|
|
+
|