# 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 `custIsn` and `custName`, and makes both fields read-only. - 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`: ```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`: ```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: ```bash 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`: ```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: ```bash 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`: ```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: ```bash 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: ```vue ``` - [ ] **Step 4: Run the selector test** Run: ```bash 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: ```js const workflowIndex = read('src/views/loanPricing/workflow/index.vue') assert( workflowIndex.includes('CustomerMapSelector') && workflowIndex.includes('/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.vue` template and script** Add component usage after the customer type selector: ```vue ``` Pass the selected record into both create dialogs: ```vue ``` Import and register the selector: ```js import CustomerMapSelector from "./components/CustomerMapSelector" ``` Add state: ```js showCustomerMapSelector: false, selectedCustomerType: undefined, selectedCustomerMap: null, ``` Change `handleSelectType`: ```js handleSelectType(type) { this.selectedCustomerType = type this.selectedCustomerMap = null this.showCustomerMapSelector = true } ``` Add selected-row handler: ```js 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: ```js handleCreateSuccess() { this.selectedCustomerMap = null this.selectedCustomerType = undefined this.getList() } ``` Add watchers for dialog close so cancellation also clears the selected record: ```js 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: ```bash 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: ```js 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: ```bash 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: ```js customerMap: { type: Object, default: null } ``` Make customer fields read-only: ```vue ``` 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: ```js 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: ```bash 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: ```bash 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: ```bash 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: ```bash 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: 1. Open the workflow list page. 2. Click “新增”. 3. Select “个人客户”. 4. Confirm the customer-id query dialog opens. 5. Query any customer number. 6. Select one result row. 7. Confirm the personal create dialog opens and `客户内码` / `客户名称` are auto-filled and read-only. 8. Fill the remaining required fields and submit. 9. Repeat the same flow for “企业客户”. 10. Close and reopen the create flow at least once with a second selected row, and confirm the new row's `cust_isn` / `cust_name` replace the previous values in the create dialog. 11. 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: ```bash 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: ```bash 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: ```markdown # 2026-04-29 客户号查询选择客户内码实施记录 ## 修改内容 - 后端新增个人/企业客户号映射业务接口。 - 后端新增个人/企业客户号映射 mock 接口。 - 配置文件新增 `customer-map` 个人/企业地址并指向本项目 mock。 - 前端新增客户号查询选择弹窗。 - 个人/企业新增流程改为先查询客户号、选择客户内码,再打开新增弹窗。 - 新增弹窗客户内码和客户名称由选中记录自动带入并只读。 ## 验证结果 - 后端测试:填写实际执行命令和通过/失败结果。 - 前端测试:填写实际执行命令和通过/失败结果。 - 真实页面验证:填写个人、企业两条浏览器验证流程和结果。 - 进程清理:填写本次启动的前后端进程是否已关闭。 ``` - [ ] **Step 4: Commit implementation report** ```bash git add doc/implementation-report-2026-04-29-customer-map-selection.md git commit -m "补充客户号映射选择实施记录" ```