# 互换债券三字段互算踩坑总结与测试指南 > 本文档总结 EQD-6838 债券净价/全价/收益率三字段互算功能从初始实现到最终可用过程中踩过的所有坑, > 包括根因分析、修复方案、能否被不同技术栈避免、以及如何用单测在早期拦截。 > 关联文件:`swapTradeEdit.js` / `swapCalc.js` / `fastVue.base.js` / `TradeEdit.cshtml` / `base/main.js` --- ## 一、功能背景 债券标的在互换交易录入页有三个价格字段:净价(`PosiNetNoFeePrice`)、全价(`PosiGrossPrice`)、收益率(`InitYtm`)。 三者可互相推导(知道任一可算另两个,通过后端 `/Bond/CalcBond` 接口)。 **交互约定**: - **约定1**:在任一字段**敲回车** → 以该字段为"源"调计算器,反算另两个并覆盖。 - **约定2**:计算器调用成功→源字段标"源"、另两个标"AUTO";失败→保留源字段值、另两个清空、三标识全清。 - **约定3**:编辑某字段(失焦/按键输入)但**未回车** → 不联动另两字段,清掉源/AUTO标识,本字段标"REV"(人工输入)。**连续手动编辑多字段(均不回车)时各被改过的字段均累计REV**(不清空其它已手工编辑字段的REV),使 UI 正确显示"全部为人工输入"、且后续计算器回写时不会被误覆盖。 --- ## 二、踩坑时间线与根因 从 d662399a 到 56256cad 共 7 次提交,涉及 5 个独立根因。每次修复后部署测试发现问题仍未解决,再定位新根因。 ### 根因 1:`.native` 修饰符被 jQuery 事件系统拦截 | 项目 | 内容 | |------|------| | **提交** | d662399a(初始实现)→ cee69a02(修复) | | **现象** | 用户按键时"源/AUTO"标识不消失,`onBondPriceKeydown` 从未执行 | | **根因** | `vue-number-input` 组件在 `mounted()` 中通过 jQuery `$(_el).on('keydown', __keyHandle)` 重新绑定了 keydown。jQuery 的事件绑定机制与 Vue 的 `.native` 修饰符冲突,导致父组件的 `v-on:keydown.native` 监听器收不到事件 | | **修复** | 在组件 `mounted()` 内部用原生 `this.$el.addEventListener('keydown', fn)` + `self.$emit('keydown', e)` 绕过 jQuery 事件系统;cshtml 从 `v-on:keydown.native` 改为 `v-on:keydown` | | **代码位置** | `fastVue.base.js` mounted() 钩子 | ### 根因 2:Vue 2 响应式失效——非 `data()` 声明的属性不可响应 | 项目 | 内容 | |------|------| | **提交** | d662399a(初始实现)→ cee69a02(修复) | | **现象** | `applyBondManualEdit` 等纯函数正确修改了 `item.bondDriverType` 等属性,但视图不更新 | | **根因** | `bondDriverType`/`bondAuto`/`bondRev` 这三个属性不在后端返回的数据模型中,item 对象从未在 Vue 的 `data()` 中声明过这些属性。Vue 2 基于 `Object.defineProperty`,只能追踪初始化时已存在的属性,后加属性是非响应式的,赋值后不触发视图更新 | | **修复** | 新增 `syncBondFlags(item)` 方法,用 `this.$set(item, 'bondDriverType', ...)` 强制将属性注册为响应式 | | **代码位置** | `swapTradeEdit.js` syncBondFlags() | ### 根因 3:`$set` 设置相同对象引用不触发更新 | 项目 | 内容 | |------|------| | **提交** | cee69a02(初版修复)→ 7c0572ad(二次修复) | | **现象** | 加了 `syncBondFlags` 后标识仍不更新。排查发现 `$set` 传入了相同对象引用 | | **根因** | Vue 2 的 `$set` 内部会做 `newVal === oldVal` 判断。如果 `applyBondManualEdit` 原地修改 `state.bondAuto` 后引用没变,`this.$set(item, 'bondAuto', item.bondAuto)` 的 newVal 与 oldVal 是同一个对象引用,Vue 会跳过更新 | | **修复** | 每次都创建全新的对象字面量:`{ CP: !!item.bondAuto.CP, DP: !!item.bondAuto.DP, YD: !!item.bondAuto.YD }`,保证引用不同。再加 `this.$forceUpdate()` 兜底 | | **代码位置** | `swapTradeEdit.js` syncBondFlags() | ### 根因 4:`main.post` 业务错误走 `reject`,`.done()` 不执行 | 项目 | 内容 | |------|------| | **提交** | d662399a(初始实现)→ ce4ef629(修复) | | **现象** | 计算器返回业务错误(如债券不存在)时,"另两字段清空+三标识全清"逻辑(约定2补充)不执行 | | **根因** | `main.post` 是 jQuery `$.ajax` 的包装。`main.__post` 在 `resp.success===false`(业务错误)时调用 `deferred.reject(resp)`,而 `calcBondForItem` 只挂了 `.done()` 回调。Promise reject 不会触发 `.done()`,整个失败处理分支被静默跳过 | | **修复** | 补 `.fail(function(resp) { ... applyBondCalcFailure + syncBondFlags })` 处理 reject 路径 | | **代码位置** | `swapTradeEdit.js` calcBondForItem() + `base/main.js` __post() | ### 根因 5:部署/缓存/构建路径问题 | 项目 | 内容 | |------|------| | **提交** | 8124bb46 + 56256cad | | **现象** | 代码改了部署后不生效 | | **根因** | ① `swapTradeEdit.js` 是独立 `