✨ 评分减分累计与最高扣分、用户部门标签联动树,并修复对账导入与结算调整等
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+31
-10
@@ -105,12 +105,27 @@ const isJsonContentType = res => {
|
|||||||
return contentType.includes('application/json') || contentType.includes('text/json');
|
return contentType.includes('application/json') || contentType.includes('text/json');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为可按文本读取的 Blob / Blob-like(含跨 realm)。
|
||||||
|
* 禁止对普通业务 JSON 对象调用 .text()。
|
||||||
|
*/
|
||||||
|
const isBlobLikeData = value =>
|
||||||
|
value != null &&
|
||||||
|
typeof value === 'object' &&
|
||||||
|
typeof value.size === 'number' &&
|
||||||
|
typeof value.type === 'string' &&
|
||||||
|
typeof value.text === 'function';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断响应体是否已是解析好的对象(无需再解析)。
|
* 判断响应体是否已是解析好的对象(无需再解析)。
|
||||||
* 注意 Blob 也是 object,必须排除,否则会把待解析的文件流误判为已完成。
|
* 注意 Blob 也是 object,必须排除,否则会把待解析的文件流误判为已完成。
|
||||||
*/
|
*/
|
||||||
const isPlainObject = value =>
|
const isPlainObject = value =>
|
||||||
value !== null && typeof value === 'object' && !(value instanceof Blob);
|
value !== null &&
|
||||||
|
typeof value === 'object' &&
|
||||||
|
!(value instanceof Blob) &&
|
||||||
|
!isBlobLikeData(value) &&
|
||||||
|
!(value instanceof ArrayBuffer);
|
||||||
//跨域请求,允许保存cookie
|
//跨域请求,允许保存cookie
|
||||||
axios.defaults.withCredentials = true;
|
axios.defaults.withCredentials = true;
|
||||||
// NProgress Configuration
|
// NProgress Configuration
|
||||||
@@ -190,15 +205,20 @@ axios.interceptors.response.use(
|
|||||||
// 用户就会以为系统崩了——而后端其实返回了明确的业务提示(如「未读取到数据行…」)。
|
// 用户就会以为系统崩了——而后端其实返回了明确的业务提示(如「未读取到数据行…」)。
|
||||||
// 这里按 Content-Type 判断:JSON 才解析回对象;真·文件流(Excel 等)保持原样不动。
|
// 这里按 Content-Type 判断:JSON 才解析回对象;真·文件流(Excel 等)保持原样不动。
|
||||||
// 兼容 Blob(浏览器 responseType='blob')与 string(非浏览器环境)两种形态。
|
// 兼容 Blob(浏览器 responseType='blob')与 string(非浏览器环境)两种形态。
|
||||||
|
// 仅对真正的 Blob/Blob-like 调 .text(),避免业务 JSON 已解析后出现
|
||||||
|
// 「e.data.text is not a function」。
|
||||||
if (isJsonContentType(res) && !isPlainObject(res.data)) {
|
if (isJsonContentType(res) && !isPlainObject(res.data)) {
|
||||||
try {
|
try {
|
||||||
let text = '';
|
let text = '';
|
||||||
if (typeof res.data?.text === 'function') {
|
const raw = res.data;
|
||||||
text = await res.data.text();
|
if (isBlobLikeData(raw) || (typeof Blob !== 'undefined' && raw instanceof Blob)) {
|
||||||
} else if (typeof res.data === 'string') {
|
text = await raw.text();
|
||||||
text = res.data;
|
} else if (typeof raw === 'string') {
|
||||||
} else if (typeof res.data?.arrayBuffer === 'function') {
|
text = raw;
|
||||||
text = new TextDecoder().decode(await res.data.arrayBuffer());
|
} else if (raw && typeof raw.arrayBuffer === 'function') {
|
||||||
|
text = new TextDecoder().decode(await raw.arrayBuffer());
|
||||||
|
} else if (raw instanceof ArrayBuffer) {
|
||||||
|
text = new TextDecoder().decode(raw);
|
||||||
}
|
}
|
||||||
const parsed = typeof text === 'string' && text ? JSON.parse(text) : null;
|
const parsed = typeof text === 'string' && text ? JSON.parse(text) : null;
|
||||||
if (parsed && typeof parsed === 'object') {
|
if (parsed && typeof parsed === 'object') {
|
||||||
@@ -208,10 +228,11 @@ axios.interceptors.response.use(
|
|||||||
// 声明是 JSON 却解析失败:保持原样,交给下游按文件处理
|
// 声明是 JSON 却解析失败:保持原样,交给下游按文件处理
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//获取状态信息
|
// 获取状态信息:文件流没有 code/msg,必须退回 HTTP status,禁止读 Blob 上的幽灵字段
|
||||||
const status = res.data.error_code || res.data.code || res.status;
|
const payload = isPlainObject(res.data) ? res.data : {};
|
||||||
|
const status = Number(payload.error_code || payload.code || res.status);
|
||||||
const statusWhiteList = website.statusWhiteList || [];
|
const statusWhiteList = website.statusWhiteList || [];
|
||||||
const message = res.data.msg || res.data.error_description || '系统错误';
|
const message = payload.msg || payload.error_description || '系统错误';
|
||||||
//如果在白名单里则自行catch逻辑处理
|
//如果在白名单里则自行catch逻辑处理
|
||||||
if (statusWhiteList.includes(status)) return Promise.reject(res);
|
if (statusWhiteList.includes(status)) return Promise.reject(res);
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export const parseBlobJson = async data => {
|
|||||||
*
|
*
|
||||||
* 这里补上这一步:从错误响应的 Blob 里取出后端 `msg`,取不到再退回通用文案。
|
* 这里补上这一步:从错误响应的 Blob 里取出后端 `msg`,取不到再退回通用文案。
|
||||||
*/
|
*/
|
||||||
const resolveErrorMessage = async error => {
|
export const resolveImportErrorMessage = async error => {
|
||||||
const payload = error?.response?.data ?? error?.data;
|
const payload = error?.response?.data ?? error?.data;
|
||||||
if (isBusinessJson(payload) && payload.msg) {
|
if (isBusinessJson(payload) && payload.msg) {
|
||||||
return payload.msg;
|
return payload.msg;
|
||||||
@@ -109,6 +109,26 @@ const resolveErrorMessage = async error => {
|
|||||||
return fallback;
|
return fallback;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一消费导入接口响应:成功 JSON / 失败 Excel 流。
|
||||||
|
* 兼容 axios 已把 JSON Blob 解析成对象的情况,禁止再对普通对象调 .text()。
|
||||||
|
*/
|
||||||
|
export const consumeImportResponse = async (res, options = {}) => {
|
||||||
|
const { businessName = '导入', dayjsFormat } = options;
|
||||||
|
if (isExcelResponse(res)) {
|
||||||
|
return {
|
||||||
|
kind: 'excel',
|
||||||
|
blob: res?.data,
|
||||||
|
fileName: `${businessName}导入失败明细${dayjsFormat || ''}.xlsx`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const result = isBusinessJson(res?.data) ? res.data : await parseBlobJson(res?.data);
|
||||||
|
if (Number(result?.code) !== 200) {
|
||||||
|
throw new Error(result?.msg || '导入失败');
|
||||||
|
}
|
||||||
|
return { kind: 'success', result };
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加工后端返回的导入失败明细 Excel。
|
* 加工后端返回的导入失败明细 Excel。
|
||||||
* 若传入 failDetailDecorator(函数),则动态加载 exceljs 读取 blob 流,
|
* 若传入 failDetailDecorator(函数),则动态加载 exceljs 读取 blob 流,
|
||||||
@@ -235,7 +255,7 @@ export const handleImportExcel = async (vm, option, column, businessName, onSucc
|
|||||||
// 拦截器已提示过的错误(如后端 400 返回的业务提示)不再重复弹一次,
|
// 拦截器已提示过的错误(如后端 400 返回的业务提示)不再重复弹一次,
|
||||||
// 只保留一次清晰的中文提示。
|
// 只保留一次清晰的中文提示。
|
||||||
if (!error?._notified) {
|
if (!error?._notified) {
|
||||||
vm.$message.error(await resolveErrorMessage(error));
|
vm.$message.error(await resolveImportErrorMessage(error));
|
||||||
}
|
}
|
||||||
notifyUploadError(option, error);
|
notifyUploadError(option, error);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -13824,13 +13824,13 @@ export default {
|
|||||||
}
|
}
|
||||||
const unavailableWaybills = this.selectionList.filter(
|
const unavailableWaybills = this.selectionList.filter(
|
||||||
row =>
|
row =>
|
||||||
this.statusValue(row) !== 'pending' ||
|
!['pending', 'processing', 'running'].includes(this.statusValue(row)) ||
|
||||||
row.loadingId ||
|
row.loadingId ||
|
||||||
row.loadingManageId ||
|
row.loadingManageId ||
|
||||||
row.loadingNo
|
row.loadingNo
|
||||||
);
|
);
|
||||||
if (unavailableWaybills.length) {
|
if (unavailableWaybills.length) {
|
||||||
this.$message.warning('公路配载仅支持进行中且未配载的运单');
|
this.$message.warning('公路配载仅支持待执行/进行中且未关联有效配载单的运单');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -94,7 +94,7 @@
|
|||||||
<div class="route-node-form">
|
<div class="route-node-form">
|
||||||
<el-form-item label="途经地" required class="route-address-form-item"
|
<el-form-item label="途经地" required class="route-address-form-item"
|
||||||
><div class="route-address-inputs"
|
><div class="route-address-inputs"
|
||||||
><el-input v-if="isNonRoadAddress(`route-${index}`)" v-model="route.departureName" class="route-address-name" readonly :disabled="isBasicRouteReadonly || !route.transportType" placeholder="请先选择运输方式" /><el-cascader v-else v-model="addressRegionPaths[`route-${index}`]" class="route-address-name" :options="regionOptions" :props="regionCascaderProps" :loading="regionLoading" :disabled="isBasicRouteReadonly || !route.transportType" placeholder="请选择省市区" clearable filterable @visible-change="visible => visible && loadRegionOptions()" @change="value => handleAddressRegionChange(`route-${index}`, value)" /><el-input v-model="route.departureAddress" class="route-address-detail" readonly :disabled="isBasicRouteReadonly || !route.transportType" placeholder="请先选择运输方式" @click="handleAddressDetailClick(`route-${index}`)" /><el-tooltip content="选择常用地址" placement="top"><el-button class="address-picker-button" type="primary" link :icon="OfficeBuilding" :disabled="isBasicRouteReadonly || !route.transportType" @click="openCommonAddress(`route-${index}`)" /></el-tooltip></div
|
><el-input v-if="isNonRoadAddress(`route-${index}`)" v-model="route.departureName" class="route-address-name" readonly :disabled="isBasicRouteReadonly" placeholder="请选择站点" /><el-cascader v-else v-model="addressRegionPaths[`route-${index}`]" class="route-address-name" :options="regionOptions" :props="regionCascaderProps" :loading="regionLoading" :disabled="isBasicRouteReadonly" placeholder="请选择省市区" clearable filterable @visible-change="visible => visible && loadRegionOptions()" @change="value => handleAddressRegionChange(`route-${index}`, value)" /><el-input v-model="route.departureAddress" class="route-address-detail" readonly :disabled="isBasicRouteReadonly" placeholder="请输入详细地址" @click="handleAddressDetailClick(`route-${index}`)" /><el-tooltip content="选择常用地址" placement="top"><el-button class="address-picker-button" type="primary" link :icon="OfficeBuilding" :disabled="isBasicRouteReadonly" @click="openCommonAddress(`route-${index}`)" /></el-tooltip></div
|
||||||
></el-form-item>
|
></el-form-item>
|
||||||
<el-form-item label="联系人" class="route-contact-form-item"><el-input v-model="route.departureContact" :disabled="isBasicRouteReadonly" placeholder="请输入" /></el-form-item>
|
<el-form-item label="联系人" class="route-contact-form-item"><el-input v-model="route.departureContact" :disabled="isBasicRouteReadonly" placeholder="请输入" /></el-form-item>
|
||||||
<el-form-item label="联系方式" class="route-phone-form-item"><div class="route-contact-actions"><el-input v-model="route.departurePhone" :disabled="isBasicRouteReadonly" placeholder="请输入" /><template v-if="!isBasicRouteReadonly"><el-tooltip v-if="index === form.routes.length - 1" content="新增途经点" placement="top"><el-button class="route-action-button" type="primary" link @click="addRoute"><el-icon><CirclePlus /></el-icon></el-button></el-tooltip><el-tooltip content="删除途经点" placement="top"><el-button class="route-action-button" type="danger" link @click="removeRoute(index)"><el-icon><Remove /></el-icon></el-button></el-tooltip></template></div></el-form-item>
|
<el-form-item label="联系方式" class="route-phone-form-item"><div class="route-contact-actions"><el-input v-model="route.departurePhone" :disabled="isBasicRouteReadonly" placeholder="请输入" /><template v-if="!isBasicRouteReadonly"><el-tooltip v-if="index === form.routes.length - 1" content="新增途经点" placement="top"><el-button class="route-action-button" type="primary" link @click="addRoute"><el-icon><CirclePlus /></el-icon></el-button></el-tooltip><el-tooltip content="删除途经点" placement="top"><el-button class="route-action-button" type="danger" link @click="removeRoute(index)"><el-icon><Remove /></el-icon></el-button></el-tooltip></template></div></el-form-item>
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
<div class="route-node-form">
|
<div class="route-node-form">
|
||||||
<el-form-item label="收货地" required class="route-address-form-item"
|
<el-form-item label="收货地" required class="route-address-form-item"
|
||||||
><div class="route-address-inputs"
|
><div class="route-address-inputs"
|
||||||
><el-input v-if="isNonRoadAddress('arrival')" v-model="form.arrivalName" class="route-address-name" readonly :disabled="isBasicRouteReadonly || !form.finalTransportType" placeholder="请先选择运输方式" /><el-cascader v-else v-model="addressRegionPaths.arrival" class="route-address-name" :options="regionOptions" :props="regionCascaderProps" :loading="regionLoading" :disabled="isBasicRouteReadonly || !form.finalTransportType" placeholder="请选择省市区" clearable filterable @visible-change="visible => visible && loadRegionOptions()" @change="value => handleAddressRegionChange('arrival', value)" /><el-input v-model="form.arrivalAddress" class="route-address-detail" readonly :disabled="isBasicRouteReadonly || !form.finalTransportType" placeholder="请先选择运输方式" @click="handleAddressDetailClick('arrival')" /><el-tooltip content="选择常用地址" placement="top"><el-button class="address-picker-button" type="primary" link :icon="OfficeBuilding" :disabled="isBasicRouteReadonly || !form.finalTransportType" @click="openCommonAddress('arrival')" /></el-tooltip></div
|
><el-input v-if="isNonRoadAddress('arrival')" v-model="form.arrivalName" class="route-address-name" readonly :disabled="isBasicRouteReadonly" placeholder="请选择站点" /><el-cascader v-else v-model="addressRegionPaths.arrival" class="route-address-name" :options="regionOptions" :props="regionCascaderProps" :loading="regionLoading" :disabled="isBasicRouteReadonly" placeholder="请选择省市区" clearable filterable @visible-change="visible => visible && loadRegionOptions()" @change="value => handleAddressRegionChange('arrival', value)" /><el-input v-model="form.arrivalAddress" class="route-address-detail" readonly :disabled="isBasicRouteReadonly" placeholder="请输入详细地址" @click="handleAddressDetailClick('arrival')" /><el-tooltip content="选择常用地址" placement="top"><el-button class="address-picker-button" type="primary" link :icon="OfficeBuilding" :disabled="isBasicRouteReadonly" @click="openCommonAddress('arrival')" /></el-tooltip></div
|
||||||
></el-form-item>
|
></el-form-item>
|
||||||
<el-form-item label="联系人" class="route-contact-form-item"
|
<el-form-item label="联系人" class="route-contact-form-item"
|
||||||
><el-input v-model="form.arrivalContact" :disabled="isBasicRouteReadonly" placeholder="请输入" /></el-form-item>
|
><el-input v-model="form.arrivalContact" :disabled="isBasicRouteReadonly" placeholder="请输入" /></el-form-item>
|
||||||
@@ -714,7 +714,7 @@ export default {
|
|||||||
'msg',
|
'msg',
|
||||||
'zip',
|
'zip',
|
||||||
],
|
],
|
||||||
transports: ['公路运输', '铁路运输', '水路运输', '航空运输'],
|
transports: ['铁路运输', '水路运输', '航空运输', '公路运输'],
|
||||||
quantityUnitOptions: [],
|
quantityUnitOptions: [],
|
||||||
quantityUnitLoading: false,
|
quantityUnitLoading: false,
|
||||||
form: {
|
form: {
|
||||||
@@ -798,6 +798,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
formatContractOptionLabel,
|
formatContractOptionLabel,
|
||||||
defaultQuantityUnit() {
|
defaultQuantityUnit() {
|
||||||
|
if (this.quantityUnitOptions.includes('吨')) return '吨';
|
||||||
return this.quantityUnitOptions[0] || '吨';
|
return this.quantityUnitOptions[0] || '吨';
|
||||||
},
|
},
|
||||||
loadQuantityUnitOptions() {
|
loadQuantityUnitOptions() {
|
||||||
@@ -1164,22 +1165,30 @@ export default {
|
|||||||
const field = this.addressModel(target);
|
const field = this.addressModel(target);
|
||||||
if (!field) return false;
|
if (!field) return false;
|
||||||
const siteCode = String(field.model[field.siteCode] || '').trim();
|
const siteCode = String(field.model[field.siteCode] || '').trim();
|
||||||
// 地址展示模式由实际选择的数据决定:有站点编码表示非公路站点名称,
|
// 展示模式由已选数据决定:有站点编码则显示站点名,否则省市区;暂不按运输方式强制站点限制。
|
||||||
// 没有站点编码则保留省市区级联,避免切换运输方式时改写已有地址。
|
|
||||||
return Boolean(siteCode && siteCode !== '/');
|
return Boolean(siteCode && siteCode !== '/');
|
||||||
},
|
},
|
||||||
|
hasAddressValue(target) {
|
||||||
|
const field = this.addressModel(target);
|
||||||
|
if (!field) return false;
|
||||||
|
return Boolean(
|
||||||
|
String(field.model[field.address] || '').trim() || String(field.model[field.name] || '').trim()
|
||||||
|
);
|
||||||
|
},
|
||||||
|
tipEmptyAddressDetail(target) {
|
||||||
|
// 替代原「请先选择运输方式」placeholder:空地址点击时 toast,已有值不再提示。
|
||||||
|
if (!this.hasAddressValue(target)) {
|
||||||
|
this.$message.warning('请输入详细地址');
|
||||||
|
}
|
||||||
|
},
|
||||||
resolveAddressRegionCode(row = {}) {
|
resolveAddressRegionCode(row = {}) {
|
||||||
return row.regionCode || row.districtCode || row.areaCode || row.adcode || '';
|
return row.regionCode || row.districtCode || row.areaCode || row.adcode || '';
|
||||||
},
|
},
|
||||||
openCommonAddress(target) {
|
openCommonAddress(target) {
|
||||||
if (this.isBasicRouteReadonly) return;
|
if (this.isBasicRouteReadonly) return;
|
||||||
|
this.tipEmptyAddressDetail(target);
|
||||||
this.addressTarget = target;
|
this.addressTarget = target;
|
||||||
if (this.addressTransportMode(target) !== 'road') {
|
// 暂不按运输方式限制站点:统一走常用地址选择。
|
||||||
this.stationQuery = {};
|
|
||||||
this.stationPage.current = 1;
|
|
||||||
this.stationDialogVisible = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.addressQuery = {};
|
this.addressQuery = {};
|
||||||
this.addressPage.current = 1;
|
this.addressPage.current = 1;
|
||||||
this.addressDialogVisible = true;
|
this.addressDialogVisible = true;
|
||||||
@@ -1279,18 +1288,11 @@ export default {
|
|||||||
},
|
},
|
||||||
handleAddressDetailClick(target) {
|
handleAddressDetailClick(target) {
|
||||||
if (this.isBasicRouteReadonly) return;
|
if (this.isBasicRouteReadonly) return;
|
||||||
if (this.addressTransportMode(target) !== 'road') {
|
this.tipEmptyAddressDetail(target);
|
||||||
this.openCommonAddress(target);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.openAddressMap(target);
|
this.openAddressMap(target);
|
||||||
},
|
},
|
||||||
openAddressMap(target) {
|
openAddressMap(target) {
|
||||||
if (this.isBasicRouteReadonly) return;
|
if (this.isBasicRouteReadonly) return;
|
||||||
if (this.addressTransportMode(target) !== 'road') {
|
|
||||||
this.$message.info('非公路运输地址不能使用地图选择');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const field = this.addressModel(target);
|
const field = this.addressModel(target);
|
||||||
if (!field) return;
|
if (!field) return;
|
||||||
this.mapTarget = target;
|
this.mapTarget = target;
|
||||||
@@ -1690,7 +1692,11 @@ export default {
|
|||||||
'',
|
'',
|
||||||
cargoTypePath,
|
cargoTypePath,
|
||||||
quantity: row.quantity || '',
|
quantity: row.quantity || '',
|
||||||
quantityUnit: row.quantityUnit || this.defaultQuantityUnit(),
|
// 常用货物单位若为空或「个」,新建总单统一默认「吨」
|
||||||
|
quantityUnit:
|
||||||
|
!row.quantityUnit || String(row.quantityUnit).trim() === '个'
|
||||||
|
? this.defaultQuantityUnit()
|
||||||
|
: row.quantityUnit,
|
||||||
packageType: row.packageType || row.package || '',
|
packageType: row.packageType || row.package || '',
|
||||||
brand: row.brand || '',
|
brand: row.brand || '',
|
||||||
specification: row.specification || row.spec || '',
|
specification: row.specification || row.spec || '',
|
||||||
@@ -1775,17 +1781,21 @@ export default {
|
|||||||
return formatUpdateUserName(row);
|
return formatUpdateUserName(row);
|
||||||
},
|
},
|
||||||
selectRoute(route) {
|
selectRoute(route) {
|
||||||
|
// 先写入运输方式(会触发地点清空),再回填线路地址,避免被清空逻辑覆盖。
|
||||||
|
this.form.finalTransportType = '公路运输';
|
||||||
|
if (!this.form.routes.length) this.addRoute();
|
||||||
|
if (this.form.routes[0]) this.form.routes[0].transportType = '公路运输';
|
||||||
this.form.departureName = route.departureName || '';
|
this.form.departureName = route.departureName || '';
|
||||||
this.form.departureAddress = route.departureAddress || '';
|
this.form.departureAddress = route.departureAddress || '';
|
||||||
this.form.departureContact = route.departureContact || '';
|
this.form.departureContact = route.departureContact || '';
|
||||||
this.form.departurePhone = route.departurePhone || '';
|
this.form.departurePhone = route.departurePhone || '';
|
||||||
|
this.form.departureSiteCode = '';
|
||||||
this.form.arrivalName = route.arrivalName || '';
|
this.form.arrivalName = route.arrivalName || '';
|
||||||
this.form.arrivalAddress = route.arrivalAddress || '';
|
this.form.arrivalAddress = route.arrivalAddress || '';
|
||||||
this.form.arrivalContact = route.arrivalContact || '';
|
this.form.arrivalContact = route.arrivalContact || '';
|
||||||
this.form.arrivalPhone = route.arrivalPhone || '';
|
this.form.arrivalPhone = route.arrivalPhone || '';
|
||||||
|
this.form.arrivalSiteCode = '';
|
||||||
this.syncAddressRegionPaths();
|
this.syncAddressRegionPaths();
|
||||||
this.form.finalTransportType = '公路运输';
|
|
||||||
if (!this.form.routes.length) this.addRoute();
|
|
||||||
this.routeDialogVisible = false;
|
this.routeDialogVisible = false;
|
||||||
},
|
},
|
||||||
createRoute(index = this.form.routes.length) {
|
createRoute(index = this.form.routes.length) {
|
||||||
|
|||||||
@@ -10414,13 +10414,13 @@ export default {
|
|||||||
}
|
}
|
||||||
const unavailableWaybills = this.selectionList.filter(
|
const unavailableWaybills = this.selectionList.filter(
|
||||||
row =>
|
row =>
|
||||||
!['processing', 'running'].includes(this.statusValue(row)) ||
|
!['pending', 'processing', 'running'].includes(this.statusValue(row)) ||
|
||||||
row.loadingId ||
|
row.loadingId ||
|
||||||
row.loadingManageId ||
|
row.loadingManageId ||
|
||||||
row.loadingNo
|
row.loadingNo
|
||||||
);
|
);
|
||||||
if (unavailableWaybills.length) {
|
if (unavailableWaybills.length) {
|
||||||
this.$message.warning('公路配载仅支持进行中且未配载的运单');
|
this.$message.warning('公路配载仅支持待执行/进行中且未关联有效配载单的运单');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -635,10 +635,11 @@
|
|||||||
min-width="100" /><el-table-column
|
min-width="100" /><el-table-column
|
||||||
prop="quantity"
|
prop="quantity"
|
||||||
label="数量"
|
label="数量"
|
||||||
min-width="90" /><el-table-column
|
min-width="120"
|
||||||
prop="quantityUnit"
|
><template #default="{ row }">{{
|
||||||
label="数量单位"
|
formatCargoQuantityDisplay(row)
|
||||||
min-width="100" /><el-table-column
|
}}</template></el-table-column
|
||||||
|
><el-table-column
|
||||||
prop="materialCode"
|
prop="materialCode"
|
||||||
label="物料编码"
|
label="物料编码"
|
||||||
min-width="120" /><el-table-column
|
min-width="120" /><el-table-column
|
||||||
@@ -897,6 +898,16 @@
|
|||||||
>
|
>
|
||||||
<div title="配载信息" class="loading-manage-dialog__load-info-card">
|
<div title="配载信息" class="loading-manage-dialog__load-info-card">
|
||||||
<section-card>
|
<section-card>
|
||||||
|
<div class="loading-manage-dialog__admission-tip">
|
||||||
|
<div class="loading-manage-dialog__admission-title">可配载条件</div>
|
||||||
|
<div class="loading-manage-dialog__admission-text">
|
||||||
|
同时满足以下条件的运单可被配载:运输方式为「公路」;运单状态为「待执行」或「进行中」;未关联其他有效配载单。
|
||||||
|
</div>
|
||||||
|
<div class="loading-manage-dialog__admission-title">配载后状态说明</div>
|
||||||
|
<div class="loading-manage-dialog__admission-text">
|
||||||
|
子运单:按项目过程配置是否需司机接单,无需接单则为进行中,需接单则为待执行。配载单:按所选运单所属项目的过程配置,需接单则为待执行,无需接单则为进行中。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="loading-manage-dialog__candidate-search">
|
<div class="loading-manage-dialog__candidate-search">
|
||||||
<el-row :gutter="16">
|
<el-row :gutter="16">
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
@@ -1425,8 +1436,9 @@
|
|||||||
<el-table-column prop="cargoName" label="货物名称" min-width="140" />
|
<el-table-column prop="cargoName" label="货物名称" min-width="140" />
|
||||||
<el-table-column prop="cargoType" label="货物类型" min-width="140" />
|
<el-table-column prop="cargoType" label="货物类型" min-width="140" />
|
||||||
<el-table-column prop="packageType" label="包装" min-width="100" />
|
<el-table-column prop="packageType" label="包装" min-width="100" />
|
||||||
<el-table-column prop="quantity" label="数量" min-width="100" />
|
<el-table-column prop="quantity" label="数量" min-width="120">
|
||||||
<el-table-column prop="quantityUnit" label="数量单位" min-width="100" />
|
<template #default="{ row }">{{ formatCargoQuantityDisplay(row) }}</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="materialCode" label="物料编码" min-width="130" />
|
<el-table-column prop="materialCode" label="物料编码" min-width="130" />
|
||||||
<el-table-column prop="equipmentCode" label="设备编码" min-width="130" />
|
<el-table-column prop="equipmentCode" label="设备编码" min-width="130" />
|
||||||
<el-table-column prop="brand" label="品牌" min-width="120" />
|
<el-table-column prop="brand" label="品牌" min-width="120" />
|
||||||
@@ -3900,12 +3912,10 @@ export default {
|
|||||||
try {
|
try {
|
||||||
const query = {
|
const query = {
|
||||||
...this.buildQueryParams(this.candidateQuery),
|
...this.buildQueryParams(this.candidateQuery),
|
||||||
// 待执行(需接单未接)与进行中(无需接单或已接单)均可配载
|
// 与后端 LoadableAdmissionSupport 一致:公路 + 待执行/进行中 + 未关联有效配载单
|
||||||
businessStatus: 'pending,running',
|
loadableOnly: 1,
|
||||||
onlyUnassignedLoading: 1,
|
|
||||||
// 仅查当前账户所属组织及子组织下的运单
|
// 仅查当前账户所属组织及子组织下的运单
|
||||||
allDept: 0,
|
allDept: 0,
|
||||||
...(this.dialogMode === 'add' ? { transportType: 'road' } : {}),
|
|
||||||
};
|
};
|
||||||
const transportType = this.getCandidateTransportTypeQuery(query.transportType);
|
const transportType = this.getCandidateTransportTypeQuery(query.transportType);
|
||||||
const res = await getWaybillList(
|
const res = await getWaybillList(
|
||||||
@@ -4195,14 +4205,28 @@ export default {
|
|||||||
return [this.normalizeCargoRow(row, row)];
|
return [this.normalizeCargoRow(row, row)];
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
formatCargoQuantityDisplay(row = {}) {
|
||||||
|
const quantity = blankCargoSentinel(row.quantity ?? row.cargoQuantity ?? '');
|
||||||
|
const unit = String(
|
||||||
|
blankCargoSentinel(
|
||||||
|
row.quantityUnit ||
|
||||||
|
row.goodsQuantityUnit ||
|
||||||
|
row.cargoUnit ||
|
||||||
|
row.unit ||
|
||||||
|
''
|
||||||
|
) || ''
|
||||||
|
).trim();
|
||||||
|
if (quantity === '' || quantity === undefined || quantity === null) {
|
||||||
|
return unit || '';
|
||||||
|
}
|
||||||
|
return unit ? `${quantity} ${unit}` : String(quantity);
|
||||||
|
},
|
||||||
normalizeCargoRow(waybill, item = {}) {
|
normalizeCargoRow(waybill, item = {}) {
|
||||||
return sanitizeCargoRow({
|
return sanitizeCargoRow({
|
||||||
projectName: item.projectName || waybill.projectName || '',
|
projectName: item.projectName || waybill.projectName || '',
|
||||||
cargoName: item.cargoName || item.goodsName || waybill.cargoName || '',
|
cargoName: item.cargoName || item.goodsName || waybill.cargoName || '',
|
||||||
cargoType: item.cargoType || item.goodsType || waybill.cargoType || '',
|
cargoType: item.cargoType || item.goodsType || waybill.cargoType || '',
|
||||||
packageType: item.packageType || item.package || '',
|
packageType: item.packageType || item.package || '',
|
||||||
weight: item.weight || item.cargoWeight || '',
|
|
||||||
volume: item.volume || item.cargoVolume || '',
|
|
||||||
quantity: item.quantity || item.cargoQuantity || waybill.quantity || '',
|
quantity: item.quantity || item.cargoQuantity || waybill.quantity || '',
|
||||||
quantityUnit:
|
quantityUnit:
|
||||||
item.quantityUnit ||
|
item.quantityUnit ||
|
||||||
@@ -4399,8 +4423,8 @@ export default {
|
|||||||
try {
|
try {
|
||||||
const payload = {
|
const payload = {
|
||||||
...this.normalizePayload(),
|
...this.normalizePayload(),
|
||||||
// 进行中编辑保持 running;其余确认生成正式配载单为待执行
|
// 进行中编辑保持 running;新建/草稿确认由后端按项目是否需接单写入 pending/running
|
||||||
businessStatus: this.isRunningEdit ? 'running' : 'pending',
|
...(this.isRunningEdit ? { businessStatus: 'running' } : {}),
|
||||||
};
|
};
|
||||||
if (this.dialogMode === 'reassign') {
|
if (this.dialogMode === 'reassign') {
|
||||||
await loadingApi.reassign(payload);
|
await loadingApi.reassign(payload);
|
||||||
@@ -4969,6 +4993,30 @@ export default {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading-manage-dialog__admission-tip {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
background: #f5f9ff;
|
||||||
|
border: 1px solid #d9ecff;
|
||||||
|
border-radius: 4px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-manage-dialog__admission-title {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
color: #303133;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-manage-dialog__admission-title + .loading-manage-dialog__admission-text + .loading-manage-dialog__admission-title {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-manage-dialog__admission-text {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
.loading-manage-dialog__candidate-search {
|
.loading-manage-dialog__candidate-search {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|||||||
@@ -203,7 +203,11 @@
|
|||||||
@click="openBatchDialog(row)"
|
@click="openBatchDialog(row)"
|
||||||
>更换运单批次</el-link
|
>更换运单批次</el-link
|
||||||
><el-link
|
><el-link
|
||||||
v-if="(row.processStatus !== '处理完成' || row.auditStatus === '待审核') && row.auditStatus !== '审核驳回'"
|
v-if="
|
||||||
|
row.processStatus !== '处理失败' &&
|
||||||
|
(row.processStatus !== '处理完成' || row.auditStatus === '待审核') &&
|
||||||
|
row.auditStatus !== '审核驳回'
|
||||||
|
"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="openBatchDialog(row)"
|
@click="openBatchDialog(row)"
|
||||||
>更换运单批次</el-link
|
>更换运单批次</el-link
|
||||||
@@ -320,7 +324,7 @@
|
|||||||
<el-form-item label="执行凭证" required
|
<el-form-item label="执行凭证" required
|
||||||
><el-upload
|
><el-upload
|
||||||
action="#"
|
action="#"
|
||||||
accept=".zip,.7z"
|
accept=".zip"
|
||||||
:auto-upload="false"
|
:auto-upload="false"
|
||||||
:limit="1"
|
:limit="1"
|
||||||
:on-change="selectVoucherFile"
|
:on-change="selectVoucherFile"
|
||||||
@@ -332,7 +336,7 @@
|
|||||||
>上传</el-button
|
>上传</el-button
|
||||||
><template #tip
|
><template #tip
|
||||||
><span class="voucher-manage-page__upload-tip"
|
><span class="voucher-manage-page__upload-tip"
|
||||||
>请上传车辆的执行凭证压缩包,支持zip、7z格式。 上传附件大于50M时为超大附件,确认提交后可在“批量补录->上传任务”中查看上传进度。</span
|
>请上传车辆的执行凭证压缩包,仅支持 zip 格式;无法解析的文件将标记为「处理失败」,可重新上传。上传附件大于50M时为超大附件,确认提交后可在“批量补录->上传任务”中查看上传进度。</span
|
||||||
></template
|
></template
|
||||||
></el-upload
|
></el-upload
|
||||||
><el-progress
|
><el-progress
|
||||||
@@ -549,7 +553,7 @@
|
|||||||
ref="resumeFileInput"
|
ref="resumeFileInput"
|
||||||
class="voucher-manage-page__hidden-file"
|
class="voucher-manage-page__hidden-file"
|
||||||
type="file"
|
type="file"
|
||||||
accept=".zip,.7z"
|
accept=".zip"
|
||||||
@change="resumeFileSelected"
|
@change="resumeFileSelected"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -998,6 +1002,12 @@ const uploadVoucher = async file => {
|
|||||||
};
|
};
|
||||||
const selectVoucherFile = file => {
|
const selectVoucherFile = file => {
|
||||||
if (!file?.raw) return;
|
if (!file?.raw) return;
|
||||||
|
const fileName = String(file.raw.name || '').toLowerCase();
|
||||||
|
if (!fileName.endsWith('.zip')) {
|
||||||
|
ElMessage.warning('仅支持 zip 压缩包,无法解析的文件将标记为处理失败');
|
||||||
|
clearFile();
|
||||||
|
return;
|
||||||
|
}
|
||||||
selectedVoucherFile.value = file.raw;
|
selectedVoucherFile.value = file.raw;
|
||||||
editing.fileName = file.raw.name;
|
editing.fileName = file.raw.name;
|
||||||
editing.fileUrl = '';
|
editing.fileUrl = '';
|
||||||
|
|||||||
@@ -244,7 +244,7 @@
|
|||||||
<el-table-column prop="paymentRatio" label="付款比例(%)" min-width="160">
|
<el-table-column prop="paymentRatio" label="付款比例(%)" min-width="160">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-input-number
|
<el-input-number
|
||||||
v-if="multipleSettlementRows && !readonly"
|
v-if="!readonly"
|
||||||
:model-value="row.paymentRatio"
|
:model-value="row.paymentRatio"
|
||||||
:min="0"
|
:min="0"
|
||||||
:max="rowPaymentRatioMax(row)"
|
:max="rowPaymentRatioMax(row)"
|
||||||
@@ -262,7 +262,7 @@
|
|||||||
<el-table-column prop="appliedAmount" label="付款金额" min-width="160">
|
<el-table-column prop="appliedAmount" label="付款金额" min-width="160">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-input-number
|
<el-input-number
|
||||||
v-if="multipleSettlementRows && !readonly"
|
v-if="!readonly"
|
||||||
:model-value="row.appliedAmount"
|
:model-value="row.appliedAmount"
|
||||||
:min="0"
|
:min="0"
|
||||||
:max="Number(row.payableAmount || 0)"
|
:max="Number(row.payableAmount || 0)"
|
||||||
@@ -1014,62 +1014,37 @@ export default {
|
|||||||
referenceSettlementCount() {
|
referenceSettlementCount() {
|
||||||
return this.referenceSettlementNos.length;
|
return this.referenceSettlementNos.length;
|
||||||
},
|
},
|
||||||
multipleSettlementRows() {
|
|
||||||
return this.referenceRows.length > 1;
|
|
||||||
},
|
|
||||||
// 结算单号展示:单张显示单号;多张仅提示数量,明细在下方「结算信息」表,悬停可看全部单号
|
// 结算单号展示:单张显示单号;多张仅提示数量,明细在下方「结算信息」表,悬停可看全部单号
|
||||||
referenceDisplayText() {
|
referenceDisplayText() {
|
||||||
if (this.referenceSettlementCount > 1) return `已选 ${this.referenceSettlementCount} 张结算单`;
|
if (this.referenceSettlementCount > 1) return `已选 ${this.referenceSettlementCount} 张结算单`;
|
||||||
return this.referenceLabel;
|
return this.referenceLabel;
|
||||||
},
|
},
|
||||||
settlementRows() {
|
settlementRows() {
|
||||||
if (this.referenceRows.length > 1) {
|
const source = this.referenceRows.length
|
||||||
// 多结算单:行内比例/金额独立维护,不回落到表头比例(表头与结算信息不关联)
|
? this.referenceRows
|
||||||
return this.referenceRows.map(item => {
|
: this.referenceLabel
|
||||||
const paymentRatio =
|
? [
|
||||||
item.paymentRatio === undefined || item.paymentRatio === null || item.paymentRatio === ''
|
{
|
||||||
? 0
|
settlementNo: this.referenceLabel,
|
||||||
: Number(item.paymentRatio);
|
settlementAmount: this.form.settlementAmount,
|
||||||
const appliedAmount = Number(item.appliedAmount || 0);
|
payableAmount: this.form.payableAmount,
|
||||||
return {
|
paymentRatio: null,
|
||||||
...item,
|
appliedAmount: null,
|
||||||
paymentRatio,
|
paymentSequence: 1,
|
||||||
appliedAmount,
|
},
|
||||||
...this.buildSettlementRatioHint({ ...item, paymentRatio }),
|
]
|
||||||
};
|
: [];
|
||||||
});
|
// 结算行比例/金额只取行内数据,不回落到付款基本信息
|
||||||
}
|
return source.map(item => {
|
||||||
if (this.referenceRows.length) {
|
const paymentRatio = this.nullableNumber(item.paymentRatio);
|
||||||
return this.referenceRows.map(item => {
|
const appliedAmount = this.nullableNumber(item.appliedAmount);
|
||||||
const appliedAmount =
|
return {
|
||||||
item.fixedAppliedAmount === true
|
...item,
|
||||||
? Number(item.appliedAmount || 0)
|
paymentRatio,
|
||||||
: Number(
|
appliedAmount,
|
||||||
(
|
...this.buildSettlementRatioHint({ ...item, paymentRatio }),
|
||||||
(Number(item.settlementAmount || 0) * Number(this.form.paymentRatio || 0)) /
|
};
|
||||||
100
|
});
|
||||||
).toFixed(2)
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
paymentRatio: this.form.paymentRatio,
|
|
||||||
appliedAmount,
|
|
||||||
...this.buildSettlementRatioHint(item),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!this.referenceLabel) return [];
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
settlementNo: this.referenceLabel,
|
|
||||||
settlementAmount: this.form.settlementAmount,
|
|
||||||
payableAmount: this.form.payableAmount,
|
|
||||||
paymentRatio: this.form.paymentRatio,
|
|
||||||
appliedAmount: this.form.appliedAmount,
|
|
||||||
paymentSequence: 1,
|
|
||||||
...this.buildSettlementRatioHint({ paymentSequence: 1 }),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
},
|
||||||
highRiskPayee() {
|
highRiskPayee() {
|
||||||
const level = String(this.payeeCustomerLevel || '')
|
const level = String(this.payeeCustomerLevel || '')
|
||||||
@@ -1127,11 +1102,12 @@ export default {
|
|||||||
if (
|
if (
|
||||||
this.amountSyncing ||
|
this.amountSyncing ||
|
||||||
this.form.paymentType === 'project_advance' ||
|
this.form.paymentType === 'project_advance' ||
|
||||||
!this.paymentAmountBase
|
!this.paymentAmountBase ||
|
||||||
|
this.nullableNumber(this.form.paymentRatio) === null
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 结算金额变化时按上方比例重算申请付款金额,仍不联动结算信息明细
|
// 结算金额变化时只按上方比例重算申请付款金额,不改结算信息明细
|
||||||
this.amountSyncing = true;
|
this.amountSyncing = true;
|
||||||
this.form.appliedAmount = Number(
|
this.form.appliedAmount = Number(
|
||||||
((this.paymentAmountBase * Number(this.form.paymentRatio || 0)) / 100).toFixed(2)
|
((this.paymentAmountBase * Number(this.form.paymentRatio || 0)) / 100).toFixed(2)
|
||||||
@@ -1532,8 +1508,6 @@ export default {
|
|||||||
paymentSequence: amount.paymentSequence,
|
paymentSequence: amount.paymentSequence,
|
||||||
}),
|
}),
|
||||||
settlementNo: item.formalSettlementNo || item.settlementNo || '',
|
settlementNo: item.formalSettlementNo || item.settlementNo || '',
|
||||||
appliedAmount: Number(item.appliedAmount || 0),
|
|
||||||
fixedAppliedAmount: true,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
this.seedMultipleRowPayments(referenceRows);
|
this.seedMultipleRowPayments(referenceRows);
|
||||||
@@ -1545,27 +1519,6 @@ export default {
|
|||||||
(total, item) => total + Number(item.payableAmount || 0),
|
(total, item) => total + Number(item.payableAmount || 0),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
const multiple = referenceRows.length > 1;
|
|
||||||
// 多结算单:表头比例/申请金额独立;单结算单:沿用行金额或按比例计算
|
|
||||||
const paymentRatio = Number(this.form.paymentRatio || 0);
|
|
||||||
const rowsAppliedTotal = referenceRows.reduce(
|
|
||||||
(total, item) => total + Number(item.appliedAmount || 0),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
const headerAppliedAmount = multiple
|
|
||||||
? Number(((settlementAmount * paymentRatio) / 100).toFixed(2))
|
|
||||||
: Number(
|
|
||||||
(
|
|
||||||
rowsAppliedTotal > 0
|
|
||||||
? rowsAppliedTotal
|
|
||||||
: (payableAmount * paymentRatio) / 100
|
|
||||||
).toFixed(2)
|
|
||||||
);
|
|
||||||
const headerPaymentRatio = multiple
|
|
||||||
? paymentRatio
|
|
||||||
: settlementAmount
|
|
||||||
? Number(((headerAppliedAmount / settlementAmount) * 100).toFixed(2))
|
|
||||||
: paymentRatio;
|
|
||||||
const settlementIds = rows.map(item => item.settlementId || item.id).filter(Boolean);
|
const settlementIds = rows.map(item => item.settlementId || item.id).filter(Boolean);
|
||||||
this.amountSyncing = true;
|
this.amountSyncing = true;
|
||||||
this.referenceRows = referenceRows;
|
this.referenceRows = referenceRows;
|
||||||
@@ -1592,8 +1545,7 @@ export default {
|
|||||||
settlementAmount: Number(settlementAmount.toFixed(2)),
|
settlementAmount: Number(settlementAmount.toFixed(2)),
|
||||||
payableAmount: Number(payableAmount.toFixed(2)),
|
payableAmount: Number(payableAmount.toFixed(2)),
|
||||||
billType: '',
|
billType: '',
|
||||||
paymentRatio: headerPaymentRatio,
|
appliedAmount: this.headerAppliedAmountByRatio(settlementAmount),
|
||||||
appliedAmount: headerAppliedAmount,
|
|
||||||
invoices: rows.flatMap(item =>
|
invoices: rows.flatMap(item =>
|
||||||
this.createInvoiceRows(item.invoices || [], item.formalSettlementNo || item.settlementNo)
|
this.createInvoiceRows(item.invoices || [], item.formalSettlementNo || item.settlementNo)
|
||||||
),
|
),
|
||||||
@@ -1649,7 +1601,7 @@ export default {
|
|||||||
referenceType: isPre ? 'pre' : 'formal',
|
referenceType: isPre ? 'pre' : 'formal',
|
||||||
settlementAmount: Number(Number(item.settlementAmount || 0).toFixed(2)),
|
settlementAmount: Number(Number(item.settlementAmount || 0).toFixed(2)),
|
||||||
payableAmount: Number(Number(item.payableAmount ?? item.settlementAmount ?? 0).toFixed(2)),
|
payableAmount: Number(Number(item.payableAmount ?? item.settlementAmount ?? 0).toFixed(2)),
|
||||||
appliedAmount: Number(Number(item.appliedAmount || 0).toFixed(2)),
|
appliedAmount: this.nullableNumber(item.appliedAmount),
|
||||||
paymentRatio: Number(item.settlementAmount || 0)
|
paymentRatio: Number(item.settlementAmount || 0)
|
||||||
? Number(
|
? Number(
|
||||||
(
|
(
|
||||||
@@ -1658,11 +1610,10 @@ export default {
|
|||||||
).toFixed(2)
|
).toFixed(2)
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
fixedAppliedAmount: this.readonly,
|
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
if (rows.length > 1) {
|
if (rows.length) {
|
||||||
this.form.preSettlementIds = rows.filter(item => item.referenceType === 'pre').map(item => item.id);
|
this.form.preSettlementIds = rows.filter(item => item.referenceType === 'pre').map(item => item.id);
|
||||||
this.form.settlementIds = rows.filter(item => item.referenceType === 'formal').map(item => item.id);
|
this.form.settlementIds = rows.filter(item => item.referenceType === 'formal').map(item => item.id);
|
||||||
if (this.form.preSettlementIds.length) this.form.preSettlementId = this.form.preSettlementIds[0];
|
if (this.form.preSettlementIds.length) this.form.preSettlementId = this.form.preSettlementIds[0];
|
||||||
@@ -1696,18 +1647,27 @@ export default {
|
|||||||
settlementAmount: Number(Number(settlementAmount || 0).toFixed(2)),
|
settlementAmount: Number(Number(settlementAmount || 0).toFixed(2)),
|
||||||
payableAmount: Number(Math.max(0, payableAmount).toFixed(2)),
|
payableAmount: Number(Math.max(0, payableAmount).toFixed(2)),
|
||||||
paymentSequence: Number.isFinite(paymentSequence) && paymentSequence > 0 ? paymentSequence : 1,
|
paymentSequence: Number.isFinite(paymentSequence) && paymentSequence > 0 ? paymentSequence : 1,
|
||||||
|
paymentRatio: null,
|
||||||
|
appliedAmount: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
nullableNumber(value) {
|
||||||
|
if (value === undefined || value === null || value === '') return null;
|
||||||
|
const amount = Number(value);
|
||||||
|
return Number.isFinite(amount) ? amount : null;
|
||||||
|
},
|
||||||
|
/** 付款基本信息内部:已填写比例时,按结算金额重算申请付款金额;不读取结算行付款金额。 */
|
||||||
|
headerAppliedAmountByRatio(settlementAmount) {
|
||||||
|
if (this.nullableNumber(this.form.paymentRatio) === null) return this.form.appliedAmount;
|
||||||
|
return Number(((Number(settlementAmount || 0) * Number(this.form.paymentRatio)) / 100).toFixed(2));
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* 按合同付款比例配置,判断当前付款次序是否超上限。
|
* 按合同付款比例配置,判断当前付款次序是否超上限。
|
||||||
* @param {Object} row 结算信息行(含 paymentSequence)
|
* @param {Object} row 结算信息行(含 paymentSequence)
|
||||||
*/
|
*/
|
||||||
buildSettlementRatioHint(row = {}) {
|
buildSettlementRatioHint(row = {}) {
|
||||||
const sequence = Number(row.paymentSequence || 1);
|
const sequence = Number(row.paymentSequence || 1);
|
||||||
const rawRatio =
|
const rawRatio = row.paymentRatio;
|
||||||
row.paymentRatio !== undefined && row.paymentRatio !== null && row.paymentRatio !== ''
|
|
||||||
? row.paymentRatio
|
|
||||||
: this.form.paymentRatio;
|
|
||||||
const paymentRatio = Number(rawRatio);
|
const paymentRatio = Number(rawRatio);
|
||||||
const ratioConfig = this.contractPaymentRatioRows[sequence - 1];
|
const ratioConfig = this.contractPaymentRatioRows[sequence - 1];
|
||||||
const ratioLimitValue = ratioConfig?.ratioLimit;
|
const ratioLimitValue = ratioConfig?.ratioLimit;
|
||||||
@@ -1850,21 +1810,6 @@ export default {
|
|||||||
(total, item) => total + Number(item.payableAmount || 0),
|
(total, item) => total + Number(item.payableAmount || 0),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
const multiple = this.referenceRows.length > 1;
|
|
||||||
const paymentRatio = Number(this.form.paymentRatio || 0);
|
|
||||||
const rowsAppliedTotal = this.referenceRows.reduce(
|
|
||||||
(total, item) => total + Number(item.appliedAmount || 0),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
// 多结算单:表头申请金额只由上方比例计算;单结算单仍按可付款金额×比例
|
|
||||||
const headerAppliedAmount = multiple
|
|
||||||
? Number(((settlementAmount * paymentRatio) / 100).toFixed(2))
|
|
||||||
: Number(((payableAmount * paymentRatio) / 100).toFixed(2));
|
|
||||||
const headerPaymentRatio = multiple
|
|
||||||
? paymentRatio
|
|
||||||
: settlementAmount && rowsAppliedTotal > 0
|
|
||||||
? Number(((rowsAppliedTotal / settlementAmount) * 100).toFixed(2))
|
|
||||||
: paymentRatio;
|
|
||||||
// 尾款付款场景下选择预付结算单时保留尾款付款类型,不强制改为进度预付
|
// 尾款付款场景下选择预付结算单时保留尾款付款类型,不强制改为进度预付
|
||||||
this.amountSyncing = true;
|
this.amountSyncing = true;
|
||||||
Object.assign(this.form, {
|
Object.assign(this.form, {
|
||||||
@@ -1890,8 +1835,7 @@ export default {
|
|||||||
settlementAmount: Number(settlementAmount.toFixed(2)),
|
settlementAmount: Number(settlementAmount.toFixed(2)),
|
||||||
payableAmount: Number(payableAmount.toFixed(2)),
|
payableAmount: Number(payableAmount.toFixed(2)),
|
||||||
billType: '',
|
billType: '',
|
||||||
paymentRatio: headerPaymentRatio,
|
appliedAmount: this.headerAppliedAmountByRatio(settlementAmount),
|
||||||
appliedAmount: headerAppliedAmount,
|
|
||||||
});
|
});
|
||||||
this.rememberSettlementParties(first.payerName, first.payeeName);
|
this.rememberSettlementParties(first.payerName, first.payeeName);
|
||||||
this.paymentRecords = [];
|
this.paymentRecords = [];
|
||||||
@@ -3080,22 +3024,54 @@ export default {
|
|||||||
return Number(Math.min(100, (payableAmount / settlementAmount) * 100).toFixed(2));
|
return Number(Math.min(100, (payableAmount / settlementAmount) * 100).toFixed(2));
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 多结算单行金额规范化:仅在行内反算比例,不使用表头比例灌入(表头与结算信息不关联)。
|
* 结算行金额规范化:只在行内反算比例,不使用付款基本信息的比例和申请金额。
|
||||||
*/
|
*/
|
||||||
seedMultipleRowPayments(rows = []) {
|
seedMultipleRowPayments(rows = []) {
|
||||||
if (rows.length <= 1) return;
|
|
||||||
rows.forEach(item => {
|
rows.forEach(item => {
|
||||||
const settlementAmount = Number(item.settlementAmount || 0);
|
if (this.nullableNumber(item.appliedAmount) === null && this.nullableNumber(item.paymentRatio) === null) {
|
||||||
const payableAmount = Number(item.payableAmount || 0);
|
item.appliedAmount = null;
|
||||||
let applied = Number(item.appliedAmount || 0);
|
item.paymentRatio = null;
|
||||||
if (!Number.isFinite(applied) || applied < 0) applied = 0;
|
return;
|
||||||
if (applied > payableAmount) applied = Number(payableAmount.toFixed(2));
|
}
|
||||||
item.appliedAmount = Number(applied.toFixed(2));
|
this.syncRowPayment(item, 'amount');
|
||||||
item.paymentRatio = settlementAmount
|
|
||||||
? Number(((item.appliedAmount / settlementAmount) * 100).toFixed(2))
|
|
||||||
: 0;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 结算行付款比例与付款金额联动。比例 = 付款金额 / 结算总金额。
|
||||||
|
* @param {'ratio'|'amount'} source 以哪一侧为准
|
||||||
|
*/
|
||||||
|
syncRowPayment(row, source) {
|
||||||
|
const settlementAmount = Number(row.settlementAmount || 0);
|
||||||
|
const payableAmount = Number(row.payableAmount || 0);
|
||||||
|
if (source === 'ratio') {
|
||||||
|
const ratio = this.nullableNumber(row.paymentRatio);
|
||||||
|
if (ratio === null) {
|
||||||
|
row.paymentRatio = null;
|
||||||
|
row.appliedAmount = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let applied = Number(((settlementAmount * ratio) / 100).toFixed(2));
|
||||||
|
if (applied > payableAmount) applied = Number(payableAmount.toFixed(2));
|
||||||
|
row.appliedAmount = applied;
|
||||||
|
row.paymentRatio = settlementAmount
|
||||||
|
? Number(((applied / settlementAmount) * 100).toFixed(2))
|
||||||
|
: Number(ratio.toFixed(2));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const appliedValue = this.nullableNumber(row.appliedAmount);
|
||||||
|
if (appliedValue === null) {
|
||||||
|
row.appliedAmount = null;
|
||||||
|
row.paymentRatio = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let applied = appliedValue;
|
||||||
|
if (applied < 0) applied = 0;
|
||||||
|
if (applied > payableAmount) applied = payableAmount;
|
||||||
|
row.appliedAmount = Number(applied.toFixed(2));
|
||||||
|
row.paymentRatio = settlementAmount
|
||||||
|
? Number(((row.appliedAmount / settlementAmount) * 100).toFixed(2))
|
||||||
|
: 0;
|
||||||
|
},
|
||||||
findReferenceRow(row) {
|
findReferenceRow(row) {
|
||||||
return this.referenceRows.find(
|
return this.referenceRows.find(
|
||||||
item =>
|
item =>
|
||||||
@@ -3104,55 +3080,19 @@ export default {
|
|||||||
item.settlementNo === row.settlementNo
|
item.settlementNo === row.settlementNo
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
/** 结算信息付款金额合计 */
|
|
||||||
settlementRowsAppliedTotal(rows = this.referenceRows) {
|
|
||||||
return (rows || []).reduce((total, item) => total + Number(item.appliedAmount || 0), 0);
|
|
||||||
},
|
|
||||||
/** 结算信息合计不得超过上方申请付款金额 */
|
|
||||||
ensureSettlementAppliedWithinHeader(showWarning = true) {
|
|
||||||
if (!this.multipleSettlementRows) return true;
|
|
||||||
const headerApplied = Number(this.form.appliedAmount || 0);
|
|
||||||
const rowsTotal = Number(this.settlementRowsAppliedTotal().toFixed(2));
|
|
||||||
if (rowsTotal - headerApplied > 1e-8) {
|
|
||||||
if (showWarning) {
|
|
||||||
this.$message.warning(
|
|
||||||
`结算信息付款金额合计(${rowsTotal.toFixed(2)})不能大于申请付款金额(${headerApplied.toFixed(2)})`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
handleRowPaymentRatioChange(row, value) {
|
handleRowPaymentRatioChange(row, value) {
|
||||||
const target = this.findReferenceRow(row);
|
const target = this.findReferenceRow(row);
|
||||||
if (!target) return;
|
if (!target) return;
|
||||||
const settlementAmount = Number(target.settlementAmount || 0);
|
target.paymentRatio = value;
|
||||||
const payableAmount = Number(target.payableAmount || 0);
|
this.syncRowPayment(target, 'ratio');
|
||||||
const ratio = value === null || value === undefined || value === '' ? 0 : Number(value);
|
|
||||||
let applied = Number(((settlementAmount * ratio) / 100).toFixed(2));
|
|
||||||
if (applied > payableAmount) applied = Number(payableAmount.toFixed(2));
|
|
||||||
target.appliedAmount = applied;
|
|
||||||
target.paymentRatio = settlementAmount
|
|
||||||
? Number(((applied / settlementAmount) * 100).toFixed(2))
|
|
||||||
: Number(ratio.toFixed(2));
|
|
||||||
// 仅刷新结算金额/可付款金额合计,不回写表头申请金额与比例
|
|
||||||
this.syncHeaderSettlementTotalsFromRows();
|
this.syncHeaderSettlementTotalsFromRows();
|
||||||
this.ensureSettlementAppliedWithinHeader(true);
|
|
||||||
},
|
},
|
||||||
handleRowAppliedAmountChange(row, value) {
|
handleRowAppliedAmountChange(row, value) {
|
||||||
const target = this.findReferenceRow(row);
|
const target = this.findReferenceRow(row);
|
||||||
if (!target) return;
|
if (!target) return;
|
||||||
const settlementAmount = Number(target.settlementAmount || 0);
|
target.appliedAmount = value;
|
||||||
const payableAmount = Number(target.payableAmount || 0);
|
this.syncRowPayment(target, 'amount');
|
||||||
let applied = value === null || value === undefined || value === '' ? 0 : Number(value);
|
|
||||||
if (!Number.isFinite(applied) || applied < 0) applied = 0;
|
|
||||||
if (applied > payableAmount) applied = payableAmount;
|
|
||||||
target.appliedAmount = Number(applied.toFixed(2));
|
|
||||||
target.paymentRatio = settlementAmount
|
|
||||||
? Number(((target.appliedAmount / settlementAmount) * 100).toFixed(2))
|
|
||||||
: 0;
|
|
||||||
this.syncHeaderSettlementTotalsFromRows();
|
this.syncHeaderSettlementTotalsFromRows();
|
||||||
this.ensureSettlementAppliedWithinHeader(true);
|
|
||||||
},
|
},
|
||||||
/** 多结算单时仅同步结算金额/可付款金额合计,申请付款金额与比例由上方独立维护 */
|
/** 多结算单时仅同步结算金额/可付款金额合计,申请付款金额与比例由上方独立维护 */
|
||||||
syncHeaderSettlementTotalsFromRows() {
|
syncHeaderSettlementTotalsFromRows() {
|
||||||
@@ -3261,8 +3201,8 @@ export default {
|
|||||||
settlementLines: (this.referenceRows || []).map(item => ({
|
settlementLines: (this.referenceRows || []).map(item => ({
|
||||||
formalSettlementId: item.referenceType === 'formal' ? item.id : null,
|
formalSettlementId: item.referenceType === 'formal' ? item.id : null,
|
||||||
preSettlementId: item.referenceType === 'pre' ? item.id : null,
|
preSettlementId: item.referenceType === 'pre' ? item.id : null,
|
||||||
paymentRatio: item.paymentRatio,
|
paymentRatio: this.nullableNumber(item.paymentRatio),
|
||||||
appliedAmount: item.appliedAmount,
|
appliedAmount: this.nullableNumber(item.appliedAmount),
|
||||||
})),
|
})),
|
||||||
paymentMethod,
|
paymentMethod,
|
||||||
billLedgerId,
|
billLedgerId,
|
||||||
@@ -3323,21 +3263,22 @@ export default {
|
|||||||
ElMessage.warning('申请付款金额必填且必须大于0');
|
ElMessage.warning('申请付款金额必填且必须大于0');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.multipleSettlementRows) {
|
if (this.isSettlementRelatedType && this.referenceRows.length) {
|
||||||
const invalidRow = this.referenceRows.find(
|
const invalidRow = this.referenceRows.find(item => {
|
||||||
item => Number(item.appliedAmount || 0) > Number(item.payableAmount || 0)
|
const rowApplied = Number(item.appliedAmount || 0);
|
||||||
);
|
return !Number.isFinite(rowApplied) || rowApplied <= 0;
|
||||||
|
});
|
||||||
if (invalidRow) {
|
if (invalidRow) {
|
||||||
this.clearMessages();
|
this.clearMessages();
|
||||||
ElMessage.warning(`${invalidRow.settlementNo || '结算单'}付款金额不能超过可付款金额`);
|
ElMessage.warning(`${invalidRow.settlementNo || '结算单'}请填写付款金额,且必须大于0`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const rowsTotal = Number(this.settlementRowsAppliedTotal().toFixed(2));
|
const exceededRow = this.referenceRows.find(
|
||||||
if (rowsTotal - appliedAmount > 1e-8) {
|
item => Number(item.appliedAmount || 0) > Number(item.payableAmount || 0)
|
||||||
|
);
|
||||||
|
if (exceededRow) {
|
||||||
this.clearMessages();
|
this.clearMessages();
|
||||||
ElMessage.warning(
|
ElMessage.warning(`${exceededRow.settlementNo || '结算单'}付款金额不能超过可付款金额`);
|
||||||
`结算信息付款金额合计(${rowsTotal.toFixed(2)})不能大于申请付款金额(${appliedAmount.toFixed(2)})`
|
|
||||||
);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -380,7 +380,13 @@ export default {
|
|||||||
},
|
},
|
||||||
async loadFormalSettlements(keyword = '') {
|
async loadFormalSettlements(keyword = '') {
|
||||||
const { data } = await api.getFormalSettlements(keyword);
|
const { data } = await api.getFormalSettlements(keyword);
|
||||||
this.candidates = data?.data || data || [];
|
const payload = data?.data ?? data;
|
||||||
|
// 兼容误返回分页对象的情况,只取正式结算单列表
|
||||||
|
this.candidates = Array.isArray(payload)
|
||||||
|
? payload
|
||||||
|
: Array.isArray(payload?.records)
|
||||||
|
? payload.records
|
||||||
|
: [];
|
||||||
},
|
},
|
||||||
async loadFeeOptions() {
|
async loadFeeOptions() {
|
||||||
const { data } = await api.getFeeOptions();
|
const { data } = await api.getFeeOptions();
|
||||||
|
|||||||
@@ -926,14 +926,34 @@ const stripInternalRowFields = row => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 合并费用列名与各行 feeItems 键,避免保存/回显时丢动态费用列 */
|
||||||
|
const collectFeeItemNames = (preferred = [], detailRows = []) => {
|
||||||
|
const names = [];
|
||||||
|
const seen = new Set();
|
||||||
|
const pushName = value => {
|
||||||
|
const name = String(value || '').trim();
|
||||||
|
if (!name || seen.has(name)) return;
|
||||||
|
seen.add(name);
|
||||||
|
names.push(name);
|
||||||
|
};
|
||||||
|
(preferred || []).forEach(pushName);
|
||||||
|
(detailRows || []).forEach(row => {
|
||||||
|
const feeItems = row?.feeItems;
|
||||||
|
if (!feeItems || typeof feeItems !== 'object' || Array.isArray(feeItems)) return;
|
||||||
|
Object.keys(feeItems).forEach(pushName);
|
||||||
|
});
|
||||||
|
return names;
|
||||||
|
};
|
||||||
|
|
||||||
const buildPayload = () => {
|
const buildPayload = () => {
|
||||||
const { file, ...batch } = form;
|
const { file, ...batch } = form;
|
||||||
|
const payloadRows = rows.value.map(stripInternalRowFields);
|
||||||
return {
|
return {
|
||||||
...batch,
|
...batch,
|
||||||
settlementBillType: form.settlementType === 'payable' ? form.settlementBillType || 'formal' : 'formal',
|
settlementBillType: form.settlementType === 'payable' ? form.settlementBillType || 'formal' : 'formal',
|
||||||
waybillMode: form.waybillMode === 'history' ? 'history' : 'new',
|
waybillMode: form.waybillMode === 'history' ? 'history' : 'new',
|
||||||
feeItemNames: [...feeItemNames.value],
|
feeItemNames: collectFeeItemNames(feeItemNames.value, payloadRows),
|
||||||
rows: rows.value.map(stripInternalRowFields),
|
rows: payloadRows,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1436,9 +1456,13 @@ const loadDetail = async id => {
|
|||||||
if (detail.projectId) {
|
if (detail.projectId) {
|
||||||
await loadContractsByProject(detail.projectId, detail.projectName || '');
|
await loadContractsByProject(detail.projectId, detail.projectName || '');
|
||||||
}
|
}
|
||||||
feeItemNames.value = Array.isArray(detail.feeItemNames) ? detail.feeItemNames : [];
|
|
||||||
parsedHeaders.value = [];
|
parsedHeaders.value = [];
|
||||||
const detailRows = Array.isArray(detail.rows) ? detail.rows : [];
|
const detailRows = Array.isArray(detail.rows) ? detail.rows : [];
|
||||||
|
// 先按库中列名与明细 feeItems 合并,再规范化行数据,保证草稿再打开仍展示全部费用列
|
||||||
|
feeItemNames.value = collectFeeItemNames(
|
||||||
|
Array.isArray(detail.feeItemNames) ? detail.feeItemNames : [],
|
||||||
|
detailRows
|
||||||
|
);
|
||||||
rows.value = detailRows.map((item, index) => normalizeParsedRow(item, index));
|
rows.value = detailRows.map((item, index) => normalizeParsedRow(item, index));
|
||||||
refreshDuplicateFlags();
|
refreshDuplicateFlags();
|
||||||
// 查看模式始终只读;编辑时仅草稿可改,已完成/已作废强制只读
|
// 查看模式始终只读;编辑时仅草稿可改,已完成/已作废强制只读
|
||||||
|
|||||||
@@ -125,15 +125,17 @@
|
|||||||
<span class="reconciliation-editor__filter-label">货物名称</span>
|
<span class="reconciliation-editor__filter-label">货物名称</span>
|
||||||
<el-input v-model="internalQuery.cargoName" placeholder="请输入" clearable />
|
<el-input v-model="internalQuery.cargoName" placeholder="请输入" clearable />
|
||||||
</div>
|
</div>
|
||||||
<el-button @click="resetInternalQuery">重置</el-button>
|
<div class="reconciliation-editor__filter-actions">
|
||||||
<el-button type="primary" @click="internalFilterTick++">查询</el-button>
|
<el-button @click="resetInternalQuery">重置</el-button>
|
||||||
<el-button
|
<el-button type="primary" @click="internalFilterTick++">查询</el-button>
|
||||||
type="primary"
|
<el-button
|
||||||
plain
|
type="primary"
|
||||||
:disabled="!filteredInternalRows.length"
|
plain
|
||||||
@click="exportInternalDetails"
|
:disabled="!filteredInternalRows.length"
|
||||||
>导出</el-button
|
@click="exportInternalDetails"
|
||||||
>
|
>导出</el-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-table
|
<el-table
|
||||||
:data="filteredInternalRows"
|
:data="filteredInternalRows"
|
||||||
@@ -189,6 +191,9 @@
|
|||||||
"
|
"
|
||||||
>{{ formatDate(row[column.prop]) }}</span
|
>{{ formatDate(row[column.prop]) }}</span
|
||||||
>
|
>
|
||||||
|
<span v-else-if="column.prop === 'unitPrice'">{{
|
||||||
|
formatOptionalMoney(row.unitPrice)
|
||||||
|
}}</span>
|
||||||
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -318,6 +323,9 @@
|
|||||||
"
|
"
|
||||||
>{{ formatDate(row[column.prop]) }}</span
|
>{{ formatDate(row[column.prop]) }}</span
|
||||||
>
|
>
|
||||||
|
<span v-else-if="column.prop === 'unitPrice'">{{
|
||||||
|
formatOptionalMoney(row.unitPrice)
|
||||||
|
}}</span>
|
||||||
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -476,6 +484,7 @@
|
|||||||
<span v-else-if="column.feeItemName">{{ formatMoney(getFeeItemAmount(row, column.feeItemName)) }}</span>
|
<span v-else-if="column.feeItemName">{{ formatMoney(getFeeItemAmount(row, column.feeItemName)) }}</span>
|
||||||
<span v-else-if="column.prop === 'mileage'">{{ formatMileage(row.mileage) }}</span>
|
<span v-else-if="column.prop === 'mileage'">{{ formatMileage(row.mileage) }}</span>
|
||||||
<span v-else-if="column.prop === 'actualDepartureTime' || column.prop === 'actualCompletionTime'">{{ formatDate(row[column.prop]) }}</span>
|
<span v-else-if="column.prop === 'actualDepartureTime' || column.prop === 'actualCompletionTime'">{{ formatDate(row[column.prop]) }}</span>
|
||||||
|
<span v-else-if="column.prop === 'unitPrice'">{{ formatOptionalMoney(row.unitPrice) }}</span>
|
||||||
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -630,7 +639,10 @@ import { getDictionary } from '@/api/system/dictbiz';
|
|||||||
import { Search } from '@element-plus/icons-vue';
|
import { Search } from '@element-plus/icons-vue';
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
import { downloadXls } from '@/utils/util';
|
import { downloadXls } from '@/utils/util';
|
||||||
import { isExcelResponse, parseBlobJson } from '@/utils/import-excel';
|
import {
|
||||||
|
consumeImportResponse,
|
||||||
|
resolveImportErrorMessage,
|
||||||
|
} from '@/utils/import-excel';
|
||||||
import { transportReconciliationFormFields } from '@/option/settlement/transportReconciliationForm';
|
import { transportReconciliationFormFields } from '@/option/settlement/transportReconciliationForm';
|
||||||
import {
|
import {
|
||||||
internalColumns,
|
internalColumns,
|
||||||
@@ -1042,12 +1054,24 @@ export default {
|
|||||||
.map(normalized)
|
.map(normalized)
|
||||||
.join('|');
|
.join('|');
|
||||||
},
|
},
|
||||||
|
blankOptionalNumber(value) {
|
||||||
|
if (value === null || value === undefined || value === '' || Number(value) === -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
normalizeInternalRow(row) {
|
normalizeInternalRow(row) {
|
||||||
const feeItems =
|
const feeItems =
|
||||||
row.feeItems && typeof row.feeItems === 'object' && Object.keys(row.feeItems).length
|
row.feeItems && typeof row.feeItems === 'object' && Object.keys(row.feeItems).length
|
||||||
? row.feeItems
|
? row.feeItems
|
||||||
: this.parseFeeItems(row.feeItemsJson);
|
: this.parseFeeItems(row.feeItemsJson);
|
||||||
return { ...row, feeItems };
|
return {
|
||||||
|
...row,
|
||||||
|
feeItems,
|
||||||
|
unitPrice: this.blankOptionalNumber(row.unitPrice),
|
||||||
|
mileage: this.blankOptionalNumber(row.mileage),
|
||||||
|
freightAmount: this.blankOptionalNumber(row.freightAmount),
|
||||||
|
};
|
||||||
},
|
},
|
||||||
normalizeExternalRow(row, index) {
|
normalizeExternalRow(row, index) {
|
||||||
const feeItems =
|
const feeItems =
|
||||||
@@ -1057,6 +1081,9 @@ export default {
|
|||||||
return {
|
return {
|
||||||
...row,
|
...row,
|
||||||
feeItems,
|
feeItems,
|
||||||
|
unitPrice: this.blankOptionalNumber(row.unitPrice),
|
||||||
|
mileage: this.blankOptionalNumber(row.mileage),
|
||||||
|
freightAmount: this.blankOptionalNumber(row.freightAmount),
|
||||||
_externalKey: row.id || row.externalLineNo || `external-${index}`,
|
_externalKey: row.id || row.externalLineNo || `external-${index}`,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -1284,6 +1311,9 @@ export default {
|
|||||||
snapshot.feeItemsJson = row.feeItemsJson || JSON.stringify(row.feeItems || {});
|
snapshot.feeItemsJson = row.feeItemsJson || JSON.stringify(row.feeItems || {});
|
||||||
snapshot.actualDepartureTime = this.normalizeExternalDateTime(row.actualDepartureTime);
|
snapshot.actualDepartureTime = this.normalizeExternalDateTime(row.actualDepartureTime);
|
||||||
snapshot.actualCompletionTime = this.normalizeExternalDateTime(row.actualCompletionTime);
|
snapshot.actualCompletionTime = this.normalizeExternalDateTime(row.actualCompletionTime);
|
||||||
|
snapshot.unitPrice = this.blankOptionalNumber(row.unitPrice);
|
||||||
|
snapshot.mileage = this.blankOptionalNumber(row.mileage);
|
||||||
|
snapshot.freightAmount = this.blankOptionalNumber(row.freightAmount);
|
||||||
return snapshot;
|
return snapshot;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -1297,6 +1327,9 @@ export default {
|
|||||||
snapshot.feeItemsJson = row.feeItemsJson || JSON.stringify(row.feeItems || {});
|
snapshot.feeItemsJson = row.feeItemsJson || JSON.stringify(row.feeItems || {});
|
||||||
snapshot.actualDepartureTime = this.normalizeExternalDateTime(row.actualDepartureTime);
|
snapshot.actualDepartureTime = this.normalizeExternalDateTime(row.actualDepartureTime);
|
||||||
snapshot.actualCompletionTime = this.normalizeExternalDateTime(row.actualCompletionTime);
|
snapshot.actualCompletionTime = this.normalizeExternalDateTime(row.actualCompletionTime);
|
||||||
|
snapshot.unitPrice = this.blankOptionalNumber(row.unitPrice);
|
||||||
|
snapshot.mileage = this.blankOptionalNumber(row.mileage);
|
||||||
|
snapshot.freightAmount = this.blankOptionalNumber(row.freightAmount);
|
||||||
snapshot.suspectedDuplicate = duplicateIds.has(String(row.id));
|
snapshot.suspectedDuplicate = duplicateIds.has(String(row.id));
|
||||||
return snapshot;
|
return snapshot;
|
||||||
});
|
});
|
||||||
@@ -1326,7 +1359,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async handleUpdate() {
|
async handleUpdate() {
|
||||||
await this.$confirm('将以外部账单数据更新运单及内部结算明细,是否继续?', '更新账单', {
|
await this.$confirm('将以外部账单数据更新内部结算明细,是否继续?', '更新账单', {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
});
|
});
|
||||||
this.actionLoading = true;
|
this.actionLoading = true;
|
||||||
@@ -1508,22 +1541,23 @@ export default {
|
|||||||
? await api.importCargo(this.currentId, file)
|
? await api.importCargo(this.currentId, file)
|
||||||
: await api.importVehicle(this.currentId, file);
|
: await api.importVehicle(this.currentId, file);
|
||||||
// 成功时 axios 拦截器可能已把 JSON Blob 解析成对象,不能再调 .text()
|
// 成功时 axios 拦截器可能已把 JSON Blob 解析成对象,不能再调 .text()
|
||||||
if (isExcelResponse(response)) {
|
const consumed = await consumeImportResponse(response, {
|
||||||
downloadXls(
|
businessName: '运输对账',
|
||||||
response.data,
|
dayjsFormat: this.$dayjs().format('YYYY-MM-DD HH-mm-ss'),
|
||||||
`运输对账导入失败明细${this.$dayjs().format('YYYY-MM-DD HH-mm-ss')}.xlsx`
|
});
|
||||||
);
|
if (consumed.kind === 'excel') {
|
||||||
|
downloadXls(consumed.blob, consumed.fileName);
|
||||||
this.$message.warning('部分数据导入失败,已下载失败明细');
|
this.$message.warning('部分数据导入失败,已下载失败明细');
|
||||||
} else {
|
} else {
|
||||||
const result = await parseBlobJson(response.data);
|
|
||||||
if (Number(result.code) !== 200) throw new Error(result.msg || '导入失败');
|
|
||||||
this.$message.success('外部账单导入成功');
|
this.$message.success('外部账单导入成功');
|
||||||
}
|
}
|
||||||
this.matchingStarted = false;
|
this.matchingStarted = false;
|
||||||
await this.loadDetail();
|
await this.loadDetail();
|
||||||
this.matchingStarted = false;
|
this.matchingStarted = false;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$message.error(error.message || '外部账单导入失败');
|
if (!error?._notified) {
|
||||||
|
this.$message.error(await resolveImportErrorMessage(error));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async downloadTemplate(mode) {
|
async downloadTemplate(mode) {
|
||||||
@@ -1831,7 +1865,10 @@ export default {
|
|||||||
: Number(value || 0).toFixed(2);
|
: Number(value || 0).toFixed(2);
|
||||||
},
|
},
|
||||||
formatMileage(value) {
|
formatMileage(value) {
|
||||||
return Number(value) === -1 ? '' : this.displayValue(value);
|
return this.blankOptionalNumber(value) === null ? '' : this.displayValue(value);
|
||||||
|
},
|
||||||
|
formatOptionalMoney(value, currency = 'RMB') {
|
||||||
|
return this.blankOptionalNumber(value) === null ? '-' : this.formatMoney(value, currency);
|
||||||
},
|
},
|
||||||
formatDate(value) {
|
formatDate(value) {
|
||||||
if (value === null || value === undefined || value === '') return '-';
|
if (value === null || value === undefined || value === '') return '-';
|
||||||
@@ -1952,15 +1989,26 @@ export default {
|
|||||||
}
|
}
|
||||||
.reconciliation-editor__filter {
|
.reconciliation-editor__filter {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, minmax(160px, 1fr)) auto auto;
|
grid-template-columns: repeat(4, minmax(160px, 1fr)) auto;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
.reconciliation-editor__filter-item {
|
.reconciliation-editor__filter-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
.reconciliation-editor__filter-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.reconciliation-editor__filter-actions .el-button {
|
||||||
|
min-width: 64px;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
.reconciliation-editor__filter-label {
|
.reconciliation-editor__filter-label {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
|
|||||||
@@ -6,15 +6,33 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue';
|
import { computed, getCurrentInstance, nextTick, onMounted } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useStore } from 'vuex';
|
||||||
import SettlementImportPage from './components/settlement-import-page.vue';
|
import SettlementImportPage from './components/settlement-import-page.vue';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const store = useStore();
|
||||||
|
const instance = getCurrentInstance();
|
||||||
|
|
||||||
const pageTitle = computed(() => {
|
const pageTitle = computed(() => {
|
||||||
if (route.query.mode === 'view') return '查看导入结算单';
|
if (route.query.mode === 'view') return '查看导入结算单';
|
||||||
return route.query.id ? '编辑导入结算单' : '新增导入结算单';
|
return route.query.id ? '编辑导入结算单' : '新增导入结算单';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const syncTagTitle = () => {
|
||||||
|
nextTick(() => {
|
||||||
|
store.commit('SET_TAG', {
|
||||||
|
fullPath: route.fullPath,
|
||||||
|
name: pageTitle.value,
|
||||||
|
});
|
||||||
|
instance?.proxy?.$router?.$avueRouter?.setTitle?.(pageTitle.value);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
syncTagTitle();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -239,19 +239,22 @@ export default {
|
|||||||
this.onLoad(this.page);
|
this.onLoad(this.page);
|
||||||
},
|
},
|
||||||
openCreate() {
|
openCreate() {
|
||||||
this.$router.push({ path: '/settlement/settlement-import/form' });
|
this.$router.push({
|
||||||
|
path: '/settlement/settlement-import/form',
|
||||||
|
query: { name: '新增导入结算单' },
|
||||||
|
});
|
||||||
},
|
},
|
||||||
openDetail(row) {
|
openDetail(row) {
|
||||||
if (!row?.id) return;
|
if (!row?.id) return;
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
path: '/settlement/settlement-import/form',
|
path: '/settlement/settlement-import/form',
|
||||||
query: { id: row.id, mode: 'view' },
|
query: { id: row.id, mode: 'view', name: '查看导入结算单' },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
editDraft(row) {
|
editDraft(row) {
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
path: '/settlement/settlement-import/form',
|
path: '/settlement/settlement-import/form',
|
||||||
query: { id: row.id },
|
query: { id: row.id, name: '编辑导入结算单' },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async handleDelete(row) {
|
async handleDelete(row) {
|
||||||
|
|||||||
+105
-2
@@ -115,9 +115,10 @@
|
|||||||
<template #deptName="{ row }">
|
<template #deptName="{ row }">
|
||||||
<template v-if="formatDeptNames(row.deptName).length">
|
<template v-if="formatDeptNames(row.deptName).length">
|
||||||
<el-tag
|
<el-tag
|
||||||
v-for="name in formatDeptNames(row.deptName)"
|
v-for="(name, index) in formatDeptNames(row.deptName)"
|
||||||
:key="name"
|
:key="`${row.id}-${index}-${name}`"
|
||||||
class="user-dept-tag"
|
class="user-dept-tag"
|
||||||
|
@click.stop="handleDeptTagClick(row, index)"
|
||||||
>{{ name }}</el-tag>
|
>{{ name }}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
<span v-else>-</span>
|
<span v-else>-</span>
|
||||||
@@ -666,6 +667,104 @@ export default {
|
|||||||
this.page.currentPage = 1;
|
this.page.currentPage = 1;
|
||||||
this.onLoad(this.page);
|
this.onLoad(this.page);
|
||||||
},
|
},
|
||||||
|
formatDeptIds(deptId) {
|
||||||
|
if (!deptId && deptId !== 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return String(deptId)
|
||||||
|
.split(',')
|
||||||
|
.map(item => item.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
},
|
||||||
|
handleDeptTagClick(row, index) {
|
||||||
|
const deptIds = this.formatDeptIds(row.deptId);
|
||||||
|
let deptId = deptIds[index];
|
||||||
|
if (!deptId) {
|
||||||
|
const names = this.formatDeptNames(row.deptName);
|
||||||
|
deptId = this.findDeptIdByName(names[index]);
|
||||||
|
}
|
||||||
|
if (!deptId) {
|
||||||
|
this.$message.warning('未找到对应组织');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.selectDeptOnTree(deptId);
|
||||||
|
},
|
||||||
|
findDeptIdByName(name, tree = this.permissionDeptTree) {
|
||||||
|
if (!name) return null;
|
||||||
|
for (const node of tree || []) {
|
||||||
|
if (node.title === name || node.deptName === name || node.label === name) {
|
||||||
|
return node.id;
|
||||||
|
}
|
||||||
|
const matched = this.findDeptIdByName(name, node.children);
|
||||||
|
if (matched != null) return matched;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 展开左侧懒加载组织树到目标节点并设为选中,同时按该组织筛选用户列表
|
||||||
|
*/
|
||||||
|
async selectDeptOnTree(deptId) {
|
||||||
|
if (deptId == null || deptId === '') return;
|
||||||
|
if (!this.permissionDeptTree?.length) {
|
||||||
|
const tenantId = this.userInfo.tenantId || this.website.tenantId;
|
||||||
|
try {
|
||||||
|
const res = await getDeptTree(tenantId);
|
||||||
|
this.permissionDeptTree = res.data.data || [];
|
||||||
|
} catch (error) {
|
||||||
|
this.$message.error('组织树加载失败');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const path = this.findDeptPath(deptId);
|
||||||
|
if (!path?.length) {
|
||||||
|
this.$message.warning('未在组织树中找到该部门');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tree = this.$refs.deptTree;
|
||||||
|
if (!tree) return;
|
||||||
|
for (let i = 0; i < path.length - 1; i++) {
|
||||||
|
await this.expandLazyTreeNode(path[i]);
|
||||||
|
}
|
||||||
|
await this.$nextTick();
|
||||||
|
const targetId = path[path.length - 1];
|
||||||
|
tree.setCurrentKey?.(targetId);
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const currentEl = tree.$el?.querySelector?.('.el-tree-node.is-current');
|
||||||
|
currentEl?.scrollIntoView?.({ block: 'nearest', behavior: 'smooth' });
|
||||||
|
});
|
||||||
|
if (String(this.treeDeptId) === String(targetId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.treeDeptId = targetId;
|
||||||
|
this.page.currentPage = 1;
|
||||||
|
this.onLoad(this.page);
|
||||||
|
},
|
||||||
|
expandLazyTreeNode(key) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const tree = this.$refs.deptTree;
|
||||||
|
if (!tree?.getNode) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tryExpand = (retries = 40) => {
|
||||||
|
const node = tree.getNode(key);
|
||||||
|
if (!node) {
|
||||||
|
if (retries <= 0) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setTimeout(() => tryExpand(retries - 1), 50);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (node.expanded || node.isLeaf) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
node.expand(() => resolve());
|
||||||
|
};
|
||||||
|
tryExpand();
|
||||||
|
});
|
||||||
|
},
|
||||||
handleDropdownCommand(command, row, index) {
|
handleDropdownCommand(command, row, index) {
|
||||||
if (command === 'setLeader') {
|
if (command === 'setLeader') {
|
||||||
this.handleSetLeader(row);
|
this.handleSetLeader(row);
|
||||||
@@ -1537,6 +1636,10 @@ export default {
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user-dept-tag {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.user-dept-tag + .user-dept-tag {
|
.user-dept-tag + .user-dept-tag {
|
||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,17 @@
|
|||||||
style="width: 500px"
|
style="width: 500px"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="减分最高扣分">
|
||||||
|
<el-input
|
||||||
|
:model-value="configForm.maxMinusScore"
|
||||||
|
placeholder="所有减分项合计不得超过该分值"
|
||||||
|
clearable
|
||||||
|
style="width: 500px"
|
||||||
|
@input="handleMaxMinusScoreInput"
|
||||||
|
>
|
||||||
|
<template #append>分</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="备注">
|
<el-form-item label="备注">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="configForm.remark"
|
v-model="configForm.remark"
|
||||||
@@ -628,6 +639,7 @@ export default {
|
|||||||
name: '',
|
name: '',
|
||||||
remark: '',
|
remark: '',
|
||||||
standardDescription: '',
|
standardDescription: '',
|
||||||
|
maxMinusScore: '',
|
||||||
status: 3,
|
status: 3,
|
||||||
categories: this.defaultCategories(),
|
categories: this.defaultCategories(),
|
||||||
standards: [],
|
standards: [],
|
||||||
@@ -684,6 +696,7 @@ export default {
|
|||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
...detail,
|
...detail,
|
||||||
|
maxMinusScore: this.normalizePlainDecimal(detail.maxMinusScore),
|
||||||
categories: categories.map(category => ({
|
categories: categories.map(category => ({
|
||||||
...(map[category.categoryCode] || category),
|
...(map[category.categoryCode] || category),
|
||||||
categoryName: category.categoryName,
|
categoryName: category.categoryName,
|
||||||
@@ -691,6 +704,14 @@ export default {
|
|||||||
standards: (detail.standards || []).map(standard => this.normalizeStandard(standard)),
|
standards: (detail.standards || []).map(standard => this.normalizeStandard(standard)),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
handleMaxMinusScoreInput(value) {
|
||||||
|
const raw = String(value ?? '')
|
||||||
|
.replace(/[^\d.]/g, '')
|
||||||
|
.replace(/^\./, '')
|
||||||
|
.replace(/(\..*)\./g, '$1')
|
||||||
|
.replace(/^(\d+)(\.\d{0,2})?.*$/, '$1$2');
|
||||||
|
this.configForm.maxMinusScore = raw;
|
||||||
|
},
|
||||||
openConfig(row, readonly = false) {
|
openConfig(row, readonly = false) {
|
||||||
this.readonly = readonly;
|
this.readonly = readonly;
|
||||||
if (row && row.id) {
|
if (row && row.id) {
|
||||||
@@ -816,6 +837,9 @@ export default {
|
|||||||
},
|
},
|
||||||
prepareSubmitConfig(config) {
|
prepareSubmitConfig(config) {
|
||||||
const payload = JSON.parse(JSON.stringify(config || {}));
|
const payload = JSON.parse(JSON.stringify(config || {}));
|
||||||
|
const maxMinus = this.normalizePlainDecimal(payload.maxMinusScore);
|
||||||
|
payload.maxMinusScore =
|
||||||
|
maxMinus === '' || maxMinus == null ? null : this.toScoreValue(maxMinus);
|
||||||
(payload.categories || []).forEach(category => {
|
(payload.categories || []).forEach(category => {
|
||||||
(category.items || []).forEach(item => {
|
(category.items || []).forEach(item => {
|
||||||
item.score = this.toScoreValue(item.score);
|
item.score = this.toScoreValue(item.score);
|
||||||
@@ -1072,6 +1096,14 @@ export default {
|
|||||||
this.$message.warning('请输入评定表名称');
|
this.$message.warning('请输入评定表名称');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
this.configForm.maxMinusScore !== '' &&
|
||||||
|
this.configForm.maxMinusScore != null &&
|
||||||
|
!this.isNonNegativeDecimal(this.configForm.maxMinusScore)
|
||||||
|
) {
|
||||||
|
this.$message.warning('减分最高扣分须为不小于0的数字,最多两位小数');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const basic = this.configForm.categories.find(item => item.categoryCode === 'basic');
|
const basic = this.configForm.categories.find(item => item.categoryCode === 'basic');
|
||||||
if (!basic || basic.items.length === 0) {
|
if (!basic || basic.items.length === 0) {
|
||||||
this.$message.warning('至少配置1个基础得分项目');
|
this.$message.warning('至少配置1个基础得分项目');
|
||||||
|
|||||||
@@ -592,18 +592,27 @@
|
|||||||
<span>{{ row.reviewStatus }}</span>
|
<span>{{ row.reviewStatus }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="160" fixed="right" align="center">
|
<el-table-column label="操作" min-width="220" fixed="right" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-link type="primary" @click="openScore(row.__raw, row.__rawIndex)">
|
<div class="score-list-table__actions">
|
||||||
详情
|
<el-link type="primary" @click="openScore(row.__raw, row.__rawIndex)">
|
||||||
</el-link>
|
详情
|
||||||
<el-link
|
</el-link>
|
||||||
v-if="showScoreDelete"
|
<el-link
|
||||||
type="danger"
|
v-if="canReviewScore(row)"
|
||||||
@click="deleteScore(row.__rawIndex)"
|
type="primary"
|
||||||
>
|
@click="startReviewScore(row.__raw, row.__rawIndex)"
|
||||||
删除
|
>
|
||||||
</el-link>
|
复评
|
||||||
|
</el-link>
|
||||||
|
<el-link
|
||||||
|
v-if="showScoreDelete"
|
||||||
|
type="danger"
|
||||||
|
@click="deleteScore(row.__rawIndex)"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-link>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -2231,7 +2240,8 @@ export default {
|
|||||||
showScoreDelete() {
|
showScoreDelete() {
|
||||||
if (this.readonly) return false;
|
if (this.readonly) return false;
|
||||||
const status = this.archiveForm.approvalStatus || 'draft';
|
const status = this.archiveForm.approvalStatus || 'draft';
|
||||||
return status === 'draft' || status === 'rejected';
|
// 草稿/驳回可删;审批通过后也允许删除并重建评分表(旧评分逻辑删除保留,新评分审核通过前仍用旧额度)
|
||||||
|
return status === 'draft' || status === 'rejected' || status === 'approved';
|
||||||
},
|
},
|
||||||
isNewScore() {
|
isNewScore() {
|
||||||
return this.scoreRecordIndex < 0 && !this.reviewMode;
|
return this.scoreRecordIndex < 0 && !this.reviewMode;
|
||||||
@@ -2281,10 +2291,17 @@ export default {
|
|||||||
{ categoryCode: 'plus', categoryName: '加分项目' },
|
{ categoryCode: 'plus', categoryName: '加分项目' },
|
||||||
{ categoryCode: 'minus', categoryName: '减分项目' },
|
{ categoryCode: 'minus', categoryName: '减分项目' },
|
||||||
];
|
];
|
||||||
|
const maxMinus = Number(this.currentScore?.maxMinusScore);
|
||||||
|
const hasMaxMinus = Number.isFinite(maxMinus) && maxMinus >= 0;
|
||||||
const rows = categoryMap.map((category, index) => {
|
const rows = categoryMap.map((category, index) => {
|
||||||
const details = this.getScoreCategoryDetails(category.categoryCode);
|
const details = this.getScoreCategoryDetails(category.categoryCode);
|
||||||
const selfScore = details.reduce((sum, item) => sum + Number(item.selfScore || 0), 0);
|
let selfScore = details.reduce((sum, item) => sum + Number(item.selfScore || 0), 0);
|
||||||
const reviewScore = details.reduce((sum, item) => sum + Number(item.reviewScore || 0), 0);
|
let reviewScore = details.reduce((sum, item) => sum + Number(item.reviewScore || 0), 0);
|
||||||
|
// 减分项目合计受量化表「最高扣分」限制
|
||||||
|
if (category.categoryCode === 'minus' && hasMaxMinus) {
|
||||||
|
selfScore = Math.min(selfScore, maxMinus);
|
||||||
|
reviewScore = Math.min(reviewScore, maxMinus);
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...category,
|
...category,
|
||||||
index: index + 1,
|
index: index + 1,
|
||||||
@@ -4199,6 +4216,7 @@ export default {
|
|||||||
scoreDate: this.$dayjs().format('YYYY-MM-DD'),
|
scoreDate: this.$dayjs().format('YYYY-MM-DD'),
|
||||||
applyCreditLimit: this.archiveForm.applyCreditLimit || '',
|
applyCreditLimit: this.archiveForm.applyCreditLimit || '',
|
||||||
maxCreditLimit: '',
|
maxCreditLimit: '',
|
||||||
|
maxMinusScore: '',
|
||||||
creditLevel: '',
|
creditLevel: '',
|
||||||
standards: [],
|
standards: [],
|
||||||
tempApplyCreditLimit: '',
|
tempApplyCreditLimit: '',
|
||||||
@@ -4218,6 +4236,10 @@ export default {
|
|||||||
: score.applyCreditLimit
|
: score.applyCreditLimit
|
||||||
),
|
),
|
||||||
maxCreditLimit: this.normalizeOptionalAmount(score.maxCreditLimit),
|
maxCreditLimit: this.normalizeOptionalAmount(score.maxCreditLimit),
|
||||||
|
maxMinusScore:
|
||||||
|
score.maxMinusScore === undefined || score.maxMinusScore === null
|
||||||
|
? ''
|
||||||
|
: score.maxMinusScore,
|
||||||
tempApplyCreditLimit: this.normalizeOptionalAmount(score.tempApplyCreditLimit),
|
tempApplyCreditLimit: this.normalizeOptionalAmount(score.tempApplyCreditLimit),
|
||||||
attachments: score.attachments || this.parseAttachments(score.proofAttachments),
|
attachments: score.attachments || this.parseAttachments(score.proofAttachments),
|
||||||
details: (score.details || []).map(detail => ({
|
details: (score.details || []).map(detail => ({
|
||||||
@@ -4256,7 +4278,14 @@ export default {
|
|||||||
String(item.score.creditLevel || '').trim()
|
String(item.score.creditLevel || '').trim()
|
||||||
)[0]?.score;
|
)[0]?.score;
|
||||||
},
|
},
|
||||||
|
/** 曾审核通过后:删除/重建评分在再次审核通过前,主表等级与额度保持旧值暂时生效 */
|
||||||
|
shouldFreezeEffectiveCredit(archive = this.archiveForm) {
|
||||||
|
return Boolean(archive?.approvedTime);
|
||||||
|
},
|
||||||
applyArchiveCreditFromScores(archive) {
|
applyArchiveCreditFromScores(archive) {
|
||||||
|
if (this.shouldFreezeEffectiveCredit(archive)) {
|
||||||
|
return archive;
|
||||||
|
}
|
||||||
const latestScore = this.getLatestCreditScore(archive.scores || []);
|
const latestScore = this.getLatestCreditScore(archive.scores || []);
|
||||||
archive.customerLevel = latestScore?.creditLevel || archive.customerLevel || '';
|
archive.customerLevel = latestScore?.creditLevel || archive.customerLevel || '';
|
||||||
archive.maxCreditLimit = latestScore?.maxCreditLimit || archive.maxCreditLimit || '';
|
archive.maxCreditLimit = latestScore?.maxCreditLimit || archive.maxCreditLimit || '';
|
||||||
@@ -4588,9 +4617,31 @@ export default {
|
|||||||
}
|
}
|
||||||
this.$nextTick(() => this.openScore(scores[0], 0));
|
this.$nextTick(() => this.openScore(scores[0], 0));
|
||||||
},
|
},
|
||||||
|
/** 提交后详情页评分表:审批中且未完成复评时可复评 */
|
||||||
|
canReviewScore(row = {}) {
|
||||||
|
return (
|
||||||
|
this.hasPermission('customer_type_re_cert') &&
|
||||||
|
this.archiveForm.approvalStatus === 'reviewing' &&
|
||||||
|
row.reviewStatus !== '已完成'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
startReviewScore(row, index = 0) {
|
||||||
|
if (!row) {
|
||||||
|
this.$message.warning('暂无评分记录,无法复评');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 详情只读进入时也要能改复评得分并确认
|
||||||
|
this.reviewMode = true;
|
||||||
|
this.openScore(row, index);
|
||||||
|
},
|
||||||
addScore() {
|
addScore() {
|
||||||
if ((this.archiveForm.scores || []).length) {
|
const existingScores = this.archiveForm.scores || [];
|
||||||
this.$message.warning('请勿重复添加');
|
if (existingScores.length) {
|
||||||
|
this.$message.warning(
|
||||||
|
this.shouldFreezeEffectiveCredit()
|
||||||
|
? '请先删除原评分记录后再添加新评分表'
|
||||||
|
: '请勿重复添加'
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.scoreRecordIndex = -1;
|
this.scoreRecordIndex = -1;
|
||||||
@@ -4611,9 +4662,9 @@ export default {
|
|||||||
const loadOptions = this.scoreQuantificationOptions.length
|
const loadOptions = this.scoreQuantificationOptions.length
|
||||||
? Promise.resolve()
|
? Promise.resolve()
|
||||||
: this.initScoreQuantificationOptions();
|
: this.initScoreQuantificationOptions();
|
||||||
loadOptions.then(() =>
|
loadOptions
|
||||||
this.ensureScoreQuantificationOption(this.currentScore.quantificationId)
|
.then(() => this.ensureScoreQuantificationOption(this.currentScore.quantificationId))
|
||||||
);
|
.then(() => this.applyMaxMinusScoreFromQuantification(this.currentScore.quantificationId));
|
||||||
},
|
},
|
||||||
resetScoreDialog() {
|
resetScoreDialog() {
|
||||||
this.scoreRecordIndex = -1;
|
this.scoreRecordIndex = -1;
|
||||||
@@ -4648,6 +4699,11 @@ export default {
|
|||||||
applyCreditLimit: this.currentScore.applyCreditLimit,
|
applyCreditLimit: this.currentScore.applyCreditLimit,
|
||||||
maxCreditLimit: '',
|
maxCreditLimit: '',
|
||||||
creditLevel: '',
|
creditLevel: '',
|
||||||
|
maxMinusScore:
|
||||||
|
quantificationData.maxMinusScore ??
|
||||||
|
scoreTemplate.maxMinusScore ??
|
||||||
|
this.currentScore.maxMinusScore ??
|
||||||
|
'',
|
||||||
tempApplyCreditLimit: this.currentScore.tempApplyCreditLimit,
|
tempApplyCreditLimit: this.currentScore.tempApplyCreditLimit,
|
||||||
tempCreditStartDate: this.currentScore.tempCreditStartDate,
|
tempCreditStartDate: this.currentScore.tempCreditStartDate,
|
||||||
tempCreditEndDate: this.currentScore.tempCreditEndDate,
|
tempCreditEndDate: this.currentScore.tempCreditEndDate,
|
||||||
@@ -4664,6 +4720,17 @@ export default {
|
|||||||
this.calculateScore(this.currentScore);
|
this.calculateScore(this.currentScore);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
applyMaxMinusScoreFromQuantification(quantificationId) {
|
||||||
|
if (!quantificationId) return Promise.resolve();
|
||||||
|
return getCreditScoreQuantificationDetail(quantificationId).then(res => {
|
||||||
|
const data = res?.data?.data || {};
|
||||||
|
this.currentScore.maxMinusScore =
|
||||||
|
data.maxMinusScore === undefined || data.maxMinusScore === null
|
||||||
|
? ''
|
||||||
|
: data.maxMinusScore;
|
||||||
|
this.calculateScore(this.currentScore);
|
||||||
|
});
|
||||||
|
},
|
||||||
normalizeScoreTemplateDetails(template = {}) {
|
normalizeScoreTemplateDetails(template = {}) {
|
||||||
const categoryDetails = (template.categories || []).flatMap(category =>
|
const categoryDetails = (template.categories || []).flatMap(category =>
|
||||||
(category.items || []).map(item => ({
|
(category.items || []).map(item => ({
|
||||||
@@ -4808,13 +4875,24 @@ export default {
|
|||||||
if (!valid) return null;
|
if (!valid) return null;
|
||||||
|
|
||||||
const distance = increase ? input - base : base - input;
|
const distance = increase ? input - base : base - input;
|
||||||
|
const changeValue = Number(rule.changeValue);
|
||||||
|
const stepSize = Number.isFinite(changeValue) && changeValue > 0 ? changeValue : 1;
|
||||||
|
const steps = Math.floor(distance / stepSize + 1e-9);
|
||||||
const stepScore = Math.abs(Number(rule.score || 0));
|
const stepScore = Math.abs(Number(rule.score || 0));
|
||||||
if (!Number.isFinite(stepScore)) return null;
|
if (!Number.isFinite(stepScore) || steps < 0) return null;
|
||||||
|
|
||||||
const projectScore = this.getScoreItemFullScore(detail);
|
// 减分项:按阶梯累计扣分,存正数,总分计算时再减;不做项目满分截断
|
||||||
|
if (this.isMinusScoreDetail(detail)) {
|
||||||
|
return Number((steps * stepScore).toFixed(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基础/加分项:从项目分值起步,按阶梯加减,限制在 [0, 项目满分]
|
||||||
|
const baseScore = Number(detail.score || 0);
|
||||||
const signedStep = rule.scoreType === 'subtract' ? -stepScore : stepScore;
|
const signedStep = rule.scoreType === 'subtract' ? -stepScore : stepScore;
|
||||||
const calculatedScore = Number(detail.score || 0) + distance * signedStep;
|
const calculatedScore = baseScore + steps * signedStep;
|
||||||
return Number(Math.min(Math.max(calculatedScore, 0), projectScore).toFixed(2));
|
const projectScore = this.getScoreItemFullScore(detail);
|
||||||
|
const upper = Math.max(projectScore, baseScore, 0);
|
||||||
|
return Number(Math.min(Math.max(calculatedScore, 0), upper).toFixed(2));
|
||||||
},
|
},
|
||||||
handleScoreValueInput(detail, value) {
|
handleScoreValueInput(detail, value) {
|
||||||
const raw = String(value ?? '')
|
const raw = String(value ?? '')
|
||||||
@@ -4995,14 +5073,17 @@ export default {
|
|||||||
const plusDetails = this.getScoreCategoryDetailsByList(details, 'plus');
|
const plusDetails = this.getScoreCategoryDetailsByList(details, 'plus');
|
||||||
const minusDetails = this.getScoreCategoryDetailsByList(details, 'minus');
|
const minusDetails = this.getScoreCategoryDetailsByList(details, 'minus');
|
||||||
const sumScore = (list, prop) => list.reduce((sum, item) => sum + Number(item[prop] || 0), 0);
|
const sumScore = (list, prop) => list.reduce((sum, item) => sum + Number(item[prop] || 0), 0);
|
||||||
|
const rawMinusSelf = sumScore(minusDetails, 'selfScore');
|
||||||
|
const rawMinusReview = sumScore(minusDetails, 'reviewScore');
|
||||||
|
// 量化表「减分最高扣分」:所有减分项合计不得超过该上限
|
||||||
|
const maxMinus = Number(score.maxMinusScore);
|
||||||
|
const hasMaxMinus = Number.isFinite(maxMinus) && maxMinus >= 0;
|
||||||
|
const minusSelf = hasMaxMinus ? Math.min(rawMinusSelf, maxMinus) : rawMinusSelf;
|
||||||
|
const minusReview = hasMaxMinus ? Math.min(rawMinusReview, maxMinus) : rawMinusReview;
|
||||||
const selfScore =
|
const selfScore =
|
||||||
sumScore(basicDetails, 'selfScore') +
|
sumScore(basicDetails, 'selfScore') + sumScore(plusDetails, 'selfScore') - minusSelf;
|
||||||
sumScore(plusDetails, 'selfScore') -
|
|
||||||
sumScore(minusDetails, 'selfScore');
|
|
||||||
const reviewScore =
|
const reviewScore =
|
||||||
sumScore(basicDetails, 'reviewScore') +
|
sumScore(basicDetails, 'reviewScore') + sumScore(plusDetails, 'reviewScore') - minusReview;
|
||||||
sumScore(plusDetails, 'reviewScore') -
|
|
||||||
sumScore(minusDetails, 'reviewScore');
|
|
||||||
const hasReviewScore = details.some(
|
const hasReviewScore = details.some(
|
||||||
item => !this.isBlankScore(item.reviewScore)
|
item => !this.isBlankScore(item.reviewScore)
|
||||||
);
|
);
|
||||||
@@ -5076,7 +5157,11 @@ export default {
|
|||||||
return pending;
|
return pending;
|
||||||
},
|
},
|
||||||
deleteScore(index) {
|
deleteScore(index) {
|
||||||
this.$confirm('确定删除该评分记录?', '提示', {
|
const freezeCredit = this.shouldFreezeEffectiveCredit();
|
||||||
|
const confirmText = freezeCredit
|
||||||
|
? '确定删除该评分记录?删除后可重新添加评分表;原评分数据会保留,新评分再次审核通过前,客商等级与额度仍按原评分暂时生效。'
|
||||||
|
: '确定删除该评分记录?';
|
||||||
|
this.$confirm(confirmText, '提示', {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
@@ -5089,16 +5174,23 @@ export default {
|
|||||||
} else if (this.scoreRecordIndex > index) {
|
} else if (this.scoreRecordIndex > index) {
|
||||||
this.scoreRecordIndex -= 1;
|
this.scoreRecordIndex -= 1;
|
||||||
}
|
}
|
||||||
const latestScore = this.getLatestCreditScore(this.archiveForm.scores);
|
if (!freezeCredit) {
|
||||||
if (latestScore) {
|
const latestScore = this.getLatestCreditScore(this.archiveForm.scores);
|
||||||
this.syncArchiveCreditFromScores();
|
if (latestScore) {
|
||||||
} else {
|
this.syncArchiveCreditFromScores();
|
||||||
this.archiveForm.customerLevel = '';
|
} else {
|
||||||
this.archiveForm.maxCreditLimit = '';
|
this.archiveForm.customerLevel = '';
|
||||||
this.archiveForm.applyCreditLimit = '';
|
this.archiveForm.maxCreditLimit = '';
|
||||||
|
this.archiveForm.applyCreditLimit = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.handleScoreCurrentChange(this.scorePage.currentPage);
|
this.handleScoreCurrentChange(this.scorePage.currentPage);
|
||||||
this.saveScoreArchive('评分记录已删除', '评分记录已删除,请保存客商档案后生效');
|
this.saveScoreArchive(
|
||||||
|
freezeCredit
|
||||||
|
? '评分记录已删除,原等级与额度暂时继续生效'
|
||||||
|
: '评分记录已删除',
|
||||||
|
'评分记录已删除,请保存客商档案后生效'
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
},
|
},
|
||||||
@@ -5795,6 +5887,14 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.score-list-table__actions {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.contact-form,
|
.contact-form,
|
||||||
.receipt-form,
|
.receipt-form,
|
||||||
.invoice-form {
|
.invoice-form {
|
||||||
|
|||||||
+15
-15
@@ -49,28 +49,28 @@ export default ({ mode, command }) => {
|
|||||||
__VUE_I18N_LEGACY_API__: true,
|
__VUE_I18N_LEGACY_API__: true,
|
||||||
__INTLIFY_PROD_DEVTOOLS__: false,
|
__INTLIFY_PROD_DEVTOOLS__: false,
|
||||||
},
|
},
|
||||||
// server: {
|
|
||||||
// port: 2888,
|
|
||||||
// proxy: {
|
|
||||||
// '/api': {
|
|
||||||
// // 本地网关默认 80 端口(blade-gateway bootstrap.yml);开发时需先启动网关及 blade-auth/blade-system 等服务
|
|
||||||
// // 注意:本机 8080 一般是 Nacos,不是业务网关
|
|
||||||
// target: 'http://localhost',
|
|
||||||
// // target: 'http://172.16.203.228:8000', // 仅联调远程网关时启用(且不要 rewrite)
|
|
||||||
// changeOrigin: true,
|
|
||||||
// rewrite: path => path.replace(/^\/api/, ''),
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
server: {
|
server: {
|
||||||
port: 2889,
|
port: 2888,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://172.16.203.228:8000',
|
// 本地网关默认 80 端口(blade-gateway bootstrap.yml);开发时需先启动网关及 blade-auth/blade-system 等服务
|
||||||
|
// 注意:本机 8080 一般是 Nacos,不是业务网关
|
||||||
|
target: 'http://localhost',
|
||||||
|
// target: 'http://172.16.203.228:8000', // 仅联调远程网关时启用(且不要 rewrite)
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
|
rewrite: path => path.replace(/^\/api/, ''),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// server: {
|
||||||
|
// port: 2889,
|
||||||
|
// proxy: {
|
||||||
|
// '/api': {
|
||||||
|
// target: 'http://172.16.203.228:8000',
|
||||||
|
// changeOrigin: true,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'~': resolve(__dirname, './'),
|
'~': resolve(__dirname, './'),
|
||||||
|
|||||||
Reference in New Issue
Block a user