Revert "feat(aiot): 截图持久化 + ROI 显示修复 + 告警图片代理"

This reverts commit 547dfdd5f4.
This commit is contained in:
2026-03-03 17:04:23 +08:00
parent 547dfdd5f4
commit 86518ab163
16 changed files with 25 additions and 399 deletions

View File

@@ -1,51 +0,0 @@
import request from '@/utils/request'
export function queryAlertList(params) {
const { page, count, cameraId, alertType, startTime, endTime } = params
return request({
method: 'get',
url: '/api/ai/alert/list',
params: { page, count, cameraId, alertType, startTime, endTime }
})
}
export function queryAlertDetail(alertId) {
return request({
method: 'get',
url: `/api/ai/alert/${alertId}`
})
}
export function deleteAlert(alertId) {
return request({
method: 'delete',
url: '/api/ai/alert/delete',
params: { alertId }
})
}
export function deleteAlertBatch(alertIds) {
return request({
method: 'delete',
url: '/api/ai/alert/delete',
params: { alertIds }
})
}
export function queryAlertStatistics(startTime) {
return request({
method: 'get',
url: '/api/ai/alert/statistics',
params: { startTime }
})
}
/**
* 构建告警图片代理 URL
* @param {string} imagePath COS 对象键image_path
* @returns {string} 代理图片 URL
*/
export function getAlertImageUrl(imagePath) {
if (!imagePath) return ''
return '/api/ai/alert/image?imagePath=' + encodeURIComponent(imagePath)
}

View File

@@ -63,27 +63,10 @@ export function updateAlgoParams(data) {
})
}
/**
* 构建截图代理 URL。
* 指向 /api/ai/roi/snap/image 代理端点,优先从 DB 读持久化 COS key。
* force=true 时先触发 Edge 截新图并更新 DB。
*/
export function getSnapUrl(cameraCode, force = false) {
if (force) {
// force 时先触发一次截图请求(确保 Edge 截新图并更新 DB
return request({
method: 'get',
url: '/api/ai/roi/snap',
params: { cameraCode, force: true }
}).then(() => {
return { data: { status: 'ok', url: '/api/ai/roi/snap/image?cameraCode=' + encodeURIComponent(cameraCode) + '&t=' + Date.now() } }
}).catch(() => {
// 截图请求可能超时,但 DB 会被更新,仍返回代理 URL
return { data: { status: 'ok', url: '/api/ai/roi/snap/image?cameraCode=' + encodeURIComponent(cameraCode) + '&t=' + Date.now() } }
})
}
// 非 force直接返回代理 URL从 DB 读已有截图,不触发 Edge
return Promise.resolve({
data: { status: 'ok', url: '/api/ai/roi/snap/image?cameraCode=' + encodeURIComponent(cameraCode) }
return request({
method: 'get',
url: '/api/ai/roi/snap',
params: { cameraCode, force }
})
}

View File

@@ -233,17 +233,6 @@ export const constantRoutes = [
}
]
},
{
path: '/alertList',
component: Layout,
redirect: '/alertList',
children: [{
path: '',
name: 'AlertList',
component: () => import('@/views/alertList/index'),
meta: { title: '告警记录', icon: 'el-icon-warning' }
}]
},
{
path: '/cameraConfig',
component: Layout,

View File

@@ -1,166 +0,0 @@
<template>
<div class="alert-list-page">
<div class="page-header">
<h3>告警记录</h3>
</div>
<div class="search-bar">
<el-input v-model="searchCameraId" placeholder="摄像头编号" size="small" style="width: 180px" clearable @clear="loadData"></el-input>
<el-select v-model="searchAlertType" placeholder="告警类型" size="small" style="width: 150px; margin-left: 10px" clearable @change="loadData">
<el-option label="离岗" value="leave_post"></el-option>
<el-option label="入侵" value="intrusion"></el-option>
<el-option label="人群聚集" value="crowd_detection"></el-option>
</el-select>
<el-date-picker v-model="dateRange" type="datetimerange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" size="small" style="margin-left: 10px" value-format="yyyy-MM-dd HH:mm:ss" @change="loadData"></el-date-picker>
<el-button size="small" type="primary" style="margin-left: 10px" @click="loadData">查询</el-button>
</div>
<el-table :data="alertList" border stripe style="width: 100%; margin-top: 15px" v-loading="loading">
<el-table-column label="截图" width="120">
<template slot-scope="scope">
<el-image
v-if="scope.row.imagePath"
:src="getImageUrl(scope.row.imagePath)"
:preview-src-list="[getImageUrl(scope.row.imagePath)]"
style="width: 80px; height: 60px"
fit="cover"
>
<div slot="error" style="display:flex;align-items:center;justify-content:center;width:80px;height:60px;background:#f5f7fa;color:#999;font-size:12px">
暂无图片
</div>
</el-image>
<span v-else style="color: #999; font-size: 12px"></span>
</template>
</el-table-column>
<el-table-column prop="alertType" label="告警类型" width="120">
<template slot-scope="scope">
<el-tag size="mini" :type="alertTypeTag(scope.row.alertType)">
{{ alertTypeLabel(scope.row.alertType) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="cameraName" label="摄像头" min-width="140" show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.cameraName || scope.row.cameraId || '-' }}
</template>
</el-table-column>
<el-table-column prop="roiName" label="ROI区域" width="120" show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.roiName || scope.row.roiId || '-' }}
</template>
</el-table-column>
<el-table-column prop="confidence" label="置信度" width="90">
<template slot-scope="scope">
{{ scope.row.confidence ? (scope.row.confidence * 100).toFixed(1) + '%' : '-' }}
</template>
</el-table-column>
<el-table-column prop="durationMinutes" label="持续(分钟)" width="110">
<template slot-scope="scope">
{{ scope.row.durationMinutes ? scope.row.durationMinutes.toFixed(1) : '-' }}
</template>
</el-table-column>
<el-table-column prop="receivedAt" label="时间" min-width="160"></el-table-column>
<el-table-column label="操作" width="100" fixed="right">
<template slot-scope="scope">
<el-button size="mini" type="danger" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
style="margin-top: 15px; text-align: right"
@current-change="handlePageChange"
@size-change="handleSizeChange"
:current-page="page"
:page-sizes="[15, 25, 50]"
:page-size="count"
:total="total"
layout="total, sizes, prev, pager, next"
></el-pagination>
</div>
</template>
<script>
import { queryAlertList, deleteAlert, getAlertImageUrl } from '@/api/aiAlert'
const ALERT_TYPE_MAP = {
leave_post: { label: '离岗', tag: 'warning' },
intrusion: { label: '入侵', tag: 'danger' },
crowd_detection: { label: '人群聚集', tag: '' }
}
export default {
name: 'AlertList',
data() {
return {
loading: false,
alertList: [],
page: 1,
count: 15,
total: 0,
searchCameraId: '',
searchAlertType: '',
dateRange: null
}
},
mounted() {
this.loadData()
},
methods: {
loadData() {
this.loading = true
queryAlertList({
page: this.page,
count: this.count,
cameraId: this.searchCameraId || null,
alertType: this.searchAlertType || null,
startTime: this.dateRange ? this.dateRange[0] : null,
endTime: this.dateRange ? this.dateRange[1] : null
}).then(res => {
const data = res.data
this.alertList = data.list || []
this.total = data.total || 0
}).catch(() => {
this.$message.error('加载失败')
}).finally(() => {
this.loading = false
})
},
handlePageChange(page) {
this.page = page
this.loadData()
},
handleSizeChange(size) {
this.count = size
this.page = 1
this.loadData()
},
handleDelete(row) {
this.$confirm('确定删除该告警记录?', '提示', { type: 'warning' }).then(() => {
deleteAlert(row.alertId).then(() => {
this.$message.success('已删除')
this.loadData()
}).catch(() => {
this.$message.error('删除失败')
})
}).catch(() => {})
},
getImageUrl(imagePath) {
return getAlertImageUrl(imagePath)
},
alertTypeLabel(type) {
return (ALERT_TYPE_MAP[type] || {}).label || type || '未知'
},
alertTypeTag(type) {
return (ALERT_TYPE_MAP[type] || {}).tag || 'info'
}
}
}
</script>
<style scoped>
.alert-list-page { padding: 15px; }
.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
.page-header h3 { margin: 0; }
.search-bar { display: flex; align-items: center; flex-wrap: wrap; }
</style>

