21 KiB
Customer Map Selection Frontend Implementation Plan
For agentic workers: Steps use checkbox (
- [ ]) syntax for tracking. Follow this repository's AGENTS.md rule: do not enable subagents or superpowers execution modes unless the user explicitly requests them for the implementation session.
Goal: Add the frontend customer-id query and customer-internal-code selection step before opening personal or corporate workflow creation dialogs.
Architecture: Keep the existing list page and personal/corporate creation dialogs. Insert a shared customer-map selector dialog between customer-type selection and workflow creation, then pass the selected underscore-field record into the existing create dialogs. Existing workflow create APIs stay unchanged and still receive custIsn and custName in camelCase.
Tech Stack: Vue 2, Element UI, existing RuoYi request wrapper, Node static assertion tests, nvm-managed frontend runtime, Playwright/browser verification after implementation.
File Structure
- Modify:
ruoyi-ui/src/api/loanPricing/workflow.js- Adds personal/corporate customer-map query functions.
- Create:
ruoyi-ui/src/views/loanPricing/workflow/components/CustomerMapSelector.vue- Owns customer-id input, query action, result table, loading state, and row selection.
- Modify:
ruoyi-ui/src/views/loanPricing/workflow/index.vue- Opens customer-map selector after customer type selection and then opens the correct create dialog after row selection.
- Modify:
ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue- Accepts selected customer-map record, fills
custIsnandcustName, and makes both fields read-only.
- Accepts selected customer-map record, fills
- Modify:
ruoyi-ui/src/views/loanPricing/workflow/components/CorporateCreateDialog.vue- Same selected-record behavior for corporate creation.
- Modify:
ruoyi-ui/package.json- Adds a focused test script for the new customer-map selection checks.
- Create:
ruoyi-ui/tests/customer-map-selection.test.js- Static test coverage for API paths, selector fields, parent wiring, selected underscore fields, and read-only create dialog inputs.
Task 1: Add Frontend API Methods
Files:
-
Modify:
ruoyi-ui/src/api/loanPricing/workflow.js -
Create:
ruoyi-ui/tests/customer-map-selection.test.js -
Modify:
ruoyi-ui/package.json -
Step 1: Write failing API assertions
Create the first version of ruoyi-ui/tests/customer-map-selection.test.js:
const fs = require('fs')
const path = require('path')
const assert = require('assert')
function read(relativePath) {
return fs.readFileSync(path.join(__dirname, '..', relativePath), 'utf8')
}
const workflowApi = read('src/api/loanPricing/workflow.js')
assert(
workflowApi.includes('export function queryPersonalCustomerMap') &&
workflowApi.includes("url: '/loanPricing/workflow/customer-map/personal'") &&
workflowApi.includes('params: { custId: custId }'),
'缺少个人客户号映射查询 API'
)
assert(
workflowApi.includes('export function queryCorporateCustomerMap') &&
workflowApi.includes("url: '/loanPricing/workflow/customer-map/corporate'") &&
workflowApi.includes('params: { custId: custId }'),
'缺少企业客户号映射查询 API'
)
console.log('customer map selection assertions passed')
Add a script in ruoyi-ui/package.json:
"test:customer-map-selection": "node tests/customer-map-selection.test.js"
- Step 2: Run the test to verify it fails
Run with the project Node version:
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:customer-map-selection'
Expected: FAIL because the API methods do not exist.
- Step 3: Implement the API methods
Append to workflow.js:
// 查询个人客户号映射
export function queryPersonalCustomerMap(custId) {
return request({
url: '/loanPricing/workflow/customer-map/personal',
method: 'get',
params: { custId: custId }
})
}
// 查询企业客户号映射
export function queryCorporateCustomerMap(custId) {
return request({
url: '/loanPricing/workflow/customer-map/corporate',
method: 'get',
params: { custId: custId }
})
}
- Step 4: Run the API test
Run:
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:customer-map-selection'
Expected: PASS for API assertions.
Task 2: Create Customer Map Selector Dialog
Files:
-
Create:
ruoyi-ui/src/views/loanPricing/workflow/components/CustomerMapSelector.vue -
Modify:
ruoyi-ui/tests/customer-map-selection.test.js -
Step 1: Extend the static test for selector requirements
Add these assertions to customer-map-selection.test.js:
const selector = read('src/views/loanPricing/workflow/components/CustomerMapSelector.vue')
assert(
selector.includes('title="客户号查询"') &&
selector.includes('v-model="queryForm.custId"') &&
selector.includes('handleQuery'),
'客户号查询弹窗缺少客户号输入或查询动作'
)
assert(
selector.includes("queryPersonalCustomerMap") &&
selector.includes("queryCorporateCustomerMap") &&
selector.includes("this.customerType === 'personal'"),
'客户号查询弹窗未按客户类型调用个人/企业接口'
)
;['cust_id', 'cust_isn', 'cust_name', 'faith_day', 'balance_avg', 'loan_count_his', 'last_loan_date'].forEach((field) => {
assert(selector.includes(`prop="${field}"`) || selector.includes(`row.${field}`), `查询结果表格缺少字段 ${field}`)
})
assert(
selector.includes("this.$emit('select', row)") &&
selector.includes('未查询到客户信息') &&
selector.includes('请输入客户号'),
'客户号查询弹窗缺少选择事件或关键提示'
)
- Step 2: Run the test to verify it fails
Run:
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:customer-map-selection'
Expected: FAIL because CustomerMapSelector.vue does not exist.
- Step 3: Implement
CustomerMapSelector.vue
Create the component:
<template>
<el-dialog title="客户号查询" :visible.sync="dialogVisible" width="900px" append-to-body
:close-on-click-modal="false" @close="handleClose">
<el-form :model="queryForm" inline size="small">
<el-form-item label="客户号">
<el-input v-model="queryForm.custId" placeholder="请输入客户号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" :loading="loading" @click="handleQuery">查询</el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="customerList">
<el-table-column label="客户号" prop="cust_id" align="center" :show-overflow-tooltip="true"/>
<el-table-column label="客户内码" prop="cust_isn" align="center" :show-overflow-tooltip="true"/>
<el-table-column label="客户名称" prop="cust_name" align="center" :show-overflow-tooltip="true"/>
<el-table-column label="用信天数" prop="faith_day" align="center"/>
<el-table-column label="存款年日均" prop="balance_avg" align="center"/>
<el-table-column label="历史贷款次数" prop="loan_count_his" align="center"/>
<el-table-column label="上次贷款日期" prop="last_loan_date" align="center" width="130"/>
<el-table-column label="操作" align="center" width="90">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="handleSelect(scope.row)">选择</el-button>
</template>
</el-table-column>
</el-table>
</el-dialog>
</template>
<script>
import {queryPersonalCustomerMap, queryCorporateCustomerMap} from "@/api/loanPricing/workflow"
export default {
name: "CustomerMapSelector",
props: {
visible: {
type: Boolean,
default: false
},
customerType: {
type: String,
default: undefined
}
},
data() {
return {
loading: false,
queryForm: {
custId: undefined
},
customerList: []
}
},
computed: {
dialogVisible: {
get() {
return this.visible
},
set(val) {
this.$emit('update:visible', val)
}
}
},
methods: {
handleQuery() {
if (!this.queryForm.custId) {
this.$modal.msgWarning("请输入客户号")
return
}
this.loading = true
const request = this.customerType === 'personal'
? queryPersonalCustomerMap
: queryCorporateCustomerMap
request(this.queryForm.custId).then(response => {
this.customerList = response.data || []
if (this.customerList.length === 0) {
this.$modal.msgWarning("未查询到客户信息")
}
}).finally(() => {
this.loading = false
})
},
handleSelect(row) {
this.$emit('select', row)
this.dialogVisible = false
},
handleClose() {
this.queryForm.custId = undefined
this.customerList = []
this.loading = false
}
}
}
</script>
- Step 4: Run the selector test
Run:
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:customer-map-selection'
Expected: PASS through selector assertions.
Task 3: Wire Selector Into Workflow List Page
Files:
-
Modify:
ruoyi-ui/src/views/loanPricing/workflow/index.vue -
Modify:
ruoyi-ui/tests/customer-map-selection.test.js -
Step 1: Extend the test for parent wiring
Add assertions:
const workflowIndex = read('src/views/loanPricing/workflow/index.vue')
assert(
workflowIndex.includes('CustomerMapSelector') &&
workflowIndex.includes('<customer-map-selector') &&
workflowIndex.includes(':customer-type="selectedCustomerType"') &&
workflowIndex.includes('@select="handleCustomerMapSelect"'),
'流程列表页未接入客户号查询选择弹窗'
)
assert(
workflowIndex.includes('selectedCustomerType') &&
workflowIndex.includes('selectedCustomerMap') &&
workflowIndex.includes('showCustomerMapSelector'),
'流程列表页缺少客户类型、客户映射选择状态'
)
assert(
workflowIndex.includes("this.selectedCustomerType = type") &&
workflowIndex.includes('this.showCustomerMapSelector = true') &&
!workflowIndex.includes("if (type === 'personal') {\\n this.showPersonalDialog = true"),
'选择客户类型后应先打开客户号查询弹窗,而不是直接打开新增弹窗'
)
assert(
workflowIndex.includes('handleCustomerMapSelect') &&
workflowIndex.includes('this.selectedCustomerMap = row') &&
workflowIndex.includes('this.showPersonalDialog = true') &&
workflowIndex.includes('this.showCorporateDialog = true'),
'选择客户内码后未打开对应新增弹窗'
)
- Step 2: Run the test to verify it fails
Run:
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:customer-map-selection'
Expected: FAIL because the list page does not yet use the selector.
- Step 3: Update
index.vuetemplate and script
Add component usage after the customer type selector:
<customer-map-selector
:visible.sync="showCustomerMapSelector"
:customer-type="selectedCustomerType"
@select="handleCustomerMapSelect"
/>
Pass the selected record into both create dialogs:
<personal-create-dialog
:visible.sync="showPersonalDialog"
:customer-map="selectedCustomerMap"
@success="handleCreateSuccess"
/>
<corporate-create-dialog
:visible.sync="showCorporateDialog"
:customer-map="selectedCustomerMap"
@success="handleCreateSuccess"
/>
Import and register the selector:
import CustomerMapSelector from "./components/CustomerMapSelector"
Add state:
showCustomerMapSelector: false,
selectedCustomerType: undefined,
selectedCustomerMap: null,
Change handleSelectType:
handleSelectType(type) {
this.selectedCustomerType = type
this.selectedCustomerMap = null
this.showCustomerMapSelector = true
}
Add selected-row handler:
handleCustomerMapSelect(row) {
this.selectedCustomerMap = row
if (this.selectedCustomerType === 'personal') {
this.showPersonalDialog = true
} else if (this.selectedCustomerType === 'corporate') {
this.showCorporateDialog = true
}
}
Clear selected state when the create flow completes:
handleCreateSuccess() {
this.selectedCustomerMap = null
this.selectedCustomerType = undefined
this.getList()
}
Add watchers for dialog close so cancellation also clears the selected record:
watch: {
showPersonalDialog(val) {
if (!val && !this.showCorporateDialog) {
this.selectedCustomerMap = null
this.selectedCustomerType = undefined
}
},
showCorporateDialog(val) {
if (!val && !this.showPersonalDialog) {
this.selectedCustomerMap = null
this.selectedCustomerType = undefined
}
}
}
- Step 4: Run the parent wiring test
Run:
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:customer-map-selection'
Expected: PASS through parent wiring assertions.
Task 4: Make Create Dialog Customer Fields Read-Only and Auto-Filled
Files:
-
Modify:
ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue -
Modify:
ruoyi-ui/src/views/loanPricing/workflow/components/CorporateCreateDialog.vue -
Modify:
ruoyi-ui/tests/customer-map-selection.test.js -
Step 1: Extend test for create dialog behavior
Add assertions:
const personalCreateDialog = read('src/views/loanPricing/workflow/components/PersonalCreateDialog.vue')
const corporateCreateDialog = read('src/views/loanPricing/workflow/components/CorporateCreateDialog.vue')
;[
['个人新增弹窗', personalCreateDialog],
['企业新增弹窗', corporateCreateDialog]
].forEach(([name, source]) => {
assert(source.includes('customerMap'), `${name} 缺少 customerMap 入参`)
assert(source.includes(':readonly="true"') || source.includes('readonly'), `${name} 客户内码和客户名称应只读`)
assert(source.includes('this.customerMap.cust_isn'), `${name} 未从 cust_isn 自动带入客户内码`)
assert(source.includes('this.customerMap.cust_name'), `${name} 未从 cust_name 自动带入客户名称`)
assert(source.includes('clearValidate'), `${name} 应清空校验而不是用 resetFields 覆盖已选客户`)
assert(!source.includes('this.resetForm("form")'), `${name} 不应在 reset() 中调用 resetForm("form") 覆盖只读客户字段`)
})
- Step 2: Run the test to verify it fails
Run:
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:customer-map-selection'
Expected: FAIL because dialogs do not yet consume customerMap.
- Step 3: Update personal create dialog
Add prop:
customerMap: {
type: Object,
default: null
}
Make customer fields read-only:
<el-input v-model="form.custIsn" placeholder="请选择客户内码" :readonly="true"/>
<el-input v-model="form.custName" placeholder="请选择客户名称" :readonly="true"/>
In reset(), initialize from selected row and do not call this.resetForm("form"). The existing RuoYi helper delegates to Element UI resetFields(), which can restore stale initial values and overwrite the selected read-only customer fields. After assigning the new form object, only clear validation:
reset() {
this.form = {
orgCode: '892000',
runType: '1',
custIsn: this.customerMap ? this.customerMap.cust_isn : undefined,
custName: this.customerMap ? this.customerMap.cust_name : undefined,
idType: undefined,
idNum: undefined,
guarType: undefined,
applyAmt: undefined,
loanPurpose: undefined,
loanTerm: undefined,
bizProof: false,
loanLoop: false,
collType: undefined,
collThirdParty: false
}
this.submitting = false
this.$nextTick(() => {
if (this.$refs.form) {
this.$refs.form.clearValidate()
}
})
}
Keep existing required validation for custIsn and custName.
- Step 4: Update corporate create dialog
Apply the same prop, read-only inputs, and reset() initialization to CorporateCreateDialog.vue. Do not call this.resetForm("form") in the corporate dialog reset path; assign the new form object with custIsn and custName from customerMap, then use this.$refs.form.clearValidate() inside $nextTick().
- Step 5: Run the create dialog test
Run:
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:customer-map-selection'
Expected: PASS through create dialog assertions.
Task 5: Frontend Verification
Files:
-
Verify only, no new source files.
-
Step 1: Run focused customer-map test
Run:
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:customer-map-selection'
Expected: PASS.
- Step 2: Run affected existing frontend tests
Run:
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:personal-create-input-params && npm --prefix ruoyi-ui run test:corporate-create-input-params && node ruoyi-ui/tests/workflow-index-refresh.test.js'
Expected: PASS.
- Step 3: Build frontend
Run:
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run build:prod'
Expected: build succeeds.
- Step 4: Browser verification after backend and frontend are running
Use the real application page, not a prototype page:
- Open the workflow list page.
- Click “新增”.
- Select “个人客户”.
- Confirm the customer-id query dialog opens.
- Query any customer number.
- Select one result row.
- Confirm the personal create dialog opens and
客户内码/客户名称are auto-filled and read-only. - Fill the remaining required fields and submit.
- Repeat the same flow for “企业客户”.
- Close and reopen the create flow at least once with a second selected row, and confirm the new row's
cust_isn/cust_namereplace the previous values in the create dialog. - Confirm both created records appear in the list or detail page.
Expected: both personal and corporate flows pass through customer-map selection before creation.
- Step 5: Cleanup test processes
Stop any backend or frontend processes started for verification before ending the task.
- Step 6: Commit frontend work
Use a Chinese commit message and avoid unrelated dirty files:
git status --short
git add ruoyi-ui/src/api/loanPricing/workflow.js \
ruoyi-ui/src/views/loanPricing/workflow/components/CustomerMapSelector.vue \
ruoyi-ui/src/views/loanPricing/workflow/index.vue \
ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue \
ruoyi-ui/src/views/loanPricing/workflow/components/CorporateCreateDialog.vue \
ruoyi-ui/package.json \
ruoyi-ui/tests/customer-map-selection.test.js
git commit -m "新增客户号查询选择前端流程"
Do not commit screenshots, browser traces, temporary spreadsheets, or generated test data.
Task 6: Final End-to-End Verification and Implementation Record
Files:
-
Create:
doc/implementation-report-2026-04-29-customer-map-selection.md -
Step 1: Run backend and frontend verification together
After backend and frontend implementation are both complete:
mvn -pl ruoyi-loan-pricing -am -Dtest=CustomerMapRecordVOTest,LoanPricingCustomerMapServiceTest,LoanRatePricingMockControllerCustomerMapTest,LoanPricingWorkflowControllerCustomerMapTest,LoanPricingModelServiceTest,LoanPricingWorkflowServiceImplTest -Dsurefire.failIfNoSpecifiedTests=false test
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:customer-map-selection && npm --prefix ruoyi-ui run test:personal-create-input-params && npm --prefix ruoyi-ui run test:corporate-create-input-params && node ruoyi-ui/tests/workflow-index-refresh.test.js'
Expected: all tests pass.
- Step 2: Complete browser verification
Run the browser flow from Task 5 for both personal and corporate customers. Confirm the backend API response wrapper is still the existing RuoYi wrapper while records inside data keep underscore fields.
- Step 3: Write implementation report
Create doc/implementation-report-2026-04-29-customer-map-selection.md with:
# 2026-04-29 客户号查询选择客户内码实施记录
## 修改内容
- 后端新增个人/企业客户号映射业务接口。
- 后端新增个人/企业客户号映射 mock 接口。
- 配置文件新增 `customer-map` 个人/企业地址并指向本项目 mock。
- 前端新增客户号查询选择弹窗。
- 个人/企业新增流程改为先查询客户号、选择客户内码,再打开新增弹窗。
- 新增弹窗客户内码和客户名称由选中记录自动带入并只读。
## 验证结果
- 后端测试:填写实际执行命令和通过/失败结果。
- 前端测试:填写实际执行命令和通过/失败结果。
- 真实页面验证:填写个人、企业两条浏览器验证流程和结果。
- 进程清理:填写本次启动的前后端进程是否已关闭。
- Step 4: Commit implementation report
git add doc/implementation-report-2026-04-29-customer-map-selection.md
git commit -m "补充客户号映射选择实施记录"