Files
zszq-trs/YLErpWeb/wwwroot/Scripts/app/client/clientMarginTemplateEdit.js
T
hjhan 5f951d33f6 feat: 预付金模板各 Tab 弹窗标题区分,页面标题去 V2,客户多选保存改为读控件值
- 四个 Tab 的新增/编辑/复制弹窗标题带各自归属(全局/客户/自定义/绑定),
  客户预付金 tab 原与全局同为"编辑默认预付金模板"无法区分
- 列表页 ViewBag.Title 去掉 V2
- 客户多选保存取值改为 $("#ClientId").val()(与兄弟页面读 chosen 一致),
  不再依赖 v-model 同步;编辑态仍走表单对象原绑定客户
2026-08-28 10:48:18 +08:00

287 lines
14 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.
//定价格式化(来自配置)
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()
}
});