6d70520e79
- 新增后端模块:Alert、APIAsset、Compliance、Lineage、Masking、Risk、SchemaChange、Unstructured、Watermark - 新增前端模块页面与API接口 - 新增Alembic迁移脚本(002-014)覆盖全量业务表 - 新增测试数据生成脚本与集成测试脚本 - 修复metadata模型JSON类型导入缺失导致启动失败的问题 - 修复前端Alert/APIAsset页面request模块路径错误 - 更新docker-compose与开发计划文档
144 lines
5.1 KiB
Vue
144 lines
5.1 KiB
Vue
<template>
|
||
<div class="page-container">
|
||
<h2 class="page-title">非结构化文件识别</h2>
|
||
<div class="upload-area card-shadow" style="padding: 24px; margin-bottom: 16px;">
|
||
<el-upload
|
||
drag
|
||
action="#"
|
||
:auto-upload="false"
|
||
:on-change="handleFileChange"
|
||
accept=".docx,.xlsx,.pdf,.txt"
|
||
:limit="1"
|
||
>
|
||
<el-icon class="el-icon--upload"><upload-icon /></el-icon>
|
||
<div class="el-upload__text">拖拽文件到此处或 <em>点击上传</em></div>
|
||
<template #tip>
|
||
<div class="el-upload__tip">支持 Word、Excel、PDF、TXT 格式</div>
|
||
</template>
|
||
</el-upload>
|
||
<el-button type="primary" :loading="uploadLoading" @click="handleUpload" style="margin-top: 12px;">开始识别</el-button>
|
||
</div>
|
||
|
||
<div class="table-card card-shadow">
|
||
<el-table :data="fileList" v-loading="listLoading" stripe size="default" border>
|
||
<el-table-column prop="original_name" label="文件名" min-width="180" />
|
||
<el-table-column prop="file_type" label="类型" width="80" />
|
||
<el-table-column prop="status" label="状态" width="100">
|
||
<template #default="{ row }">
|
||
<el-tag :type="row.status === 'processed' ? 'success' : row.status === 'error' ? 'danger' : 'warning'">
|
||
{{ ({ pending: '待处理', processed: '已处理', error: '失败' } as Record<string, string>)[row.status as string] || row.status }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="敏感信息" min-width="200">
|
||
<template #default="{ row }">
|
||
<div v-if="row.analysis_result?.matches?.length">
|
||
<el-tag v-for="(m, idx) in row.analysis_result.matches.slice(0, 3)" :key="idx" size="small" type="danger" style="margin-right: 4px;">
|
||
{{ m.rule_name }}
|
||
</el-tag>
|
||
<span v-if="row.analysis_result.matches.length > 3" class="more-tag">+{{ row.analysis_result.matches.length - 3 }}</span>
|
||
</div>
|
||
<span v-else class="empty-text">未检测到</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="140" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-button type="primary" link size="small" @click="handleView(row)">查看详情</el-button>
|
||
<el-button type="primary" link size="small" @click="handleReprocess(row)">重新识别</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
|
||
<el-dialog v-model="detailVisible" title="识别详情" width="640px" destroy-on-close>
|
||
<div v-if="currentFile?.analysis_result?.matches?.length">
|
||
<div style="margin-bottom: 12px;">共发现 {{ currentFile.analysis_result.matches.length }} 处敏感信息:</div>
|
||
<el-timeline>
|
||
<el-timeline-item v-for="(m, idx) in currentFile.analysis_result.matches" :key="idx" :type="m.level_code === 'L4' || m.level_code === 'L5' ? 'danger' : 'warning'">
|
||
<div style="font-weight: 600;">{{ m.rule_name }} ({{ m.level_code }})</div>
|
||
<div style="color: #909399; font-size: 13px; margin-top: 4px;">片段:{{ m.snippet }}</div>
|
||
</el-timeline-item>
|
||
</el-timeline>
|
||
</div>
|
||
<el-empty v-else description="未检测到敏感信息" />
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, onMounted } from 'vue'
|
||
import { ElMessage } from 'element-plus'
|
||
import { Upload as UploadIcon } from '@element-plus/icons-vue'
|
||
import { uploadUnstructuredFile, getUnstructuredFiles, reprocessUnstructuredFile } from '@/api/unstructured'
|
||
|
||
const uploadLoading = ref(false)
|
||
const listLoading = ref(false)
|
||
const fileList = ref<any[]>([])
|
||
const selectedFile = ref<File | null>(null)
|
||
const detailVisible = ref(false)
|
||
const currentFile = ref<any>(null)
|
||
|
||
function handleFileChange(file: any) {
|
||
selectedFile.value = file.raw
|
||
}
|
||
|
||
async function handleUpload() {
|
||
if (!selectedFile.value) {
|
||
ElMessage.warning('请选择文件')
|
||
return
|
||
}
|
||
uploadLoading.value = true
|
||
try {
|
||
const res: any = await uploadUnstructuredFile(selectedFile.value)
|
||
ElMessage.success('上传并识别成功')
|
||
selectedFile.value = null
|
||
fetchList()
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.message || '上传失败')
|
||
} finally {
|
||
uploadLoading.value = false
|
||
}
|
||
}
|
||
|
||
async function fetchList() {
|
||
listLoading.value = true
|
||
try {
|
||
const res: any = await getUnstructuredFiles()
|
||
fileList.value = res.data || []
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.message || '加载失败')
|
||
} finally {
|
||
listLoading.value = false
|
||
}
|
||
}
|
||
|
||
function handleView(row: any) {
|
||
currentFile.value = row
|
||
detailVisible.value = true
|
||
}
|
||
|
||
async function handleReprocess(row: any) {
|
||
try {
|
||
const res: any = await reprocessUnstructuredFile(row.id)
|
||
ElMessage.success('重新识别完成')
|
||
fetchList()
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.message || '识别失败')
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
fetchList()
|
||
})
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.empty-text {
|
||
color: #c0c4cc;
|
||
}
|
||
.more-tag {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
}
|
||
</style>
|