feat: 客户预付金新增支持客户多选批量绑定,移除广发商贸定制分支

- 新增弹窗客户名称改为 chosen 多选:保存时按选中客户逐条调用原有单条接口
  (后端零改动,每条走完整互斥校验),逐条收集成败后统一汇总提示;
  部分失败时刷新列表、弹窗保留供修正重试(重试已成功客户会被互斥校验拦截,不重复入库)
- 编辑弹窗仍为单条绑定不变
- 删除 isGFSM(广发商贸)定制:客户等级下拉与切换 watchers、Span涨跌幅参数块;
  本部署(国联民生)不涉及,后端接口保持原样
- 左栏加 tpl-base 统一控件宽度,多选搜索框隐藏行为与模板编辑页一致
This commit is contained in:
hjhan
2026-08-28 09:54:30 +08:00
parent b867d2959a
commit 3f331f0e83
2 changed files with 92 additions and 149 deletions
@@ -10,8 +10,7 @@
isAdd = Model == null || Model.id == 0,
marginTemplates = ViewBag.MarginTemplates,
marginTemplate = ViewBag.MarginTemplate,
clientMarginTemplate = Model,
isGFSM = PS.Config.Company == CompanyEnum.广发商贸
clientMarginTemplate = Model
};
}
@@ -43,26 +42,26 @@
<form id="marginTemplateV2Form" method="post" onsubmit="return false;">
<div class="row no-gutters">
<div class="col form-layout" style="height: 520px; overflow-y: auto;">
<div class="col form-layout tpl-base" style="height: 520px; overflow-y: auto;">
<div class="border">
<P>新模板信息</P>
@if (pageObj.isGFSM)
{
<div class="form-group">
<label class="formlabel">客户等级</label>
<select v-model="clientMarginTemplate.ClientLevel" :disabled="isClientLevelDisabled" id="ClientLevel">
<option value="">---</option>
<option v-for="item in clientLevels" :value="item.Value">{{item.Text}}</option>
</select>
</div>
}
<div class="form-group">
<label class="formlabel">客户名称</label>
<select v-model="clientMarginTemplate.ClientId" :disabled="isClientNameDisabled" id="ClientId">
<option value="0">---</option>
<option v-for="item in clientNames" :value="item.Value">{{item.Text}}</option>
</select>
@if (pageObj.isAdd)
{
@*新增:客户多选(类似适用簿记账户),保存时按选中客户逐条调用原有单条保存接口*@
<select id="ClientId" class="chosen-select" multiple data-placeholder="请选择客户" style="width:260px;" v-model="selectedClientIds">
<option v-for="item in clientNames" v-bind:value="item.Value">{{item.Text}}</option>
</select>
}
else
{
@*编辑:单条绑定,仅改模板/日期,客户不可多选*@
<select v-model="clientMarginTemplate.ClientId" id="ClientId">
<option v-for="item in clientNames" v-bind:value="item.Value">{{item.Text}}</option>
</select>
}
</div>
<div class="form-group">
@@ -98,7 +97,7 @@
<div class="form-group">
<label class="formlabel">规则描述</label>
<textarea class="text-box" rows="3" style="width: 320px; text-align:left;" v-model="marginTemplate.Comments" v-bind:title="marginTemplate.Comments" disabled>
<textarea class="text-box" rows="3" style="text-align:left;" v-model="marginTemplate.Comments" v-bind:title="marginTemplate.Comments" disabled>
</textarea>
</div>
</div>
@@ -109,26 +108,6 @@
<div class="border detail">
<div class="row no-gutters">
<div class="col">
@if (PS.Config.Company == CompanyEnum.广发商贸)
{
<template v-if="marginTemplate.RuleType == @((int)MarginRuleTypeEnum.默认预付金规则)">
<div class="form-group">
<label class="formlabel">Span涨跌幅选择</label>
<select v-model="detail.SpanConfig.SpanRateType" disabled>
<option value="@((int)DetailSpanRateTypeEnum.Span涨跌幅度)">@DetailSpanRateTypeEnum.Span涨跌幅度.ToString()</option>
<option value="@((int)DetailSpanRateTypeEnum.Span涨跌幅度2)">@DetailSpanRateTypeEnum.Span涨跌幅度2.ToString()</option>
</select>
</div>
<div class="form-group">
<label class="formlabel">初始预付金系数</label>
<input class="text-box" v-model="detail.SpanConfig.InitialMarginFactor" disabled />
</div>
<div class="form-group">
<label class="formlabel">发生追保时系数</label>
<input class="text-box" v-model="detail.SpanConfig.CoefficientFactor" disabled/>
</div>
</template>
}
<template v-if="marginTemplate.RuleType == @((int)MarginRuleTypeEnum.按固定利率) || marginTemplate.RuleType == @((int)MarginRuleTypeEnum.按浮动盈亏)">
<div class="form-group">
<label class="formlabel">持仓预付金率</label>
@@ -1,6 +1,4 @@
const consClients = ylotc.clients;
//定价格式化(来自配置)
//定价格式化(来自配置)
const inputFormatPercent = Object.freeze({ precision: 2, append: '%' });
const inputFormatPercentNegative = Object.freeze({ precision: 2, append: '%', negative: true });
const inputFormatNegative = Object.freeze({ precision: 2, negative: true });
@@ -11,20 +9,15 @@ const vue = new Vue({
clientMarginTemplate: page.clientMarginTemplate,
marginTemplates: page.marginTemplates,
marginTemplate: page.marginTemplate,
clientLevels: [],
clientNames: [],
isClientLevelDisabled: false,
isClientNameDisabled: false,
//新增模式的客户多选(编辑模式仍为单条绑定,走 clientMarginTemplate.ClientId
selectedClientIds: [],
isAdd: page.isAdd
},
created: function () {
this.clientMarginTemplate.ValueDate = new moment(this.clientMarginTemplate.ValueDate).format("YYYY-MM-DD");
},
methods: {
//changeClient(yldata) {
// let client = yldata || { id: 0 };
// this.clientMarginTemplate.ClientId = client.id;
//},
changeMarginTemplate() {
this.marginTemplates.forEach(x => {
if (this.clientMarginTemplate.MarginTemplateId === x.id) {
@@ -32,15 +25,54 @@ const vue = new Vue({
$('#suitableType').prop('title', this.marginTemplate.TradeTypes);
}
});
},
//新增=多选:按选中客户逐条调用原有单条保存接口(后端不变,每条走完整互斥校验);
//编辑=单条,保持原行为。逐条收集成败,结束统一汇总——部分失败时列表刷新已成功部分,弹窗留在
//当前内容上供修正重试(已成功的客户重试会因同天同结构互斥校验失败,不会重复入库)
saveMarginTemplateV2() {
var thisObj = this;
main.post("/client_margin_template/saveClientMarginTemplate", { clientMarginTemplate: thisObj.clientMarginTemplate }).done(function (resp) {
try {
window.parent.reloadmargin_template();
(parent || window).layer.closeAll();
} catch (e) { }
var ids = this.isAdd ? (this.selectedClientIds || []) : [this.clientMarginTemplate.ClientId];
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) {
@@ -51,28 +83,6 @@ const vue = new Vue({
vueDetail.ruleType = vue.marginTemplate.RuleType;
$("#myModal").modal("show");
},
SetClientLevelDropDown() {
$.ajax({
type: "get",
url: "/client_margin_template/GetAllClientLevels",
success: function (data) {
if (data.obj) {
for (var i = 0; i < data.obj.length; i++) {
vue.clientLevels.push(data.obj[i]);
}
if (vue.isAdd) {
vue.clientMarginTemplate.ClientLevel = data.obj[0].Value;
}
}
},
error: function (msg) {
alert("error:" + msg);
}
});
},
SetClientNameDropDown() {
$.ajax({
type: "get",
@@ -84,6 +94,14 @@ const vue = new Vue({
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);
@@ -92,58 +110,11 @@ const vue = new Vue({
}
},
mounted() {
if (page.isGFSM) {
if (this.isAdd) {
this.isClientNameDisabled = true;
}
else {
if (this.clientMarginTemplate.ClientId == "0") this.isClientNameDisabled = true;
if (this.clientMarginTemplate.ClientLevel == "") this.isClientLevelDisabled = true;
}
this.SetClientLevelDropDown();
}
this.SetClientNameDropDown();
if (this.marginTemplate.TradeTypes != null && this.marginTemplate.TradeTypes != 'undefined') {
$('#suitableType').prop('title', this.marginTemplate.TradeTypes);
}
},
watch: {
'clientMarginTemplate.ClientLevel': {
handler(newVal, oldVal) {
if (newVal == '') {
this.isClientLevelDisabled = true;
this.isClientNameDisabled = false;
if (this.clientMarginTemplate.ClientId == '0') {
if (this.clientNames.length > 0) {
this.clientMarginTemplate.ClientId = this.clientNames[0].Value;
}
}
}
},
immediate: false
},
'clientMarginTemplate.ClientId': {
handler(newVal, oldVal) {
if (newVal == '0') {
this.isClientNameDisabled = true;
this.isClientLevelDisabled = false;
if (this.clientMarginTemplate.ClientLevel == '') {
if (this.clientLevels.length > 0) {
this.clientMarginTemplate.ClientLevel = this.clientLevels[0].Value;
}
}
}
},
immediate: false
}
},
components: {
'vue-datepicker': FastVue.vueDatePicker(),
@@ -151,6 +122,17 @@ const vue = new Vue({
}
});
//多选 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();
@@ -174,33 +156,15 @@ $(function () {
$(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();
var width = 152;
for (var i = 0; i < consClients.length; i++) {
let ele = document.createElement('span')
ele.innerText = consClients[i].Name;
ele.style.fontSize = '14px';
document.documentElement.append(ele);
var charLength = ele.offsetWidth + 28;//滚动条
document.documentElement.removeChild(ele);
if (charLength > width) {
width = charLength
}
}
//if (!page.isGFSM) {
// let autoClient = FastVue.autocomplete(document.getElementById('ClientId'), {
// nameField: 'Name', valueField: 'id', searchField: ['Name', 'PinYin'], width: width,
// lookup: consClients, onSelect: vue.changeClient
// });
// let clientId = page.clientMarginTemplate.ClientId;
// let client = clientId ? consClients.find(x => x.id === clientId) : null;
// !client && page.isAdd && (client = consClients[0]);
// autoClient.setData(client);
// vue.changeClient(client);
//}
$('select.chosen-select[multiple]').each(function () {
refreshChosenSearchField(this);
});
});
var vueDetail = new Vue({