- 四个 Tab 的新增/编辑/复制弹窗标题带各自归属(全局/客户/自定义/绑定),
客户预付金 tab 原与全局同为"编辑默认预付金模板"无法区分
- 列表页 ViewBag.Title 去掉 V2
- 客户多选保存取值改为 $("#ClientId").val()(与兄弟页面读 chosen 一致),
不再依赖 v-model 同步;编辑态仍走表单对象原绑定客户
287 lines
14 KiB
JavaScript
287 lines
14 KiB
JavaScript
//定价格式化(来自配置)
|
||
const inputFormatPercent = Object.freeze({ precision: 2, append: '%' });
|
||
const inputFormatPercentNegative = Object.freeze({ precision: 2, append: '%', negative: true });
|
||
const inputFormatNegative = Object.freeze({ precision: 2, negative: true });
|
||
|
||
//区间追保结构(RuleType=15)只读回显用常量,与 marginTemplateV2Edit.js 保持一致
|
||
const RangeMarginRuleType = 15;
|
||
//允许配期限档(SpanConfig.BondTerm)的资产类型掩码,本期仅利率债
|
||
const TermTierEnabledUnderlyingMask = 16;
|
||
//利率债区块固定展开的4个期限档([BondTerm值, 展示文案])
|
||
const SpanBondTerms = [['<5y', '≤5y'], ['5y-10y', '(5y-10y]'], ['10y-30y', '(10y-30y]'], ['>30y', '>30y']];
|
||
//标的资产类型标志位 -> 名称(与 marginTemplateV2Edit 的录入区块标题一致)
|
||
const UnderlyingTypeNames = [
|
||
[16, "利率债"], [32, "信用债"], [64, "其它债券"], [128, "股票"], [256, "股指"], [512, "股指期货"],
|
||
[1024, "商品期货"], [2048, "商品现货"], [4096, "新三板挂牌股票"], [8192, "香港股票"], [16384, "香港股指"],
|
||
[32768, "基金及基金专户"], [65536, "黄金期货"], [131072, "国债期货"], [262144, "其他期货"], [524288, "黄金现货"],
|
||
[1048576, "其他现货"], [2097152, "境外期货"], [4194304, "境外现货"], [8388608, "境外股票"], [16777216, "境外股指"],
|
||
[33554432, "汇率"], [67108864, "Shibor"], [134217728, "银行间回购定盘"], [268435456, "利率收益率"], [536870912, "债券指数"]
|
||
];
|
||
|
||
const vue = new Vue({
|
||
el: '#marginTemplateV2Form',
|
||
data: {
|
||
clientMarginTemplate: page.clientMarginTemplate,
|
||
marginTemplates: page.marginTemplates,
|
||
marginTemplate: page.marginTemplate,
|
||
clientNames: [],
|
||
isAdd: page.isAdd
|
||
},
|
||
created: function () {
|
||
this.clientMarginTemplate.ValueDate = new moment(this.clientMarginTemplate.ValueDate).format("YYYY-MM-DD");
|
||
},
|
||
computed: {
|
||
//区间追保结构只读参数组:连续利率债行归一个区块、按固定4档对位(缺档不补造、同档重复/异常档独立成段保留展示);
|
||
//其余行(含ETF子类行)每行独立成块——分组口径与 marginTemplateV2Edit.rebuildSpanBlocks 一致,但只回显不新增行
|
||
ruleRangeBlocks() {
|
||
if (this.marginTemplate.RuleType != RangeMarginRuleType) return [];
|
||
var details = this.marginTemplate.Details || [];
|
||
var that = this;
|
||
var blocks = [];
|
||
var i = 0;
|
||
while (i < details.length) {
|
||
if ((details[i].UnderlyingType || 0) === TermTierEnabledUnderlyingMask) {
|
||
var j = i;
|
||
while (j < details.length && (details[j].UnderlyingType || 0) === TermTierEnabledUnderlyingMask) j++;
|
||
var byTerm = {};
|
||
var extras = [];
|
||
for (var k = i; k < j; k++) {
|
||
var bt = (details[k].SpanConfig && details[k].SpanConfig.BondTerm) || '';
|
||
var canonical = SpanBondTerms.some(function (t) { return t[0] === bt; });
|
||
if (canonical && !byTerm[bt]) byTerm[bt] = details[k]; else extras.push(details[k]);
|
||
}
|
||
var sections = SpanBondTerms.map(function (t) {
|
||
return { key: 't' + t[0], termLabel: t[1], detail: byTerm[t[0]] || null };
|
||
});
|
||
extras.forEach(function (r, xi) {
|
||
sections.push({ key: 'x' + xi, termLabel: that.bondTermLabel(r), detail: r });
|
||
});
|
||
blocks.push({ key: 'i' + i, no: blocks.length + 1, ut: TermTierEnabledUnderlyingMask, isTBond: true, etfKind: '', sections: sections });
|
||
i = j;
|
||
} else {
|
||
var d = details[i];
|
||
blocks.push({
|
||
key: 'i' + i, no: blocks.length + 1, ut: d.UnderlyingType || 0, isTBond: false,
|
||
etfKind: (d.SpanConfig && d.SpanConfig.EtfKind) || '',
|
||
sections: [{ key: 'single', termLabel: '', detail: d }]
|
||
});
|
||
i++;
|
||
}
|
||
}
|
||
return blocks;
|
||
}
|
||
},
|
||
methods: {
|
||
//资产类型标志位掩码 -> 名称(0=全部),与 marginTemplateV2Edit.underlyingTypeNames 一致
|
||
underlyingTypeNames(mask) {
|
||
mask = parseInt(mask) || 0;
|
||
if (mask === 0) return "全部";
|
||
var names = [];
|
||
for (var i = 0; i < UnderlyingTypeNames.length; i++) {
|
||
if ((mask & UnderlyingTypeNames[i][0]) > 0) names.push(UnderlyingTypeNames[i][1]);
|
||
}
|
||
return names.length > 0 ? names.join("、") : "全部";
|
||
},
|
||
//期限档 BondTerm -> 展示文案(空=全部)
|
||
bondTermLabel(detail) {
|
||
var v = (detail.SpanConfig && detail.SpanConfig.BondTerm) || '';
|
||
var map = { '': '全部', '<5y': '≤5y', '5y-10y': '(5y-10y]', '10y-30y': '(10y-30y]', '>30y': '>30y' };
|
||
return map[v] !== undefined ? map[v] : v;
|
||
},
|
||
//区间计价口径文案:按 detail 资产类型切换,与取数侧对齐(债券类=中债估值净价/期初全价×券面总额,其余=收盘价口径),
|
||
//直接沿用 marginTemplateV2Edit.spanText 的判定
|
||
spanText(detail) {
|
||
var ut = (detail && detail.UnderlyingType) || 0;
|
||
var bondMask = 16 | 32 | 64;
|
||
var isBond = ut !== 0 && (ut & ~bondMask) === 0;
|
||
if (!isBond) {
|
||
return {
|
||
priceInit: '参考标的期初净价',
|
||
priceCur: '参考标的当前收盘价',
|
||
priceCurShort1: '参考标的当前收盘价',
|
||
priceCurShort: '参考标的当前收盘价',
|
||
amountBase: '参考标的期初价格 × 参考标的名义份额'
|
||
};
|
||
}
|
||
return {
|
||
priceInit: '期初净价',
|
||
priceCur: '当前净价',
|
||
priceCurShort1: '当前净价',
|
||
priceCurShort: '当前净价',
|
||
amountBase: '期初全价 × 券面总额'
|
||
};
|
||
},
|
||
changeMarginTemplate() {
|
||
this.marginTemplates.forEach(x => {
|
||
if (this.clientMarginTemplate.MarginTemplateId === x.id) {
|
||
this.marginTemplate = x;
|
||
$('#suitableType').prop('title', this.marginTemplate.TradeTypes);
|
||
}
|
||
});
|
||
|
||
},
|
||
//新增=多选:按选中客户逐条调用原有单条保存接口(后端不变,每条走完整互斥校验);
|
||
//编辑=单条,保持原行为。逐条收集成败,结束统一汇总——部分失败时列表刷新已成功部分,弹窗留在
|
||
//当前内容上供修正重试(已成功的客户重试会因同天同结构互斥校验失败,不会重复入库)
|
||
saveMarginTemplateV2() {
|
||
var thisObj = this;
|
||
//取值直接读控件(与兄弟页面读 chosen 的 $("#tradeTypes").val() 一致,不依赖 v-model 同步):
|
||
//新增=多选控件 val()(数组);编辑=表单对象上的原绑定客户
|
||
var chosenVal = this.isAdd ? $("#ClientId").val() : this.clientMarginTemplate.ClientId;
|
||
var ids = this.isAdd
|
||
? (Array.isArray(chosenVal) ? chosenVal : (chosenVal ? [chosenVal] : []))
|
||
: [chosenVal];
|
||
if (!ids.length) {
|
||
main.message("请至少选择一个客户");
|
||
return;
|
||
}
|
||
if (!this.clientMarginTemplate.MarginTemplateId) {
|
||
main.message("请选择有效的预付金模板");
|
||
return;
|
||
}
|
||
var okCount = 0;
|
||
var fails = [];
|
||
var chain = Promise.resolve();
|
||
ids.forEach(function (clientId) {
|
||
chain = chain.then(function () {
|
||
//深拷贝:逐条改写 ClientId/id,绝不污染 Vue 表单对象(部分失败时弹窗保持打开)
|
||
var payload = _.cloneDeep(thisObj.clientMarginTemplate);
|
||
payload.ClientId = clientId;
|
||
payload.id = thisObj.isAdd ? 0 : thisObj.clientMarginTemplate.id;
|
||
//alertFn 置空:单条失败不逐个弹窗,由循环结束后统一汇总
|
||
return main.post("/client_margin_template/saveClientMarginTemplate",
|
||
{ clientMarginTemplate: payload },
|
||
{ alertFn: function () { }, suppressError: true }
|
||
).then(function () {
|
||
okCount++;
|
||
}, function (resp) {
|
||
fails.push({ id: clientId, msg: (resp && (resp.msg || resp.errmsg)) || "保存失败" });
|
||
});
|
||
});
|
||
});
|
||
chain.then(function () {
|
||
try { window.parent.reloadmargin_template(); } catch (e) { }
|
||
if (!fails.length) {
|
||
try { (parent || window).layer.closeAll(); } catch (e) { }
|
||
return;
|
||
}
|
||
var nameOf = function (cid) {
|
||
var c = thisObj.clientNames.find(function (x) { return String(x.Value) === String(cid); });
|
||
return c ? c.Text : cid;
|
||
};
|
||
var detail = fails.map(function (f) { return nameOf(f.id) + ":" + f.msg; }).join("<br>");
|
||
main.alert("成功 " + okCount + " 条,失败 " + fails.length + " 条:<br>" + detail);
|
||
});
|
||
},
|
||
showTemplateDetail(detail) {
|
||
vueDetail.backupDetail = _.cloneDeep(detail);
|
||
detail.ValueDate = new moment(detail.ValueDate).format("YYYY-MM-DD");
|
||
vueDetail.detail = detail;
|
||
vueDetail.isAdd = false;
|
||
vueDetail.ruleType = vue.marginTemplate.RuleType;
|
||
$("#myModal").modal("show");
|
||
},
|
||
SetClientNameDropDown() {
|
||
$.ajax({
|
||
type: "get",
|
||
url: "/client_margin_template/GetAllClients",
|
||
success: function (data) {
|
||
if (data.obj) {
|
||
for (var i = 0; i < data.obj.length; i++) {
|
||
|
||
vue.clientNames.push(data.obj[i]);
|
||
}
|
||
}
|
||
//选项就位后再初始化/刷新 chosen(多选客户),并同步"有选中即隐藏搜索框"行为
|
||
Vue.nextTick(function () {
|
||
SetAceDropDown();
|
||
$('.chosen-select').trigger('chosen:updated');
|
||
$('select.chosen-select[multiple]').each(function () {
|
||
refreshChosenSearchField(this);
|
||
});
|
||
});
|
||
},
|
||
error: function (msg) {
|
||
alert("error:" + msg);
|
||
}
|
||
});
|
||
}
|
||
},
|
||
mounted() {
|
||
this.SetClientNameDropDown();
|
||
if (this.marginTemplate.TradeTypes != null && this.marginTemplate.TradeTypes != 'undefined') {
|
||
$('#suitableType').prop('title', this.marginTemplate.TradeTypes);
|
||
}
|
||
|
||
},
|
||
components: {
|
||
'vue-datepicker': FastVue.vueDatePicker(),
|
||
'vue-number-input': FastVue.vueNumberInput()
|
||
}
|
||
});
|
||
|
||
//多选 chosen:已有选中项后隐藏搜索框(与 marginTemplateV2*Edit.js 同款行为)——
|
||
//空框没有输入内容却占一行,放不下时还会换行留白;全部取消后恢复占位提示。
|
||
//须为顶层函数:SetClientNameDropDown 的 nextTick 回调也会调用
|
||
function refreshChosenSearchField(select) {
|
||
var $sel = $(select);
|
||
var $container = $sel.next('.chosen-container-multi');
|
||
if ($container.length === 0) return;
|
||
var noneSelected = $sel.find('option:selected').length === 0;
|
||
$container.find('li.search-field').toggle(noneSelected);
|
||
}
|
||
|
||
$(function () {
|
||
$(".datepicker").change(function () {
|
||
var dateVal = $(this).val();
|
||
if (!dateVal) return;
|
||
dateVal = dateVal.replace(/\D/g, '').padStart(4, '0');
|
||
switch (dateVal.length) {
|
||
case 4:
|
||
dateVal = new Date().getFullYear() + dateVal; break;
|
||
case 6:
|
||
dateVal = '20' + dateVal; break;
|
||
case 8: break;
|
||
default:
|
||
dateVal = dateVal.length > 8 ? dateVal.substring(0, 8) : ''; break;
|
||
}
|
||
|
||
if (dateVal) {
|
||
dateVal = moment(dateVal).format('YYYY-MM-DD');
|
||
!/^\d+/.test(dateVal) && (dateVal = '');
|
||
}
|
||
|
||
$(this).datepicker("setDate", dateVal);
|
||
});
|
||
|
||
//勾选变化走原 select 的 jQuery change 委托(chosen trigger_form_field_change),在此统一刷新搜索框
|
||
$(document).on('change chosen:updated', 'select.chosen-select[multiple]', function () {
|
||
refreshChosenSearchField(this);
|
||
});
|
||
|
||
SetAceDropDown();
|
||
$('select.chosen-select[multiple]').each(function () {
|
||
refreshChosenSearchField(this);
|
||
});
|
||
});
|
||
|
||
var vueDetail = new Vue({
|
||
el: "#myModal",
|
||
data: {
|
||
detail: {},
|
||
backupDetail: {},
|
||
isAdd: true,
|
||
ruleType: 0,
|
||
},
|
||
created: function () {
|
||
},
|
||
methods: {
|
||
closeModal: function () {
|
||
$('#myModal').modal('hide');
|
||
}
|
||
},
|
||
components: {
|
||
'vue-datepicker': FastVue.vueDatePicker(),
|
||
'vue-number-input': FastVue.vueNumberInput()
|
||
}
|
||
}); |