View File

@@ -153,7 +153,9 @@ export default {
getSnapUrl(this.cameraId, force).then(res => {
const data = res.data || res
if (data.status === 'ok' && data.url) {
this.snapUrl = data.url
// 添加时间戳防止浏览器缓存旧截图
const url = data.url
this.snapUrl = url + (url.includes('?') ? '&' : '?') + '_t=' + Date.now()
if (data.stale) {
this.$message.warning('截图为缓存数据,边缘设备可能离线')
}

View File

@@ -57,26 +57,16 @@ export default {
snapUrl() {
this.loading = true
this.errorMsg = ''
this.$nextTick(() => this.initCanvas())
}
},
mounted() {
if (this.$refs.wrapper) {
this._resizeObserver = new ResizeObserver(() => {
if (this.$refs.wrapper && this.$refs.wrapper.clientWidth > 0) {
this.initCanvas()
}
})
this._resizeObserver.observe(this.$refs.wrapper)
}
window.addEventListener('resize', this.handleResize)
this.$nextTick(() => {
this.initCanvas()
window.addEventListener('resize', this.handleResize)
})
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize)
if (this._resizeObserver) {
this._resizeObserver.disconnect()
this._resizeObserver = null
}
},
methods: {
onImageLoad() {
@@ -88,7 +78,6 @@ export default {
onImageError() {
this.loading = false
this.errorMsg = '截图加载失败,请确认摄像头正在拉流'
this.$nextTick(() => this.initCanvas())
},
initCanvas() {
const canvas = this.$refs.canvas