中介新增 修改接口

This commit is contained in:
wkc
2026-01-29 22:03:42 +08:00
parent 1b043fa2d6
commit ac4e02e8c5
214 changed files with 9397 additions and 671 deletions

View File

@@ -0,0 +1,715 @@
# 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. **向后兼容**:保持现有功能不变,只做增强
## 测试要点
### 功能测试
- [ ] 详情对话框正确显示个人类型字段
- [ ] 详情对话框正确显示机构类型字段
- [ ] 新增/编辑对话框正确切换标签页
- [ ] 个人中介模板下载正常
- [ ] 机构中介模板下载正常
- [ ] 个人中介数据导入正常
- [ ] 机构中介数据导入正常
### 兼容性测试
- [ ] 旧数据详情显示正常(字段为空时显示"-"
- [ ] 现有列表查询功能正常
- [ ] 现有新增/编辑功能正常
- [ ] 现有删除功能正常
- [ ] 现有导出功能正常

View File

@@ -0,0 +1,159 @@
# Proposal: 同步前端以支持中介黑名单详细字段和类型化模板导入
## Change ID
`sync-intermediary-frontend-with-detailed-fields`
## Summary
同步前端代码,使其与后端 API 文档中定义的中介黑名单增强功能保持一致,支持个人和机构类型的详细字段展示,以及类型化的模板下载和导入功能。
## Motivation
### 问题背景
后端已完成中介黑名单的增强(变更 `enhance-intermediary-with-detailed-fields`),新增了:
1. 个人类型的详细字段(人员类型、性别、证件类型、手机号码、微信号等)
2. 机构类型的详细字段(统一社会信用代码、主体类型、企业性质、法定代表人、股东等)
3. 分离的个人和机构导入模板下载接口
4. 分离的个人和机构导入接口
### 现状
当前前端代码 [ruoyi-ui/src/views/dpcIntermediary/index.vue](../../../../ruoyi-ui/src/views/dpcIntermediary/index.vue) 和 [ruoyi-ui/src/api/dpcIntermediary.js](../../../../ruoyi-ui/src/api/dpcIntermediary.js) 仅支持基础字段:
- 只显示核心字段(姓名/机构名称、证件号、中介类型、状态、备注)
- 只有一个通用的导入模板下载接口
- 只有一个通用的导入接口
- 详情对话框不支持根据类型显示不同字段
### 业务需求
1. 列表页面保持简洁,显示核心字段
2. 详情对话框需要根据中介类型显示不同的详细字段
3. 导入功能需要支持分别下载和导入个人/机构模板
4. 新增/编辑对话框需要支持详细字段的录入
## Scope
### 包含的功能
#### 2.1 API 接口层修改
- 添加个人中介模板下载接口调用
- 添加机构中介模板下载接口调用
- 添加个人中介数据导入接口调用
- 添加机构中介数据导入接口调用
- 保留原有通用接口以保持向后兼容
#### 2.2 列表页面修改
- 保持列表显示简洁(核心字段)
- 添加"查看详情"按钮
- 导入功能改为支持类型选择(个人/机构)
#### 2.3 详情对话框新增
- 根据中介类型动态显示不同的字段
- 个人类型显示:人员类型、人员子类型、性别、证件类型、手机号码、微信号等
- 机构类型显示:统一社会信用代码、主体类型、企业性质、法定代表人、股东等
- 支持只读模式,不提供编辑功能
#### 2.4 新增/编辑对话框增强
- 根据中介类型显示对应的字段组
- 个人类型显示个人相关字段
- 机构类型显示机构相关字段
- 使用表单验证确保数据完整性
### 明确排除
- 后端代码修改(已在 `enhance-intermediary-with-detailed-fields` 中完成)
- 数据库结构修改(已在 `enhance-intermediary-with-detailed-fields` 中完成)
- 路由和菜单配置修改(保持现有配置)
## Proposed Design
详见 [design.md](./design.md)
## Alternatives Considered
### 选项1在列表中展开显示所有字段
**优点**
- 用户无需点击详情即可看到完整信息
**缺点**
- 列表过于拥挤,不利于快速浏览
- 个人和机构字段混杂,难以阅读
**决定**:不采用。保持列表简洁,详情通过对话框展示。
### 选项2使用两个独立的页面个人列表和机构列表
**优点**
- 页面结构清晰,各自独立
**缺点**
- 需要添加新的路由和菜单配置
- 代码重复度高
- 不符合单一数据源的管理模式
**决定**:不采用。使用同一个列表页面,通过中介类型区分。
### 选项3在现有对话框中内嵌详情视图
**优点**
- 减少对话框数量
**缺点**
- 编辑和查看模式混合,逻辑复杂
- 表单验证逻辑难以处理
**决定**:不采用。分离详情对话框和编辑对话框,职责更清晰。
## Impact
### 前端影响
#### API 层
- 新增 4 个接口调用函数(模板下载 × 2数据导入 × 2
- 修改导入对话框以支持类型选择
#### 视图层
- 新增详情对话框组件
- 修改列表操作列(添加"查看详情"按钮)
- 修改新增/编辑对话框(添加详细字段)
- 修改导入对话框(支持类型选择和对应的模板下载)
#### 数据流
- 详情接口返回的数据结构根据类型不同
- 表单提交需要包含对应类型的字段
### 用户体验影响
- **正面**:可以看到更完整的中介信息
- **正面**:导入模板更符合实际需求,减少录入错误
- **中性**:导入时需要先选择类型,增加一步操作
## Dependencies
- 依赖后端变更 `enhance-intermediary-with-detailed-fields` 必须先完成
- 依赖后端 API 接口按 [doc/中介黑名单管理API文档.md](../../../../doc/中介黑名单管理API文档.md) 定义实现
## Related Changes
- `enhance-intermediary-with-detailed-fields` - 后端增强功能
## Open Questions
1. **详情对话框是否支持编辑?**
- 建议:不支持编辑,只做展示。编辑使用现有的编辑对话框。
2. **新增/编辑对话框如何处理大量字段?**
- 建议:使用 el-tabs 或 el-row/el-col 分组展示,提高可读性。
3. **导入时是否需要类型验证?**
- 建议:在后端验证,前端只负责选择正确的模板和接口。
4. **旧数据的详情如何处理?**
- 建议:旧数据没有详细字段,详情对话框显示空值或占位符(如"未填写")。
## Success Criteria
- [ ] API 接口层添加新的接口调用函数
- [ ] 列表页面添加"查看详情"按钮
- [ ] 详情对话框根据类型正确显示不同字段
- [ ] 新增/编辑对话框支持详细字段录入
- [ ] 导入功能支持个人/机构类型选择
- [ ] 可以下载个人中介专用模板
- [ ] 可以下载机构中介专用模板
- [ ] 可以使用个人模板导入数据
- [ ] 可以使用机构模板导入数据
- [ ] 现有功能保持正常运行
## References
- [doc/中介黑名单管理API文档.md](../../../../doc/中介黑名单管理API文档.md)
- [openspec/changes/enhance-intermediary-with-detailed-fields/proposal.md](../enhance-intermediary-with-detailed-fields/proposal.md)
- [openspec/changes/enhance-intermediary-with-detailed-fields/design.md](../enhance-intermediary-with-detailed-fields/design.md)

View File

@@ -0,0 +1,67 @@
# Spec: 前端 API 层扩展
## ADDED Requirements
### Requirement: 添加个人中介模板下载接口调用
前端 SHALL 提供调用个人中介模板下载接口的函数。
#### Scenario: 用户下载个人中介导入模板
**Given** 用户在中介黑名单列表页面
**And** 用户点击"导入"按钮
**And** 用户选择"个人中介"类型
**When** 用户点击"下载模板"链接
**Then** 系统调用 `/dpc/intermediary/importPersonTemplate` 接口
**And** 系统下载个人中介黑名单模板 Excel 文件
**And** 文件名格式为 `个人中介黑名单模板_{timestamp}.xlsx`
---
### Requirement: 添加机构中介模板下载接口调用
前端 SHALL 提供调用机构中介模板下载接口的函数。
#### Scenario: 用户下载机构中介导入模板
**Given** 用户在中介黑名单列表页面
**And** 用户点击"导入"按钮
**And** 用户选择"机构中介"类型
**When** 用户点击"下载模板"链接
**Then** 系统调用 `/dpc/intermediary/importEntityTemplate` 接口
**And** 系统下载机构中介黑名单模板 Excel 文件
**And** 文件名格式为 `机构中介黑名单模板_{timestamp}.xlsx`
---
### Requirement: 添加个人中介数据导入接口调用
前端 SHALL 提供调用个人中介数据导入接口的函数。
#### Scenario: 用户导入个人中介数据
**Given** 用户在中介黑名单列表页面
**And** 用户点击"导入"按钮
**And** 用户选择"个人中介"类型
**And** 用户已下载并填写了个人中介模板
**When** 用户上传 Excel 文件
**Then** 系统调用 `/dpc/intermediary/importPersonData` 接口
**And** 系统传递 `updateSupport` 参数
**And** 系统显示导入结果
---
### Requirement: 添加机构中介数据导入接口调用
前端 SHALL 提供调用机构中介数据导入接口的函数。
#### Scenario: 用户导入机构中介数据
**Given** 用户在中介黑名单列表页面
**And** 用户点击"导入"按钮
**And** 用户选择"机构中介"类型
**And** 用户已下载并填写了机构中介模板
**When** 用户上传 Excel 文件
**Then** 系统调用 `/dpc/intermediary/importEntityData` 接口
**And** 系统传递 `updateSupport` 参数
**And** 系统显示导入结果

View File

@@ -0,0 +1,58 @@
# Spec: 前端详情视图
## ADDED Requirements
### Requirement: 添加中介详情查看功能
前端 SHALL 提供查看中介详细信息的对话框,根据中介类型显示不同的字段。
#### Scenario: 查看个人类型中介详情
**Given** 用户在中介黑名单列表页面
**And** 列表中存在一条个人类型的中介记录
**When** 用户点击该记录的"详情"按钮
**Then** 系统打开详情对话框
**And** 标题为"中介黑名单详情"
**And** 显示核心字段中介ID、中介类型、姓名、证件号、状态、数据来源
**And** 显示个人专属字段人员类型、人员子类型、性别、证件类型、手机号码、微信号、联系地址、所在公司、职位、关联人员ID、关联关系
**And** 不显示机构专属字段
**And** 空值字段显示"-"
**And** 对话框只有"关闭"按钮
#### Scenario: 查看机构类型中介详情
**Given** 用户在中介黑名单列表页面
**And** 列表中存在一条机构类型的中介记录
**When** 用户点击该记录的"详情"按钮
**Then** 系统打开详情对话框
**And** 标题为"中介黑名单详情"
**And** 显示核心字段中介ID、中介类型、机构名称、统一社会信用代码、状态、数据来源
**And** 显示机构专属字段主体类型、企业性质、行业分类、所属行业、成立日期、注册地址、法定代表人、法定代表人证件类型、法定代表人证件号码、股东1-5
**And** 不显示个人专属字段
**And** 空值字段显示"-"
**And** 对话框只有"关闭"按钮
#### Scenario: 查看旧数据详情(无详细字段)
**Given** 用户在中介黑名单列表页面
**And** 列表中存在一条旧数据记录(详细字段为空)
**When** 用户点击该记录的"详情"按钮
**Then** 系统打开详情对话框
**And** 核心字段正常显示
**And** 详细字段显示"-"
**And** 对话框正常关闭
---
### Requirement: 列表操作列添加详情按钮
列表 SHALL 在操作列中添加"详情"按钮。
#### Scenario: 操作列显示详情按钮
**Given** 用户在中介黑名单列表页面
**When** 列表加载完成
**Then** 操作列显示"详情"按钮
**And** "详情"按钮不需要权限控制
**And** "详情"按钮图标为 "el-icon-view"
**And** 操作列宽度调整为 240px原 180px

View File

@@ -0,0 +1,139 @@
# Spec: 前端表单增强
## ADDED Requirements
### Requirement: 新增/编辑对话框支持详细字段录入
新增和编辑对话框 SHALL 扩展以支持个人和机构类型的详细字段录入。
#### Scenario: 新增个人类型中介
**Given** 用户点击"新增"按钮
**And** 对话框打开,默认选择"个人"类型
**When** 用户查看对话框
**Then** 显示"基本信息"、"个人信息"两个标签页
**And** 自动切换到"个人信息"标签页
**When** 用户填写个人信息字段
**And** 用户点击"确定"按钮
**Then** 系统提交核心字段和个人字段到后端
**And** 显示"新增成功"提示
**And** 列表刷新
#### Scenario: 新增机构类型中介
**Given** 用户点击"新增"按钮
**And** 用户将中介类型切换为"机构"
**When** 用户查看对话框
**Then** 显示"基本信息"、"机构信息"两个标签页
**And** 自动切换到"机构信息"标签页
**When** 用户填写机构信息字段
**And** 用户点击"确定"按钮
**Then** 系统提交核心字段和机构字段到后端
**And** 显示"新增成功"提示
**And** 列表刷新
#### Scenario: 编辑个人类型中介
**Given** 用户点击一条个人类型记录的"修改"按钮
**When** 对话框打开
**Then** 系统加载该记录的详细信息
**And** "个人信息"标签页显示已保存的个人字段值
**When** 用户修改个人信息字段
**And** 用户点击"确定"按钮
**Then** 系统提交修改后的数据到后端
**And** 显示"修改成功"提示
**And** 列表刷新
#### Scenario: 编辑机构类型中介
**Given** 用户点击一条机构类型记录的"修改"按钮
**When** 对话框打开
**Then** 系统加载该记录的详细信息
**And** "机构信息"标签页显示已保存的机构字段值
**When** 用户修改机构信息字段
**And** 用户点击"确定"按钮
**Then** 系统提交修改后的数据到后端
**And** 显示"修改成功"提示
**And** 列表刷新
#### Scenario: 切换中介类型时自动跳转标签页
**Given** 用户在新增或编辑对话框中
**And** 当前选择的是"个人"类型
**When** 用户将中介类型切换为"机构"
**Then** 对话框自动切换到"机构信息"标签页
**And** 个人字段保持不变(隐藏但保留值)
---
### Requirement: 导入对话框支持类型选择
导入对话框 SHALL 支持选择导入类型(个人/机构),并根据类型下载对应模板。
#### Scenario: 选择个人类型导入
**Given** 用户点击"导入"按钮
**When** 对话框打开
**Then** 默认选择"个人中介"类型
**And** 显示"是否更新已经存在的数据"复选框
**And** 显示"下载模板"链接
**When** 用户保持"个人中介"类型
**And** 用户点击"下载模板"链接
**Then** 系统下载个人中介模板
#### Scenario: 选择机构类型导入
**Given** 用户点击"导入"按钮
**When** 对话框打开
**And** 用户选择"机构中介"类型
**And** 用户点击"下载模板"链接
**Then** 系统下载机构中介模板
#### Scenario: 上传个人中介文件
**Given** 用户在导入对话框中
**And** 用户选择"个人中介"类型
**And** 用户勾选"是否更新已经存在的数据"
**When** 用户上传 Excel 文件
**And** 用户点击"确定"按钮
**Then** 系统调用 `/dpc/intermediary/importPersonData` 接口
**And** 系统传递 `updateSupport=true` 参数
**And** 显示导入结果
#### Scenario: 上传机构中介文件
**Given** 用户在导入对话框中
**And** 用户选择"机构中介"类型
**And** 用户不勾选"是否更新已经存在的数据"
**When** 用户上传 Excel 文件
**And** 用户点击"确定"按钮
**Then** 系统调用 `/dpc/intermediary/importEntityData` 接口
**And** 系统传递 `updateSupport=false` 参数
**And** 显示导入结果
---
### Requirement: 表单字段布局优化
表单 SHALL 使用标签页和响应式布局优化大量字段的展示。
#### Scenario: 个人信息标签页布局
**Given** 用户在新增或编辑对话框中
**And** 中介类型为"个人"
**When** 用户查看"个人信息"标签页
**Then** 字段使用两列布局el-col :span="12"
**And** 长文本字段(如联系地址)使用整行布局
**And** 字段标签宽度为 120px
**And** 输入框有合适的 maxlength 限制
#### Scenario: 机构信息标签页布局
**Given** 用户在新增或编辑对话框中
**And** 中介类型为"机构"
**When** 用户查看"机构信息"标签页
**Then** 字段使用两列布局
**And** 长文本字段(如注册地址)使用整行布局
**And** 股东信息区域使用分隔线el-divider分隔
**And** 字段标签宽度为 140px accommodate 更长的标签名称)
**And** 输入框有合适的 maxlength 限制

View File

@@ -0,0 +1,139 @@
# Tasks: 同步前端以支持中介黑名单详细字段和类型化模板导入
## 任务列表
### 阶段 1API 接口层扩展
- [x] 1.1 在 `ruoyi-ui/src/api/dpcIntermediary.js` 中添加 `importPersonTemplate` 函数
- 调用 `POST /dpc/intermediary/importPersonTemplate`
- 返回个人中介模板文件
- [x] 1.2 在 `ruoyi-ui/src/api/dpcIntermediary.js` 中添加 `importEntityTemplate` 函数
- 调用 `POST /dpc/intermediary/importEntityTemplate`
- 返回机构中介模板文件
- [x] 1.3 在 `ruoyi-ui/src/api/dpcIntermediary.js` 中添加 `importPersonData` 函数
- 调用 `POST /dpc/intermediary/importPersonData`
- 支持传递 `updateSupport` 参数
- [x] 1.4 在 `ruoyi-ui/src/api/dpcIntermediary.js` 中添加 `importEntityData` 函数
- 调用 `POST /dpc/intermediary/importEntityData`
- 支持传递 `updateSupport` 参数
### 阶段 2详情对话框实现
- [x] 2.1 在 `ruoyi-ui/src/views/dpcIntermediary/index.vue` 的 data 中添加详情相关属性
- 添加 `detailOpen: false`
- 添加 `detailData: {}`
- [x] 2.2 在 `ruoyi-ui/src/views/dpcIntermediary/index.vue` 的 template 中添加详情对话框
- 使用 `el-descriptions` 组件展示字段
- 根据中介类型条件渲染个人/机构专属字段
- [x] 2.3 实现 `handleDetail` 方法
- 调用 `getIntermediary` 接口获取详情
- 将数据存储到 `detailData`
- 打开详情对话框
- [x] 2.4 在列表操作列添加"详情"按钮
- 使用 `el-icon-view` 图标
- 绑定 `handleDetail` 方法
- 调整操作列宽度为 240px
### 阶段 3新增/编辑对话框增强
- [x] 3.1 在 `ruoyi-ui/src/views/dpcIntermediary/index.vue` 的 data 中添加 `activeTab` 属性
- 默认值为 `'basic'`
- [x] 3.2 在表单中添加 `el-tabs` 组件
- 添加"基本信息"标签页name="basic"
- 添加"个人信息"标签页name="person"
- 添加"机构信息"标签页name="entity"
- [x] 3.3 实现"个人信息"标签页内容
- 添加人员类型、人员子类型、性别、证件类型字段
- 添加手机号码、微信号、联系地址字段
- 添加所在公司、职位、关联人员ID、关联关系字段
- 使用两列响应式布局
- [x] 3.4 实现"机构信息"标签页内容
- 添加统一社会信用代码、主体类型、企业性质字段
- 添加行业分类、所属行业、成立日期、注册地址字段
- 添加法定代表人及其证件信息字段
- 添加股东1-5字段使用分隔线分组
- [x] 3.5 更新 `reset` 方法
- 添加所有个人字段的初始化
- 添加所有机构字段的初始化
- 重置 `activeTab``'basic'`
- [x] 3.6 实现 `handleTypeChange` 方法
- 监听中介类型变化
- 自动切换到对应的标签页
- [x] 3.7 调整对话框宽度
- 将宽度从 600px 调整为 900px
- 适应更多字段的展示
### 阶段 4导入对话框改造
- [x] 4.1 在导入对话框中添加类型选择单选框组
- 选项个人中介person、机构中介entity
- 默认选择"个人中介"
- [x] 4.2 在 `upload` 数据中添加 `importType` 属性
- 默认值为 `'person'`
- [x] 4.3 修改 `downloadImportTemplate` 方法
- 根据 `upload.importType` 调用对应的模板下载接口
- 个人类型调用 `importPersonTemplate`
- 机构类型调用 `importEntityTemplate`
- [x] 4.4 修改 `submitFileForm` 方法
- 根据 `upload.importType` 动态设置 `upload.url`
- 个人类型使用 `/dpc/intermediary/importPersonData`
- 机构类型使用 `/dpc/intermediary/importEntityData`
- 添加 `updateSupport` 参数到 URL
- [x] 4.5 调整导入对话框宽度
- 从 400px 调整为 450px
- 适应新增的类型选择区域
### 阶段 5测试验证
- [x] 5.1 测试详情对话框功能
- 测试个人类型详情显示
- 测试机构类型详情显示
- 测试旧数据详情显示(字段为空)
- [x] 5.2 测试新增/编辑功能
- 测试新增个人类型中介
- 测试新增机构类型中介
- 测试编辑个人类型中介
- 测试编辑机构类型中介
- 测试类型切换时标签页自动跳转
- [x] 5.3 测试导入功能
- 测试个人类型模板下载
- 测试机构类型模板下载
- 测试个人类型数据导入
- 测试机构类型数据导入
- 测试更新支持选项
- [x] 5.4 测试兼容性
- 测试现有列表查询功能
- 测试现有删除功能
- 测试现有导出功能
## 依赖关系
- 阶段 1 必须首先完成,为后续阶段提供 API 接口
- 阶段 2、3、4 可以并行开发
- 阶段 5 必须在所有开发阶段完成后进行
## 验收标准
每个任务完成后应满足:
- 代码符合 Vue 2.x 和 Element UI 规范
- 功能与设计文档描述一致
- 不影响现有功能的正常运行