var swapPricePrecision = (function (global) { const defaults = Object.freeze({ common: { amount: { precision: 2, grouping: true }, quantity: { integerDigits: 8, precision: 2, grouping: true }, rate: { precision: 4 } }, Stock: { integerDigits: 7, precision: 2, quantityPrecision: 2, quantityIntegerDigits: 8 }, StockIndex: { integerDigits: 7, precision: 2, quantityPrecision: 2, quantityIntegerDigits: 8 }, StockIF: { integerDigits: 7, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 8 }, CommodityFutures: { integerDigits: 7, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 8 }, CommoditySpot: { integerDigits: 7, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 8 }, NewOtcStock: { integerDigits: 7, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 8 }, HKStock: { integerDigits: 7, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 8 }, HKStockIndex: { integerDigits: 7, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 8 }, Fund: { integerDigits: 7, precision: 4, quantityPrecision: 4, quantityIntegerDigits: 8 }, Bond: { quantityPrecision: 0, quantityIntegerDigits: 12, grossPrice: { integerDigits: 6, precision: 9 }, netPrice: { integerDigits: 6, precision: 9 }, yield: { integerDigits: 2, precision: 4 } }, TBonds: { quantityPrecision: 0, quantityIntegerDigits: 12, grossPrice: { integerDigits: 6, precision: 9 }, netPrice: { integerDigits: 6, precision: 9 }, yield: { integerDigits: 2, precision: 4 } }, CreditBonds: { quantityPrecision: 0, quantityIntegerDigits: 12, grossPrice: { integerDigits: 6, precision: 9 }, netPrice: { integerDigits: 6, precision: 9 }, yield: { integerDigits: 2, precision: 4 } }, OtherBonds: { quantityPrecision: 0, quantityIntegerDigits: 12, grossPrice: { integerDigits: 6, precision: 9 }, netPrice: { integerDigits: 6, precision: 9 }, yield: { integerDigits: 2, precision: 4 } }, TBFutures: { integerDigits: 8, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 8 }, OtherFutures: { integerDigits: 8, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 8 }, GoldFutures: { integerDigits: 6, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 8 }, GoldSpot: { integerDigits: 8, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 8 }, OtherSpot: { integerDigits: 8, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 8 }, AbroadFutures: { integerDigits: 8, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 8 }, AbroadSpot: { integerDigits: 8, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 8 }, AbroadStock: { integerDigits: 8, precision: 2, quantityPrecision: 2, quantityIntegerDigits: 8 }, AbroadStockIndex: { integerDigits: 8, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 8 }, ExRate: { integerDigits: 2, precision: 8, quantityPrecision: 4, quantityIntegerDigits: 8 }, Shibor: { integerDigits: 2, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 8 }, FixingRepoRate: { integerDigits: 2, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 8 }, RateYield: {integerDigits: 6, precision: 8, quantityPrecision: 2, quantityIntegerDigits: 8}, BondIndex: {integerDigits: 6, precision: 4, quantityPrecision: 2, quantityIntegerDigits: 8}, }); function normalizeDecimal(value) { if (value === null || value === undefined || value === '') return ''; let text = String(value).trim(); if (/[eE]/.test(text)) { const number = Number(text); if (!Number.isFinite(number)) return null; text = number.toFixed(20).replace(/0+$/, '').replace(/\.$/, ''); } if (!/^[+-]?(?:\d+|\d*\.\d+)$/.test(text)) return null; const negative = text.charAt(0) === '-'; text = text.replace(/^[+-]/, ''); const parts = text.split('.'); const integerPart = parts[0].replace(/^0+(?=\d)/, '') || '0'; const decimalPart = parts.length > 1 ? parts[1] : ''; const result = integerPart + (decimalPart ? '.' + decimalPart : ''); return negative && !/^0(?:\.0*)?$/.test(result) ? '-' + result : result; } function shiftDecimal(value, places) { let normalized = normalizeDecimal(value); if (!normalized || !Number.isInteger(places) || places === 0) return normalized; const negative = normalized.charAt(0) === '-'; const parts = (negative ? normalized.substring(1) : normalized).split('.'); const integerPart = parts[0]; const decimalPart = parts.length > 1 ? parts[1] : ''; const digits = integerPart + decimalPart; const decimalIndex = integerPart.length + places; let text; if (decimalIndex <= 0) text = '0.' + '0'.repeat(-decimalIndex) + digits; else if (decimalIndex >= digits.length) text = digits + '0'.repeat(decimalIndex - digits.length); else text = digits.substring(0, decimalIndex) + '.' + digits.substring(decimalIndex); return normalizeDecimal((negative ? '-' : '') + text); } function incrementDigits(value) { let carry = 1; let result = ''; for (let index = value.length - 1; index >= 0; index--) { const digit = value.charCodeAt(index) - 48 + carry; if (digit === 10) { result = '0' + result; carry = 1; } else { result = String(digit) + result; carry = 0; } } return carry ? '1' + result : result; } function roundDecimal(value, precision) { const normalized = normalizeDecimal(value); if (normalized === null || !Number.isInteger(precision) || precision < 0) return value; const negative = normalized.charAt(0) === '-'; const parts = (negative ? normalized.substring(1) : normalized).split('.'); let integerPart = parts[0]; const decimalPart = parts.length > 1 ? parts[1] : ''; if (decimalPart.length <= precision) return normalized; let digits = integerPart + decimalPart.substring(0, precision); if (decimalPart.charAt(precision) >= '5') digits = incrementDigits(digits); if (digits.length <= precision) digits = digits.padStart(precision + 1, '0'); integerPart = precision === 0 ? digits : digits.substring(0, digits.length - precision); const roundedDecimal = precision === 0 ? '' : digits.substring(digits.length - precision); return normalizeDecimal((negative ? '-' : '') + integerPart + (roundedDecimal ? '.' + roundedDecimal : '')); } function multiplyDecimal(left, right) { const normalizedLeft = normalizeDecimal(left); const normalizedRight = normalizeDecimal(right); if (normalizedLeft === null || normalizedRight === null) return null; const leftNegative = normalizedLeft.charAt(0) === '-'; const rightNegative = normalizedRight.charAt(0) === '-'; const leftParts = (leftNegative ? normalizedLeft.substring(1) : normalizedLeft).split('.'); const rightParts = (rightNegative ? normalizedRight.substring(1) : normalizedRight).split('.'); const leftDigits = leftParts.join(''); const rightDigits = rightParts.join(''); const result = Array(leftDigits.length + rightDigits.length).fill(0); for (let leftIndex = leftDigits.length - 1; leftIndex >= 0; leftIndex--) { for (let rightIndex = rightDigits.length - 1; rightIndex >= 0; rightIndex--) { const index = leftIndex + rightIndex + 1; const product = (leftDigits.charCodeAt(leftIndex) - 48) * (rightDigits.charCodeAt(rightIndex) - 48) + result[index]; result[index] = product % 10; result[index - 1] += Math.floor(product / 10); } } const product = result.join('').replace(/^0+/, '') || '0'; const decimalPlaces = (leftParts[1] || '').length + (rightParts[1] || '').length; return shiftDecimal((leftNegative !== rightNegative ? '-' : '') + product, -decimalPlaces); } function normalizeRule(rule) { if (!rule || typeof rule !== 'object') return null; const integerDigits = Number(rule.integerDigits); const precision = Number(rule.precision); if (!Number.isInteger(integerDigits) || integerDigits < 1 || integerDigits > 18 || !Number.isInteger(precision) || precision < 0 || precision > 13) return null; return { integerDigits: integerDigits, precision: precision }; } function normalizeCommonRule(rule) { if (!rule || typeof rule !== 'object') return null; const precision = Number(rule.precision); if (!Number.isInteger(precision) || precision < 0 || precision > 13) return null; 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) { const typeRule = source && source[instrumentType]; return typeRule ? normalizeRule(typeRule[field] || typeRule) : null; } function getRule(instrumentType, field) { const fallback = findRule(defaults, instrumentType, field); if (!fallback) return null; return findRule(global.main && global.main.swapPricePrecision, instrumentType, field) || fallback; } function formatFixed(value, precision) { const normalized = normalizeDecimal(roundDecimal(value, precision)); if (!normalized) return ''; const negative = normalized.charAt(0) === '-'; const parts = (negative ? normalized.substring(1) : normalized).split('.'); const integerPart = parts[0]; if (precision === 0) return (negative ? '-' : '') + integerPart; return (negative ? '-' : '') + integerPart + '.' + (parts[1] || '').padEnd(precision, '0'); } function groupDecimal(value) { if (!value) return value; const negative = value.charAt(0) === '-'; const source = negative ? value.substring(1) : value; const parts = source.split('.'); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ','); return (negative ? '-' : '') + parts.join('.'); } function getQuantityPrecision(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, 'quantityPrecision')) return fallback; const precision = Number(typeRule.quantityPrecision); 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 && global.main.swapPricePrecision.common; const configuredRule = normalizeCommonRule(configured && configured[kind]); const rule = configuredRule || fallback; if (rule.grouping === undefined) rule.grouping = fallback.grouping; if (rule.integerDigits === undefined) rule.integerDigits = fallback.integerDigits; if (kind === 'quantity') { rule.precision = Math.min(8, getQuantityPrecision(instrumentType, rule.precision)); rule.integerDigits = Math.min(16, getQuantityIntegerDigits(instrumentType, rule.integerDigits)); } return rule; } function formatCommon(kind, value, instrumentType, options) { if (value === null || value === undefined || value === '') return ''; if (instrumentType && typeof instrumentType === 'object') { options = instrumentType; instrumentType = options.instrumentType; } const rule = getCommonRule(kind, instrumentType); const displayValue = kind === 'rate' ? shiftDecimal(value, 2) : value; const formatted = formatFixed(displayValue, rule.precision); if (!formatted) return ''; const grouping = options && options.grouping !== undefined ? !!options.grouping : rule.grouping; const trimmed = options && options.trimTailZeros === true ? formatted.replace(/(\.\d*?[1-9])0+$/, '$1').replace(/\.0+$/, '') : formatted; const text = grouping ? groupDecimal(trimmed) : trimmed; return kind === 'rate' ? text + '%' : text; } function format(value, instrumentType, field) { const rule = getRule(instrumentType, field); if (value === null || value === undefined || value === '') return ''; if (!rule) return global.otcformat.trading.umprice(value); const rounded = roundDecimal(value, rule.precision); return rounded === null ? '' : rounded.replace(/(\.\d*?[1-9])0+$/, '$1').replace(/\.0+$/, ''); } function normalizeInput(value, format, shouldRound) { const options = format || {}; const maxIntegerDigits = Number(options.integerDigits) || 0; const precision = Number(options.precision) || 0; const source = String(value === null || value === undefined ? '' : value).trim().replaceAll(',', '').replaceAll('。', '.'); let negative = false; let hasDot = false; let integerPart = ''; let decimalPart = ''; for (let index = 0; index < source.length; index++) { const ch = source.charAt(index); if (ch >= '0' && ch <= '9') { if (hasDot) { if (shouldRound || decimalPart.length < precision) decimalPart += ch; } else if (!maxIntegerDigits || integerPart.length < maxIntegerDigits) { integerPart += ch; } } else if (ch === '.' && !hasDot && precision > 0) { hasDot = true; } else if (ch === '-' && index === 0 && options.negative) { negative = true; } } if (!integerPart && !decimalPart) return negative ? '-' : ''; const text = (negative ? '-' : '') + (integerPart || '0') + (hasDot ? '.' + decimalPart : ''); if (!shouldRound || text.endsWith('.')) return text; return roundDecimal(text, precision); } function createVueInputComponent() { return { props: { value: { type: [Number, String], default: '' }, format: { type: Object, default: function () { return {}; } }, disabled: { type: Boolean } }, data: function () { return { text: '', enterPressed: false, formatSnapshot: '' }; }, mounted: function () { this.text = this.toDisplay(this.value); this.formatSnapshot = JSON.stringify(this.format || {}); }, methods: { toDisplay: function (value) { if (value === null || value === undefined || value === '') return ''; const displayValue = this.format && this.format.percent ? shiftDecimal(value, 2) : String(value); return normalizeInput(displayValue, this.format, true); }, toModel: function (value) { if (!value || value === '-') return ''; return this.format && this.format.percent ? shiftDecimal(value, -2) : value; }, updateValue: function (value, shouldRound, shouldCommit) { this.text = normalizeInput(value, this.format, shouldRound); if (this.text.endsWith('.') && !shouldRound) return; if (shouldCommit) this.$emit('input', this.toModel(this.text)); }, onInput: function (event) { this.updateValue(event.target.value, false, false); // When the normalized value is unchanged, Vue skips the DOM patch. // Write it directly so excess digits do not remain in the native input. event.target.value = this.text; }, onPaste: function (event) { const clipboard = event.clipboardData || global.clipboardData; if (!clipboard) return; event.preventDefault(); this.updateValue(clipboard.getData('text'), true, false); event.target.value = this.text; // 粘贴即显式赋值意图:直接派发 input,使「单击空白处标REV/其余清空」「回车前已标记」等行为生效。 // 原实现只把粘贴内容写回DOM、等失焦时的原生change才派发,而原值粘贴时change不触发→静默无反应。 this.$emit('input', this.toModel(this.text)); }, onKeydown: function (event) { this.enterPressed = false; this.$emit('keydown', event); const keyCode = event.which || event.keyCode; if (event.key !== 'Enter' && keyCode !== 13 && keyCode !== 108) return; // 回车=用户显式提交意图:无论值是否相对聚焦时变化,都直接派发 input(标REV) + enter(触发计算)。 // 原实现把 enter 锁在原生 change 里,而 change 仅在值变化时触发, // 导致「重输/重贴原值 + 回车」静默不计算(QA 高优 Bug)。 this.updateValue(this.text, true, false); const value = this.toModel(this.text); event.target.blur(); // 同步触发原生 change(值变化时再派发一次 input,幂等无害) this.$emit('input', value); this.$emit('enter', value); this.enterPressed = false; }, onChange: function (event) { if (this.text.endsWith('.')) this.text = this.text.substring(0, this.text.length - 1); this.updateValue(this.text, true, false); const value = this.toModel(this.text); // 失焦(非回车)→ 仅派发 input 标 REV;enter(计算) 改由 onKeydown 直接派发, // 避免 change 不触发时丢失,也避免与 onKeydown 重复派发 enter 造成重复计算。 this.$emit('input', value); event.target.value = this.text; } }, watch: { value: function (value) { const display = this.toDisplay(value); if (display !== this.text) this.text = display; }, format: { deep: true, handler: function () { const snapshot = JSON.stringify(this.format || {}); if (snapshot === this.formatSnapshot) return; this.formatSnapshot = snapshot; this.text = this.toDisplay(this.value); } } }, template: '' }; } return Object.freeze({ getRule: getRule, getCommonPrecision: function (kind, instrumentType) { return getCommonRule(kind, instrumentType).precision; }, getCommonInputFormat: function (kind, options, instrumentType) { const inputOptions = Object.assign({}, options || {}); const type = instrumentType || inputOptions.instrumentType; delete inputOptions.instrumentType; const rule = getCommonRule(kind, type); const result = Object.assign(inputOptions, { precision: rule.precision, grouping: inputOptions.grouping === undefined ? !!rule.grouping : !!inputOptions.grouping, trimTailZeros: false }); if (kind === 'quantity') result.stringMode = true; if (rule.integerDigits !== undefined) result.integerDigits = rule.integerDigits; return result; }, formatCommon: formatCommon, normalizeCommon: function (kind, value, instrumentType) { return formatCommon(kind, value, instrumentType, { grouping: false }); }, getInputFormat: function (instrumentType, field, options) { const rule = getRule(instrumentType, field); return rule ? Object.assign({}, options, rule) : Object.assign({}, options); }, format: format, multiplyDecimal: multiplyDecimal, roundDecimal: roundDecimal, roundForSubmit: function (value, instrumentType, field, storagePrecisionOffset) { const rule = getRule(instrumentType, field); if (value === null || value === undefined || value === '' || !rule) return value; const offset = Number.isInteger(storagePrecisionOffset) ? storagePrecisionOffset : 0; return roundDecimal(value, rule.precision + offset); }, shiftDecimal: shiftDecimal, createVueInputComponent: createVueInputComponent }); }(typeof window !== 'undefined' ? window : globalThis)); if (typeof module === 'object' && module.exports) module.exports = swapPricePrecision;