Files
2026-01-29 22:03:42 +08:00

716 lines
25 KiB
Markdown
Raw Permalink 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.
# Design: 同步前端以支持中介黑名单详细字段和类型化模板导入
## 前端架构设计
### 文件结构
```
ruoyi-ui/src/
├── api/
│ └── dpcIntermediary.js (修改 - 添加新接口)
├── views/
│ └── dpcIntermediary/
│ └── index.vue (修改 - 添加详情对话框和表单增强)
```
## API 接口层设计
### 新增接口函数
`ruoyi-ui/src/api/dpcIntermediary.js` 中添加:
```javascript
// 下载个人中介导入模板
export function importPersonTemplate() {
return request({
url: '/dpc/intermediary/importPersonTemplate',
method: 'post'
})
}
// 下载机构中介导入模板
export function importEntityTemplate() {
return request({
url: '/dpc/intermediary/importEntityTemplate',
method: 'post'
})
}
// 导入个人中介黑名单
export function importPersonData(data, updateSupport) {
return request({
url: '/dpc/intermediary/importPersonData?updateSupport=' + updateSupport,
method: 'post',
data: data
})
}
// 导入机构中介黑名单
export function importEntityData(data, updateSupport) {
return request({
url: '/dpc/intermediary/importEntityData?updateSupport=' + updateSupport,
method: 'post',
data: data
})
}
```
## 视图层设计
### 列表页面修改
#### 1. 操作列添加"查看详情"按钮
```vue
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="240">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click="handleDetail(scope.row)"
>详情</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['dpc:intermediary:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['dpc:intermediary:remove']"
>删除</el-button>
</template>
</el-table-column>
```
#### 2. 导入对话框改造
支持选择导入类型,并根据类型下载对应模板:
```vue
<!-- 导入对话框 -->
<el-dialog :title="upload.title" :visible.sync="upload.open" width="450px" append-to-body>
<el-form label-width="100px">
<el-form-item label="导入类型" prop="importType">
<el-radio-group v-model="upload.importType">
<el-radio label="person">个人中介</el-radio>
<el-radio label="entity">机构中介</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<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-checkbox v-model="upload.updateSupport" />是否更新已经存在的数据
<el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline;" @click="downloadImportTemplate">下载模板</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"> </el-button>
<el-button @click="upload.open = false"> </el-button>
</div>
</el-dialog>
```
### 详情对话框设计
#### 对话框结构
```vue
<!-- 详情对话框 -->
<el-dialog title="中介黑名单详情" :visible.sync="detailOpen" width="800px" append-to-body>
<el-descriptions :column="2" border>
<!-- 核心字段 -->
<el-descriptions-item label="中介ID">{{ detailData.intermediaryId }}</el-descriptions-item>
<el-descriptions-item label="中介类型">{{ detailData.intermediaryTypeName }}</el-descriptions-item>
<el-descriptions-item label="姓名/机构名称">{{ detailData.name }}</el-descriptions-item>
<el-descriptions-item label="证件号/信用代码">{{ detailData.certificateNo }}</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag v-if="detailData.status === '0'" type="success">正常</el-tag>
<el-tag v-else type="danger">停用</el-tag>
</el-descriptions-item>
<el-descriptions-item label="数据来源">{{ detailData.dataSourceName }}</el-descriptions-item>
<!-- 个人类型专属字段 -->
<template v-if="detailData.intermediaryType === '1'">
<el-descriptions-item label="人员类型">{{ detailData.indivType || '-' }}</el-descriptions-item>
<el-descriptions-item label="人员子类型">{{ detailData.indivSubType || '-' }}</el-descriptions-item>
<el-descriptions-item label="性别">{{ detailData.indivGenderName || '-' }}</el-descriptions-item>
<el-descriptions-item label="证件类型">{{ detailData.indivCertType || '-' }}</el-descriptions-item>
<el-descriptions-item label="手机号码">{{ detailData.indivPhone || '-' }}</el-descriptions-item>
<el-descriptions-item label="微信号">{{ detailData.indivWechat || '-' }}</el-descriptions-item>
<el-descriptions-item label="联系地址" :span="2">{{ detailData.indivAddress || '-' }}</el-descriptions-item>
<el-descriptions-item label="所在公司">{{ detailData.indivCompany || '-' }}</el-descriptions-item>
<el-descriptions-item label="职位">{{ detailData.indivPosition || '-' }}</el-descriptions-item>
<el-descriptions-item label="关联人员ID">{{ detailData.indivRelatedId || '-' }}</el-descriptions-item>
<el-descriptions-item label="关联关系">{{ detailData.indivRelation || '-' }}</el-descriptions-item>
</template>
<!-- 机构类型专属字段 -->
<template v-if="detailData.intermediaryType === '2'">
<el-descriptions-item label="统一社会信用代码" :span="2">{{ detailData.corpCreditCode || '-' }}</el-descriptions-item>
<el-descriptions-item label="主体类型">{{ detailData.corpType || '-' }}</el-descriptions-item>
<el-descriptions-item label="企业性质">{{ detailData.corpNature || '-' }}</el-descriptions-item>
<el-descriptions-item label="行业分类">{{ detailData.corpIndustryCategory || '-' }}</el-descriptions-item>
<el-descriptions-item label="所属行业">{{ detailData.corpIndustry || '-' }}</el-descriptions-item>
<el-descriptions-item label="成立日期">{{ detailData.corpEstablishDate || '-' }}</el-descriptions-item>
<el-descriptions-item label="注册地址" :span="2">{{ detailData.corpAddress || '-' }}</el-descriptions-item>
<el-descriptions-item label="法定代表人">{{ detailData.corpLegalRep || '-' }}</el-descriptions-item>
<el-descriptions-item label="法定代表人证件类型">{{ detailData.corpLegalCertType || '-' }}</el-descriptions-item>
<el-descriptions-item label="法定代表人证件号码" :span="2">{{ detailData.corpLegalCertNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="股东1">{{ detailData.corpShareholder1 || '-' }}</el-descriptions-item>
<el-descriptions-item label="股东2">{{ detailData.corpShareholder2 || '-' }}</el-descriptions-item>
<el-descriptions-item label="股东3">{{ detailData.corpShareholder3 || '-' }}</el-descriptions-item>
<el-descriptions-item label="股东4">{{ detailData.corpShareholder4 || '-' }}</el-descriptions-item>
<el-descriptions-item label="股东5">{{ detailData.corpShareholder5 || '-' }}</el-descriptions-item>
</template>
<!-- 通用字段 -->
<el-descriptions-item label="备注" :span="2">{{ detailData.remark || '-' }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ detailData.createTime }}</el-descriptions-item>
<el-descriptions-item label="创建人">{{ detailData.createBy }}</el-descriptions-item>
</el-descriptions>
<div slot="footer" class="dialog-footer">
<el-button @click="detailOpen = false"> </el-button>
</div>
</el-dialog>
```
#### 数据属性
```javascript
data() {
return {
// ... 现有数据 ...
// 详情对话框
detailOpen: false,
detailData: {},
// 导入参数
upload: {
open: false,
title: "",
isUploading: false,
updateSupport: 0,
importType: "person", // person 或 entity
headers: { Authorization: "Bearer " + getToken() },
url: "" // 动态设置
}
}
}
```
#### 方法实现
```javascript
methods: {
/** 查看详情操作 */
handleDetail(row) {
const intermediaryId = row.intermediaryId;
getIntermediary(intermediaryId).then(response => {
this.detailData = response.data;
this.detailOpen = true;
});
},
/** 导入按钮操作 */
handleImport() {
this.upload.title = "中介黑名单数据导入";
this.upload.importType = "person"; // 默认个人
this.upload.updateSupport = 0;
this.upload.open = true;
},
/** 下载导入模板 */
downloadImportTemplate() {
if (this.upload.importType === 'person') {
this.download('dpc/intermediary/importPersonTemplate', {}, `个人中介黑名单模板_${new Date().getTime()}.xlsx`);
} else {
this.download('dpc/intermediary/importEntityTemplate', {}, `机构中介黑名单模板_${new Date().getTime()}.xlsx`);
}
},
// 文件上传中处理
handleFileUploadProgress(event, file, fileList) {
this.upload.isUploading = true;
},
// 文件上传成功处理
handleFileSuccess(response, file, fileList) {
this.upload.isUploading = false;
this.upload.open = false;
this.getList();
this.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果");
},
// 提交上传文件
submitFileForm() {
// 根据导入类型设置上传地址
if (this.upload.importType === 'person') {
this.upload.url = process.env.VUE_APP_BASE_API + "/dpc/intermediary/importPersonData?updateSupport=" + this.upload.updateSupport;
} else {
this.upload.url = process.env.VUE_APP_BASE_API + "/dpc/intermediary/importEntityData?updateSupport=" + this.upload.updateSupport;
}
this.$refs.upload.submit();
}
}
```
### 新增/编辑对话框增强
#### 使用 Tabs 分组展示字段
```vue
<!-- 添加或修改对话框 -->
<el-dialog :title="title" :visible.sync="open" width="900px" append-to-body>
<el-tabs v-model="activeTab" type="border-card">
<!-- 基本信息标签页 -->
<el-tab-pane label="基本信息" name="basic">
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
<el-row>
<el-col :span="12">
<el-form-item label="姓名/机构名称" prop="name">
<el-input v-model="form.name" placeholder="请输入姓名/机构名称" maxlength="100"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="中介类型" prop="intermediaryType">
<el-radio-group v-model="form.intermediaryType" @change="handleTypeChange">
<el-radio label="1">个人</el-radio>
<el-radio label="2">机构</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="证件号" prop="certificateNo">
<el-input v-model="form.certificateNo" placeholder="请输入证件号" maxlength="50"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio label="0">正常</el-radio>
<el-radio label="1">停用</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" maxlength="500"/>
</el-form-item>
</el-form>
</el-tab-pane>
<!-- 个人详细信息标签页 -->
<el-tab-pane label="个人信息" name="person" v-if="form.intermediaryType === '1'">
<el-form ref="personForm" :model="form" label-width="120px">
<el-row>
<el-col :span="12">
<el-form-item label="人员类型">
<el-input v-model="form.indivType" placeholder="请输入人员类型" maxlength="30"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="人员子类型">
<el-input v-model="form.indivSubType" placeholder="请输入人员子类型" maxlength="50"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="性别">
<el-select v-model="form.indivGender" placeholder="请选择性别" clearable>
<el-option label="男" value="M" />
<el-option label="女" value="F" />
<el-option label="其他" value="O" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="证件类型">
<el-input v-model="form.indivCertType" placeholder="请输入证件类型" maxlength="30"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="手机号码">
<el-input v-model="form.indivPhone" placeholder="请输入手机号码" maxlength="20"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="微信号">
<el-input v-model="form.indivWechat" placeholder="请输入微信号" maxlength="50"/>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="联系地址">
<el-input v-model="form.indivAddress" placeholder="请输入联系地址" maxlength="200"/>
</el-form-item>
<el-row>
<el-col :span="12">
<el-form-item label="所在公司">
<el-input v-model="form.indivCompany" placeholder="请输入所在公司" maxlength="100"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="职位">
<el-input v-model="form.indivPosition" placeholder="请输入职位" maxlength="100"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="关联人员ID">
<el-input v-model="form.indivRelatedId" placeholder="请输入关联人员ID" maxlength="20"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="关联关系">
<el-input v-model="form.indivRelation" placeholder="请输入关联关系" maxlength="50"/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-tab-pane>
<!-- 机构详细信息标签页 -->
<el-tab-pane label="机构信息" name="entity" v-if="form.intermediaryType === '2'">
<el-form ref="entityForm" :model="form" label-width="140px">
<el-row>
<el-col :span="12">
<el-form-item label="统一社会信用代码">
<el-input v-model="form.corpCreditCode" placeholder="请输入统一社会信用代码" maxlength="18"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="主体类型">
<el-input v-model="form.corpType" placeholder="请输入主体类型" maxlength="50"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="企业性质">
<el-input v-model="form.corpNature" placeholder="请输入企业性质" maxlength="50"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="行业分类">
<el-input v-model="form.corpIndustryCategory" placeholder="请输入行业分类" maxlength="100"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="所属行业">
<el-input v-model="form.corpIndustry" placeholder="请输入所属行业" maxlength="100"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="成立日期">
<el-date-picker
v-model="form.corpEstablishDate"
type="date"
placeholder="选择成立日期"
value-format="yyyy-MM-dd"
style="width: 100%">
</el-date-picker>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="注册地址">
<el-input v-model="form.corpAddress" type="textarea" placeholder="请输入注册地址" maxlength="500"/>
</el-form-item>
<el-row>
<el-col :span="12">
<el-form-item label="法定代表人">
<el-input v-model="form.corpLegalRep" placeholder="请输入法定代表人" maxlength="50"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="法定代表人证件类型">
<el-input v-model="form.corpLegalCertType" placeholder="请输入证件类型" maxlength="30"/>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="法定代表人证件号码">
<el-input v-model="form.corpLegalCertNo" placeholder="请输入法定代表人证件号码" maxlength="30"/>
</el-form-item>
<el-divider content-position="left">股东信息</el-divider>
<el-row>
<el-col :span="12">
<el-form-item label="股东1">
<el-input v-model="form.corpShareholder1" placeholder="请输入股东1" maxlength="30"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="股东2">
<el-input v-model="form.corpShareholder2" placeholder="请输入股东2" maxlength="30"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="股东3">
<el-input v-model="form.corpShareholder3" placeholder="请输入股东3" maxlength="30"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="股东4">
<el-input v-model="form.corpShareholder4" placeholder="请输入股东4" maxlength="30"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="股东5">
<el-input v-model="form.corpShareholder5" placeholder="请输入股东5" maxlength="30"/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-tab-pane>
</el-tabs>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
```
#### 数据属性和方法
```javascript
data() {
return {
// ... 现有数据 ...
activeTab: 'basic'
}
},
methods: {
// 表单重置
reset() {
this.form = {
intermediaryId: null,
name: null,
certificateNo: null,
intermediaryType: "1",
status: "0",
remark: null,
// 个人字段
indivType: null,
indivSubType: null,
indivGender: null,
indivCertType: null,
indivPhone: null,
indivWechat: null,
indivAddress: null,
indivCompany: null,
indivPosition: null,
indivRelatedId: null,
indivRelation: null,
// 机构字段
corpCreditCode: null,
corpType: null,
corpNature: null,
corpIndustryCategory: null,
corpIndustry: null,
corpEstablishDate: null,
corpAddress: null,
corpLegalRep: null,
corpLegalCertType: null,
corpLegalCertNo: null,
corpShareholder1: null,
corpShareholder2: null,
corpShareholder3: null,
corpShareholder4: null,
corpShareholder5: null
};
this.activeTab = 'basic';
this.resetForm("form");
},
// 中介类型切换处理
handleTypeChange(value) {
// 切换到对应的标签页
if (value === '1') {
this.activeTab = 'person';
} else if (value === '2') {
this.activeTab = 'entity';
}
}
}
```
## 数据流设计
### 详情数据流
```
用户点击"详情"按钮
调用 getIntermediary(id)
后端根据 intermediaryType 返回不同的 VO
前端接收响应并存储到 detailData
详情对话框根据 detailData.intermediaryType 渲染不同字段
```
### 导入数据流
```
用户点击"导入"按钮
打开导入对话框,默认选择"个人"类型
用户选择导入类型(个人/机构)
用户点击"下载模板"
根据类型调用对应的模板下载接口
用户填写 Excel 并上传
根据类型设置上传地址
调用对应的导入接口
后端处理并返回结果
```
### 表单提交数据流
```
用户点击"新增"或"修改"
打开表单对话框
用户选择中介类型(自动切换到对应标签页)
用户填写基本信息和详细字段
用户点击"确定"
前端合并所有字段(核心字段 + 类型专属字段)
调用 addIntermediary 或 updateIntermediary
后端根据 intermediaryType 保存对应字段
```
## 字段映射表
### 个人字段映射
| 前端表单字段 | 后端字段 | 显示名称 |
|------------|---------|---------|
| indivType | indiv_type | 人员类型 |
| indivSubType | indiv_sub_type | 人员子类型 |
| indivGender | indiv_gender | 性别 |
| indivCertType | indiv_cert_type | 证件类型 |
| indivPhone | indiv_phone | 手机号码 |
| indivWechat | indiv_wechat | 微信号 |
| indivAddress | indiv_address | 联系地址 |
| indivCompany | indiv_company | 所在公司 |
| indivPosition | indiv_position | 职位 |
| indivRelatedId | indiv_related_id | 关联人员ID |
| indivRelation | indiv_relation | 关联关系 |
### 机构字段映射
| 前端表单字段 | 后端字段 | 显示名称 |
|------------|---------|---------|
| corpCreditCode | corp_credit_code | 统一社会信用代码 |
| corpType | corp_type | 主体类型 |
| corpNature | corp_nature | 企业性质 |
| corpIndustryCategory | corp_industry_category | 行业分类 |
| corpIndustry | corp_industry | 所属行业 |
| corpEstablishDate | corp_establish_date | 成立日期 |
| corpAddress | corp_address | 注册地址 |
| corpLegalRep | corp_legal_rep | 法定代表人 |
| corpLegalCertType | corp_legal_cert_type | 法定代表人证件类型 |
| corpLegalCertNo | corp_legal_cert_no | 法定代表人证件号码 |
| corpShareholder1 | corp_shareholder_1 | 股东1 |
| corpShareholder2 | corp_shareholder_2 | 股东2 |
| corpShareholder3 | corp_shareholder_3 | 股东3 |
| corpShareholder4 | corp_shareholder_4 | 股东4 |
| corpShareholder5 | corp_shareholder_5 | 股东5 |
## 用户体验设计
### 交互细节
1. **类型切换自动跳转**
- 用户选择"个人"类型,自动跳转到"个人信息"标签页
- 用户选择"机构"类型,自动跳转到"机构信息"标签页
2. **导入类型提示**
- 导入对话框顶部显示当前选择的导入类型
- 下载模板链接根据类型变化
3. **详情字段空值处理**
- 字段值为空时显示 "-"
- 避免显示空白或 undefined
4. **表单验证**
- 基本信息标签页的字段保持原有验证规则
- 详细信息字段暂时设为可选,减少录入压力
### 样式优化
- 使用 `el-tabs` 组织大量字段,提高可读性
- 使用 `el-row``el-col` 实现响应式布局
- 详情对话框使用 `el-descriptions` 组件,展示更美观
- 对话框宽度适当增加,容纳更多字段
## 技术约束
1. **前端框架**Vue 2.6.12
2. **UI 组件库**Element UI 2.15.14
3. **HTTP 客户端**Axios 0.28.1
4. **向后兼容**:保持现有功能不变,只做增强
## 测试要点
### 功能测试
- [ ] 详情对话框正确显示个人类型字段
- [ ] 详情对话框正确显示机构类型字段
- [ ] 新增/编辑对话框正确切换标签页
- [ ] 个人中介模板下载正常
- [ ] 机构中介模板下载正常
- [ ] 个人中介数据导入正常
- [ ] 机构中介数据导入正常
### 兼容性测试
- [ ] 旧数据详情显示正常(字段为空时显示"-"
- [ ] 现有列表查询功能正常
- [ ] 现有新增/编辑功能正常
- [ ] 现有删除功能正常
- [ ] 现有导出功能正常