<template>
|
<TableLayout :permissions="['business:collectionMedia:query', 'business:collectionStation:query']">
|
<div ref="QueryFormRef" slot="search-form">
|
<el-form ref="searchForm" :model="searchForm" label-width="100px" inline>
|
<el-form-item label="采集站" prop="stationId">
|
<el-select v-model="searchForm.stationId" placeholder="全部" clearable filterable @change="onStationChange">
|
<el-option v-for="item in stationOptions" :key="item.id" :label="item.name" :value="item.id" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="下载状态" prop="downloadStatus">
|
<el-select v-model="searchForm.downloadStatus" placeholder="请选择" clearable>
|
<el-option label="待下载" :value="0" />
|
<el-option label="已下载" :value="1" />
|
<el-option label="失败" :value="2" />
|
<el-option label="下载中" :value="3" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="类型" prop="mediaType">
|
<el-select v-model="searchForm.mediaType" placeholder="请选择" clearable>
|
<el-option label="视频" :value="0" />
|
<el-option label="图片" :value="1" />
|
<el-option label="音频" :value="2" />
|
</el-select>
|
</el-form-item>
|
<section>
|
<el-button type="primary" @click="search">搜索</el-button>
|
<el-button @click="reset">重置</el-button>
|
</section>
|
</el-form>
|
</div>
|
|
<template v-slot:table-wrap>
|
<ul class="toolbar">
|
<li>
|
<el-button v-permissions="['business:collectionMedia:sync', 'business:collectionStation:sync']" @click="handleSyncMedia">同步索引</el-button>
|
</li>
|
<li>
|
<el-button type="primary" v-permissions="['business:collectionMedia:download', 'business:collectionStation:sync']" @click="handleBatchDownload">批量下载</el-button>
|
</li>
|
</ul>
|
<el-table :height="tableHeightNew" v-loading="isWorking.search" :data="tableData.list" stripe>
|
<el-table-column label="序号" width="55"><template slot-scope="scope">{{ scope.$index + 1 }}</template></el-table-column>
|
<el-table-column prop="fileName" label="文件名" min-width="180" show-overflow-tooltip />
|
<el-table-column prop="mediaType" label="类型" width="80">
|
<template slot-scope="{row}">
|
<span>{{ mediaTypeLabel(row) }}</span>
|
</template>
|
</el-table-column>
|
<el-table-column prop="fileSize" label="大小(字节)" min-width="100" />
|
<el-table-column prop="startTime" label="开始时间" min-width="160" />
|
<el-table-column prop="endTime" label="结束时间" min-width="160" />
|
<el-table-column prop="downloadStatus" label="下载状态" width="90">
|
<template slot-scope="{row}">
|
<span :class="downloadStatusClass(row)">{{ downloadStatusLabel(row) }}</span>
|
</template>
|
</el-table-column>
|
<el-table-column prop="snapshotStatus" label="快照" width="90">
|
<template slot-scope="{row}">
|
<span :class="snapshotStatusClass(row)">{{ snapshotStatusLabel(row) }}</span>
|
</template>
|
</el-table-column>
|
<el-table-column prop="filePathLocal" label="本地路径" min-width="120">
|
<template slot-scope="{row}">
|
<div v-if="isDownloadedVideo(row)" class="list-video-cell" @click="handlePreview(row)">
|
<video
|
:key="'thumb-' + row.id"
|
:src="buildListVideoSrc(row)"
|
class="list-video-thumb"
|
muted
|
preload="metadata"
|
playsinline
|
/>
|
</div>
|
<span v-else class="path-text" :title="row.filePathLocal">{{ row.filePathLocal || '-' }}</span>
|
</template>
|
</el-table-column>
|
<el-table-column label="操作" width="240" fixed="right">
|
<template slot-scope="{row}">
|
<el-button type="text" v-if="canDownloadToStation(row)" v-permissions="['business:collectionMedia:download', 'business:collectionStation:sync']"
|
@click="handleDownload(row)">下载</el-button>
|
<el-button type="text" v-if="row.downloadStatus === 1" @click="handleSaveLocal(row)">下载到本地</el-button>
|
<el-button type="text" v-if="row.downloadStatus === 1 && row.snapshotStatus !== 1"
|
@click="handleAnalyzeSnapshot(row)">生成快照</el-button>
|
<el-button type="text" v-if="row.downloadStatus === 1" @click="openManualCorrect(row)">人工纠正</el-button>
|
<el-button type="text" v-if="row.snapshotStatus === 2" @click="handleViewSnapshot(row)">查看快照</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
<pagination @size-change="handleSizeChange" @current-change="handlePageChange" :pagination="tableData.pagination" />
|
</template>
|
|
<el-dialog :title="previewTitle" :visible.sync="previewVisible" width="860px" append-to-body @close="closePreview">
|
<div v-loading="previewLoading" class="preview-wrap">
|
<video v-if="previewMode === 'video' && previewSrc" :key="previewSrc" :src="previewSrc" controls
|
preload="metadata" playsinline class="preview-video" @error="onPreviewMediaError" />
|
<audio v-else-if="previewMode === 'audio' && previewSrc" :key="previewSrc" :src="previewSrc" controls class="preview-audio"
|
@error="onPreviewMediaError" />
|
<el-image v-else-if="previewMode === 'image' && previewSrc" :src="previewSrc" fit="contain" class="preview-image"
|
:preview-src-list="[previewSrc]" />
|
<pre v-else-if="previewMode === 'text'" class="preview-text">{{ previewText }}</pre>
|
<div v-else-if="!previewLoading" class="preview-empty">暂不支持该类型预览</div>
|
</div>
|
</el-dialog>
|
|
<el-dialog :title="snapshotTitle" :visible.sync="snapshotVisible" width="720px" append-to-body>
|
<div v-loading="snapshotLoading" class="snapshot-wrap">
|
<div v-if="snapshotList.length" class="snapshot-grid">
|
<div v-for="item in snapshotList" :key="item.id + '-' + snapshotCacheVersion" class="snapshot-item">
|
<div class="snapshot-label">{{ snapshotTypeLabel(item.snapshotType) }}</div>
|
<el-image v-if="item.fileUrlFull" :src="buildSnapshotImageUrl(item.fileUrlFull)" fit="contain"
|
class="snapshot-image" :preview-src-list="[buildSnapshotImageUrl(item.fileUrlFull)]" />
|
<div class="snapshot-meta">
|
<span>时刻: {{ item.timestampSec }}s</span>
|
<span v-if="item.confidence">置信度: {{ item.confidence }}</span>
|
<span>来源: {{ item.source || '-' }}</span>
|
</div>
|
</div>
|
</div>
|
<div v-else-if="!snapshotLoading" class="preview-empty">暂无快照,请先点击「生成快照」</div>
|
<div v-if="snapshotMediaRow" class="snapshot-actions">
|
<el-button type="primary" plain @click="openManualCorrect(snapshotMediaRow)">人工纠正时刻</el-button>
|
</div>
|
</div>
|
</el-dialog>
|
|
<el-dialog title="人工纠正快照时刻" :visible.sync="manualCorrectVisible" width="860px" append-to-body
|
@close="closeManualCorrect">
|
<div v-loading="manualCorrectLoading" class="manual-correct-wrap">
|
<video v-if="manualPreviewSrc" ref="manualVideo" :src="manualPreviewSrc" controls preload="metadata"
|
playsinline class="preview-video" @loadedmetadata="onManualVideoLoaded" @timeupdate="onManualTimeUpdate" />
|
<div class="manual-slider">
|
<span>当前时刻: {{ manualTimestampSec.toFixed(1) }}s</span>
|
<el-slider v-model="manualTimestampSec" :min="0" :max="manualDurationSec" :step="0.5"
|
@input="seekManualVideo" />
|
</div>
|
<div class="manual-buttons">
|
<el-button type="primary" :loading="manualSaving" @click="saveManualSnapshot(1)">设为门头图</el-button>
|
<el-button type="success" :loading="manualSaving" @click="saveManualSnapshot(2)">设为交付图</el-button>
|
</div>
|
</div>
|
</el-dialog>
|
</TableLayout>
|
</template>
|
|
<script>
|
import BaseTable from '@/components/base/BaseTable'
|
import TableLayout from '@/layouts/TableLayout'
|
import Pagination from '@/components/common/Pagination'
|
import { syncMedia, downloadMedia, batchDownloadMedia, list as fetchStationList } from '@/api/business/collectionStation'
|
import {
|
fetchPreviewText,
|
fetchPreviewBlob,
|
fetchMediaFile,
|
ensureMp4Blob,
|
buildPreviewUrl,
|
analyzeMediaSnapshot,
|
fetchMediaSnapshots,
|
saveManualMediaSnapshot
|
} from '@/api/business/collectionMedia'
|
|
export default {
|
name: 'CollectionMedia',
|
extends: BaseTable,
|
components: { TableLayout, Pagination },
|
data () {
|
return {
|
stationOptions: [],
|
searchForm: {
|
stationId: null,
|
downloadStatus: null,
|
mediaType: null
|
},
|
previewVisible: false,
|
previewLoading: false,
|
previewTitle: '媒体预览',
|
previewMode: '',
|
previewSrc: '',
|
previewText: '',
|
previewRow: null,
|
previewUseDirectUrl: false,
|
previewBlobUrl: '',
|
downloadPollTimer: null,
|
snapshotVisible: false,
|
snapshotLoading: false,
|
snapshotTitle: '配送快照',
|
snapshotList: [],
|
snapshotPollTimer: null,
|
snapshotMediaRow: null,
|
manualCorrectVisible: false,
|
manualCorrectLoading: false,
|
manualCorrectRow: null,
|
manualPreviewSrc: '',
|
manualBlobUrl: '',
|
manualTimestampSec: 0,
|
manualDurationSec: 600,
|
manualSaving: false,
|
manualSeekFromSlider: false,
|
snapshotCacheVersion: 0
|
}
|
},
|
created () {
|
if (this.$route.query.stationId) {
|
this.searchForm.stationId = parseInt(this.$route.query.stationId)
|
}
|
this.config({
|
module: '采集站媒体',
|
api: '/business/collectionMedia',
|
'field.id': 'id',
|
'field.main': 'id'
|
})
|
this.loadStations()
|
this.search()
|
},
|
beforeDestroy () {
|
this.revokePreviewUrl()
|
this.revokeManualPreviewUrl()
|
this.stopDownloadPoll()
|
this.stopSnapshotPoll()
|
},
|
methods: {
|
loadStations () {
|
fetchStationList({ status: 1 }).then(res => {
|
this.stationOptions = res || []
|
})
|
},
|
onStationChange () {
|
this.search()
|
},
|
mediaTypeLabel (row) {
|
const mode = this.resolvePreviewMode(row)
|
if (mode === 'image') return '图片'
|
if (mode === 'audio') return '音频'
|
if (mode === 'text') return '文本'
|
if (row.mediaType === 1) return '图片'
|
if (row.mediaType === 2) return '音频'
|
return '视频'
|
},
|
isDownloadedVideo (row) {
|
return row.downloadStatus === 1 && row.filePathLocal && this.resolvePreviewMode(row) === 'video'
|
},
|
buildListVideoSrc (row) {
|
return row.fileUrlFull || buildPreviewUrl(row.id)
|
},
|
canDownloadToStation (row) {
|
return row.downloadStatus !== 1 && row.downloadStatus !== 3
|
},
|
resolvePreviewMode (row) {
|
const name = (row.fileName || '').toLowerCase()
|
if (/\.(jpg|jpeg|png|gif|bmp|webp)$/.test(name) || row.mediaType === 1) {
|
return 'image'
|
}
|
if (/\.(mp3|wav|aac|m4a)$/.test(name) || row.mediaType === 2) {
|
return 'audio'
|
}
|
if (/\.(txt|log)$/.test(name)) {
|
return 'text'
|
}
|
if (/\.(mp4|mov|avi|mkv|webm|flv|m4v)$/.test(name) || row.mediaType === 0) {
|
return 'video'
|
}
|
return 'video'
|
},
|
handleSyncMedia () {
|
syncMedia({ stationId: this.searchForm.stationId }).then(res => {
|
this.$message.success(res || '同步完成')
|
this.search()
|
})
|
},
|
handleBatchDownload () {
|
batchDownloadMedia({ stationId: this.searchForm.stationId, limit: 10 }).then(res => {
|
this.$message.success(res || '已提交下载任务')
|
this.search()
|
this.startDownloadPoll()
|
})
|
},
|
handleDownload (row) {
|
downloadMedia(row.id).then(res => {
|
this.$message.success(res || '已提交下载任务')
|
this.search()
|
this.startDownloadPoll()
|
})
|
},
|
handleSaveLocal (row) {
|
fetchMediaFile(row.id).then(response => {
|
this.download(response)
|
}).catch(err => {
|
this.$message.error(err.message || '下载到本地失败')
|
})
|
},
|
snapshotTypeLabel (type) {
|
return type === 2 ? '货品交付图' : '到店门头图'
|
},
|
downloadStatusLabel (row) {
|
const map = { 0: '待下载', 1: '已下载', 2: '失败', 3: '下载中' }
|
return map[row.downloadStatus] != null ? map[row.downloadStatus] : '待下载'
|
},
|
downloadStatusClass (row) {
|
const map = { 0: 'status-info', 1: 'status-success', 2: 'status-danger', 3: 'status-primary' }
|
return map[row.downloadStatus] || 'status-info'
|
},
|
snapshotStatusLabel (row) {
|
if (row.snapshotStatus === 2) return '已完成'
|
if (row.snapshotStatus === 1) return '分析中'
|
if (row.snapshotStatus === 3) return '失败'
|
if (row.downloadStatus === 1) return '未分析'
|
return '-'
|
},
|
snapshotStatusClass (row) {
|
if (row.snapshotStatus === 2) return 'status-success'
|
if (row.snapshotStatus === 1) return 'status-warning'
|
if (row.snapshotStatus === 3) return 'status-danger'
|
if (row.downloadStatus === 1) return 'status-info'
|
return 'status-muted'
|
},
|
buildSnapshotImageUrl (url) {
|
if (!url) return ''
|
const sep = url.indexOf('?') >= 0 ? '&' : '?'
|
return `${url}${sep}_t=${this.snapshotCacheVersion}`
|
},
|
handleAnalyzeSnapshot (row) {
|
analyzeMediaSnapshot(row.id).then(res => {
|
this.$message.success(res || '已提交快照分析')
|
this.search()
|
this.startSnapshotPoll(row.id)
|
})
|
},
|
handleViewSnapshot (row) {
|
this.snapshotMediaRow = row
|
this.snapshotTitle = (row.fileName || '媒体') + ' - 配送快照'
|
this.snapshotVisible = true
|
this.loadSnapshots(row.id)
|
},
|
loadSnapshots (mediaId) {
|
this.snapshotLoading = true
|
this.snapshotList = []
|
this.snapshotCacheVersion = Date.now()
|
fetchMediaSnapshots(mediaId, this.snapshotCacheVersion).then(list => {
|
this.snapshotList = list || []
|
this.snapshotLoading = false
|
}).catch(err => {
|
this.snapshotLoading = false
|
this.$message.error(err.message || '加载快照失败')
|
})
|
},
|
startSnapshotPoll (mediaId) {
|
this.stopSnapshotPoll()
|
let count = 0
|
this.snapshotPollTimer = setInterval(() => {
|
count++
|
if (count > 40) {
|
this.stopSnapshotPoll()
|
return
|
}
|
this.api.fetchList({
|
page: this.tableData.pagination.pageIndex,
|
capacity: this.tableData.pagination.pageSize,
|
model: this.searchForm,
|
sorts: this.tableData.sorts
|
}).then(data => {
|
this.tableData.list = data.records
|
this.tableData.pagination.total = data.total
|
const row = (data.records || []).find(item => item.id === mediaId)
|
if (row && row.snapshotStatus !== 1) {
|
this.stopSnapshotPoll()
|
if (row.snapshotStatus === 2 && this.snapshotVisible) {
|
this.loadSnapshots(mediaId)
|
}
|
}
|
}).catch(() => {})
|
}, 3000)
|
},
|
stopSnapshotPoll () {
|
if (this.snapshotPollTimer) {
|
clearInterval(this.snapshotPollTimer)
|
this.snapshotPollTimer = null
|
}
|
},
|
startDownloadPoll () {
|
this.stopDownloadPoll()
|
let count = 0
|
this.downloadPollTimer = setInterval(() => {
|
count++
|
if (count > 40) {
|
this.stopDownloadPoll()
|
return
|
}
|
this.api.fetchList({
|
page: this.tableData.pagination.pageIndex,
|
capacity: this.tableData.pagination.pageSize,
|
model: this.searchForm,
|
sorts: this.tableData.sorts
|
}).then(data => {
|
this.tableData.list = data.records
|
this.tableData.pagination.total = data.total
|
if (!(data.records || []).some(item => item.downloadStatus === 3)) {
|
this.stopDownloadPoll()
|
}
|
}).catch(() => {})
|
}, 3000)
|
},
|
stopDownloadPoll () {
|
if (this.downloadPollTimer) {
|
clearInterval(this.downloadPollTimer)
|
this.downloadPollTimer = null
|
}
|
},
|
handlePreview (row) {
|
if (row.downloadStatus !== 1) {
|
this.$message.warning('请先下载文件后再预览')
|
return
|
}
|
this.previewTitle = row.fileName || '媒体预览'
|
this.previewVisible = true
|
this.previewLoading = true
|
this.previewMode = this.resolvePreviewMode(row)
|
this.previewRow = row
|
this.previewSrc = ''
|
this.previewText = ''
|
this.previewUseDirectUrl = false
|
this.revokePreviewUrl()
|
|
if (this.previewMode === 'text') {
|
fetchPreviewText(row.id).then(text => {
|
this.previewText = text || ''
|
this.previewLoading = false
|
}).catch(err => {
|
this.previewLoading = false
|
this.$message.error(err.message || '文本预览失败')
|
})
|
return
|
}
|
|
if (this.previewMode === 'video') {
|
this.loadVideoPreview(row)
|
return
|
}
|
if (this.previewMode === 'audio') {
|
this.previewUseDirectUrl = !!row.fileUrlFull
|
this.previewSrc = row.fileUrlFull || buildPreviewUrl(row.id)
|
this.previewLoading = false
|
return
|
}
|
|
if (row.fileUrlFull) {
|
this.previewSrc = row.fileUrlFull
|
this.previewLoading = false
|
return
|
}
|
|
this.previewLoading = false
|
this.$message.warning('暂无可预览地址')
|
},
|
loadVideoPreview (row) {
|
this.revokePreviewUrl()
|
fetchPreviewBlob(row.id)
|
.then(blob => ensureMp4Blob(blob))
|
.then(blob => {
|
this.previewBlobUrl = URL.createObjectURL(blob)
|
this.previewSrc = this.previewBlobUrl
|
this.previewUseDirectUrl = false
|
this.previewLoading = false
|
})
|
.catch(err => {
|
if (row.fileUrlFull) {
|
this.previewUseDirectUrl = true
|
this.previewSrc = row.fileUrlFull
|
this.previewLoading = false
|
return
|
}
|
this.previewLoading = false
|
this.$message.error(err.message || '视频预览失败,请重新下载该文件')
|
})
|
},
|
onPreviewMediaError () {
|
if (!this.previewRow) {
|
this.$message.error('媒体加载失败')
|
return
|
}
|
if (this.previewMode === 'video') {
|
if (this.previewUseDirectUrl) {
|
this.$message.error('视频无法播放,请重新下载该文件(需采集站转 MP4)')
|
return
|
}
|
if (this.previewRow.fileUrlFull) {
|
this.previewUseDirectUrl = true
|
this.revokePreviewUrl()
|
this.previewSrc = this.previewRow.fileUrlFull
|
return
|
}
|
}
|
if (this.previewUseDirectUrl && this.previewMode === 'audio') {
|
this.previewUseDirectUrl = false
|
this.previewSrc = buildPreviewUrl(this.previewRow.id)
|
return
|
}
|
this.$message.error('视频加载失败,请重新下载该文件或联系管理员检查采集站转码配置')
|
},
|
closePreview () {
|
this.revokePreviewUrl()
|
this.previewSrc = ''
|
this.previewText = ''
|
this.previewMode = ''
|
this.previewRow = null
|
this.previewUseDirectUrl = false
|
},
|
revokePreviewUrl () {
|
if (this.previewBlobUrl) {
|
URL.revokeObjectURL(this.previewBlobUrl)
|
this.previewBlobUrl = ''
|
}
|
},
|
openManualCorrect (row) {
|
if (row.downloadStatus !== 1) {
|
this.$message.warning('请先下载媒体文件')
|
return
|
}
|
this.manualCorrectRow = row
|
this.manualCorrectVisible = true
|
this.manualCorrectLoading = true
|
this.manualTimestampSec = 0
|
this.manualDurationSec = 600
|
this.revokeManualPreviewUrl()
|
fetchPreviewBlob(row.id)
|
.then(blob => ensureMp4Blob(blob))
|
.then(blob => {
|
this.manualBlobUrl = URL.createObjectURL(blob)
|
this.manualPreviewSrc = this.manualBlobUrl
|
this.manualCorrectLoading = false
|
})
|
.catch(err => {
|
this.manualCorrectLoading = false
|
this.$message.error(err.message || '加载视频失败')
|
})
|
},
|
closeManualCorrect () {
|
this.revokeManualPreviewUrl()
|
this.manualPreviewSrc = ''
|
this.manualCorrectRow = null
|
},
|
revokeManualPreviewUrl () {
|
if (this.manualBlobUrl) {
|
URL.revokeObjectURL(this.manualBlobUrl)
|
this.manualBlobUrl = ''
|
}
|
},
|
onManualVideoLoaded () {
|
const video = this.$refs.manualVideo
|
if (video && video.duration && isFinite(video.duration)) {
|
this.manualDurationSec = Math.max(1, Math.floor(video.duration))
|
}
|
},
|
onManualTimeUpdate () {
|
if (this.manualSeekFromSlider) {
|
return
|
}
|
const video = this.$refs.manualVideo
|
if (video) {
|
this.manualTimestampSec = Math.round(video.currentTime * 2) / 2
|
}
|
},
|
seekManualVideo (val) {
|
const video = this.$refs.manualVideo
|
if (!video) {
|
return
|
}
|
this.manualSeekFromSlider = true
|
video.currentTime = val
|
setTimeout(() => {
|
this.manualSeekFromSlider = false
|
}, 200)
|
},
|
saveManualSnapshot (snapshotType) {
|
if (!this.manualCorrectRow) {
|
return
|
}
|
this.manualSaving = true
|
saveManualMediaSnapshot({
|
mediaId: this.manualCorrectRow.id,
|
snapshotType,
|
timestampSec: this.manualTimestampSec
|
}).then(res => {
|
this.manualSaving = false
|
this.$message.success(res || '保存成功')
|
const mediaId = this.manualCorrectRow.id
|
this.snapshotCacheVersion = Date.now()
|
this.search()
|
if (this.snapshotMediaRow && this.snapshotMediaRow.id === mediaId) {
|
this.snapshotMediaRow = { ...this.snapshotMediaRow, snapshotStatus: 2 }
|
}
|
if (this.snapshotVisible && this.snapshotMediaRow && this.snapshotMediaRow.id === mediaId) {
|
this.loadSnapshots(mediaId)
|
}
|
}).catch(err => {
|
this.manualSaving = false
|
this.$message.error(err.message || '保存失败')
|
})
|
}
|
}
|
}
|
</script>
|
|
<style scoped>
|
.preview-wrap {
|
min-height: 200px;
|
}
|
.preview-video {
|
width: 100%;
|
max-height: 520px;
|
background: #000;
|
}
|
.preview-audio {
|
width: 100%;
|
}
|
.preview-image {
|
width: 100%;
|
max-height: 520px;
|
}
|
.preview-text {
|
max-height: 520px;
|
overflow: auto;
|
white-space: pre-wrap;
|
word-break: break-all;
|
margin: 0;
|
padding: 12px;
|
background: #f5f7fa;
|
border-radius: 4px;
|
}
|
.preview-empty {
|
text-align: center;
|
color: #909399;
|
padding: 40px 0;
|
}
|
.snapshot-wrap {
|
min-height: 120px;
|
}
|
.snapshot-grid {
|
display: flex;
|
gap: 16px;
|
flex-wrap: wrap;
|
}
|
.snapshot-item {
|
flex: 1;
|
min-width: 280px;
|
}
|
.snapshot-label {
|
font-weight: 600;
|
margin-bottom: 8px;
|
}
|
.snapshot-image {
|
width: 100%;
|
max-height: 280px;
|
background: #f5f7fa;
|
}
|
.snapshot-meta {
|
margin-top: 8px;
|
font-size: 12px;
|
color: #606266;
|
display: flex;
|
flex-direction: column;
|
gap: 4px;
|
}
|
.snapshot-actions {
|
margin-top: 16px;
|
text-align: center;
|
}
|
.manual-correct-wrap {
|
min-height: 200px;
|
}
|
.manual-slider {
|
margin-top: 16px;
|
}
|
.manual-buttons {
|
margin-top: 16px;
|
display: flex;
|
gap: 12px;
|
justify-content: center;
|
}
|
.list-video-cell {
|
display: inline-block;
|
cursor: pointer;
|
line-height: 0;
|
}
|
.list-video-thumb {
|
width: 70px;
|
height: 70px;
|
object-fit: cover;
|
background: #000;
|
border-radius: 4px;
|
vertical-align: middle;
|
}
|
.path-text {
|
display: inline-block;
|
max-width: 160px;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
}
|
.status-success {
|
color: #67c23a;
|
font-weight: 500;
|
}
|
.status-danger {
|
color: #f56c6c;
|
font-weight: 500;
|
}
|
.status-warning {
|
color: #e6a23c;
|
font-weight: 500;
|
}
|
.status-primary {
|
color: #409eff;
|
font-weight: 500;
|
}
|
.status-info {
|
color: #909399;
|
}
|
.status-muted {
|
color: #c0c4cc;
|
}
|
</style>
|