Files
zszq-trs/YLErpWeb/wwwroot/Scripts/fast/fastVue.base.js
T
hjhan b9f2bd14f6 feat(swaptrade): 债券三字段计算器交互改为回车触发并加源/AUTO/REV 三态标识
- 约定1:仅在净价/全价/收益率输入框敲回车触发计算器,失焦不触发
- 约定2:计算成功->源字段标"源"、另两字段标"AUTO";失败->保留源值、清空另两字段与标识
- 约定3:编辑某字段未回车->不联动另两字段,清源/AUTO、仅本字段标"REV"
- swapCalc.js 新增 applyBondCalcSuccess/Failure/ManualEdit 纯函数,applyBondCalcResult 第三参由 manualSet 改为 driver(跳过源字段写回)
- fastVue.base.js 新增 enter 事件区分回车/失焦,向后兼容旧的 input 行为
- TradeEdit.cshtml 三字段 input/enter 分离绑定 + 源/AUTO/REV 三态标签(去除旧铅笔图标)
- 重写 bondCalc.test.js / 更新 swapCalc.test.js 覆盖状态机;jest 68 项全过
2026-07-29 14:34:25 +08:00

598 lines
22 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, $) {
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 };
function getValue() {
let val = _el.value;
if (val && val !== _options.append) {
_options.append && _options.append !== '%' && _options.append !== '‱' && (val = val.replace(new RegExp(_options.append + "$"), ''));
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;
options.negative = !!options.negative;
_options = options;
setValue(getValue());
}
function setValue(value) {
if (value || value === 0) {
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 (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 __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 (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 = '';
if (this.value && this.value !== _options.append) {
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.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) {
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,
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)
},
methods: {
onchange(value, text, isEnter) {
this.init = false;
this.$emit('input', this.ret_type === 1 ? text : value);
if (isEnter) this.$emit('enter', this.ret_type === 1 ? text : value);
}
},
watch: {
value(val, old) {
let val1 = '';
switch (typeof val) {
case 'number':
val1 = val || 0; break;
case 'string':
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));