From 72b2a4cbf62035237313c6908e7a576d8422b025 Mon Sep 17 00:00:00 2001 From: hjhan Date: Thu, 30 Jul 2026 10:15:00 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=80=BA=E5=88=B8=E4=B8=89=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E8=B0=83=E8=AF=95=E6=97=A5=E5=BF=97=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=20otcdebug=20gated=EF=BC=8C=E5=87=BA=E9=97=AE=E9=A2=98?= =?UTF-8?q?=E6=97=B6=E4=B8=80=E9=94=AE=E5=8F=AF=E5=AE=9A=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户指出:上一笔直接删了 onBondPriceKeydown 的逐键 console.log 又没补 gated 版, 等于平时没噪音了、真出问题也看不见——这正是反复改几十遍都满足不了需求时才需要日志兜底的场景。 修复: - onBondPriceKeydown 逐键 before/after(driver/rev) trace 改为 otcDebug.log(?otcdebug=1 才输出) - onBondPriceEdit(失焦/粘贴) 补同款 gated trace - calcBondForItem 补 enter/success/calc-error/post-fail 四条 gated trace → 三字段整条链路(逐键→回车→计算器→源/AUTO或失败)开开关即得完整现场 - main.js 第226/669行 catch 内 error console.log 保留(错误日志本就应在生产可见,利于定位) - otcDebug 机制本身已存在且唯一:?otcdebug=1 或 localStorage.otcdebug=1 开启, _MainLayout 注入 jsVersion/git/built,加载时打带版本 banner,平时零噪音 测试: - diag.test.js 新增源码守卫:swapTradeEdit.js 三字段埋点必须 otcDebug.log、不得退回裸 console.log - diag 18 例 / bondCalc 81 例全过 --- YLErpWeb/fe-tests/diag.test.js | 12 ++++++++++++ .../wwwroot/Scripts/app/swaptrade/swapTradeEdit.js | 13 +++++++++++++ 2 files changed, 25 insertions(+) diff --git a/YLErpWeb/fe-tests/diag.test.js b/YLErpWeb/fe-tests/diag.test.js index b47c3ff2..7a8ba756 100644 --- a/YLErpWeb/fe-tests/diag.test.js +++ b/YLErpWeb/fe-tests/diag.test.js @@ -135,6 +135,18 @@ describe('diag: 源码守卫(防回归——不许再硬编码易过期版本 expect(src).toMatch(/otcDebug\.banner|window\.ylotc\.__diag/); }); + test('swapTradeEdit.js 三字段调试埋点必须 gated(otcDebug.log),不得裸 console.log 退回', () => { + // 防回归:历史多次"改了不生效"靠日志兜底,曾出现①把 console.log 直接删了(出问题时看不见) + // ②或解除了开关控制(生产噪音)。正确做法是 otcDebug.log(?otcdebug=1 才输出)。 + const src = fs.readFileSync(path.join(SCRIPTS, 'app/swaptrade/swapTradeEdit.js'), 'utf8'); + // 存在 gated 埋点 + expect(src).toMatch(/otcDebug\.log\(/); + // 关键路径不得出现裸 console.log 调试行 + expect(src).not.toMatch(/console\.log\('\[onBondPriceKeydown\]/); + expect(src).not.toMatch(/console\.log\('\[calcBondForItem\]/); + expect(src).not.toMatch(/console\.log\('\[onBondPriceEdit\]/); + }); + test('fastVue.base.js 顶部 banner 不再硬编码 v20260729a', () => { const src = fs.readFileSync(path.join(SCRIPTS, 'fast/fastVue.base.js'), 'utf8'); expect(src).not.toContain('v20260729a'); diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js index 22914749..52858f91 100644 --- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js +++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js @@ -352,6 +352,7 @@ const vue = new Vue({ if (!item.isBond) return; SwapCalc.applyBondManualEdit(item, type); this.syncBondFlags(item); + otcDebug.log('[onBondPriceEdit] type=' + type + ' after: driver=' + item.bondDriverType + ' rev=' + JSON.stringify(item.bondRev)); }, //交互约定3(按键实时触发):vue-number-input 的 input 事件只在失焦/回车时触发, // 按键输入过程中不触发,导致用户输入时 源/AUTO 标识不会消失。 @@ -360,13 +361,19 @@ const vue = new Vue({ // 修饰键(Shift/Ctrl/Alt/Tab/方向键/F键等)除外——这些不改变数值,不应清标识。 onBondPriceKeydown(item, type, event) { if (!item.isBond) return; + var kc = event.which || event.keyCode; // 仅"会改变数值"的按键才清 源/AUTO 标识并标本字段 REV; // 回车/修饰键/导航键/功能键/纯 Shift 不改标识(见 SwapCalc.isBondPriceValueKey)。 // 修复:此前守卫漏掉 ↑(38)/↓(40)/Insert(45)/PageUp(33)/PageDown(34)/F键/Shift(16), // 导致"鼠标放进去没改值、只按了方向键/功能键"也会误标 REV。 if (!SwapCalc.isBondPriceValueKey(event)) return; + // 调试埋点(otcDebug 仅在 ?otcdebug=1 / localStorage.otcdebug=1 时输出,平时零噪音): + // 抓"逐键前后 driver/rev 变化",用于定位 REV 标记异常。 + otcDebug.log('[onBondPriceKeydown] type=' + type + ' kc=' + kc + + ' before: driver=' + item.bondDriverType + ' rev=' + JSON.stringify(item.bondRev)); SwapCalc.applyBondManualEdit(item, type); this.syncBondFlags(item); + otcDebug.log('[onBondPriceKeydown] after: driver=' + item.bondDriverType + ' rev=' + JSON.stringify(item.bondRev)); }, //确保 bondDriverType/bondAuto/bondRev 的变更触发 Vue 响应式更新。 //swapCalc.js 的纯函数直接对 state 赋值,但这些属性不在后端数据中(非响应式), @@ -433,6 +440,9 @@ const vue = new Vue({ // 该字段在页面上本就是可选择的日期控件(vue-datepicker),用户可改;现已默认即有。 // 不再"悄悄回退到交易日(TradeDate)"——真的为空时直接报错提示,由用户填写/手动填三数(需求2)。 var targetDate = (this.trade && this.trade.StartDate) ? this.trade.StartDate : null; + // 调试埋点(otcDebug 仅在 ?otcdebug=1 时输出):抓"回车→调计算器→源/结果"全链路,定位三字段互算异常。 + otcDebug.log('[calcBondForItem] enter driver=' + driver + ' priceType=' + priceType + + ' price(sent)=' + SwapCalc.bondPriceToCalc(price) + ' targetDate=' + targetDate); var sdMsg = SwapCalc.getBondStartDateMissingMsg(targetDate); if (sdMsg) { // D3 同款去重:开始日缺失时用户在价格格逐键输入会连刷相同提示,故同一文案只弹一次 @@ -466,6 +476,7 @@ const vue = new Vue({ if (SwapCalc.shouldShowBondErr(item, err)) main.message(err); SwapCalc.applyBondCalcFailure(item, driver); // 交互约定2:失败→清另两字段+标识 self.syncBondFlags(item); + otcDebug.log('[calcBondForItem] calc-error driver=' + driver + ' msg=' + err); return; } item._lastBondErr = null; // 成功则清标记,便于下次真出不同错误时仍能提示 @@ -487,6 +498,7 @@ const vue = new Vue({ item.InitYtm = SwapCalc.bondCalcPriceToStorage(proxy.ytm); SwapCalc.applyBondCalcSuccess(item, driver); // 交互约定2:成功→标 源/AUTO self.syncBondFlags(item); + otcDebug.log('[calcBondForItem] success driver=' + driver + ' auto=' + JSON.stringify(item.bondAuto) + ' rev=' + JSON.stringify(item.bondRev)); // 名义本金依赖全价(PosiGrossPrice):以净价/收益率为源反算出的全价被回写后, // 直接赋值不会触发组件 input 事件,需在此显式重算,保持名义本金与全价一致。 if (self.calcNotional) self.calcNotional(); @@ -501,6 +513,7 @@ const vue = new Vue({ // (与 .done 内 !resp.obj 分支行为一致;左侧 .done 的 getBondCalcErrorMessage 分支为兼容冗余,主路径在此)。 SwapCalc.applyBondCalcFailure(item, driver); self.syncBondFlags(item); + otcDebug.log('[calcBondForItem] post-fail/reject driver=' + driver); }); }, //变更收费基本单位