Files
erqi-web/src/views/business/identification/alarm/index.vue

718 lines
18 KiB
Vue
Raw Normal View History

2025-12-24 18:19:05 +08:00
<template>
<div class="content-container">
<!-- 识别记录数据 -->
<div class="identificate-index">
<FilterCom ref="Filter" :filter-buttons="filterButtons" :filter-items="items" :filter-model="model" @handle="handle"/>
<!-- 多选框筛选 -->
<div class="Form">
<!-- 暂时只支持单个类型的筛选 -->
<!-- <el-checkbox-group v-model="illegalType" :max="1">
<el-checkbox
v-for="item in illegalTypes"
:key="item.value"
:label="item.label"
:value="item.value" />
</el-checkbox-group> -->
<el-radio-group v-model="illegalType" @change="changeRadio">
<el-radio v-for="item in illegalTypes"
:key="item.value"
:label="item.label"
:value="item.value" ></el-radio>
</el-radio-group>
</div>
<!-- 表格数据 -->
<TableComponent style="height: 616px;" :loading="loading" :data="tableData" :columns="columns" :config="config" :operate="operate" @handle="handle"/>
<el-pagination
v-model:current-page="pagination.current"
v-model:page-size="pagination.size"
:total="pagination.total"
:page-sizes="[ 10, 15, 20, 50 ]"
layout="total, sizes, prev, pager, next, jumper"
@size-change="initData"
@current-change="initData"
/>
</div>
<!-- 数据详情 -->
<div class="identificate-detail">
<div class="detail-item" v-for="item in detail.data" :key="item.id">
<TitleComponent :title="item.videoName"/>
<div class="detail">
<el-image
v-if="item.trackerPicPath.length>0"
:src="item.trackerPicPath[0]"
:zoom-rate="1.2"
:max-scale="7"
:min-scale="0.2"
:scale="0.8"
:preview-src-list="item.trackerPicPath"
show-progress
/>
<div class="none" v-else>暂无图片</div>
<div class="half">
<div class="boatName">{{detail.boatName}}</div>
<div class="col" v-for="col in detail.columns.slice(0,5)" :key="col.value">
<span class="label">{{ col.label }}:</span>
<span class="value">{{ item[col.prop] }}{{ col.unit }}</span>
</div>
</div>
</div>
<div class="row">
<div class="col" v-for="col in detail.columns.slice(5)" :key="col.value">
<span class="label">{{ col.label }}:</span>
<span class="value">{{ item[col.prop] }}</span>
</div>
</div>
<div class="row process-title">
<div class="col">
<span class="label">过程分析</span>
</div>
</div>
<div
class="play-back-wrapper"
:class="[`video-window0`]">
<HikPlayerBackCom
ref="appPlayer"
v-if="detail.code"
:cameraIndexCode="detail.code"
videoWindowClassName="play-back-wrapper"
@close="closeVideo"/>
</div>
</div>
</div>
</div>
<DialogComponent v-if="edit.visible" title="编辑" :modal="true" width="800"
@close="closeDetail">
<DetailComponent :data="edit.data" @close="closeDetail" />
</DialogComponent>
</template>
<script setup>
import FilterCom from '@/components/Filter/index.vue'
import { nextTick, onBeforeUnmount, onMounted, onUnmounted, reactive, ref } from 'vue'
import TableComponent from '@/components/Table/index2.vue'
import TitleComponent from '@/components/Title/index.vue'
2025-12-25 18:33:15 +08:00
import { videoIdentificationPage, removeVideoIdentification } from '@/api/identification.js'
2025-12-26 00:34:12 +08:00
2025-12-24 18:19:05 +08:00
import HikPlayerBackCom from '@/components/Player/HikPlayer-back.vue'
import { dayjs, ElMessage, ElMessageBox } from 'element-plus'
import { useRoute } from 'vue-router'
import DialogComponent from '@/components/Dialog/screen.vue'
import DetailComponent from './detail.vue'
const route = useRoute()
const illegalTypes = [
{ value: '未封舱预警', label: '未封舱预警', prop: 'warning' },
{ value: '未穿救生衣预警', label: '未穿救生衣预警', prop: 'warning' },
{ value: '吃水线预警', label: '吃水线预警', prop: 'yellow' },
{ value: '人员落水协助', label: '人员落水协助', prop: 'primary' },
{ value: '未悬挂国旗', label: '未悬挂国旗', prop: 'primary' },
{ value: '异常停靠预警', label: '异常停靠预警', prop: 'yellow' },
{ value: '船只漏油预警', label: '船只漏油预警', prop: 'warning' },
{ value: '船舶火灾预警', label: '船舶火灾预警', prop: 'danger' },
{ value: '超速预警', label: '超速预警', prop: 'primary' },
{ value: '偏航预警', label: '偏航预警', prop: 'yellow' }
]
const Filter = ref(null)
const items = ref([
{
label: '抓拍时间',
prop: 'time',
type: 'datetimerange'
},
{
label: '船型',
prop: 'shipType',
type: 'select',
options: [ {
label: '集装箱船',
value: '集装箱船'
}, {
label: '液货船',
value: '液货船'
},
{
label: '散杂货船',
value: '散杂货船'
},
{
label: '客渡船',
value: '客渡船'
},
{
label: '渔船',
value: '渔船'
},
{
label: '公务船',
value: '公务船'
},
{
label: '引航船',
value: '引航船'
},
{
label: '拖轮',
value: '拖轮'
} ]
},
{
label: '船牌名称',
prop: 'boatName',
type: 'input'
},
{
label: '抓拍类型',
prop: 'takeType',
type: 'select',
options: [
{ value: '', label: '全部' },
{ value: '无人机', label: '无人机' },
{ value: '卡口', label: '卡口' }
],
emptyValues: [ null, undefined ],
valueOnClear: null
},
{
label: '抓拍设备',
prop: 'videoName',
type: 'input'
}
])
const filterButtons = ref([
{
name: '检索',
prop: 'query',
theme: 'primary',
type: 'button'
},
{
name: '重置',
prop: 'reset',
theme: 'primary',
type: 'button'
}
])
const resetModel = reactive({
time: [ null, null ],
shipType: '',
boatName: '',
takeType: '',
videoName: ''
})
const model = reactive({
time: [ null, null ],
shipType: '',
boatName: '',
takeType: '',
videoName: ''
})
const illegalType = ref('')
const loading = ref(false)
const tableData = ref([])
const columns = ref([
{
label: '抓拍时间',
prop: 'takeTime',
width: 190
},
{
label: '船牌名称',
prop: 'boatName',
width: 120
},
{
label: '船型',
prop: 'shipType',
width: 80
},
{
label: '是否有AIS信号',
prop: 'isHasAis',
width: 120
},
{
label: 'MMSI',
prop: 'mmsi',
width: 100
},
{
label: '违规类型',
prop: 'illegalType',
width: 310,
sign: true,
tooltip: false
},
{
label: '抓拍设备',
prop: 'videoName',
width: 160
},
{
label: '抓拍类型',
prop: 'takeType',
width: 80
}
])
const config = {
label: '操作',
// width: 60
width: 150
}
const operate = [
{
label: '查看',
prop: 'check',
theme: 'text'
},
{
label: '编辑',
prop: 'edit',
theme: 'text'
},
{
label: '删除',
prop: 'delete',
theme: 'danger'
}
]
const pagination = reactive({
current: 1,
size: 15,
total: 0
})
const detail = reactive({
boatName: '',
columns: [
{
label: '船型',
prop: 'shipType'
},
{
label: '名称',
prop: 'boatNameEn'
},
{
label: '船长',
prop: 'length',
unit: 'm'
},
{
label: '航速',
prop: 'speed',
unit: '节'
},
{
label: '船宽',
prop: 'width',
unit: 'm'
},
{
label: 'MMSI',
prop: 'mmsi'
},
{
label: '方向',
prop: 'entryOut'
},
{
label: '抓拍类型',
prop: 'takeType'
},
{
label: '时间',
prop: 'takeTime'
},
{
label: 'AIS状态',
prop: 'aisStatus'
}
],
data: [],
code: ''
})
const appPlayer = ref(null)
const edit = reactive({
visible: false,
data: {}
})
const initData = () => {
loading.value = true
2025-12-26 00:34:12 +08:00
const params = new FormData()
const obj = {
...model,
// illegalType: illegalType.value.join(','),
illegalType: illegalType.value,
beginTime: model.time && model.time[0] ? model.time[0] : '',
endTime: model.time && model.time[1] ? model.time[1] : '',
pageNo: pagination.current,
pageSize: pagination.size
}
delete obj.time
2025-12-25 18:33:15 +08:00
// Object.keys(obj).forEach((key) => {
// params.append(key, obj[key])
// })
2025-12-26 00:34:12 +08:00
videoIdentificationPage(obj).then(res => {
if (res.success) {
tableData.value = res.result.records.map(i => {
return {
...i,
illegalType: i.illegalType.split(',').map(j => {
2025-12-24 18:19:05 +08:00
return {
value: illegalTypes.find(type => type.value === j)?.prop,
label: j
}
})
2025-12-26 00:34:12 +08:00
}
})
pagination.total = res.result.total
// 有识别记录数据,默认查看第一条详情
handle('check', res.result.total > 0 ? tableData.value[0] : {})
} else {
ElMessage.error(res.msg || '查询失败!')
2025-12-24 18:19:05 +08:00
}
2025-12-26 00:34:12 +08:00
}).finally(() => {
loading.value = false
2025-12-24 18:19:05 +08:00
})
2025-12-26 00:34:12 +08:00
// tableData.value = [
// {
// 'aisStatus': '未开启',
// 'belongPort': '',
// 'boatCodePath': '',
// 'boatName': '浙周田货0998',
// 'boatNameEn': 'WUCHUANHAO',
// 'cog': '0',
// 'createAt': '2025-12-01 11:04:44',
// 'createBy': '',
// 'crossLineTime': null,
// 'delFlag': 0,
// 'distance': null,
// 'draftMarks': null,
// 'entryOut': '',
// 'height': 0.00000000,
// 'heightRange': '',
// 'hkResult': '',
// 'id': 76168,
// 'identificationType': '',
// 'illegalType': '未穿救生衣预警,未封舱预警,未悬挂国旗',
// 'isCloseDoor': '否',
// 'isHasAis': '否',
// 'jacketStatus': '',
// 'latitude': null,
// 'length': 0.00000000,
// 'longitude': null,
// 'mmsi': '413823183',
// 'shipType': '集装箱船',
// 'sourcePicPath': 'http://198.16.74.209:6060/pic/2025-12-01/198.16.74.187/20251201_110559/ship_1_20251201_110559_644.jpg',
// 'speed': null,
// 'streetName': '',
// 'sysShipName': '',
// 'sysUpdateName': '',
// 'systemResult': '',
// 'takeTime': '2025-12-23 11:06:08',
// 'takeType': '卡口',
// 'trackerPicPath': 'http://198.16.74.209:6060/pic/2025-12-01/198.16.74.187/20251201_110559/ship_1_20251201_110559_644.jpg,http://198.16.74.209:6060/pic/2025-12-01/198.16.74.187/20251201_110559/ship_2_20251201_110603_161.jpg,http://198.16.74.209:6060/pic/2025-12-01/198.16.74.187/20251201_110559/ship_3_20251201_110605_535.jpg,http://198.16.74.209:6060/pic/2025-12-01/198.16.74.187/20251201_110559/ship_4_20251201_110608_298.jpg',
// 'updateAt': '2025-12-01 11:04:44',
// 'updateBy': '',
// 'videoCode': 'fd3b45e1429a4e47bba873af602a9bed',
// 'videoName': '卧旗--雷云一体机',
// 'videoUrl': '',
// 'width': 0.00000000
// }, {}
// ].map(i => {
// return {
// ...i,
// illegalType: i.illegalType?.split(',').map(j => {
// return {
// value: illegalTypes.find(type => type.value === j)?.prop,
// label: j
// }
// })
// }
// })
// handle('check', tableData.value.length > 0 ? tableData.value[0] : {})
2025-12-24 18:19:05 +08:00
}
const changeRadio = () => {
nextTick(() => {
Object.keys(model).forEach(key => {
model[key] = Filter.value.model[key]
})
pagination.current = 1
initData()
})
}
/**
* 按钮操作
* @param type 操作类型
* @param data 数据
*/
const handle = (type, data) => {
switch (type) {
case 'query':
Object.keys(model).forEach(key => {
model[key] = data[key]
})
pagination.current = 1
initData()
break
case 'reset': {
Object.keys(resetModel).forEach(i => {
model[i] = resetModel[i]
})
illegalType.value = ''
Filter.value.reset()
break
}
case 'check': {
closeVideo()
detail.data = [ { ...data,
videoName: '',
trackerPicPath: data.trackerPicPath.split(',')
} ]
detail.boatName = data.boatName
detail.code = data.videoCode
// 设置播放结束时间
let startTime = new Date(dayjs(data.takeTime).subtract(1.5, 'minute').format('YYYY-MM-DD HH:mm:ss'))
let endTime = new Date(dayjs(data.takeTime).add(1.5, 'minute').format('YYYY-MM-DD HH:mm:ss'))
startTime.setMinutes(startTime.getMinutes())
endTime.setMinutes(endTime.getMinutes())
setTimeout(() => {
appPlayer.value[0].ready().then(() => {
appPlayer.value[0].playBack(
detail.code,
new Date(startTime),
new Date(endTime)
)
})
}, 500)
break
}
case 'edit':
edit.data = { ...data }
edit.visible = true
break
case 'delete':
ElMessageBox.confirm('是否删除该条数据?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
2025-12-25 18:33:15 +08:00
removeVideoIdentification({ id: data.id }).then(() => {
ElMessage.success('删除成功')
initData()
})
2025-12-24 18:19:05 +08:00
})
break
default:
break
}
}
const closeVideo = () => {
if(appPlayer.value && appPlayer.value.length > 0) {
detail.code = ''
appPlayer.value[0].uninit()
}
}
/**
* 关闭详情弹窗
* @param refresh 是否刷新数据
*/
const closeDetail = () => {
edit.visible = false
initData()
}
onMounted(() => {
if(Object.keys(route.query).length > 0 && route.query.type === 'alarm') {
model.takeType = route.query.takeType
illegalType.value = route.query.illegalType
nextTick(() => {
if (Filter.value && Filter.value.model) {
Filter.value.model.takeType = route.query.takeType
}
})
}
initData()
})
onBeforeUnmount(() => {
closeVideo()
})
2025-12-25 18:33:15 +08:00
defineExpose({
appPlayer
})
2025-12-24 18:19:05 +08:00
</script>
<style scoped lang="scss">
.content-container{
display: flex;
color: #fff;
gap: 20px;
height: 812px;
.identificate-index{
width: 1200px;
display: flex;
flex-direction: column;
align-items: flex-start;
.line{
width: 100%;
height: 0px;
border-radius: 0px 0px 0px 0px;
border: 1px solid rgba(0,192,255,0.2);
margin: 8px 0 15px 0;
}
:deep .filter{
.el-form-item:last-child{
.el-input{
width: 180px !important;
}
}
}
// 分页器样式
.el-pagination{
width: 100%;
margin-top: 24px;
font-size: 12px ;
color: #6281A8;
gap: 10px;
justify-content: center;
// 选项条数
:deep(.el-pagination__sizes){
margin-left: 0;
.el-select{
width: 96px;
}
.el-select__wrapper{
width: 96px;
min-height: 24px;
background: #093369;
box-shadow: none;
font-size: 12px;
.el-select__placeholder,.el-select__caret{
color: #BAE7FF;
}
}
}
// 页数
:deep(.el-pager){
gap: 10px;
// 选中当前页
.is-active{
background: #033E8C;
border: 1px solid #128CE4;
color: #fff;
}
}
:deep(.btn-prev),:deep(.el-pager .number),:deep(.btn-next),:deep(.more){
background: #093369;
min-width: 24px;
height: 24px;
color: #BAE7FF;
font-size: 12px;
margin-left: 0 !important;
}
:deep(.el-pagination__jump){
margin-left: 0;
.el-input{
// width: 32px;
height: 24px;
.el-input__wrapper{
background: #093369;
box-shadow: none;
height: 24px;
box-sizing: border-box;
.el-input__inner{
color: #BAE7FF;
font-size: 12px;
}
}
}
}
:deep(.el-pagination__goto){
margin-right: 6px;
}
:deep(.el-pagination__classifier){
margin-left: 6px;
}
}
}
.identificate-detail{
width: 545px;
height: 802px;
overflow: auto;
// 单个设备抓拍详情
.detail-item{
.detail{
margin: 10px 0 16px;
display: flex;
.el-image,.none{
width: 159px;
height: 105px;
border: 1px solid #2C75A8;
box-sizing: border-box;
margin-right: 14px;
}
.none{
line-height: 105px;
}
}
.half,.row{
display: grid;
grid-template-columns: 1fr 1fr; // 两列等宽
gap: 10px 0;
text-align: left;
font-family: 'PingFangMedium';
.boatName{
grid-column: 1 / -1;
width: 100%;
font-size: 20px;
color: #FFFFFF;
}
.col{
display: flex;
.label {
width: 70px;
font-weight: 400;
font-size: 14px;
color: #00C0FF;
display: inline-block;
flex-shrink: 0;
}
.value {
flex: 1; // 自适应剩余空间
white-space: nowrap; // 不换行
overflow: hidden; // 超出隐藏
text-overflow: ellipsis; // 省略号显示
}
}
}
.row .col{
.label{
width: 100px;
}
}
.process-title{
margin-top: 10px;
}
video,.none-video{
display: block;
width: 273px;
height: 150px;
object-fit: cover;
border: 1px solid #2C75A8;
box-sizing: border-box;
margin: 10px 0 20px;
}
.none-video{
line-height: 150px;
}
.play-back-wrapper{
width: 100%;
height: 300px;
margin: 10px 0 20px;
}
}
}
}
</style>