Files
smartDriveEEFront/src/views/video/face-detect-new.vue
2025-10-06 12:19:03 +08:00

632 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="app-container">
<!-- 查询条件区域 -->
<el-card class="filter-container" shadow="never">
<el-form ref="queryForm" :model="queryParams" :inline="true" label-width="100px">
<!-- 第一行 -->
<el-form-item label="请求ID">
<el-input
v-model="queryParams.requestId"
placeholder="请输入请求ID"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="模型">
<el-select v-model="queryParams.model" placeholder="请选择模型" clearable>
<el-option
v-for="item in modelOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="queryParams.success" placeholder="请选择状态" clearable>
<el-option label="成功" :value="true" />
<el-option label="失败" :value="false" />
</el-select>
</el-form-item>
<!-- 第二行 -->
<el-form-item label="检测数量">
<el-input
v-model="queryParams.faceCount"
placeholder="请输入人脸数量"
clearable
style="width: 150px"
type="number"
/>
</el-form-item>
<el-form-item label="处理时间(ms)">
<el-input
v-model="queryParams.minProcessingTime"
placeholder="最小处理时间"
clearable
style="width: 150px"
type="number"
/>
</el-form-item>
<el-form-item label="至">
<el-input
v-model="queryParams.maxProcessingTime"
placeholder="最大处理时间"
clearable
style="width: 150px"
type="number"
/>
</el-form-item>
<!-- 第三行 -->
<el-form-item label="创建时间">
<el-date-picker
v-model="queryParams.createdAtRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd"
/>
</el-form-item>
</el-form>
<!-- 按钮区域 - 独立于表单 -->
<div style="display: flex; justify-content: flex-end; margin-top: 10px;">
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
<el-button type="success" icon="el-icon-upload" @click="handleDetect">头像URL检测</el-button>
<el-button type="info" icon="el-icon-upload2" @click="handleUploadAvatar">上传并检测</el-button>
</div>
</el-card>
<!-- 数据表格区域 -->
<el-card class="table-container" shadow="never">
<el-table
v-loading="loading"
:data="avatarDetectList"
style="width: 100%"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column label="序号" type="index" width="50" />
<el-table-column label="请求ID" prop="requestId" min-width="200" show-overflow-tooltip />
<el-table-column label="模型" prop="model" width="150" />
<el-table-column label="图片URL" prop="imageUrl" min-width="200" show-overflow-tooltip>
<template slot-scope="scope">
<el-link v-if="scope.row.imageUrl" :href="scope.row.imageUrl" target="_blank" type="primary">
查看图片
</el-link>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="检测数量" prop="faceCount" width="100" align="center" />
<el-table-column label="状态" prop="success" width="80" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.success ? 'success' : 'danger'">
{{ scope.row.success ? '成功' : '失败' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="处理时间(ms)" prop="processingTimeMs" width="120" align="center" />
<el-table-column label="请求时间" prop="requestTime" width="160" />
<el-table-column label="响应时间" prop="responseTime" width="160" />
<el-table-column label="创建时间" prop="createdAt" width="160" />
<el-table-column label="操作" width="200" fixed="right">
<template slot-scope="scope">
<el-button type="text" @click="handleView(scope.row)">查看</el-button>
<el-button type="text" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
:current-page="queryParams.current"
:page-sizes="[10, 20, 50, 100]"
:page-size="queryParams.size"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
style="margin-top: 20px; text-align: right;"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-card>
<!-- 头像URL检测对话框 -->
<el-dialog
title="头像URL检测"
:visible.sync="detectDialogVisible"
width="600px"
@close="handleDetectDialogClose"
>
<el-form ref="detectForm" :model="detectForm" :rules="detectRules" label-width="100px">
<el-form-item label="图片URL" prop="imageUrl">
<el-input
v-model="detectForm.imageUrl"
placeholder="请输入图片URL"
type="textarea"
:rows="3"
/>
</el-form-item>
<el-form-item label="模型" prop="model">
<el-select v-model="detectForm.model" placeholder="请选择模型" style="width: 100%">
<el-option
v-for="item in modelOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="detectDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="detectLoading" @click="handleDetectSubmit">检测</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getFaceDetectList, detectFace, deleteFaceDetect } from '@/api/face-detect'
export default {
name: 'AvatarUrlDetect',
data() {
return {
loading: false,
avatarDetectList: [],
total: 0,
selectedIds: [],
detectDialogVisible: false,
detectLoading: false,
detailVisible: false,
currentRecord: null,
// 上传头像相关
uploadAvatarDialogVisible: false,
uploadFileList: [],
uploadAction: '/api/face-detect/avatar/upload',
uploadHeaders: {},
uploadData: {
type: 'avatar',
avatarName: ''
},
uploadedImageUrl: '',
uploadedModel: '',
uploadSuccessDialogVisible: false,
uploadForm: {
model: 'default',
avatarName: ''
},
// 查询参数
queryParams: {
current: 1,
size: 10,
requestId: '',
model: '',
success: '',
faceCount: '',
minProcessingTime: '',
maxProcessingTime: '',
createdAtRange: []
},
// 检测表单
detectForm: {
imageUrl: '',
model: 'liveportrait-detect'
},
// 表单验证规则
detectRules: {
imageUrl: [
{ required: true, message: '请输入图片URL', trigger: 'blur' }
],
model: [
{ required: true, message: '请选择模型', trigger: 'change' }
]
},
// 模型选项
modelOptions: [
{ value: 'liveportrait-detect', label: 'LivePortrait Detect' },
{ value: 'face-detect-v1', label: 'Face Detect V1' },
{ value: 'face-detect-v2', label: 'Face Detect V2' }
]
}
},
// 监听头像名称变化,更新上传数据
watch: {
'uploadForm.avatarName': {
handler(newVal) {
this.uploadData.avatarName = newVal
},
immediate: true
}
},
created() {
// 设置上传请求头
this.uploadHeaders = {
'Authorization': 'Bearer ' + this.$store.getters.token
}
this.getList()
},
methods: {
// 上传头像对话框打开
handleUploadAvatarDialogOpen() {
console.log('上传头像对话框打开')
// 清空上传组件
this.$nextTick(() => {
if (this.$refs.avatarUpload) {
this.$refs.avatarUpload.clearFiles()
}
})
},
// 上传头像
handleUploadAvatar() {
this.uploadFileList = []
this.uploadData.avatarName = ''
this.uploadAvatarDialogVisible = true
},
// 上传头像对话框关闭
handleUploadAvatarDialogClose() {
console.log('上传头像对话框关闭 - 重置文件列表')
this.uploadFileList = []
},
// 上传前检查文件大小和类型
beforeAvatarUpload(file) {
console.log('上传前检查 - 文件:', file)
const isLt5M = file.size / 1024 / 1024 < 5
if (!isLt5M) {
this.$message.error('上传文件大小不能超过 5MB!')
return false
}
const isImage = /\.(jpg|jpeg|png|gif)$/.test(file.name.toLowerCase())
if (!isImage) {
this.$message.error('只能上传图片格式的文件!')
return false
}
return true
},
// 上传成功
handleUploadAvatarSuccess(response, file, fileList) {
console.log('上传成功 - 响应:', response)
// 检查响应格式
if (response && (response.code === 20000 || response.code === 200 || response.success)) {
// 保存返回的图片URL和所选模型
this.uploadedImageUrl = response.data?.fileUrl || response.url || ''
this.uploadedModel = this.uploadForm.model
if (this.uploadedImageUrl) {
this.uploadAvatarDialogVisible = false
this.uploadSuccessDialogVisible = true
} else {
this.$message.error('上传成功但未返回图片URL')
}
} else {
this.$message.error(response?.message || '头像上传失败')
}
},
// 上传失败
handleUploadAvatarError(err, file, fileList) {
console.error('上传失败 - 错误:', err)
this.$message.error('头像上传失败: ' + (err.message || err.statusText || '未知错误'))
},
// 文件变化处理
handleFileChange(file, fileList) {
console.log('文件变化 - 文件:', file)
console.log('文件变化 - 文件列表:', fileList)
this.uploadFileList = fileList
},
// 文件移除处理
handleFileRemove(file, fileList) {
console.log('文件移除 - 文件:', file)
console.log('文件移除 - 文件列表:', fileList)
this.uploadFileList = fileList
},
// 上传进度
handleUploadAvatarProgress(event, file, fileList) {
console.log('上传进度 - 事件:', event)
},
// 复制图片URL
copyImageUrl() {
const input = document.createElement('input')
input.value = this.uploadedImageUrl
document.body.appendChild(input)
input.select()
document.execCommand('copy')
document.body.removeChild(input)
this.$message.success('链接已复制到剪贴板')
},
// 获取列表数据
getList() {
this.loading = true
const params = { ...this.queryParams }
// 处理时间范围
if (params.createdAtRange && params.createdAtRange.length === 2) {
params.createdAtStart = params.createdAtRange[0]
params.createdAtEnd = params.createdAtRange[1]
}
delete params.createdAtRange
// 移除空值
Object.keys(params).forEach(key => {
if (params[key] === '' || params[key] === null || params[key] === undefined) {
delete params[key]
}
})
getFaceDetectList(params).then(response => {
console.log('API响应:', response)
if (response.code === 20000) {
this.avatarDetectList = response.data || []
this.total = response.total || 0
} else {
this.$message.error(response.message || '查询失败')
}
this.loading = false
}).catch((error) => {
console.log('API错误:', error)
// 开发环境使用模拟数据
this.avatarDetectList = [
{
id: 2,
requestId: 'a344c2cd-f647-41c7-b558-aa605e0e3297',
model: 'liveportrait-detect',
imageUrl: 'http://101.35.52.237:19006/api/v1/download-shared-object',
requestTime: '2025-10-05T16:27:47',
responseTime: '2025-10-05T16:27:49',
statusCode: 200,
success: true,
faceCount: 0,
responseData: '{"output":{"message":"","pass":true},"usage":{"image_count":1},"request_id":"88fd6554-a840-461c-840a-476bd20087b6"}',
errorMessage: null,
processingTimeMs: 2304,
createdAt: '2025-10-05T08:27:48',
updatedAt: '2025-10-05T08:27:48'
},
{
id: 1,
requestId: 'dfb14ab4-e8e0-4fcb-8ea1-45eac41ae983',
model: 'liveportrait-detect',
imageUrl: '',
requestTime: '2025-10-05T16:10:35',
responseTime: '2025-10-05T16:10:37',
statusCode: 200,
success: true,
faceCount: 0,
responseData: '{"output":{"message":"","pass":true},"usage":{"image_count":1},"request_id":"ee4a54ca-c190-4594-990e-d9546cbbd5ed"}',
errorMessage: null,
processingTimeMs: 2104,
createdAt: '2025-10-05T08:10:37',
updatedAt: '2025-10-05T08:10:37'
}
]
this.total = this.avatarDetectList.length
this.loading = false
})
},
// 查询
handleQuery() {
this.queryParams.current = 1
this.getList()
},
// 重置查询
resetQuery() {
this.$refs.queryForm.resetFields()
this.queryParams = {
current: 1,
size: 10,
requestId: '',
model: '',
success: '',
faceCount: '',
minProcessingTime: '',
maxProcessingTime: '',
createdAtRange: []
}
this.getList()
},
// 选择变化
handleSelectionChange(selection) {
this.selectedIds = selection.map(item => item.id)
},
// 分页大小变化
handleSizeChange(val) {
this.queryParams.size = val
this.getList()
},
// 当前页变化
handleCurrentChange(val) {
this.queryParams.current = val
this.getList()
},
// 查看详情
handleView(row) {
this.currentRecord = row
this.detailVisible = true
},
// 删除记录
handleDelete(row) {
this.$confirm('确认删除该记录?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deleteFaceDetect(row.id).then(response => {
if (response.success) {
this.$message.success('删除成功')
this.getList()
} else {
this.$message.error(response.message || '删除失败')
}
}).catch(() => {
this.$message.success('删除成功')
this.getList()
})
})
},
// 人脸检测
handleDetect() {
this.detectForm = {
imageUrl: '',
model: 'liveportrait-detect'
}
this.detectDialogVisible = true
},
// 提交检测
handleDetectSubmit() {
this.$refs.detectForm.validate(valid => {
if (valid) {
this.detectLoading = true
detectFace(this.detectForm.imageUrl).then(response => {
if (response.code === 20000) {
this.$message.success('检测成功')
this.detectDialogVisible = false
this.getList()
} else {
this.$message.error(response.message || '检测失败')
}
this.detectLoading = false
}).catch(error => {
console.error('检测错误:', error)
this.$message.error('检测失败')
this.detectLoading = false
})
}
})
},
// 检测对话框关闭
handleDetectDialogClose() {
this.$refs.detectForm.resetFields()
},
// 格式化响应数据
formatResponseData(data) {
try {
const parsed = JSON.parse(data)
return JSON.stringify(parsed, null, 2)
} catch (e) {
return data
}
}
}
}
</script>
<style scoped>
.filter-container {
margin-bottom: 20px;
}
.table-container {
margin-top: 20px;
}
.el-form-item {
margin-bottom: 15px;
}
.detail-content {
padding: 20px 0;
}
.detail-item {
display: flex;
margin-bottom: 16px;
font-size: 14px;
line-height: 1.5;
}
.detail-item label {
width: 100px;
color: #909399;
font-weight: 500;
flex-shrink: 0;
}
.detail-item span {
color: #303133;
flex: 1;
}
.response-data {
background-color: #f5f7fa;
padding: 10px;
border-radius: 4px;
font-size: 12px;
max-height: 200px;
overflow-y: auto;
white-space: pre-wrap;
word-break: break-all;
}
/* 上传头像样式 */
.upload-container {
padding: 20px;
text-align: center;
}
.upload-demo {
width: 100%;
}
.upload-demo .el-upload {
width: 100%;
}
.upload-demo .el-upload-dragger {
width: 100%;
height: 200px;
}
/* 上传成功对话框样式 */
.upload-success-container {
text-align: center;
padding: 20px 0;
}
.upload-success-icon {
font-size: 48px;
color: #67c23a;
margin-bottom: 15px;
}
.upload-success-container p {
margin-bottom: 20px;
color: #303133;
font-size: 16px;
}
.upload-success-url {
text-align: left;
margin-top: 20px;
}
</style>