Files
zszq-trs/YLErpWeb/wwwroot/Scripts/fast/fastVue.base.js
T
张名锐 c40b1ba416 feat(swap): 添加字符串模式支持和高精度计算功能
- 在输入组件中实现 stringMode 模式,支持字符串类型数值处理
- 添加 normalizeStringValue 函数用于标准化字符串数值格式
- 修改 calcStockEqvNotional 函数以支持高精度十进制乘积计算
- 引入 multiplyDecimal 函数处理大数相乘避免精度丢失
- 更新 swapPricePrecision 工具库以支持字符串模式和精度限制
- 添加相关测试用例验证 16 位数量的十进制乘积准确性
- 调整输入组件类型定义支持 Number 和 String 类型
- 优化数量和金额字段的精度配置和显示格式
2026-08-07 11:16:43 +08:00

691 lines
27 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function (window, $) {
// 模块加载横幅(排查用):仅在 ?otcdebug=1 或 localStorage.otcdebug=1 开启时打印,含 bundle 版本+git sha。
// 注意:本文件在 bundle.js 中早于 main.jsotcDebug 定义处)加载,故直接读 window.ylotc.__diag,不依赖 otcDebug。
var __diag = window.ylotc && window.ylotc.__diag;
if (__diag && __diag.debug && window.console) {
var git = (__diag.git || '').slice(0, 7);
console.log('%c[fastVue.base.js] v1.4.2 (bundle=' + __diag.jsVersion + ', git=' + git + ', built=' + __diag.built + ')',
'color:#06c;font-weight:bold');
}
function FastVue() {
}
FastVue.noop = function (a, b, c) { };
//解析数字,返回值:NAN或正常数字或''(输入参数为空)
FastVue.parseNumber = function (val,percent) {
switch (typeof val) {
case 'number': return val;
case 'string':
{
val = val.trim();
if (val === '') return '';
let isPercent = val.endsWith('%') || percent==true;
if (isPercent) {
val = val.replace(/%+$/, '');
if (val === '') return '';
}
if (val.endsWith('‱')) {
val = val.replace(/‱+$/, '');
if (val === '') return '';
}
let number = new Number(val);
return isPercent ? number / 100 : number;
}
default: return Number.NaN;
}
};
FastVue.extend = $.extend;
FastVue.setWidth = function (el, width) {
width && (el.style.width = typeof width === 'number' ? width + 'px' : width);
};
FastVue.getDomElement = function (el) {
if (!el) return null;
if (typeof el === 'string') {
return document.getElementById(el.startsWith('#') ? el.substring(1) : el);
}
el instanceof jQuery && (el = el[0]);
return el;
};
function Form(options) {
if (!(this instanceof Form)) {
throw 'FastVue.Form is a constructor and should be called with the `new` keyword';
}
this.data = {};
this.layout = {
display: 'inline',
title: {
width: 100
},
input: {
width: '165px'
}
};
this.submit = {
url: '',
before() { },
after() { }
};
this.fields = {};
this.view = {};
this.init(options);
}
FastVue.Form = Form;
window.FastVue = FastVue;
}(window, jQuery));
//autocomplete
(function (window, $) {
function __resetLookup(options, context) {
if (options.lookupFn) {
let lookupFn = options.lookupFn;
return options.lookup = function (query, callback) {
let queryLowerCase = (query || '').toLowerCase();
let suggestions = lookupFn.call(context) || [];
if (queryLowerCase) {
if (options.searchField) {
suggestions = suggestions.filter(data => {
return options.searchField.some(x => (data[x] || '').toLowerCase().indexOf(queryLowerCase) !== -1);
});
} else {
suggestions = suggestions.filter(data => {
return (data[options.valueField] || '').toLowerCase().indexOf(queryLowerCase) !== -1;
});
}
}
if (typeof suggestions[0] !== 'object') {
suggestions = suggestions.map(x => new Object({ value: x, data: x }));
} else {
suggestions = suggestions ? suggestions.map(x => {
return { value: x[options.nameField], data: x };
}) : [];
}
callback({ suggestions: suggestions });
};
}
if (!options.lookup) return;
if (typeof options.lookup === 'function') {
let lookupFn = options.lookup;
return options.lookup = function (query, callback) {
let queryLowerCase = (query || '').toLowerCase();
let suggestions = lookupFn.call(context, queryLowerCase) || [];
if (typeof suggestions[0] !== 'object') {
suggestions = suggestions.map(x => new Object({ value: x, data: x }));
} else {
suggestions = suggestions.map(x => {
return { value: x[options.nameField], data: x };
});
}
callback({ suggestions: suggestions });
};
}
if (Array.isArray(options.lookup)) {
let lookup = options.lookup;
if (typeof lookup[0] !== 'object') {
return options.lookup = lookup.map(x => new Object({ value: x, data: x }));
}
options.lookup = lookup.map(x => {
return { value: x[options.nameField], data: x };
});
if (options.searchField) {
options.lookupFilter = function (suggestion, originalQuery, queryLowerCase) {
return options.searchField.some(x => (suggestion.data[x] || '').toLowerCase().indexOf(queryLowerCase) !== -1);
};
} else {
options.lookupFilter = function (suggestion, originalQuery, queryLowerCase) {
return (suggestion.value || '').toLowerCase().indexOf(queryLowerCase) !== -1;
};
}
} else {
throw '[autocomplete.lookup]必须为数组或函数';
}
}
FastVue.autocomplete = function (el, options, context) {
!context && (context = this);
el = FastVue.getDomElement(el)
if (!el) throw '[FastVue.autocomplete]el is null or undefined:' + (options ? options.debugInfo : '');
let _el = el;//_el对应nameField
options = Object.assign({ minChars: 0, autoSelectFirst: true }, options);
!options.valueField && (options.valueField = options.nameField || 'value');
!options.nameField && (options.nameField = options.valueField);
if (options.nameField !== options.valueField) {
_el = el.cloneNode(true);
_el.setAttribute('name', '');
_el.setAttribute('value', '');
_el.id && _el.setAttribute('id', _el.id + '_auto');
el.setAttribute('type', 'hidden');
if (!el.parentNode) {
document.createElement("div").append(el);
}
el.parentNode.insertBefore(_el, el);
el.dataset.autocompleteClone = _el;
}
__resetLookup(options, context);
const onSelectFn = options.onSelect;
options.onSelect = function (suggestion) {
_el.dataset.select = '1';
if (options.nameField !== options.valueField) {
el.value = suggestion.data[options.valueField];
}
onSelectFn && onSelectFn(suggestion.data);
};
$(_el).autocomplete('dispose');
$(_el).on('focus', function () {
this.setAttribute("placeholder", this.value);
this.value = '';
this.dataset.select = '';
}).on('blur', function () {
!this.dataset.select && (this.value = this.getAttribute("placeholder"));
this.setAttribute("placeholder", '');
}).on('keydown', function (event) {
if ((event.which || event.keyCode) === 13) {
event.preventDefault();
event.stopPropagation();
$(this).blur();
}
}).autocomplete(options);
return {
setData(data) {
if (options.nameField === options.valueField) {
_el.value = (typeof data === 'object' ? data[options.nameField] : data) || '';
} else if (data) {
el.value = data[options.valueField] || '';
_el.value = data[options.nameField] || '';
} else {
_el.value = el.value = '';
}
},
dispose() {
$(_el).autocomplete('dispose');
$(_el).remove();
},
toggleDisabled(disabled) {
_el.disabled = typeof disabled === 'undefined' ? !_el.disabled : !!disabled;
},
clearCache() {
$(_el).autocomplete('clearCache');
},
setOptions(newOptions) {
$(_el).autocomplete('setOptions', Object.assign({}, options, newOptions));
}
};
};
//静态dispose方法
FastVue.autocomplete.dispose = function (el) {
(el instanceof jQuery) && (el = el.get(0));
if (!el) return;
$(el).autocomplete('dispose');
el.dataset && $(el.dataset.autocompleteClone).autocomplete('dispose');
};
}(FastVue, jQuery));
//numberInput
(function (window, $) {
FastVue.numberInput = function (el, options) {
let _el = typeof el === 'string' ? document.getElementById(el.replace(/^#/, '')) : el instanceof jQuery ? el[0] : el;
if (!_el) {
throw "[FastVue.numberInput]not found el:" + el;
}
let _ctrlV = false, _chnInput = -1;
let _options = { append: '', precision: 2, negative: false, grouping: false, trimTailZeros: true };
function getValue() {
let val = _el.value;
if (val && val !== _options.append) {
_options.append && _options.append !== '%' && _options.append !== '‱' && (val = val.replace(new RegExp(_options.append + "$"), ''));
if (_options.stringMode) return val.replaceAll(",", "");
return FastVue.parseNumber(val, _options.percent);
}
return '';
}
function setOptions(options) {
options = Object.assign(_options, options);
!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());
}
function setValue(value) {
if (value || value === 0) {
if (_options.stringMode && typeof value === 'string') {
let text = value.trim().replaceAll(",", "");
if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(text)) {
_el.value = '';
return;
}
const negative = text.charAt(0) === '-';
text = text.replace(/^[+-]/, '');
const parts = text.split('.');
if (_options.grouping) parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
_el.value = (negative ? '-' : '') + parts.join('.') + _options.append;
return;
}
let oval = parseFloat(value) || 0;
if (_options.append === '%' || _options.percent == true) {
oval *= 100;
}
else if (_options.append ==="‱") {
oval *= 10000;
}
if (_options.precision > 0) {
let str = main.formatNumber(oval, _options.precision, { grouping: _options.grouping });
if (_options.trimTailZeros !== false && typeof value === 'number' && str.endsWith("0") && str.includes('.')) {
str = str.replace(/\.?0+$/, '');
}
_el.value = str + _options.append;
} else {
let str = main.formatNumber(Math.floor(oval), 0, { grouping: _options.grouping });
_el.value = str + _options.append;
}
} else {
_el.value = '';
}
}
function checkDotInput() {
if (this.selectionStart >= this.value.length) {
return _options.append && this.value.endsWith(_options.append) ? false : this.value.indexOf('.') < 0;
}
if (this.value.length - _options.append.length - this.selectionEnd > _options.precision) {
return false;
}
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 normalizeStringValue(value) {
let text = String(value === null || value === undefined ? '' : value).trim().replaceAll(',', '');
if (!text) return '';
const negative = text.charAt(0) === '-';
text = text.replace(/^[+-]/, '');
if (!/^\d*(?:\.\d*)?$/.test(text)) return '';
let parts = text.split('.');
let integer = parts[0] || '0';
if (_options.integerDigits) integer = integer.substring(0, _options.integerDigits);
const hasDot = parts.length > 1 && _options.precision > 0;
const decimal = hasDot ? parts[1].substring(0, _options.precision) : '';
return (negative ? '-' : '') + integer + (hasDot ? '.' + decimal : '');
}
function __keyHandle(event) {
if (!event) return false;
_chnInput = -1;
this._enterFired = false; // 每次按键先清 Enter 标记;仅在回车分支重新置位,避免误触发
let charCode = event.which || event.keyCode;
switch (charCode) {
case 8: //break space
return !_options.append || !this.value.endsWith(_options.append) || this.selectionStart < this.value.length - _options.append.length + 1;
case 9: //tab
case 35: //end
case 36: //home
case 37: //left arrow
case 39: //right arrow
case 42: //print
case 116: //F5
return true;
case 46: //del
return !_options.append || !this.value.endsWith(_options.append) || this.selectionStart < this.value.length - _options.append.length;
case 229:
return _chnInput = this.selectionStart;
}
if (event.shiftKey) return false;
switch (charCode) {
case 109: //negative sign(-)
return _options.negative && this.selectionStart < 1 && this.value.charAt(0) !== '-';
case 189: //negative sign(-_)
return _options.negative && !event.shiftKey && this.selectionStart < 1 && this.value.charAt(0) !== '-';
case 110: //dot(.)
return _options.precision > 0 && checkDotInput.call(this);
case 190: //dot(.>)
return _options.precision > 0 && !event.shiftKey && checkDotInput.call(this);
case 13: //enter
case 108: //enter
this._enterFired = true; // 标记回车触发,供 __change 区分"回车"与"失焦"
$(this).blur();
return true;
case 67: //ctrl+c
return event.ctrlKey;
case 86: //ctrl+v
return event.ctrlKey && (_ctrlV = true);
default:
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) !== '-';
}
if (_options.precision < 1) {
return true;
}
if (this.selectionStart >= this.value.length && _options.append && this.value.endsWith(_options.append)) {
return false;
}
if (this.selectionEnd > this.selectionStart) {
return /\d/.test(this.value.substring(this.selectionStart, this.selectionEnd));
} else {
let dotIndex = this.value.lastIndexOf('.', this.selectionStart - 1);
return dotIndex < 0 || !/\d/.test(this.value[dotIndex + _options.precision]);
}
}
}
//处理中文字符输入
function __onChineseInput(chnPos) {
if (this.value.length < chnPos) {
chnPos = this.value.length - 1;
}
let ch = this.value.charCodeAt(chnPos);
if (ch > 47 && ch < 58) {
chnPos += 1;
}
else if (ch === 46 || ch === 12290) {
if (_options.precision > 0 && !/\d/.test(this.value[this.selectionStart + _options.precision])
&& this.value.lastIndexOf('.', chnPos - 1) < 0 && this.value.indexOf('.', this.selectionStart) < 0) {
if (ch === 12290) {
this.value = this.value.substring(0, chnPos) + '.' + this.value.substring(this.selectionStart);
this.setSelectionRange(chnPos + 1, chnPos + 1);
return;
}
chnPos += 1;
}
}
else if (chnPos === 0 && ch === 45 && _options.negative && this.value[this.selectionStart] !== '-') { //负号
return;
} else {
for (; chnPos >= 0; chnPos--) {
ch = this.value.charCodeAt(chnPos);
if (ch === 46 || ch > 47 && ch < 58 || chnPos === 0 && ch === 45 && _options.negative) {
chnPos += 1;
break;
}
}
}
this.value = this.value.substring(0, chnPos) + this.value.substring(this.selectionStart);
this.setSelectionRange(chnPos, chnPos);
}
//处理字符输入
function __onInput() {
if (_ctrlV) {
_ctrlV = false;
// 粘贴进来的是显示值(可能带 %/‱ 后缀),先按 __change 同款口径解析为模型值再回显,
// 避免 setValue 把显示值再乘以 100/10000#EQD-5914 债券价格粘贴 ×100
let f = '';
this.value = limitIntegerDigits(this.value);
if (this.value && this.value !== _options.append) {
if (_options.stringMode) return setValue(normalizeStringValue(this.value));
f = parseFloat(this.value.replaceAll(",", "")) || 0;
if (_options.append === '%' || _options.percent == true) f /= 100;
else if (_options.append === '‱') f /= 10000;
}
return setValue(f);
}
if (_chnInput >= 0) {
__onChineseInput.call(this, _chnInput);
}
if (_options.stringMode) {
this.value = normalizeStringValue(this.value);
return;
}
this.value = limitIntegerDigits(this.value);
if (!_options.append || !this.value) return;
let appended = true;
if (this.value !== _options.append) {
appended = this.value.endsWith(_options.append);
}
if (!appended) {
let caretPos = this.selectionStart;
this.value += _options.append;
this.setSelectionRange(caretPos, caretPos);
} else if (_options.negative && this.selectionStart === 2 && this.value[1] === '-') {
this.value = this.value.substring(1);
this.setSelectionRange(1, 1);
}
}
//输入完成处理
function __change() {
let f = '';
if (this.value && this.value !== _options.append) {
if (_options.stringMode) {
f = normalizeStringValue(this.value);
setValue(f);
}
else {
f = parseFloat(this.value.replaceAll(",", "")) || 0;
if (_options.append === '%' || _options.percent == true) f /= 100;
else if (_options.append === '‱') f /= 10000;
}
}
let isEnter = !!this._enterFired;
this._enterFired = false;
_options.onchange && _options.onchange(f, this.value, isEnter);
}
//功能禁用
function __disable() {
return false;
}
setOptions(options);
$(_el).on('keydown', __keyHandle).on('dragenter', __disable).on('input', __onInput).on('change', __change);
return {
isEmpty() {
return !_el.value || _el.value === _options.append;
},
getValue: getValue,
setValue: setValue,
setOptions: setOptions,
getOptions() {
return Object.assign({}, _options);
},
dispose() {
$(_el).off();
}
};
};
//静态dispose方法
FastVue.numberInput.dispose = function (el) {
(el instanceof jQuery ? el : $(el)).off();
};
}(FastVue, jQuery));
//FastVue.vueNumberInput
//数字输入组件(借助FastVue.numberInput)
(function (global) {
global.vueNumberInput = function () {
return {
props: {
value: {
type: [Number, String],
default: ''
},
format: {
type: Object,
default: {
append: '',
precision: 2,
negative: false,
grouping: false,
percent: false
}
},
disabled: {
type: Boolean
},
ret_type: {
type: Number,
default: 0
}
},
data() {
return { inputctrl: null,init:true};
},
mounted() {
let self = this;
let options = FastVue.extend({ onchange: this.onchange }, this.format);
this.inputctrl = FastVue.numberInput(this.$el, options);
this.inputctrl.setValue(this.value);
// 按键时 emit 'keydown' 事件,供父组件监听按键输入(交互约定3)
// .native 修饰符在此组件上不可靠(jQuery 在 mounted 中重新绑定了 keydown),
// 改为在组件内部 addEventListener 并 emit,确保父组件能收到按键事件
this.$el.addEventListener('keydown', function (e) {
self.$emit('keydown', e);
});
},
methods: {
onchange(value, text, isEnter) {
this.init = false;
const result = this.ret_type === 1 ? text : value;
this.$emit('input', result);
if (isEnter) this.$emit('enter', result);
}
},
watch: {
value(val, old) {
let val1 = '';
switch (typeof val) {
case 'number':
val1 = val || 0; break;
case 'string':
if (this.format && this.format.stringMode) {
val1 = val;
break;
}
if (val) {
val1 = parseFloat(val) || 0;
if (val1 && (val.trimEnd().endsWith('%') || (this.format.percent && !this.init))) {
val1 /= 100;
} else if (val1 && (val.trimEnd().endsWith('‱'))){
val1 /= 10000;
}
}
break;
}
if (!this.inputctrl.isEmpty()) {
let val2 = this.inputctrl.getValue();
if (Math.abs(val1 - val2) < 1e-10) return;
}
this.$nextTick(function () {
this.inputctrl.setValue(val1);
});
},
format: {
deep: true,
handler(val) {
this.inputctrl.setOptions(val);
this.$nextTick(function () {
this.inputctrl.setValue(this.value);
});
}
}
},
destroyed() {
FastVue.numberInput.dispose(this.$el);
},
template: '<input type="text" v-bind:disabled="disabled">'
};
};
}(window.FastVue));