fix(EQD-7049): 期权定价页标的下拉异步结果落地后刷新显示
- 现象(客户反馈):输入关键词接口返回20条但下拉只显示2条,继续输入又正常
- 根因:FastVue.autocomplete 对函数型 lookup 只同步取返回值渲染,服务端响应
回来后仅写缓存、未触发重渲染,下拉永远显示"上一次请求结果+当前词再过滤",
慢一拍且被前端二次过滤(品种/IsCombined/contains)进一步砍少
- 修复:响应到达且输入未变、下拉可见时重触发插件 onValueChange 重渲染;
_fresh 命中当前关键词直接展示服务端结果(不再前端二次过滤,跨品种结果不误滤),
_seq 单调递增丢弃乱序响应,_inflight 防同关键词重复请求
- 移除 mounted 中无效的 autocomplete('search') 调用(插件无此方法,一直为空操作)
- 同步修改东莞定制版 structure_dz.js
- 已用 Playwright 对照验证:旧代码复现"输入20只显2项/接口返回20条",修复后
两文件 20 项断言全过(显示数=服务端返回、无请求风暴、乱序不污染、可选中)
This commit is contained in:
@@ -112,15 +112,22 @@ const vueTradeType = function () {
|
|||||||
//标的选择组件(EQD-7049:改为服务端搜索,不再依赖全量 ylotc.underlyings,避免十几万标的整段下载卡死)
|
//标的选择组件(EQD-7049:改为服务端搜索,不再依赖全量 ylotc.underlyings,避免十几万标的整段下载卡死)
|
||||||
const vueUnderlying = function () {
|
const vueUnderlying = function () {
|
||||||
const _suggestionTpl = _.template($('#underlyingSuggestionTpl').html());
|
const _suggestionTpl = _.template($('#underlyingSuggestionTpl').html());
|
||||||
// 标的缓存:按 品种|关键词 隔离;乱序响应由 token 丢弃(helper 收在函数内,避免全局绑定冲突)
|
// 标的缓存(同品种各实例共享):_cache 为最近一次服务端结果,_fresh 记录其对应的 品种|关键词,
|
||||||
|
// _seq 单调递增丢弃乱序/过期响应,_inflight 防同关键词重复请求(helper 收在函数内,避免全局绑定冲突)
|
||||||
const _cache = {};
|
const _cache = {};
|
||||||
const _tokens = {};
|
const _fresh = {};
|
||||||
|
const _seq = {};
|
||||||
|
const _inflight = {};
|
||||||
function _fetch(varietyId, query, cb) {
|
function _fetch(varietyId, query, cb) {
|
||||||
var key = (varietyId || 0) + '|' + (query || '');
|
var vid = varietyId || 0;
|
||||||
var token = (_tokens[key] = (_tokens[key] || 0) + 1);
|
var q = query || '';
|
||||||
|
var key = vid + '|' + q;
|
||||||
|
if (_inflight[key]) return;
|
||||||
|
var seq = (_seq[vid] = (_seq[vid] || 0) + 1);
|
||||||
|
_inflight[key] = true;
|
||||||
var postData = {
|
var postData = {
|
||||||
FilterCode: (query || '').toUpperCase(),
|
FilterCode: q.toUpperCase(),
|
||||||
VarietyId: varietyId || 0,
|
VarietyId: vid,
|
||||||
MaxShowLength: 20,
|
MaxShowLength: 20,
|
||||||
BlackLimit: 1,
|
BlackLimit: 1,
|
||||||
UseForTrading: true,
|
UseForTrading: true,
|
||||||
@@ -128,7 +135,8 @@ const vueUnderlying = function () {
|
|||||||
CheckLaunch: true
|
CheckLaunch: true
|
||||||
};
|
};
|
||||||
main.post('/frontdata/AjaxGetUnderlyingSelect', postData).done(function (res) {
|
main.post('/frontdata/AjaxGetUnderlyingSelect', postData).done(function (res) {
|
||||||
if (_tokens[key] !== token) return; // 丢弃过期响应
|
delete _inflight[key];
|
||||||
|
if (_seq[vid] !== seq) return; // 已有更新的关键词发起请求,丢弃本响应
|
||||||
var arr = (res && (res.obj || res.data)) || [];
|
var arr = (res && (res.obj || res.data)) || [];
|
||||||
var norm = arr.map(function (x) {
|
var norm = arr.map(function (x) {
|
||||||
return {
|
return {
|
||||||
@@ -142,7 +150,11 @@ const vueUnderlying = function () {
|
|||||||
PinYin: x.PinYin || ''
|
PinYin: x.PinYin || ''
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
cb && cb(norm);
|
_cache[vid] = norm;
|
||||||
|
_fresh[vid] = key;
|
||||||
|
cb && cb(norm, q);
|
||||||
|
}).fail(function () {
|
||||||
|
delete _inflight[key];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function _filter(list, query, varietyId) {
|
function _filter(list, query, varietyId) {
|
||||||
@@ -163,24 +175,28 @@ const vueUnderlying = function () {
|
|||||||
mounted() {
|
mounted() {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.jqInput = $(this.$el).children(0);
|
this.jqInput = $(this.$el).children(0);
|
||||||
// EQD-7049:预拉默认20条(当前品种),避免下拉空白
|
// EQD-7049:预拉默认20条(当前品种),获得焦点时由插件自身的 onValueChange 呈现
|
||||||
_fetch(self.underlying.VarietyId, '', function (list) {
|
_fetch(self.underlying.VarietyId, '');
|
||||||
_cache[self.underlying.VarietyId || 0] = list;
|
|
||||||
try { $(self.jqInput).autocomplete('search', ''); } catch (e) {}
|
|
||||||
});
|
|
||||||
this.autoctrl = FastVue.autocomplete(this.jqInput, {
|
this.autoctrl = FastVue.autocomplete(this.jqInput, {
|
||||||
valueField: 'Code',
|
valueField: 'Code',
|
||||||
lookup(query, callback) {
|
lookup(query) {
|
||||||
var varietyId = self.underlying.VarietyId;
|
var varietyId = self.underlying.VarietyId;
|
||||||
var cached = _cache[varietyId || 0] || [];
|
var vid = varietyId || 0;
|
||||||
var immediate = _filter(cached, query, varietyId);
|
var key = vid + '|' + (query || '');
|
||||||
if (query) {
|
if (_fresh[vid] === key) {
|
||||||
// 有输入时异步向服务端搜索并刷新缓存(乱序响应由 token 丢弃)
|
// 命中当前关键词的服务端结果:直接展示(服务端已按 StartsWith+品种/黑名单过滤,不再前端二次过滤)
|
||||||
_fetch(varietyId, query, function (list) {
|
return (_cache[vid] || []).slice(0, 20);
|
||||||
_cache[varietyId || 0] = list;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return immediate;
|
// 异步搜索。FastVue 包装的 lookup 只同步取返回值渲染,服务端结果到达后必须重新触发
|
||||||
|
// onValueChange 才会显示;重走 lookup 时命中上面的 _fresh 分支直接返回,不会循环请求
|
||||||
|
_fetch(varietyId, query, function (list, q) {
|
||||||
|
var inst = self.jqInput.autocomplete();
|
||||||
|
if (!inst || !inst.visible) return; // 下拉已关闭:留待下次获得焦点时呈现
|
||||||
|
if ((self.jqInput.val() || '').toLowerCase() !== q.toLowerCase()) return; // 输入已变化:等新关键词的响应
|
||||||
|
inst.onValueChange();
|
||||||
|
});
|
||||||
|
// 过渡兜底:服务端响应到达前用旧缓存按关键词过滤,避免搜索期间下拉空白
|
||||||
|
return _filter(_cache[vid] || [], query, varietyId);
|
||||||
},
|
},
|
||||||
onSelect(data) {
|
onSelect(data) {
|
||||||
if (self.underlying !== data) {
|
if (self.underlying !== data) {
|
||||||
|
|||||||
@@ -108,18 +108,26 @@ const vueTradeType = function () {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//标的选择组件(EQD-7049:改为服务端搜索,不再依赖全量 ylotc.underlyings,避免十几万标的整段下载卡死)
|
||||||
//标的选择组件(EQD-7049:改为服务端搜索,不再依赖全量 ylotc.underlyings,避免十几万标的整段下载卡死)
|
//标的选择组件(EQD-7049:改为服务端搜索,不再依赖全量 ylotc.underlyings,避免十几万标的整段下载卡死)
|
||||||
const vueUnderlying = function () {
|
const vueUnderlying = function () {
|
||||||
const _suggestionTpl = _.template($('#underlyingSuggestionTpl').html());
|
const _suggestionTpl = _.template($('#underlyingSuggestionTpl').html());
|
||||||
// 标的缓存:按 品种|关键词 隔离;乱序响应由 token 丢弃(helper 收在函数内,避免全局绑定冲突)
|
// 标的缓存(同品种各实例共享):_cache 为最近一次服务端结果,_fresh 记录其对应的 品种|关键词,
|
||||||
|
// _seq 单调递增丢弃乱序/过期响应,_inflight 防同关键词重复请求(helper 收在函数内,避免全局绑定冲突)
|
||||||
const _cache = {};
|
const _cache = {};
|
||||||
const _tokens = {};
|
const _fresh = {};
|
||||||
|
const _seq = {};
|
||||||
|
const _inflight = {};
|
||||||
function _fetch(varietyId, query, cb) {
|
function _fetch(varietyId, query, cb) {
|
||||||
var key = (varietyId || 0) + '|' + (query || '');
|
var vid = varietyId || 0;
|
||||||
var token = (_tokens[key] = (_tokens[key] || 0) + 1);
|
var q = query || '';
|
||||||
|
var key = vid + '|' + q;
|
||||||
|
if (_inflight[key]) return;
|
||||||
|
var seq = (_seq[vid] = (_seq[vid] || 0) + 1);
|
||||||
|
_inflight[key] = true;
|
||||||
var postData = {
|
var postData = {
|
||||||
FilterCode: (query || '').toUpperCase(),
|
FilterCode: q.toUpperCase(),
|
||||||
VarietyId: varietyId || 0,
|
VarietyId: vid,
|
||||||
MaxShowLength: 20,
|
MaxShowLength: 20,
|
||||||
BlackLimit: 1,
|
BlackLimit: 1,
|
||||||
UseForTrading: true,
|
UseForTrading: true,
|
||||||
@@ -127,7 +135,8 @@ const vueUnderlying = function () {
|
|||||||
CheckLaunch: true
|
CheckLaunch: true
|
||||||
};
|
};
|
||||||
main.post('/frontdata/AjaxGetUnderlyingSelect', postData).done(function (res) {
|
main.post('/frontdata/AjaxGetUnderlyingSelect', postData).done(function (res) {
|
||||||
if (_tokens[key] !== token) return; // 丢弃过期响应
|
delete _inflight[key];
|
||||||
|
if (_seq[vid] !== seq) return; // 已有更新的关键词发起请求,丢弃本响应
|
||||||
var arr = (res && (res.obj || res.data)) || [];
|
var arr = (res && (res.obj || res.data)) || [];
|
||||||
var norm = arr.map(function (x) {
|
var norm = arr.map(function (x) {
|
||||||
return {
|
return {
|
||||||
@@ -141,7 +150,11 @@ const vueUnderlying = function () {
|
|||||||
PinYin: x.PinYin || ''
|
PinYin: x.PinYin || ''
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
cb && cb(norm);
|
_cache[vid] = norm;
|
||||||
|
_fresh[vid] = key;
|
||||||
|
cb && cb(norm, q);
|
||||||
|
}).fail(function () {
|
||||||
|
delete _inflight[key];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function _filter(list, query, varietyId) {
|
function _filter(list, query, varietyId) {
|
||||||
@@ -162,24 +175,28 @@ const vueUnderlying = function () {
|
|||||||
mounted() {
|
mounted() {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.jqInput = $(this.$el).children(0);
|
this.jqInput = $(this.$el).children(0);
|
||||||
// EQD-7049:预拉默认20条(当前品种),避免下拉空白
|
// EQD-7049:预拉默认20条(当前品种),获得焦点时由插件自身的 onValueChange 呈现
|
||||||
_fetch(self.underlying.VarietyId, '', function (list) {
|
_fetch(self.underlying.VarietyId, '');
|
||||||
_cache[self.underlying.VarietyId || 0] = list;
|
|
||||||
try { $(self.jqInput).autocomplete('search', ''); } catch (e) {}
|
|
||||||
});
|
|
||||||
this.autoctrl = FastVue.autocomplete(this.jqInput, {
|
this.autoctrl = FastVue.autocomplete(this.jqInput, {
|
||||||
valueField: 'Code',
|
valueField: 'Code',
|
||||||
lookup(query, callback) {
|
lookup(query) {
|
||||||
var varietyId = self.underlying.VarietyId;
|
var varietyId = self.underlying.VarietyId;
|
||||||
var cached = _cache[varietyId || 0] || [];
|
var vid = varietyId || 0;
|
||||||
var immediate = _filter(cached, query, varietyId);
|
var key = vid + '|' + (query || '');
|
||||||
if (query) {
|
if (_fresh[vid] === key) {
|
||||||
// 有输入时异步向服务端搜索并刷新缓存(乱序响应由 token 丢弃)
|
// 命中当前关键词的服务端结果:直接展示(服务端已按 StartsWith+品种/黑名单过滤,不再前端二次过滤)
|
||||||
_fetch(varietyId, query, function (list) {
|
return (_cache[vid] || []).slice(0, 20);
|
||||||
_cache[varietyId || 0] = list;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return immediate;
|
// 异步搜索。FastVue 包装的 lookup 只同步取返回值渲染,服务端结果到达后必须重新触发
|
||||||
|
// onValueChange 才会显示;重走 lookup 时命中上面的 _fresh 分支直接返回,不会循环请求
|
||||||
|
_fetch(varietyId, query, function (list, q) {
|
||||||
|
var inst = self.jqInput.autocomplete();
|
||||||
|
if (!inst || !inst.visible) return; // 下拉已关闭:留待下次获得焦点时呈现
|
||||||
|
if ((self.jqInput.val() || '').toLowerCase() !== q.toLowerCase()) return; // 输入已变化:等新关键词的响应
|
||||||
|
inst.onValueChange();
|
||||||
|
});
|
||||||
|
// 过渡兜底:服务端响应到达前用旧缓存按关键词过滤,避免搜索期间下拉空白
|
||||||
|
return _filter(_cache[vid] || [], query, varietyId);
|
||||||
},
|
},
|
||||||
onSelect(data) {
|
onSelect(data) {
|
||||||
if (self.underlying !== data) {
|
if (self.underlying !== data) {
|
||||||
|
|||||||
Reference in New Issue
Block a user