From 40b4e2e69791ebbe9f299365f7f623906deb1ca2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Thu, 6 Aug 2026 16:30:59 +0800 Subject: [PATCH] =?UTF-8?q?feat(input):=20=E6=95=B0=E5=AD=97=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E6=95=B4=E6=95=B0=E4=BD=8D=E6=95=B0=E9=99=90=E5=88=B6?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 fastVue.base.js 中实现整数位数配置选项 integerDigits - 添加 checkIntegerDigitsInput 函数验证整数位数输入限制 - 实现 limitIntegerDigits 函数控制数值字符串中的整数位数 - 在键盘处理和粘贴事件中集成整数位数限制逻辑 - 更新掉期交易价格精度配置支持 quantityIntegerDigits 选项 - 扩展精度辅助函数支持整数位数配置和验证规则 - 添加完整的单元测试覆盖整数位数限制功能 --- .../App_Data/Config/swappriceprecision.js | 55 ++--- YLErpWeb/fe-tests/swapPrecisionConfig.test.js | 202 ++++++++++++++++++ .../app/swaptrade/swapPricePrecisionHelper.js | 81 ++++--- YLErpWeb/wwwroot/Scripts/fast/fastVue.base.js | 34 +++ YLErpWeb/wwwroot/Statics/bundles/bundle.js | 34 +++ 5 files changed, 349 insertions(+), 57 deletions(-) create mode 100644 YLErpWeb/fe-tests/swapPrecisionConfig.test.js diff --git a/YLErpWeb/App_Data/Config/swappriceprecision.js b/YLErpWeb/App_Data/Config/swappriceprecision.js index a0d631eb..70ddad58 100644 --- a/YLErpWeb/App_Data/Config/swappriceprecision.js +++ b/YLErpWeb/App_Data/Config/swappriceprecision.js @@ -3,53 +3,54 @@ window.main = window.main || {}; window.main.swapPricePrecision = { common: { amount: { precision: 2, grouping: true }, - quantity: { precision: 2, grouping: true }, + quantity: { integerDigits: 16, precision: 2, grouping: true }, rate: { precision: 4 } }, - Stock: { integerDigits: 7, precision: 2, quantityPrecision: 2 }, - StockIndex: { integerDigits: 7, precision: 2, quantityPrecision: 2 }, - StockIF: { integerDigits: 7, precision: 4, quantityPrecision: 2 }, - CommodityFutures: { integerDigits: 7, precision: 4, quantityPrecision: 2 }, - CommoditySpot: { integerDigits: 7, precision: 4, quantityPrecision: 2 }, - NewOtcStock: { integerDigits: 7, precision: 4, quantityPrecision: 2 }, - HKStock: { integerDigits: 7, precision: 4, quantityPrecision: 2 }, - HKStockIndex: { integerDigits: 7, precision: 4, quantityPrecision: 2 }, - Fund: { integerDigits: 7, precision: 4, quantityPrecision: 4 }, + Stock: { integerDigits: 7, precision: 2, quantityPrecision: 2, quantityIntegerDigits: 12 }, + StockIndex: { integerDigits: 7, precision: 2, quantityPrecision: 2, quantityIntegerDigits: 12 }, + StockIF: { integerDigits: 7, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + CommodityFutures: { integerDigits: 7, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + CommoditySpot: { integerDigits: 7, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + NewOtcStock: { integerDigits: 7, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + HKStock: { integerDigits: 7, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + HKStockIndex: { integerDigits: 7, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + Fund: { integerDigits: 7, precision: 4, quantityPrecision: 4, quantityIntegerDigits: 12 }, Bond: { - quantityPrecision: 0, + quantityPrecision: 0, quantityIntegerDigits: 16, grossPrice: { integerDigits: 6, precision: 9 }, netPrice: { integerDigits: 6, precision: 9 }, yield: { integerDigits: 2, precision: 4 } }, TBonds: { - quantityPrecision: 0, + quantityPrecision: 0, quantityIntegerDigits: 16, grossPrice: { integerDigits: 6, precision: 9 }, netPrice: { integerDigits: 6, precision: 9 }, yield: { integerDigits: 2, precision: 4 } }, CreditBonds: { - quantityPrecision: 0, + quantityPrecision: 0, quantityIntegerDigits: 16, grossPrice: { integerDigits: 6, precision: 9 }, netPrice: { integerDigits: 6, precision: 9 }, yield: { integerDigits: 2, precision: 4 } }, OtherBonds: { - quantityPrecision: 0, + quantityPrecision: 0, quantityIntegerDigits: 16, grossPrice: { integerDigits: 6, precision: 9 }, netPrice: { integerDigits: 6, precision: 9 }, yield: { integerDigits: 2, precision: 4 } }, - TBFutures: { integerDigits: 8, precision: 4, quantityPrecision: 2 }, - OtherFutures: { integerDigits: 8, precision: 4, quantityPrecision: 2 }, - GoldSpot: { integerDigits: 8, precision: 4, quantityPrecision: 2 }, - OtherSpot: { integerDigits: 8, precision: 4, quantityPrecision: 2 }, - AbroadFutures: { integerDigits: 8, precision: 4, quantityPrecision: 2 }, - AbroadSpot: { integerDigits: 8, precision: 4, quantityPrecision: 2 }, - AbroadStock: { integerDigits: 8, precision: 2, quantityPrecision: 2 }, - AbroadStockIndex: { integerDigits: 8, precision: 4, quantityPrecision: 2 }, - ExRate: { integerDigits: 2, precision: 8, quantityPrecision: 8 }, - Shibor: { integerDigits: 2, precision: 4, quantityPrecision: 2 }, - FixingRepoRate: { integerDigits: 2, precision: 4, quantityPrecision: 2 }, - RateYield: {integerDigits: 6, precision: 8, quantityPrecision: 2}, - BondIndex: {integerDigits: 6, precision: 4, quantityPrecision: 2}, + TBFutures: { integerDigits: 8, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + OtherFutures: { integerDigits: 8, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + GoldFutures: { quantityIntegerDigits: 12 }, + GoldSpot: { integerDigits: 8, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + OtherSpot: { integerDigits: 8, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + AbroadFutures: { integerDigits: 8, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + AbroadSpot: { integerDigits: 8, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + AbroadStock: { integerDigits: 8, precision: 2, quantityPrecision: 2, quantityIntegerDigits: 12 }, + AbroadStockIndex: { integerDigits: 8, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + ExRate: { integerDigits: 2, precision: 8, quantityPrecision: 8, quantityIntegerDigits: 16 }, + Shibor: { integerDigits: 2, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + FixingRepoRate: { integerDigits: 2, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + RateYield: {integerDigits: 6, precision: 8, quantityPrecision: 2, quantityIntegerDigits: 12}, + BondIndex: {integerDigits: 6, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12}, }; diff --git a/YLErpWeb/fe-tests/swapPrecisionConfig.test.js b/YLErpWeb/fe-tests/swapPrecisionConfig.test.js new file mode 100644 index 00000000..27ee7b35 --- /dev/null +++ b/YLErpWeb/fe-tests/swapPrecisionConfig.test.js @@ -0,0 +1,202 @@ +const fs = require('fs'); +const path = require('path'); +const vm = require('vm'); + +const ROOT = path.resolve(__dirname, '..'); +const read = (relativePath) => fs.readFileSync(path.join(ROOT, relativePath), 'utf8'); + +const precisionConfigSrc = read('App_Data/Config/swappriceprecision.js'); +const precisionHelperSrc = read('wwwroot/Scripts/app/swaptrade/swapPricePrecisionHelper.js'); + +function loadConfiguredPrecision() { + const context = { window: {} }; + vm.runInNewContext(precisionConfigSrc, context); + return context.window.main.swapPricePrecision; +} + +function loadHelper(config) { + const context = { window: { main: { swapPricePrecision: config } } }; + vm.runInNewContext(precisionHelperSrc, context); + return context.swapPricePrecision; +} + +function activeViewSource(source) { + return source.replace(/@\*[\s\S]*?\*@/g, ''); +} + +describe('swap price precision common wiring', () => { + test('configured and fallback common precision expose 2/2/4', () => { + const configured = loadConfiguredPrecision(); + expect(configured.common).toEqual({ + amount: { precision: 2, grouping: true }, + quantity: { integerDigits: 16, precision: 2, grouping: true }, + rate: { precision: 4 } + }); + + const helper = loadHelper(configured); + expect(helper.getCommonPrecision('amount')).toBe(2); + expect(helper.getCommonPrecision('quantity')).toBe(2); + expect(helper.getCommonPrecision('rate')).toBe(4); + + const expectedQuantityPrecisions = { + Stock: 2, + StockIndex: 2, + StockIF: 2, + CommodityFutures: 2, + CommoditySpot: 2, + NewOtcStock: 2, + HKStock: 2, + HKStockIndex: 2, + Fund: 4, + Bond: 0, + TBonds: 0, + CreditBonds: 0, + OtherBonds: 0, + TBFutures: 2, + OtherFutures: 2, + GoldSpot: 2, + OtherSpot: 2, + AbroadFutures: 2, + AbroadSpot: 2, + AbroadStock: 2, + AbroadStockIndex: 2, + ExRate: 8, + Shibor: 2, + FixingRepoRate: 2, + RateYield: 2, + BondIndex: 2 + }; + Object.entries(expectedQuantityPrecisions).forEach(([instrumentType, precision]) => { + expect(configured[instrumentType].quantityPrecision).toBe(precision); + expect(helper.getCommonPrecision('quantity', instrumentType)).toBe(precision); + expect(helper.getCommonInputFormat('quantity', { append: '' }, instrumentType).precision).toBe(precision); + }); + const expectedQuantityIntegerDigits = { + Stock: 12, StockIndex: 12, StockIF: 12, CommodityFutures: 12, CommoditySpot: 12, + NewOtcStock: 12, HKStock: 12, HKStockIndex: 12, Fund: 12, TBFutures: 12, + OtherFutures: 12, GoldFutures: 12, GoldSpot: 12, OtherSpot: 12, AbroadFutures: 12, + AbroadSpot: 12, AbroadStock: 12, AbroadStockIndex: 12, Shibor: 12, + FixingRepoRate: 12, RateYield: 12, BondIndex: 12, + Bond: 16, TBonds: 16, CreditBonds: 16, OtherBonds: 16, ExRate: 16 + }; + Object.entries(expectedQuantityIntegerDigits).forEach(([instrumentType, integerDigits]) => { + expect(configured[instrumentType].quantityIntegerDigits).toBe(integerDigits); + expect(helper.getCommonInputFormat('quantity', { append: '' }, instrumentType).integerDigits).toBe(integerDigits); + }); + expect(helper.getCommonPrecision('quantity', 'OtherRate')).toBe(2); + expect(helper.getCommonInputFormat('quantity', { append: '' }, 'Fund').precision).toBe(4); + expect(helper.getCommonInputFormat('quantity', { append: '' }, 'Bond').precision).toBe(0); + expect(helper.getCommonInputFormat('quantity', { append: '' }, 'OtherRate').integerDigits).toBe(16); + + const input = helper.getCommonInputFormat('amount', { append: '', trimTailZeros: true }); + expect(input).toEqual(expect.objectContaining({ precision: 2, grouping: true, trimTailZeros: false })); + expect(helper.getCommonInputFormat('rate', { append: '%' })).toEqual( + expect.objectContaining({ precision: 4, grouping: false })); + }); + + test('invalid common precision falls back and formatted values keep trailing zeros', () => { + const helper = loadHelper({ + common: { + amount: { precision: -1 }, + quantity: { precision: 14 }, + rate: { precision: 'invalid' } + } + }); + expect(helper.getCommonPrecision('amount')).toBe(2); + expect(helper.getCommonPrecision('quantity')).toBe(2); + expect(helper.getCommonPrecision('rate')).toBe(4); + + const configured = loadHelper({ + common: { + amount: { precision: 3 }, + quantity: { precision: 1 }, + rate: { precision: 2 } + } + }); + expect(configured.formatCommon('amount', '12.5')).toBe('12.500'); + expect(configured.formatCommon('quantity', 1)).toBe('1.0'); + expect(configured.formatCommon('rate', '0.0123')).toBe('1.23%'); + expect(configured.formatCommon('amount', '1234.5')).toBe('1,234.500'); + expect(configured.normalizeCommon('amount', '1234.5')).toBe('1234.500'); + expect(configured.formatCommon('amount', NaN)).toBe(''); + expect(configured.formatCommon('amount', Infinity)).toBe(''); + }); + + test('common grouping can be disabled independently from precision', () => { + const helper = loadHelper({ + common: { + amount: { precision: 2, grouping: false }, + quantity: { precision: 2, grouping: false }, + rate: { precision: 4, grouping: false } + } + }); + expect(helper.formatCommon('amount', '1234.5')).toBe('1234.50'); + expect(helper.getCommonInputFormat('quantity', { append: '' }).grouping).toBe(false); + }); + + test('missing or invalid asset quantity precision falls back to common quantity precision', () => { + const helper = loadHelper({ + common: { quantity: { precision: 3, grouping: true } }, + Stock: { quantityPrecision: 'invalid' }, + Bond: {} + }); + expect(helper.getCommonPrecision('quantity', 'Stock')).toBe(3); + expect(helper.getCommonPrecision('quantity', 'Bond')).toBe(3); + expect(helper.getCommonPrecision('quantity', 'OtherRate')).toBe(3); + expect(helper.getCommonInputFormat('quantity', { append: '' }, 'Stock').integerDigits).toBe(16); + expect(loadHelper({ + common: { quantity: { integerDigits: 0, precision: 3 } }, + Stock: { quantityIntegerDigits: 'invalid' } + }).getCommonInputFormat('quantity', { append: '' }, 'Stock').integerDigits).toBe(16); + }); + + test('SwapTrade2 common fields use the price helper without changing price responsibilities', () => { + const scripts = { + edit: read('wwwroot/Scripts/app/swaptrade/swapTradeEdit.js'), + income: read('wwwroot/Scripts/app/swaptrade/incomeSwapTrade.js'), + unwind: read('wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js'), + longShort: read('wwwroot/Scripts/app/swaptrade/swapLongShort.js'), + unwindLongShort: read('wwwroot/Scripts/app/swaptrade/unwindLongShort.js'), + view: read('wwwroot/Scripts/app/swaptrade/swapTradeView.js'), + flow: read('wwwroot/Scripts/app/swaptrade/SwapflowList.js'), + helper: precisionHelperSrc + }; + const view = read('Views/SwapTrade2/TradeView.cshtml'); + const activeView = activeViewSource(view); + + for (const script of Object.values(scripts)) { + expect(script).not.toMatch(/otcformat\.trading\.swap(?:Amount|Quantity|RateP)/); + } + expect(scripts.edit).toMatch(/getCommonInputFormat\('amount'/); + expect(scripts.edit).toMatch(/getCommonInputFormat\(\s*['"]quantity['"]/); + expect(scripts.edit).toMatch(/getCommonInputFormat\('rate'/); + expect(scripts.view).toContain("swapPricePrecision.formatCommon('rate', item.Rate)"); + expect(scripts.flow).toMatch(/name:\s*'ytm'[\s\S]*formatter:\s*otcformat\.trading\.marginRateP/); + expect(scripts.flow).not.toMatch(/name:\s*'ytm'[\s\S]*formatCommon\('rate'/); + + expect(scripts.unwind).toContain('ClosePercent = otcformat.fixed6'); + expect(scripts.unwind).toContain('_.round(tradingFee, 2)'); + expect(scripts.unwind).toContain('.toFixed(2)'); + expect(scripts.edit).toContain('observationRatePrecision = 12'); + expect(scripts.edit).toMatch(/roundObservationRate[\s\S]*swapPricePrecision\.roundDecimal/); + expect(scripts.helper).toContain('getRule: getRule'); + expect(scripts.helper).toContain('getInputFormat: function'); + expect(scripts.helper).toContain('roundForSubmit: function'); + expect(scripts.helper).toContain('normalizeCommon: function'); + + expect(activeView).not.toContain('OtcFormatHelper'); + expect(activeView).not.toMatch(/OtcFormatMoney|OtcFormatNotional/); + expect(activeView).toContain('SwapCommonData'); + expect(activeView).toMatch(/js-swap-common[\s\S]*data-kind="amount"/); + expect(activeView).toMatch(/js-swap-common[\s\S]*data-kind="quantity"/); + expect(activeView).toMatch(/js-swap-common[\s\S]*data-kind="rate"/); + expect(scripts.view).toContain('formatSwapCommonElements'); + expect(scripts.edit).toContain('getQuantityInputFormat'); + expect(scripts.unwind).toContain('getQuantityInputFormat'); + expect(scripts.flow).toContain('getQuantityInputFormat'); + expect(scripts.flow).toMatch(/formatSwapQuantity = function \(value, options, rowObject\)/); + expect(scripts.flow).toMatch(/row\.UnderlyingInstrumentType[\s\S]*row\.InstrumentType[\s\S]*row\.position[\s\S]*UnderlyingInstrumentType/); + expect(scripts.flow).toContain("formatCommon('quantity', value, instrumentType)"); + expect(activeView).toMatch(/data-kind="quantity"[^>]*data-instrument-type=/); + }); +}); diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapPricePrecisionHelper.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapPricePrecisionHelper.js index ebd08215..5f4d5a4c 100644 --- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapPricePrecisionHelper.js +++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapPricePrecisionHelper.js @@ -2,55 +2,56 @@ var swapPricePrecision = (function (global) { const defaults = Object.freeze({ common: { amount: { precision: 2, grouping: true }, - quantity: { precision: 2, grouping: true }, + quantity: { integerDigits: 16, precision: 2, grouping: true }, rate: { precision: 4 } }, - Stock: { integerDigits: 7, precision: 2, quantityPrecision: 2 }, - StockIndex: { integerDigits: 7, precision: 2, quantityPrecision: 2 }, - StockIF: { integerDigits: 7, precision: 4, quantityPrecision: 2 }, - CommodityFutures: { integerDigits: 7, precision: 4, quantityPrecision: 2 }, - CommoditySpot: { integerDigits: 7, precision: 4, quantityPrecision: 2 }, - NewOtcStock: { integerDigits: 7, precision: 4, quantityPrecision: 2 }, - HKStock: { integerDigits: 7, precision: 4, quantityPrecision: 2 }, - HKStockIndex: { integerDigits: 7, precision: 4, quantityPrecision: 2 }, - Fund: { integerDigits: 7, precision: 4, quantityPrecision: 4 }, + Stock: { integerDigits: 7, precision: 2, quantityPrecision: 2, quantityIntegerDigits: 12 }, + StockIndex: { integerDigits: 7, precision: 2, quantityPrecision: 2, quantityIntegerDigits: 12 }, + StockIF: { integerDigits: 7, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + CommodityFutures: { integerDigits: 7, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + CommoditySpot: { integerDigits: 7, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + NewOtcStock: { integerDigits: 7, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + HKStock: { integerDigits: 7, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + HKStockIndex: { integerDigits: 7, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + Fund: { integerDigits: 7, precision: 4, quantityPrecision: 4, quantityIntegerDigits: 12 }, Bond: { - quantityPrecision: 0, + quantityPrecision: 0, quantityIntegerDigits: 16, grossPrice: { integerDigits: 6, precision: 9 }, netPrice: { integerDigits: 6, precision: 9 }, yield: { integerDigits: 2, precision: 4 } }, TBonds: { - quantityPrecision: 0, + quantityPrecision: 0, quantityIntegerDigits: 16, grossPrice: { integerDigits: 6, precision: 9 }, netPrice: { integerDigits: 6, precision: 9 }, yield: { integerDigits: 2, precision: 4 } }, CreditBonds: { - quantityPrecision: 0, + quantityPrecision: 0, quantityIntegerDigits: 16, grossPrice: { integerDigits: 6, precision: 9 }, netPrice: { integerDigits: 6, precision: 9 }, yield: { integerDigits: 2, precision: 4 } }, OtherBonds: { - quantityPrecision: 0, + quantityPrecision: 0, quantityIntegerDigits: 16, grossPrice: { integerDigits: 6, precision: 9 }, netPrice: { integerDigits: 6, precision: 9 }, yield: { integerDigits: 2, precision: 4 } }, - TBFutures: { integerDigits: 8, precision: 4, quantityPrecision: 2 }, - OtherFutures: { integerDigits: 8, precision: 4, quantityPrecision: 2 }, - GoldSpot: { integerDigits: 8, precision: 4, quantityPrecision: 2 }, - OtherSpot: { integerDigits: 8, precision: 4, quantityPrecision: 2 }, - AbroadFutures: { integerDigits: 8, precision: 4, quantityPrecision: 2 }, - AbroadSpot: { integerDigits: 8, precision: 4, quantityPrecision: 2 }, - AbroadStock: { integerDigits: 8, precision: 2, quantityPrecision: 2 }, - AbroadStockIndex: { integerDigits: 8, precision: 4, quantityPrecision: 2 }, - ExRate: { integerDigits: 2, precision: 8, quantityPrecision: 8 }, - Shibor: { integerDigits: 2, precision: 4, quantityPrecision: 2 }, - FixingRepoRate: { integerDigits: 2, precision: 4, quantityPrecision: 2 }, - RateYield: {integerDigits: 6, precision: 8, quantityPrecision: 2}, - BondIndex: {integerDigits: 6, precision: 4, quantityPrecision: 2}, + TBFutures: { integerDigits: 8, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + OtherFutures: { integerDigits: 8, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + GoldFutures: { quantityIntegerDigits: 12 }, + GoldSpot: { integerDigits: 8, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + OtherSpot: { integerDigits: 8, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + AbroadFutures: { integerDigits: 8, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + AbroadSpot: { integerDigits: 8, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + AbroadStock: { integerDigits: 8, precision: 2, quantityPrecision: 2, quantityIntegerDigits: 12 }, + AbroadStockIndex: { integerDigits: 8, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + ExRate: { integerDigits: 2, precision: 8, quantityPrecision: 8, quantityIntegerDigits: 16 }, + Shibor: { integerDigits: 2, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + FixingRepoRate: { integerDigits: 2, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12 }, + RateYield: {integerDigits: 6, precision: 8, quantityPrecision: 2, quantityIntegerDigits: 12}, + BondIndex: {integerDigits: 6, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 12}, }); @@ -134,7 +135,12 @@ var swapPricePrecision = (function (global) { if (!rule || typeof rule !== 'object') return null; const precision = Number(rule.precision); if (!Number.isInteger(precision) || precision < 0 || precision > 13) return null; - return { precision: precision, grouping: typeof rule.grouping === 'boolean' ? rule.grouping : undefined }; + const integerDigits = Number(rule.integerDigits); + return { + precision: precision, + integerDigits: Number.isInteger(integerDigits) && integerDigits >= 1 && integerDigits <= 18 ? integerDigits : undefined, + grouping: typeof rule.grouping === 'boolean' ? rule.grouping : undefined + }; } function findRule(source, instrumentType, field) { @@ -176,6 +182,15 @@ var swapPricePrecision = (function (global) { return Number.isInteger(precision) && precision >= 0 && precision <= 13 ? precision : fallback; } + function getQuantityIntegerDigits(instrumentType, fallback) { + if (!instrumentType) return fallback; + const configured = global.main && global.main.swapPricePrecision; + const typeRule = (configured || defaults)[instrumentType]; + if (!typeRule || !Object.prototype.hasOwnProperty.call(typeRule, 'quantityIntegerDigits')) return fallback; + const integerDigits = Number(typeRule.quantityIntegerDigits); + return Number.isInteger(integerDigits) && integerDigits >= 1 && integerDigits <= 18 ? integerDigits : fallback; + } + function getCommonRule(kind, instrumentType) { const fallback = normalizeCommonRule(defaults.common[kind]) || { precision: 2, grouping: false }; const configured = global.main && global.main.swapPricePrecision @@ -183,7 +198,11 @@ var swapPricePrecision = (function (global) { const configuredRule = normalizeCommonRule(configured && configured[kind]); const rule = configuredRule || fallback; if (rule.grouping === undefined) rule.grouping = fallback.grouping; - if (kind === 'quantity') rule.precision = getQuantityPrecision(instrumentType, rule.precision); + if (rule.integerDigits === undefined) rule.integerDigits = fallback.integerDigits; + if (kind === 'quantity') { + rule.precision = getQuantityPrecision(instrumentType, rule.precision); + rule.integerDigits = getQuantityIntegerDigits(instrumentType, rule.integerDigits); + } return rule; } @@ -338,11 +357,13 @@ var swapPricePrecision = (function (global) { const type = instrumentType || inputOptions.instrumentType; delete inputOptions.instrumentType; const rule = getCommonRule(kind, type); - return Object.assign(inputOptions, { + const result = Object.assign(inputOptions, { precision: rule.precision, grouping: inputOptions.grouping === undefined ? !!rule.grouping : !!inputOptions.grouping, trimTailZeros: false }); + if (rule.integerDigits !== undefined) result.integerDigits = rule.integerDigits; + return result; }, formatCommon: formatCommon, normalizeCommon: function (kind, value, instrumentType) { diff --git a/YLErpWeb/wwwroot/Scripts/fast/fastVue.base.js b/YLErpWeb/wwwroot/Scripts/fast/fastVue.base.js index b804e5a1..1b9dcc49 100644 --- a/YLErpWeb/wwwroot/Scripts/fast/fastVue.base.js +++ b/YLErpWeb/wwwroot/Scripts/fast/fastVue.base.js @@ -297,6 +297,8 @@ !options.append && (options.append = ''); let precision = parseInt(options.precision) || 0; options.precision = precision < 0 ? 0 : precision; + let integerDigits = parseInt(options.integerDigits) || 0; + options.integerDigits = integerDigits > 0 ? integerDigits : 0; options.negative = !!options.negative; _options = options; setValue(getValue()); @@ -336,6 +338,35 @@ return this.value.substring(0, this.selectionStart).indexOf('.') < 0 && this.value.substring(this.selectionEnd, this.value.length).indexOf('.') < 0; } + function checkIntegerDigitsInput() { + if (!_options.integerDigits) return true; + let valueEnd = this.value.length; + if (_options.append && this.value.endsWith(_options.append)) valueEnd -= _options.append.length; + const dotIndex = this.value.indexOf('.'); + const integerEnd = dotIndex < 0 ? valueEnd : dotIndex; + if (this.selectionStart > integerEnd) return true; + const selectionEnd = Math.min(this.selectionEnd, integerEnd); + const integerText = this.value.substring(0, this.selectionStart) + this.value.substring(selectionEnd, integerEnd); + return integerText.replace(/\D/g, '').length < _options.integerDigits; + } + + function limitIntegerDigits(value) { + if (!_options.integerDigits) return value; + let digits = 0; + let hasDot = false; + let result = ''; + for (let index = 0; index < value.length; index++) { + const ch = value.charAt(index); + if (ch === '.' || ch === '。') hasDot = true; + if (ch >= '0' && ch <= '9' && !hasDot) { + if (digits >= _options.integerDigits) continue; + digits++; + } + result += ch; + } + return result; + } + function __keyHandle(event) { if (!event) return false; @@ -385,6 +416,7 @@ if (charCode < 48 || charCode > 57 && (charCode < 96 || charCode > 105)) { return false; } + if (!checkIntegerDigitsInput.call(this)) return false; if (this.selectionStart === 0) { return !this.value || this.value.charAt(this.selectionEnd) !== '-'; } @@ -445,6 +477,7 @@ // 粘贴进来的是显示值(可能带 %/‱ 后缀),先按 __change 同款口径解析为模型值再回显, // 避免 setValue 把显示值再乘以 100/10000(#EQD-5914 债券价格粘贴 ×100) let f = ''; + this.value = limitIntegerDigits(this.value); if (this.value && this.value !== _options.append) { f = parseFloat(this.value.replaceAll(",", "")) || 0; if (_options.append === '%' || _options.percent == true) f /= 100; @@ -455,6 +488,7 @@ if (_chnInput >= 0) { __onChineseInput.call(this, _chnInput); } + this.value = limitIntegerDigits(this.value); if (!_options.append || !this.value) return; let appended = true; if (this.value !== _options.append) { diff --git a/YLErpWeb/wwwroot/Statics/bundles/bundle.js b/YLErpWeb/wwwroot/Statics/bundles/bundle.js index d3baa7d7..2fd4e72b 100644 --- a/YLErpWeb/wwwroot/Statics/bundles/bundle.js +++ b/YLErpWeb/wwwroot/Statics/bundles/bundle.js @@ -14354,6 +14354,8 @@ $.fn.selectpicker.Constructor.DEFAULTS = Object.assign($.fn.selectpicker.Constru !options.append && (options.append = ''); let precision = parseInt(options.precision) || 0; options.precision = precision < 0 ? 0 : precision; + let integerDigits = parseInt(options.integerDigits) || 0; + options.integerDigits = integerDigits > 0 ? integerDigits : 0; options.negative = !!options.negative; _options = options; setValue(getValue()); @@ -14393,6 +14395,35 @@ $.fn.selectpicker.Constructor.DEFAULTS = Object.assign($.fn.selectpicker.Constru return this.value.substring(0, this.selectionStart).indexOf('.') < 0 && this.value.substring(this.selectionEnd, this.value.length).indexOf('.') < 0; } + function checkIntegerDigitsInput() { + if (!_options.integerDigits) return true; + let valueEnd = this.value.length; + if (_options.append && this.value.endsWith(_options.append)) valueEnd -= _options.append.length; + const dotIndex = this.value.indexOf('.'); + const integerEnd = dotIndex < 0 ? valueEnd : dotIndex; + if (this.selectionStart > integerEnd) return true; + const selectionEnd = Math.min(this.selectionEnd, integerEnd); + const integerText = this.value.substring(0, this.selectionStart) + this.value.substring(selectionEnd, integerEnd); + return integerText.replace(/\D/g, '').length < _options.integerDigits; + } + + function limitIntegerDigits(value) { + if (!_options.integerDigits) return value; + let digits = 0; + let hasDot = false; + let result = ''; + for (let index = 0; index < value.length; index++) { + const ch = value.charAt(index); + if (ch === '.' || ch === '。') hasDot = true; + if (ch >= '0' && ch <= '9' && !hasDot) { + if (digits >= _options.integerDigits) continue; + digits++; + } + result += ch; + } + return result; + } + function __keyHandle(event) { if (!event) return false; @@ -14442,6 +14473,7 @@ $.fn.selectpicker.Constructor.DEFAULTS = Object.assign($.fn.selectpicker.Constru if (charCode < 48 || charCode > 57 && (charCode < 96 || charCode > 105)) { return false; } + if (!checkIntegerDigitsInput.call(this)) return false; if (this.selectionStart === 0) { return !this.value || this.value.charAt(this.selectionEnd) !== '-'; } @@ -14502,6 +14534,7 @@ $.fn.selectpicker.Constructor.DEFAULTS = Object.assign($.fn.selectpicker.Constru // 粘贴进来的是显示值(可能带 %/‱ 后缀),先按 __change 同款口径解析为模型值再回显, // 避免 setValue 把显示值再乘以 100/10000(#EQD-5914 债券价格粘贴 ×100) let f = ''; + this.value = limitIntegerDigits(this.value); if (this.value && this.value !== _options.append) { f = parseFloat(this.value.replaceAll(",", "")) || 0; if (_options.append === '%' || _options.percent == true) f /= 100; @@ -14512,6 +14545,7 @@ $.fn.selectpicker.Constructor.DEFAULTS = Object.assign($.fn.selectpicker.Constru if (_chnInput >= 0) { __onChineseInput.call(this, _chnInput); } + this.value = limitIntegerDigits(this.value); if (!_options.append || !this.value) return; let appended = true; if (this.value !== _options.append) {