Files
ccdi/doc/信贷客户实体关联维护功能/前端实施方案.md
2026-02-13 10:15:23 +08:00

1089 lines
36 KiB
Markdown
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.
# 信贷客户实体关联维护功能 - 前端实施方案
## 一、功能概述
基于员工实体关系维护功能开发信贷客户实体关联维护功能,前端交互方式与员工实体关系完全一致,主要差异在于:
1. **无员工下拉搜索**:身份证号直接输入,不使用远程搜索
2. **无姓名列显示**:列表和详情中不显示姓名字段
3. **API路径不同**:调用 `/ccdi/custEnterpriseRelation/*` 接口
---
## 二、前端文件清单
| 文件名 | 路径 | 说明 |
|--------|------|------|
| index.vue | views/ccdiCustEnterpriseRelation/ | 主页面组件 |
| ccdiCustEnterpriseRelation.js | api/ | API接口定义 |
---
## 三、API接口定义
### 3.1 ccdiCustEnterpriseRelation.js
**与员工实体关系API的差异**仅URL路径不同
```javascript
import request from '@/utils/request'
// 查询信贷客户实体关联列表
export function listRelation(query) {
return request({
url: '/ccdi/custEnterpriseRelation/list',
method: 'get',
params: query
})
}
// 查询信贷客户实体关联详情
export function getRelation(id) {
return request({
url: '/ccdi/custEnterpriseRelation/' + id,
method: 'get'
})
}
// 新增信贷客户实体关联
export function addRelation(data) {
return request({
url: '/ccdi/custEnterpriseRelation',
method: 'post',
data: data
})
}
// 修改信贷客户实体关联
export function updateRelation(data) {
return request({
url: '/ccdi/custEnterpriseRelation',
method: 'put',
data: data
})
}
// 删除信贷客户实体关联
export function delRelation(ids) {
return request({
url: '/ccdi/custEnterpriseRelation/' + ids,
method: 'delete'
})
}
// 导出信贷客户实体关联
export function exportRelation(query) {
return request({
url: '/ccdi/custEnterpriseRelation/export',
method: 'post',
params: query
})
}
// 下载导入模板
export function importTemplate() {
return request({
url: '/ccdi/custEnterpriseRelation/importTemplate',
method: 'post'
})
}
// 导入信贷客户实体关联
export function importData(file) {
const formData = new FormData()
formData.append('file', file)
return request({
url: '/ccdi/custEnterpriseRelation/importData',
method: 'post',
data: formData
})
}
// 查询导入状态
export function getImportStatus(taskId) {
return request({
url: '/ccdi/custEnterpriseRelation/importStatus/' + taskId,
method: 'get'
})
}
// 查询导入失败记录
export function getImportFailures(taskId, pageNum, pageSize) {
return request({
url: '/ccdi/custEnterpriseRelation/importFailures/' + taskId,
method: 'get',
params: { pageNum, pageSize }
})
}
```
---
## 四、主页面组件
### 4.1 index.vue
**与员工实体关系前端的关键差异**
| 差异项 | 员工实体关系 | 信贷客户实体关联 |
|--------|-------------|-----------------|
| 员工下拉搜索 | 有 | 无,直接输入 |
| 列表姓名列 | 有 | 无 |
| 详情姓名显示 | 有 | 无 |
| API导入路径 | /ccdi/staffEnterpriseRelation/importData | /ccdi/custEnterpriseRelation/importData |
| localStorage key | staff_enterprise_relation_import_last_task | cust_enterprise_relation_import_last_task |
| 权限标识 | ccdi:staffEnterpriseRelation:* | ccdi:custEnterpriseRelation:* |
| 字典 | dicts: ['ccdi_relation_status', 'ccdi_data_source'] | 相同 |
```vue
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="120px">
<el-form-item label="身份证号" prop="personId">
<el-input
v-model="queryParams.personId"
placeholder="请输入身份证号"
clearable
style="width: 240px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="统一社会信用代码" prop="socialCreditCode">
<el-input
v-model="queryParams.socialCreditCode"
placeholder="请输入统一社会信用代码"
clearable
style="width: 240px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="企业名称" prop="enterpriseName">
<el-input
v-model="queryParams.enterpriseName"
placeholder="请输入企业名称"
clearable
style="width: 240px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 240px">
<el-option label="有效" :value="1" />
<el-option label="无效" :value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['ccdi:custEnterpriseRelation:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-upload2"
size="mini"
@click="handleImport"
v-hasPermi="['ccdi:custEnterpriseRelation:import']"
>导入</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['ccdi:custEnterpriseRelation:export']"
>导出</el-button>
</el-col>
<el-col :span="1.5" v-if="showFailureButton">
<el-tooltip
:content="getLastImportTooltip()"
placement="top"
>
<el-button
type="warning"
plain
icon="el-icon-warning"
size="mini"
@click="viewImportFailures"
>查看导入失败记录</el-button>
</el-tooltip>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="relationList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<!-- 关键差异无员工姓名列 -->
<el-table-column label="身份证号" align="center" prop="personId" width="180" :show-overflow-tooltip="true"/>
<el-table-column label="企业名称" align="center" prop="enterpriseName" :show-overflow-tooltip="true"/>
<el-table-column label="关联人在企业的职务" align="center" prop="relationPersonPost" width="150" :show-overflow-tooltip="true"/>
<el-table-column label="状态" align="center" prop="status" width="100">
<template slot-scope="scope">
<dict-tag :options="dict.type.ccdi_relation_status" :value="scope.row.status"/>
</template>
</el-table-column>
<el-table-column label="数据来源" align="center" prop="dataSource" width="120">
<template slot-scope="scope">
<dict-tag :options="dict.type.ccdi_data_source" :value="scope.row.dataSource"/>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click="handleDetail(scope.row)"
v-hasPermi="['ccdi:custEnterpriseRelation:query']"
>详情</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['ccdi:custEnterpriseRelation:edit']"
>编辑</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['ccdi:custEnterpriseRelation:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改对话框 -->
<el-dialog :title="title" :visible.sync="open" width="800px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="160px">
<el-row :gutter="16">
<el-col :span="24">
<el-form-item label="身份证号" prop="personId">
<!-- 关键差异直接输入无远程搜索 -->
<el-input
v-model="form.personId"
placeholder="请输入18位身份证号"
maxlength="18"
:disabled="!isAdd"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="统一社会信用代码" prop="socialCreditCode">
<el-input v-model="form.socialCreditCode" placeholder="请输入18位统一社会信用代码" maxlength="18" :disabled="!isAdd" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="企业名称" prop="enterpriseName">
<el-input v-model="form.enterpriseName" placeholder="请输入企业名称" maxlength="200" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="关联人在企业的职务" prop="relationPersonPost">
<el-input v-model="form.relationPersonPost" placeholder="请输入职务" maxlength="100" />
</el-form-item>
</el-col>
<el-col :span="12" v-if="!isAdd">
<el-form-item label="状态" prop="status">
<el-select v-model="form.status" placeholder="请选择状态">
<el-option label="有效" :value="1" />
<el-option label="无效" :value="0" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="24">
<el-form-item label="补充说明" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入补充说明" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</div>
</el-dialog>
<!-- 详情对话框 -->
<el-dialog title="信贷客户实体关联详情" :visible.sync="detailOpen" width="900px" append-to-body>
<div class="detail-container">
<el-divider content-position="left">基本信息</el-divider>
<el-descriptions :column="2" border>
<!-- 关键差异无员工姓名 -->
<el-descriptions-item label="身份证号">{{ relationDetail.personId || '-' }}</el-descriptions-item>
<el-descriptions-item label="统一社会信用代码">{{ relationDetail.socialCreditCode || '-' }}</el-descriptions-item>
<el-descriptions-item label="企业名称" :span="2">{{ relationDetail.enterpriseName || '-' }}</el-descriptions-item>
<el-descriptions-item label="关联人在企业的职务">{{ relationDetail.relationPersonPost || '-' }}</el-descriptions-item>
<el-descriptions-item label="状态">
<dict-tag :options="dict.type.ccdi_relation_status" :value="relationDetail.status"/>
</el-descriptions-item>
<el-descriptions-item label="数据来源">
<dict-tag :options="dict.type.ccdi_data_source" :value="relationDetail.dataSource"/>
</el-descriptions-item>
</el-descriptions>
<el-divider content-position="left">补充信息</el-divider>
<el-descriptions :column="1" border>
<el-descriptions-item label="补充说明">{{ relationDetail.remark || '-' }}</el-descriptions-item>
</el-descriptions>
<el-divider content-position="left">审计信息</el-divider>
<el-descriptions :column="2" border>
<el-descriptions-item label="创建时间">
{{ relationDetail.createTime ? parseTime(relationDetail.createTime) : '-' }}
</el-descriptions-item>
<el-descriptions-item label="创建人">{{ relationDetail.createdBy || '-' }}</el-descriptions-item>
<el-descriptions-item label="更新时间">
{{ relationDetail.updateTime ? parseTime(relationDetail.updateTime) : '-' }}
</el-descriptions-item>
<el-descriptions-item label="更新人">{{ relationDetail.updatedBy || '-' }}</el-descriptions-item>
</el-descriptions>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="detailOpen = false" icon="el-icon-close"> </el-button>
</div>
</el-dialog>
<!-- 导入对话框 -->
<el-dialog
:title="upload.title"
:visible.sync="upload.open"
width="400px"
append-to-body
@close="handleImportDialogClose"
>
<el-upload
ref="upload"
:limit="1"
accept=".xlsx, .xls"
:headers="upload.headers"
:action="upload.url"
:disabled="upload.isUploading"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess"
:auto-upload="false"
drag
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">
<el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline;" @click="importTemplate">下载模板</el-link>
</div>
<div class="el-upload__tip" slot="tip">
<span>仅允许导入"xls""xlsx"格式文件</span>
</div>
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitFileForm" :loading="upload.isUploading"> </el-button>
<el-button @click="upload.open = false" :disabled="upload.isUploading"> </el-button>
</div>
</el-dialog>
<!-- 导入结果对话框 -->
<import-result-dialog
:visible.sync="importResultVisible"
:content="importResultContent"
title="导入结果"
@close="handleImportResultClose"
/>
<!-- 导入失败记录对话框 -->
<el-dialog
title="导入失败记录"
:visible.sync="failureDialogVisible"
width="1200px"
append-to-body
>
<el-alert
v-if="lastImportInfo"
:title="lastImportInfo"
type="info"
:closable="false"
style="margin-bottom: 15px"
/>
<el-table :data="failureList" v-loading="failureLoading">
<el-table-column label="身份证号" prop="personId" align="center" width="180" />
<el-table-column label="企业名称" prop="enterpriseName" align="center" :show-overflow-tooltip="true"/>
<el-table-column label="统一社会信用代码" prop="socialCreditCode" align="center" width="180" :show-overflow-tooltip="true"/>
<el-table-column label="失败原因" prop="errorMessage" align="center" min-width="200" :show-overflow-tooltip="true" />
</el-table>
<pagination
v-show="failureTotal > 0"
:total="failureTotal"
:page.sync="failureQueryParams.pageNum"
:limit.sync="failureQueryParams.pageSize"
@pagination="getFailureList"
/>
<div slot="footer" class="dialog-footer">
<el-button @click="failureDialogVisible = false">关闭</el-button>
<el-button type="danger" plain @click="clearImportHistory">清除历史记录</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
addRelation,
delRelation,
getImportFailures,
getImportStatus,
getRelation,
listRelation,
updateRelation
} from "@/api/ccdiCustEnterpriseRelation";
import {getToken} from "@/utils/auth";
import ImportResultDialog from "@/components/ImportResultDialog.vue";
export default {
name: "CustEnterpriseRelation",
dicts: ['ccdi_relation_status', 'ccdi_data_source'],
components: { ImportResultDialog },
data() {
return {
// 遮罩层
loading: true,
// 选中数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 信贷客户实体关联表格数据
relationList: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 是否显示详情弹出层
detailOpen: false,
// 信贷客户实体关联详情
relationDetail: {},
// 是否为新增操作
isAdd: false,
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
personId: null,
socialCreditCode: null,
enterpriseName: null,
status: null
},
// 表单参数
form: {},
// 表单校验
rules: {
personId: [
{ required: true, message: "身份证号不能为空", trigger: "blur" },
{ pattern: /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/, message: "请输入正确的18位身份证号", trigger: "blur" }
],
socialCreditCode: [
{ required: true, message: "统一社会信用代码不能为空", trigger: "blur" },
{ pattern: /^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$/, message: "请输入正确的18位统一社会信用代码", trigger: "blur" }
],
enterpriseName: [
{ required: true, message: "企业名称不能为空", trigger: "blur" },
{ max: 200, message: "企业名称长度不能超过200个字符", trigger: "blur" }
],
relationPersonPost: [
{ max: 100, message: "职务长度不能超过100个字符", trigger: "blur" }
],
status: [
{ required: true, message: "状态不能为空", trigger: "change" }
],
remark: [
{ max: 500, message: "补充说明长度不能超过500个字符", trigger: "blur" }
]
},
// 【关键差异】无员工搜索相关数据
// 导入参数
upload: {
// 是否显示弹出层
open: false,
// 弹出层标题
title: "",
// 是否禁用上传
isUploading: false,
// 设置上传的请求头部
headers: { Authorization: "Bearer " + getToken() },
// 【关键差异】上传的地址不同
url: process.env.VUE_APP_BASE_API + "/ccdi/custEnterpriseRelation/importData"
},
// 导入结果弹窗
importResultVisible: false,
importResultContent: "",
// 导入轮询定时器
importPollingTimer: null,
// 是否显示查看失败记录按钮
showFailureButton: false,
// 当前导入任务ID
currentTaskId: null,
// 失败记录对话框
failureDialogVisible: false,
failureList: [],
failureLoading: false,
failureTotal: 0,
failureQueryParams: {
pageNum: 1,
pageSize: 10
}
};
},
computed: {
/**
* 上次导入信息摘要
*/
lastImportInfo() {
const savedTask = this.getImportTaskFromStorage();
if (savedTask && savedTask.totalCount) {
return `导入时间: ${this.parseTime(savedTask.saveTime)} | 总数: ${savedTask.totalCount}条 | 成功: ${savedTask.successCount}条 | 失败: ${savedTask.failureCount}`;
}
return '';
}
},
created() {
this.getList();
this.restoreImportState();
},
beforeDestroy() {
// 清理定时器
if (this.importPollingTimer) {
clearInterval(this.importPollingTimer);
this.importPollingTimer = null;
}
},
methods: {
/** 查询信贷客户实体关联列表 */
getList() {
this.loading = true;
listRelation(this.queryParams).then(response => {
this.relationList = response.rows;
this.total = response.total;
this.loading = false;
});
},
// 取消按钮
cancel() {
this.open = false;
this.reset();
},
// 表单重置
reset() {
this.form = {
id: null,
personId: null,
relationPersonPost: null,
socialCreditCode: null,
enterpriseName: null,
status: 1,
remark: null
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 多选框选中数据 */
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id);
this.single = selection.length !== 1;
this.multiple = !selection.length;
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加信贷客户实体关联";
this.isAdd = true;
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id || this.ids[0];
getRelation(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改信贷客户实体关联";
this.isAdd = false;
});
},
/** 详情按钮操作 */
handleDetail(row) {
const id = row.id;
getRelation(id).then(response => {
this.relationDetail = response.data;
this.detailOpen = true;
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.isAdd) {
addRelation(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
} else {
updateRelation(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids;
this.$modal.confirm('是否确认删除数据项?').then(function() {
return delRelation(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
this.download('ccdi/custEnterpriseRelation/export', {
...this.queryParams
}, `信贷客户实体关联_${new Date().getTime()}.xlsx`);
},
/** 导入按钮操作 */
handleImport() {
this.upload.title = "信贷客户实体关联数据导入";
this.upload.open = true;
},
/** 下载模板操作 */
importTemplate() {
this.download('ccdi/custEnterpriseRelation/importTemplate', {}, `信贷客户实体关联导入模板_${new Date().getTime()}.xlsx`);
},
// 文件上传中处理
handleFileUploadProgress(event, file, fileList) {
this.upload.isUploading = true;
},
// 文件上传成功处理
handleFileSuccess(response, file, fileList) {
this.upload.isUploading = false;
this.upload.open = false;
if (response.code === 200) {
if (!response.data || !response.data.taskId) {
this.$modal.msgError('导入任务创建失败:缺少任务ID');
this.upload.isUploading = false;
this.upload.open = true;
return;
}
const taskId = response.data.taskId;
// 清除旧的轮询定时器
if (this.importPollingTimer) {
clearInterval(this.importPollingTimer);
this.importPollingTimer = null;
}
this.clearImportTaskFromStorage();
// 保存新任务的初始状态
this.saveImportTaskToStorage({
taskId: taskId,
status: 'PROCESSING',
timestamp: Date.now(),
hasFailures: false
});
// 重置状态
this.showFailureButton = false;
this.currentTaskId = taskId;
// 显示后台处理提示
this.$notify({
title: '导入任务已提交',
message: '正在后台处理中,处理完成后将通知您',
type: 'info',
duration: 3000
});
// 开始轮询检查状态
this.startImportStatusPolling(taskId);
} else {
this.$modal.msgError(response.msg);
}
},
/** 开始轮询导入状态 */
startImportStatusPolling(taskId) {
let pollCount = 0;
const maxPolls = 150;
this.importPollingTimer = setInterval(async () => {
try {
pollCount++;
if (pollCount > maxPolls) {
clearInterval(this.importPollingTimer);
this.$modal.msgWarning('导入任务处理超时,请联系管理员');
return;
}
const response = await getImportStatus(taskId);
if (response.data && response.data.status !== 'PROCESSING') {
clearInterval(this.importPollingTimer);
this.handleImportComplete(response.data);
}
} catch (error) {
clearInterval(this.importPollingTimer);
this.$modal.msgError('查询导入状态失败: ' + error.message);
}
}, 2000);
},
/** 查询失败记录列表 */
getFailureList() {
this.failureLoading = true;
getImportFailures(
this.currentTaskId,
this.failureQueryParams.pageNum,
this.failureQueryParams.pageSize
).then(response => {
this.failureList = response.rows;
this.failureTotal = response.total;
this.failureLoading = false;
}).catch(error => {
this.failureLoading = false;
if (error.response) {
const status = error.response.status;
if (status === 404) {
this.$modal.msgWarning('导入记录已过期,无法查看失败记录');
this.clearImportTaskFromStorage();
this.showFailureButton = false;
this.currentTaskId = null;
this.failureDialogVisible = false;
} else if (status === 500) {
this.$modal.msgError('服务器错误,请稍后重试');
} else {
this.$modal.msgError(`查询失败: ${error.response.data.msg || '未知错误'}`);
}
} else if (error.request) {
this.$modal.msgError('网络连接失败,请检查网络');
} else {
this.$modal.msgError('查询失败记录失败: ' + error.message);
}
});
},
/** 查看导入失败记录 */
viewImportFailures() {
this.failureDialogVisible = true;
this.getFailureList();
},
/** 处理导入完成 */
handleImportComplete(statusResult) {
this.saveImportTaskToStorage({
taskId: statusResult.taskId,
status: statusResult.status,
hasFailures: statusResult.failureCount > 0,
totalCount: statusResult.totalCount,
successCount: statusResult.successCount,
failureCount: statusResult.failureCount
});
if (statusResult.status === 'SUCCESS') {
this.$notify({
title: '导入完成',
message: `全部成功!共导入${statusResult.totalCount}条数据`,
type: 'success',
duration: 5000
});
this.showFailureButton = false;
this.getList();
} else if (statusResult.failureCount > 0) {
this.$notify({
title: '导入完成',
message: `成功${statusResult.successCount}条,失败${statusResult.failureCount}`,
type: 'warning',
duration: 5000
});
this.showFailureButton = true;
this.currentTaskId = statusResult.taskId;
this.getList();
}
},
// 导入结果弹窗关闭
handleImportResultClose() {
this.importResultVisible = false;
this.importResultContent = "";
},
// 提交上传文件
submitFileForm() {
this.$refs.upload.submit();
},
// 关闭导入对话框
handleImportDialogClose() {
this.upload.isUploading = false;
this.$refs.upload.clearFiles();
},
/**
* 恢复导入状态
*/
restoreImportState() {
const savedTask = this.getImportTaskFromStorage();
if (!savedTask) {
this.showFailureButton = false;
this.currentTaskId = null;
return;
}
if (savedTask.hasFailures && savedTask.taskId) {
this.currentTaskId = savedTask.taskId;
this.showFailureButton = true;
}
},
/**
* 获取上次导入的提示信息
*/
getLastImportTooltip() {
const savedTask = this.getImportTaskFromStorage();
if (savedTask && savedTask.saveTime) {
const date = new Date(savedTask.saveTime);
const timeStr = this.parseTime(date, '{y}-{m}-{d} {h}:{i}');
return `上次导入: ${timeStr}`;
}
return '';
},
/**
* 保存导入任务到localStorage
* 【关键差异】localStorage key不同
*/
saveImportTaskToStorage(taskData) {
try {
const data = {
...taskData,
saveTime: Date.now()
};
localStorage.setItem('cust_enterprise_relation_import_last_task', JSON.stringify(data));
} catch (error) {
console.error('保存导入任务状态失败:', error);
}
},
/**
* 从localStorage读取导入任务
* 【关键差异】localStorage key不同
*/
getImportTaskFromStorage() {
try {
const data = localStorage.getItem('cust_enterprise_relation_import_last_task');
if (!data) return null;
const task = JSON.parse(data);
if (!task || !task.taskId) {
this.clearImportTaskFromStorage();
return null;
}
if (task.saveTime && typeof task.saveTime !== 'number') {
this.clearImportTaskFromStorage();
return null;
}
// 过期检查(7天)
const sevenDays = 7 * 24 * 60 * 60 * 1000;
if (Date.now() - task.saveTime > sevenDays) {
this.clearImportTaskFromStorage();
return null;
}
return task;
} catch (error) {
console.error('读取导入任务状态失败:', error);
this.clearImportTaskFromStorage();
return null;
}
},
/**
* 清除导入历史记录
*/
clearImportHistory() {
this.$confirm('确认清除上次导入记录?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.clearImportTaskFromStorage();
this.showFailureButton = false;
this.currentTaskId = null;
this.failureDialogVisible = false;
this.$message.success('已清除');
}).catch(() => {});
},
/**
* 清除localStorage中的导入任务
* 【关键差异】localStorage key不同
*/
clearImportTaskFromStorage() {
try {
localStorage.removeItem('cust_enterprise_relation_import_last_task');
} catch (error) {
console.error('清除导入任务状态失败:', error);
}
}
}
};
</script>
<style scoped>
.detail-container {
padding: 0 20px;
}
.el-divider {
margin: 16px 0;
}
</style>
```
---
## 五、与员工实体关系前端代码对比
### 5.1 关键差异总结
| 对比项 | 员工实体关系 | 信贷客户实体关联 |
|--------|-------------|-----------------|
| 组件名 | StaffEnterpriseRelation | CustEnterpriseRelation |
| API文件 | ccdiStaffEnterpriseRelation.js | ccdiCustEnterpriseRelation.js |
| 员工下拉搜索 | 有searchStaff方法 | 无,直接输入 |
| 列表姓名列 | 有 personName 列 | 无 |
| 详情姓名显示 | 有 | 无 |
| 上传URL | /ccdi/staffEnterpriseRelation/importData | /ccdi/custEnterpriseRelation/importData |
| localStorage key | staff_enterprise_relation_import_last_task | cust_enterprise_relation_import_last_task |
| 权限标识 | ccdi:staffEnterpriseRelation:* | ccdi:custEnterpriseRelation:* |
| 标题/提示文本 | 员工实体关系 | 信贷客户实体关联 |
### 5.2 移除的功能代码
信贷客户实体关联前端**不需要**以下员工实体关系特有的功能:
1. **员工搜索相关数据**
- `staffOptions: []`
- `staffLoading: false`
2. **员工搜索相关方法**
- `searchStaff(query)` - 远程搜索员工
- `handleSelectFocus()` - 下拉框聚焦加载
3. **员工搜索相关导入**
- `import {listBaseStaff} from "@/api/ccdiBaseStaff";`
4. **员工下拉选择组件**(表单中):
```vue
<!-- 员工实体关系使用下拉搜索 -->
<el-select v-model="form.personId" filterable remote ...>
<el-option v-for="item in staffOptions" .../>
</el-select>
```
替换为:
```vue
<!-- 信贷客户实体关联直接输入 -->
<el-input v-model="form.personId" placeholder="请输入18位身份证号" .../>
```
5. **列表中的员工姓名列**
```vue
<!-- 员工实体关系有姓名列 -->
<el-table-column label="员工姓名" align="center" prop="personName" width="100" />
```
信贷客户实体关联无此列。
---
## 六、实施步骤
1. 创建 `src/api/ccdiCustEnterpriseRelation.js` 文件
2. 创建 `src/views/ccdiCustEnterpriseRelation/index.vue` 文件
3. 配置路由(在数据库菜单表中配置)
4. 测试功能
---
## 七、菜单配置SQL
```sql
-- 信贷客户实体关联菜单
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time, remark)
VALUES ('信贷客户实体关联', 2000, 3, 'custEnterpriseRelation', 'ccdiCustEnterpriseRelation/index', 'C', '0', '0', 'ccdi:custEnterpriseRelation:list', 'peoples', 'admin', NOW(), '信贷客户实体关联菜单');
-- 按钮权限
SET @parentId = LAST_INSERT_ID();
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time) VALUES
('信贷客户实体关联查询', @parentId, 1, '#', '', 'F', '0', '0', 'ccdi:custEnterpriseRelation:query', '#', 'admin', NOW()),
('信贷客户实体关联新增', @parentId, 2, '#', '', 'F', '0', '0', 'ccdi:custEnterpriseRelation:add', '#', 'admin', NOW()),
('信贷客户实体关联修改', @parentId, 3, '#', '', 'F', '0', '0', 'ccdi:custEnterpriseRelation:edit', '#', 'admin', NOW()),
('信贷客户实体关联删除', @parentId, 4, '#', '', 'F', '0', '0', 'ccdi:custEnterpriseRelation:remove', '#', 'admin', NOW()),
('信贷客户实体关联导出', @parentId, 5, '#', '', 'F', '0', '0', 'ccdi:custEnterpriseRelation:export', '#', 'admin', NOW()),
('信贷客户实体关联导入', @parentId, 6, '#', '', 'F', '0', '0', 'ccdi:custEnterpriseRelation:import', '#', 'admin', NOW());
```
**注意**`parent_id = 2000` 需要根据实际的父菜单ID调整。