新增证件识别接口
This commit is contained in:
@@ -0,0 +1,312 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onDeactivated, onMounted, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Button, Form, Input, message } from 'ant-design-vue';
|
||||
|
||||
import * as api from '#/api';
|
||||
|
||||
const list = ref<any[]>([]);
|
||||
const error = ref<null | string>(null);
|
||||
const selectedItem = ref<any>(null);
|
||||
|
||||
async function loadList() {
|
||||
error.value = null;
|
||||
const res =
|
||||
(await api.refreshLicenseImageList(page.value, pageSize.value)) || [];
|
||||
list.value = res.items;
|
||||
total.value = res.total;
|
||||
}
|
||||
|
||||
function createTask() {
|
||||
modalApi.open();
|
||||
}
|
||||
|
||||
// 分页参数
|
||||
const page = ref(1);
|
||||
const pageSize = ref(9);
|
||||
const total = ref(0); // 总条数
|
||||
// 计算总页数
|
||||
const totalPages = computed(() => Math.ceil(total.value / pageSize.value));
|
||||
|
||||
function changePage(newPage) {
|
||||
page.value = newPage;
|
||||
loadList();
|
||||
}
|
||||
async function selectItem(item: any) {
|
||||
selectedItem.value = item;
|
||||
refreshLineChart();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadList();
|
||||
});
|
||||
|
||||
const showInfoStr = ref<Record<string, number | string>>({});
|
||||
const showInfoStr2 = ref<Record<string, number | string>>({});
|
||||
// key 映射表
|
||||
const keyMap: Record<string, string> = {
|
||||
// 身份证
|
||||
side: '证件面',
|
||||
name: '姓名',
|
||||
gender: '性别',
|
||||
ethnicity: '民族',
|
||||
id_number: '身份证号',
|
||||
birth_date: '出生日期',
|
||||
address: '住址',
|
||||
issuing_authority: '签发机关',
|
||||
valid_period_start: '开始日期',
|
||||
valid_period_end: '结束日期',
|
||||
notes: '备注',
|
||||
// 银行卡
|
||||
bank_name: '发卡行',
|
||||
card_number: '卡号',
|
||||
card_holder: '持卡人',
|
||||
expiry_date: '有效期',
|
||||
card_type: '卡种',
|
||||
result: '识别结果',
|
||||
issuer_country: '发卡国家',
|
||||
};
|
||||
|
||||
// 递归替换函数
|
||||
function translateKeys(obj: any, map: Record<string, string>): any {
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map((item) => translateKeys(item, map));
|
||||
} else if (obj !== null && typeof obj === 'object') {
|
||||
const newObj: Record<string, any> = {};
|
||||
for (const key in obj) {
|
||||
const newKey = map[key] || key; // 没映射就用原来的 key
|
||||
newObj[newKey] = translateKeys(obj[key], map);
|
||||
}
|
||||
return newObj;
|
||||
}
|
||||
return obj; // 基本类型直接返回
|
||||
}
|
||||
|
||||
function refreshLineChart() {
|
||||
const data = selectedItem.value;
|
||||
showInfoStr.value = {
|
||||
项目名: data.name,
|
||||
上传时间: data.created_at,
|
||||
文件名: data.file_name,
|
||||
文件大小: `${data.size} MB`,
|
||||
分辨率: data.resolution,
|
||||
证件类型:
|
||||
data.type === -1 ? '未知证件' : data.type === 0 ? '身份证' : '银行卡',
|
||||
};
|
||||
showInfoStr2.value = translateKeys(data.content, keyMap);
|
||||
}
|
||||
|
||||
onDeactivated(() => {
|
||||
// 离开路由时清理状态
|
||||
selectedItem.value = null;
|
||||
showInfoStr.value = {};
|
||||
});
|
||||
|
||||
const projectName = ref('');
|
||||
const fileName = ref('');
|
||||
const selectedFile = ref<File | null>(null);
|
||||
const fileInputRef = ref<HTMLInputElement | null>(null);
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
title: '新建证件照片分析任务',
|
||||
class: 'w-[600px]',
|
||||
onCancel() {
|
||||
modalApi.close();
|
||||
},
|
||||
onConfirm() {
|
||||
if (!selectedFile.value) {
|
||||
message.warning('请选择证件照片');
|
||||
return;
|
||||
}
|
||||
uploadFile();
|
||||
},
|
||||
});
|
||||
async function uploadFile() {
|
||||
if (!selectedFile.value) {
|
||||
message.warning('请选择证件照');
|
||||
return;
|
||||
}
|
||||
|
||||
// 先关闭弹窗
|
||||
modalApi.close();
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', selectedFile.value);
|
||||
formData.append('projectName', projectName.value);
|
||||
await api.createLicenseImageTask(formData);
|
||||
|
||||
// 接口完成后再触发事件
|
||||
message.success('分析任务完成');
|
||||
loadList();
|
||||
|
||||
// 清空表单
|
||||
selectedFile.value = null;
|
||||
projectName.value = '';
|
||||
fileName.value = '';
|
||||
} catch {
|
||||
message.error('上传失败');
|
||||
}
|
||||
}
|
||||
|
||||
function selectFile() {
|
||||
fileInputRef.value?.click();
|
||||
}
|
||||
|
||||
function handleFileChange(event: Event) {
|
||||
const files = (event.target as HTMLInputElement).files;
|
||||
if (files && files.length > 0) {
|
||||
selectedFile.value = files[0];
|
||||
fileName.value = files[0].name;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex h-[90dvh] w-full flex-col">
|
||||
<Modal>
|
||||
<Form layout="vertical">
|
||||
<Form.Item label="任务名称">
|
||||
<Input v-model:value="projectName" placeholder="可为空,将取随机值" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="证件照片" required>
|
||||
<div
|
||||
@click="selectFile"
|
||||
style="
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
border: 1px dashed #d9d9d9;
|
||||
"
|
||||
>
|
||||
{{ fileName || '点击选择文件' }}
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
ref="fileInputRef"
|
||||
@change="handleFileChange"
|
||||
style="display: none"
|
||||
/>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
<BaseModal />
|
||||
<div class="flex h-full w-full bg-gray-50">
|
||||
<!-- 左侧:筛选 + 列表 -->
|
||||
<div class="flex w-64 flex-col border-r bg-white p-4">
|
||||
<!-- 按钮组 -->
|
||||
<div class="mb-4 flex justify-between space-x-2">
|
||||
<Button type="primary" @click="createTask" class="flex-1">
|
||||
新建任务
|
||||
</Button>
|
||||
</div>
|
||||
<!-- 列表 -->
|
||||
<div class="flex-1 space-y-2 overflow-auto">
|
||||
<div
|
||||
v-for="item in list"
|
||||
:key="item.v_id"
|
||||
@click="selectItem(item)"
|
||||
class="cursor-pointer rounded border p-3 hover:bg-gray-100"
|
||||
:class="{ 'bg-gray-100': item.id === selectedItem?.id }"
|
||||
>
|
||||
<div class="text-base font-medium">{{ item.name }}</div>
|
||||
<div class="text-sm text-gray-400">{{ item.created_at }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 分页 -->
|
||||
<div class="mt-2 flex justify-center space-x-2">
|
||||
<button
|
||||
:disabled="page === 1"
|
||||
@click="changePage(page - 1)"
|
||||
class="rounded border px-1"
|
||||
>
|
||||
上一页
|
||||
</button>
|
||||
|
||||
<span>第 {{ page }} 页 / 共 {{ totalPages }} 页</span>
|
||||
|
||||
<button
|
||||
:disabled="page === totalPages"
|
||||
@click="changePage(page + 1)"
|
||||
class="rounded border px-1"
|
||||
>
|
||||
下一页
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:Tab 内容区 -->
|
||||
<div class="flex flex-1 flex-col overflow-hidden p-6">
|
||||
<div
|
||||
v-if="!selectedItem"
|
||||
class="flex h-full items-center justify-center text-gray-400"
|
||||
>
|
||||
请先选择左侧列表中的分析任务
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="flex h-full flex-col gap-4">
|
||||
<!-- 主内容区域:左右结构 -->
|
||||
<div class="flex flex-1 gap-4">
|
||||
<!-- 左侧 -->
|
||||
<div class="flex w-72 flex-col gap-4">
|
||||
<!-- 视频基础信息展示 -->
|
||||
<div
|
||||
class="w-full rounded border bg-white p-4"
|
||||
id="video_base_info"
|
||||
>
|
||||
<div
|
||||
v-for="(value, key) in showInfoStr"
|
||||
:key="key"
|
||||
class="mb-2 flex text-sm text-gray-700"
|
||||
>
|
||||
<div class="w-28 font-medium text-gray-900">
|
||||
{{ key }}:
|
||||
</div>
|
||||
<div class="flex-1 break-all text-gray-600">
|
||||
{{ value || '—' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 下:空白卡片 -->
|
||||
<div class="flex-1 rounded border bg-white p-4">
|
||||
<div
|
||||
v-for="(value, key) in showInfoStr2"
|
||||
:key="key"
|
||||
class="mb-2 flex text-sm text-gray-700"
|
||||
>
|
||||
<div class="w-32 font-medium text-gray-900">
|
||||
{{ key }}:
|
||||
</div>
|
||||
<div class="flex-1 break-all text-gray-600">
|
||||
{{ value || '—' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧 -->
|
||||
<div class="flex flex-1 flex-col gap-4">
|
||||
<!-- 上:左右两个图片显示 -->
|
||||
<!-- 左图 -->
|
||||
<div
|
||||
class="flex h-full w-full items-center justify-center rounded border bg-white p-4"
|
||||
>
|
||||
<img
|
||||
:src="selectedItem?.oss_url"
|
||||
alt="左图"
|
||||
class="h-[80dvh] w-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -94,15 +94,14 @@ async function uploadFile() {
|
||||
formData.append('file', selectedFile.value);
|
||||
formData.append('projectName', projectName.value);
|
||||
await api.createTicketImageTask(formData);
|
||||
|
||||
// 接口完成后再触发事件
|
||||
message.success('分析任务完成');
|
||||
loadList();
|
||||
|
||||
// 清空表单
|
||||
selectedFile.value = null;
|
||||
projectName.value = '';
|
||||
fileName.value = '';
|
||||
|
||||
// 接口完成后再触发事件
|
||||
message.success('分析任务完成');
|
||||
loadList();
|
||||
} catch {
|
||||
message.error('上传失败');
|
||||
}
|
||||
|
||||
@@ -43,6 +43,13 @@ const cv: WorkbenchQuickNavItem[] = [
|
||||
title: '仪评指标联分析',
|
||||
url: '/cv/ticket',
|
||||
},
|
||||
{
|
||||
color: '#3fb27f',
|
||||
authority: ['license'],
|
||||
icon: 'mdi:certificate',
|
||||
title: '证件照片分析',
|
||||
url: '/cv/license',
|
||||
},
|
||||
{
|
||||
color: '#3fb27f',
|
||||
icon: 'ion:bar-chart-outline',
|
||||
|
||||
Reference in New Issue
Block a user