Files
erqi-web/src/views/business/identification/alarm/index.vue
2025-12-26 00:34:12 +08:00

718 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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'
import { videoIdentificationPage, removeVideoIdentification } from '@/api/identification.js'
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
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
// Object.keys(obj).forEach((key) => {
// params.append(key, obj[key])
// })
videoIdentificationPage(obj).then(res => {
if (res.success) {
tableData.value = res.result.records.map(i => {
return {
...i,
illegalType: i.illegalType.split(',').map(j => {
return {
value: illegalTypes.find(type => type.value === j)?.prop,
label: j
}
})
}
})
pagination.total = res.result.total
// 有识别记录数据,默认查看第一条详情
handle('check', res.result.total > 0 ? tableData.value[0] : {})
} else {
ElMessage.error(res.msg || '查询失败!')
}
}).finally(() => {
loading.value = false
})
// 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] : {})
}
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(() => {
removeVideoIdentification({ id: data.id }).then(() => {
ElMessage.success('删除成功')
initData()
})
})
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()
})
defineExpose({
appPlayer
})
</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>