|
|
|
|
@ -2,26 +2,30 @@
|
|
|
|
|
<div class="recognition-records">
|
|
|
|
|
<div class="records-title">
|
|
|
|
|
<el-icon><WarningFilled /></el-icon>
|
|
|
|
|
<span>非配合目标识别记录</span>
|
|
|
|
|
<span>人员识别记录</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 当前目标 -->
|
|
|
|
|
<div class="current-target">
|
|
|
|
|
<div v-if="currentTarget" class="target-card">
|
|
|
|
|
<img class="target-avatar" :src="currentTarget.avatar" alt="avatar" />
|
|
|
|
|
<img
|
|
|
|
|
class="target-avatar"
|
|
|
|
|
:src="getAvatar(currentTarget)"
|
|
|
|
|
alt="avatar"
|
|
|
|
|
/>
|
|
|
|
|
<div class="target-info">
|
|
|
|
|
<div class="target-status" :class="currentTarget.type">
|
|
|
|
|
{{ currentTarget.type === 'internal' ? '疑似内部人员' : '陌生外来人员' }}
|
|
|
|
|
</div>
|
|
|
|
|
<div class="target-name">{{ currentTarget.name }}</div>
|
|
|
|
|
<div v-if="currentTarget.type === 'internal'" class="target-detail">
|
|
|
|
|
<el-tag size="small" type="primary">相似度 {{ currentTarget.similarity }}</el-tag>
|
|
|
|
|
<div class="target-dept">所属部门: {{ currentTarget.department }}</div>
|
|
|
|
|
<div class="target-id">工号: {{ currentTarget.employeeId }}</div>
|
|
|
|
|
<el-tag size="small" type="primary">相似度 {{ formatSimilarity(currentTarget.similarity) }}</el-tag>
|
|
|
|
|
<div class="target-dept">所属部门: {{ currentTarget.department || '-' }}</div>
|
|
|
|
|
<div class="target-id">工号: {{ currentTarget.employeeId || '-' }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-else class="target-detail">
|
|
|
|
|
<div class="stranger-tip">未匹配到内部人员</div>
|
|
|
|
|
<el-tag size="small" type="danger">相似度 {{ currentTarget.similarity }}</el-tag>
|
|
|
|
|
<el-tag size="small" type="danger">相似度 {{ formatSimilarity(currentTarget.similarity) }}</el-tag>
|
|
|
|
|
<div class="alert-tip">
|
|
|
|
|
<el-icon><WarningFilled /></el-icon>
|
|
|
|
|
<span>请注意陌生人员出入!</span>
|
|
|
|
|
@ -29,6 +33,7 @@
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-else class="no-data-tip">暂无识别记录</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 记录列表 -->
|
|
|
|
|
@ -40,12 +45,12 @@
|
|
|
|
|
:class="{ active: currentTarget?.id === record.id }"
|
|
|
|
|
@click="selectTarget(record)"
|
|
|
|
|
>
|
|
|
|
|
<img class="record-avatar" :src="record.avatar" alt="avatar" />
|
|
|
|
|
<img class="record-avatar" :src="getAvatar(record)" alt="avatar" />
|
|
|
|
|
<div class="record-info">
|
|
|
|
|
<div class="record-time">{{ record.time }}</div>
|
|
|
|
|
<div class="record-location">{{ record.location }}</div>
|
|
|
|
|
<div class="record-time">{{ formatTime(record.enterTime) }}</div>
|
|
|
|
|
<div class="record-location">{{ record.department || '未知部门' }}</div>
|
|
|
|
|
<div class="record-name">{{ record.name }}</div>
|
|
|
|
|
<div class="record-similarity">相似度 {{ record.similarity }}</div>
|
|
|
|
|
<div class="record-similarity">相似度 {{ formatSimilarity(record.similarity) }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<el-tag
|
|
|
|
|
size="small"
|
|
|
|
|
@ -60,70 +65,224 @@
|
|
|
|
|
<el-button class="more-btn" type="primary" plain @click="viewMore">
|
|
|
|
|
查看更多记录
|
|
|
|
|
</el-button>
|
|
|
|
|
|
|
|
|
|
<!-- 查看记录弹窗 -->
|
|
|
|
|
<el-dialog
|
|
|
|
|
v-model="dialogVisible"
|
|
|
|
|
title="识别记录详情"
|
|
|
|
|
width="1100px"
|
|
|
|
|
:close-on-click-modal="false"
|
|
|
|
|
@open="loadDialogData"
|
|
|
|
|
>
|
|
|
|
|
<!-- 搜索区 -->
|
|
|
|
|
<div class="dialog-search">
|
|
|
|
|
<el-date-picker
|
|
|
|
|
v-model="searchParams.startTime"
|
|
|
|
|
type="datetime"
|
|
|
|
|
placeholder="开始时间"
|
|
|
|
|
format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
style="width: 210px"
|
|
|
|
|
:clearable="true"
|
|
|
|
|
/>
|
|
|
|
|
<span class="search-separator">—</span>
|
|
|
|
|
<el-date-picker
|
|
|
|
|
v-model="searchParams.endTime"
|
|
|
|
|
type="datetime"
|
|
|
|
|
placeholder="结束时间"
|
|
|
|
|
format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
style="width: 210px"
|
|
|
|
|
:clearable="true"
|
|
|
|
|
/>
|
|
|
|
|
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
|
|
|
|
<el-button @click="handleReset">重置</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 表格 -->
|
|
|
|
|
<el-table
|
|
|
|
|
:data="dialogRecords"
|
|
|
|
|
v-loading="dialogLoading"
|
|
|
|
|
stripe
|
|
|
|
|
max-height="420"
|
|
|
|
|
style="width: 100%; margin-top: 12px"
|
|
|
|
|
>
|
|
|
|
|
<el-table-column label="头像" width="60">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<img
|
|
|
|
|
:src="getAvatar(row)"
|
|
|
|
|
class="table-avatar"
|
|
|
|
|
alt="avatar"
|
|
|
|
|
/>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column prop="name" label="姓名" width="100" />
|
|
|
|
|
<el-table-column label="性别" width="60">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
{{ row.gender === 1 ? '男' : '女' }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column prop="age" label="年龄" width="60" />
|
|
|
|
|
<el-table-column prop="department" label="部门" min-width="100">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
{{ row.department || '-' }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column prop="employeeId" label="工号" width="100">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
{{ row.employeeId || '-' }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="相似度" width="90">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<span :style="{ color: row.type === 'internal' ? '#67C23A' : '#F56C6C' }">
|
|
|
|
|
{{ formatSimilarity(row.similarity) }}
|
|
|
|
|
</span>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="进入时间" width="170">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
{{ row.enterTime || '-' }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="离开时间" width="170">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
{{ row.exitTime || '-' }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="类型" width="90">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-tag :type="row.type === 'internal' ? 'success' : 'danger'" size="small">
|
|
|
|
|
{{ row.type === 'internal' ? '内部人员' : '陌生人员' }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
|
|
<!-- 分页 -->
|
|
|
|
|
<div class="dialog-pagination">
|
|
|
|
|
<el-pagination
|
|
|
|
|
v-model:current-page="dialogPageNum"
|
|
|
|
|
v-model:page-size="dialogPageSize"
|
|
|
|
|
:page-sizes="[10, 20, 50]"
|
|
|
|
|
:total="dialogTotal"
|
|
|
|
|
layout="total, sizes, prev, pager, next"
|
|
|
|
|
@current-change="loadDialogData"
|
|
|
|
|
@size-change="loadDialogData"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { ref } from 'vue'
|
|
|
|
|
import { ref, onMounted } from 'vue'
|
|
|
|
|
import { WarningFilled } from '@element-plus/icons-vue'
|
|
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
|
|
import { getFaceRecordPage } from '@/api/face-recognition'
|
|
|
|
|
import fileHttp from '@/utils/fileHttp'
|
|
|
|
|
const currentTarget = ref(null)
|
|
|
|
|
const records = ref([])
|
|
|
|
|
|
|
|
|
|
// 格式化相似度为百分比
|
|
|
|
|
const formatSimilarity = (val) => {
|
|
|
|
|
if (val == null) return '-'
|
|
|
|
|
return (val * 100).toFixed(1) + '%'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const currentTarget = ref({
|
|
|
|
|
id: 1,
|
|
|
|
|
name: '张三',
|
|
|
|
|
type: 'internal',
|
|
|
|
|
similarity: '92.4%',
|
|
|
|
|
department: '研发部',
|
|
|
|
|
employeeId: '10086',
|
|
|
|
|
avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop&crop=face'
|
|
|
|
|
// 截取时间(只取 HH:mm:ss)
|
|
|
|
|
const formatTime = (timeStr) => {
|
|
|
|
|
if (!timeStr) return '-'
|
|
|
|
|
return timeStr.split(' ')[1] || timeStr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 判断类型:有名字则为内部人员,无名字则为陌生人员
|
|
|
|
|
const getRecordType = (name) => {
|
|
|
|
|
return name ? 'internal' : 'stranger'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取头像
|
|
|
|
|
const getAvatar = (record) => {
|
|
|
|
|
return fileHttp + record.imageUrl
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 转换 API 数据为展示格式
|
|
|
|
|
const mapRecord = (item) => ({
|
|
|
|
|
...item,
|
|
|
|
|
type: getRecordType(item.name)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const records = ref([
|
|
|
|
|
{
|
|
|
|
|
id: 1,
|
|
|
|
|
time: '14:35:20',
|
|
|
|
|
location: '视角1 - 园区北门',
|
|
|
|
|
name: '张三(研发部)',
|
|
|
|
|
similarity: '92.4%',
|
|
|
|
|
type: 'internal',
|
|
|
|
|
avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop&crop=face'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 2,
|
|
|
|
|
time: '14:32:18',
|
|
|
|
|
location: '视角2 - 园区广场',
|
|
|
|
|
name: '未知人员',
|
|
|
|
|
similarity: '32.7%',
|
|
|
|
|
type: 'stranger',
|
|
|
|
|
avatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100&h=100&fit=crop&crop=face'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 3,
|
|
|
|
|
time: '14:28:45',
|
|
|
|
|
location: '视角3 - 园区通道',
|
|
|
|
|
name: '李四(运维部)',
|
|
|
|
|
similarity: '91.7%',
|
|
|
|
|
type: 'internal',
|
|
|
|
|
avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=100&h=100&fit=crop&crop=face'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 4,
|
|
|
|
|
time: '14:25:30',
|
|
|
|
|
location: '视角4 - 园区停车场',
|
|
|
|
|
name: '王五(行政部)',
|
|
|
|
|
similarity: '89.6%',
|
|
|
|
|
type: 'internal',
|
|
|
|
|
avatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=100&h=100&fit=crop&crop=face'
|
|
|
|
|
// 加载首页记录
|
|
|
|
|
const loadRecords = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const res = await getFaceRecordPage({ pageNum: 1, pageSize: 10 })
|
|
|
|
|
if (res.code === 200 && res.data) {
|
|
|
|
|
records.value = (res.data.list || []).map(mapRecord)
|
|
|
|
|
if (records.value.length > 0 && !currentTarget.value) {
|
|
|
|
|
currentTarget.value = records.value[0]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('加载识别记录失败:', error)
|
|
|
|
|
}
|
|
|
|
|
])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const selectTarget = (record) => {
|
|
|
|
|
currentTarget.value = record
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ========== 弹窗相关 ==========
|
|
|
|
|
const dialogVisible = ref(false)
|
|
|
|
|
const dialogLoading = ref(false)
|
|
|
|
|
const dialogRecords = ref([])
|
|
|
|
|
const dialogPageNum = ref(1)
|
|
|
|
|
const dialogPageSize = ref(10)
|
|
|
|
|
const dialogTotal = ref(0)
|
|
|
|
|
|
|
|
|
|
const searchParams = ref({
|
|
|
|
|
startTime: '',
|
|
|
|
|
endTime: ''
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const loadDialogData = async () => {
|
|
|
|
|
dialogLoading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const res = await getFaceRecordPage({
|
|
|
|
|
pageNum: dialogPageNum.value,
|
|
|
|
|
pageSize: dialogPageSize.value,
|
|
|
|
|
startTime: searchParams.value.startTime || undefined,
|
|
|
|
|
endTime: searchParams.value.endTime || undefined
|
|
|
|
|
})
|
|
|
|
|
if (res.code === 200 && res.data) {
|
|
|
|
|
dialogRecords.value = (res.data.list || []).map(mapRecord)
|
|
|
|
|
dialogTotal.value = res.data.total || 0
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('加载记录详情失败:', error)
|
|
|
|
|
} finally {
|
|
|
|
|
dialogLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleSearch = () => {
|
|
|
|
|
dialogPageNum.value = 1
|
|
|
|
|
loadDialogData()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleReset = () => {
|
|
|
|
|
searchParams.value.startTime = ''
|
|
|
|
|
searchParams.value.endTime = ''
|
|
|
|
|
dialogPageNum.value = 1
|
|
|
|
|
loadDialogData()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const viewMore = () => {
|
|
|
|
|
ElMessage.info('更多记录功能开发中')
|
|
|
|
|
dialogVisible.value = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
loadRecords()
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
@ -154,6 +313,15 @@ const viewMore = () => {
|
|
|
|
|
.current-target {
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
|
|
|
|
.no-data-tip {
|
|
|
|
|
padding: 16px;
|
|
|
|
|
text-align: center;
|
|
|
|
|
color: #909399;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
background: #f5f7fa;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.target-card {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
@ -296,4 +464,29 @@ const viewMore = () => {
|
|
|
|
|
padding: 8px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 弹窗搜索样式
|
|
|
|
|
.dialog-search {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
|
|
|
|
.search-separator {
|
|
|
|
|
color: #909399;
|
|
|
|
|
margin: 0 4px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.table-avatar {
|
|
|
|
|
width: 32px;
|
|
|
|
|
height: 32px;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
object-fit: cover;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dialog-pagination {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
margin-top: 16px;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|