Files
ccdi/openspec/changes/sync-intermediary-frontend-with-detailed-fields/design.md
2026-01-29 22:03:42 +08:00

25 KiB
Raw Blame History

Design: 同步前端以支持中介黑名单详细字段和类型化模板导入

前端架构设计

文件结构

ruoyi-ui/src/
├── api/
│   └── dpcIntermediary.js (修改 - 添加新接口)
├── views/
│   └── dpcIntermediary/
│       └── index.vue (修改 - 添加详情对话框和表单增强)

API 接口层设计

新增接口函数

ruoyi-ui/src/api/dpcIntermediary.js 中添加:

// 下载个人中介导入模板
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. 操作列添加"查看详情"按钮

<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. 导入对话框改造

支持选择导入类型,并根据类型下载对应模板:

<!-- 导入对话框 -->
<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>

详情对话框设计

对话框结构

<!-- 详情对话框 -->
<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>

数据属性

data() {
  return {
    // ... 现有数据 ...

    // 详情对话框
    detailOpen: false,
    detailData: {},

    // 导入参数
    upload: {
      open: false,
      title: "",
      isUploading: false,
      updateSupport: 0,
      importType: "person", // person 或 entity
      headers: { Authorization: "Bearer " + getToken() },
      url: "" // 动态设置
    }
  }
}

方法实现

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 分组展示字段

<!-- 添加或修改对话框 -->
<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>

数据属性和方法

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-rowel-col 实现响应式布局
  • 详情对话框使用 el-descriptions 组件,展示更美观
  • 对话框宽度适当增加,容纳更多字段

技术约束

  1. 前端框架Vue 2.6.12
  2. UI 组件库Element UI 2.15.14
  3. HTTP 客户端Axios 0.28.1
  4. 向后兼容:保持现有功能不变,只做增强

测试要点

功能测试

  • 详情对话框正确显示个人类型字段
  • 详情对话框正确显示机构类型字段
  • 新增/编辑对话框正确切换标签页
  • 个人中介模板下载正常
  • 机构中介模板下载正常
  • 个人中介数据导入正常
  • 机构中介数据导入正常

兼容性测试

  • 旧数据详情显示正常(字段为空时显示"-"
  • 现有列表查询功能正常
  • 现有新增/编辑功能正常
  • 现有删除功能正常
  • 现有导出功能正常