Merge remote-tracking branch 'origin/master' into feature/hcs-api-p0-web
# Conflicts: # src/views/business/contract-manage-change.vue
This commit is contained in:
@@ -14,6 +14,13 @@ export const cancel = api.cancel;
|
|||||||
export const reassign = api.reassign;
|
export const reassign = api.reassign;
|
||||||
export const complete = api.complete;
|
export const complete = api.complete;
|
||||||
export const batchComplete = api.batchComplete;
|
export const batchComplete = api.batchComplete;
|
||||||
|
/** 确认回单:待确认回单运单完成后转为已完成 */
|
||||||
|
export const confirmReturn = id =>
|
||||||
|
request({
|
||||||
|
url: `${baseUrl}/confirm-return`,
|
||||||
|
method: 'post',
|
||||||
|
params: { id },
|
||||||
|
});
|
||||||
export const changeRoute = data =>
|
export const changeRoute = data =>
|
||||||
request({
|
request({
|
||||||
url: `${baseUrl}/change-route`,
|
url: `${baseUrl}/change-route`,
|
||||||
|
|||||||
@@ -139,3 +139,10 @@ export const getPlatformCompanySelect = () => {
|
|||||||
method: 'get',
|
method: 'get',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getBusinessDeptSelect = () => {
|
||||||
|
return request({
|
||||||
|
url: '/blade-system/dept/business-dept-select',
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
+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);
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export const waybillStatusOptions = [
|
|||||||
{ label: '草稿', value: 'draft' },
|
{ label: '草稿', value: 'draft' },
|
||||||
{ label: '待执行', value: 'pending' },
|
{ label: '待执行', value: 'pending' },
|
||||||
{ label: '进行中', value: 'running' },
|
{ label: '进行中', value: 'running' },
|
||||||
|
{ label: '待确认回单', value: 'pending_return' },
|
||||||
{ label: '已完成', value: 'completed' },
|
{ label: '已完成', value: 'completed' },
|
||||||
{ label: '已取消', value: 'cancelled' },
|
{ label: '已取消', value: 'cancelled' },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -478,7 +478,15 @@ export const config = {
|
|||||||
formatStatus(row, prop, defaultText) {
|
formatStatus(row, prop, defaultText) {
|
||||||
if (prop !== 'businessStatus') return defaultText;
|
if (prop !== 'businessStatus') return defaultText;
|
||||||
const status = String(row.businessStatus || '');
|
const status = String(row.businessStatus || '');
|
||||||
const terminalStatuses = ['draft', 'completed', 'cancelled', 'waiting_dispatch', 'dispatching'];
|
const terminalStatuses = [
|
||||||
|
'draft',
|
||||||
|
'completed',
|
||||||
|
'cancelled',
|
||||||
|
'waiting_dispatch',
|
||||||
|
'dispatching',
|
||||||
|
'pending_return',
|
||||||
|
];
|
||||||
|
if (status === 'pending_return') return '待确认回单';
|
||||||
const needAccept =
|
const needAccept =
|
||||||
row.requireAccept === true ||
|
row.requireAccept === true ||
|
||||||
row.requireAccept === 1 ||
|
row.requireAccept === 1 ||
|
||||||
@@ -497,14 +505,20 @@ export const config = {
|
|||||||
getDriverRejectReason,
|
getDriverRejectReason,
|
||||||
isDriverRejectedWaybill,
|
isDriverRejectedWaybill,
|
||||||
canEdit(row) {
|
canEdit(row) {
|
||||||
// 进行中的运单不允许编辑
|
// 进行中、待确认回单不允许编辑
|
||||||
if (isInProgressStatus(row, row.businessStatusName)) {
|
if (isInProgressStatus(row, row.businessStatusName)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (String(row.businessStatus || '') === 'pending_return') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return !isDriverRejectedWaybill(row);
|
return !isDriverRejectedWaybill(row);
|
||||||
},
|
},
|
||||||
canCancel(row) {
|
canCancel(row) {
|
||||||
// 进行中的运单不允许取消
|
// 进行中、待确认回单不允许取消
|
||||||
|
if (String(row.businessStatus || '') === 'pending_return') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return !isInProgressStatus(row, row.businessStatusName);
|
return !isInProgressStatus(row, row.businessStatusName);
|
||||||
},
|
},
|
||||||
deleteStatus: ['draft'],
|
deleteStatus: ['draft'],
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ const validatePhone = (rule, value, callback) => {
|
|||||||
callback(new Error('请输入手机号'));
|
callback(new Error('请输入手机号'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 详情回显为脱敏手机号(含 *)时放行,未修改则提交时剔除该字段
|
||||||
|
if (phone.includes('*')) {
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!/^1\d{10}$/.test(phone)) {
|
if (!/^1\d{10}$/.test(phone)) {
|
||||||
callback(new Error('请输入正确的手机号'));
|
callback(new Error('请输入正确的手机号'));
|
||||||
return;
|
return;
|
||||||
@@ -121,6 +126,62 @@ export default {
|
|||||||
order: 60,
|
order: 60,
|
||||||
rules: [{ required: true, message: '请选择小程序身份', trigger: 'change' }],
|
rules: [{ required: true, message: '请选择小程序身份', trigger: 'change' }],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '所属组织',
|
||||||
|
prop: 'deptId',
|
||||||
|
formslot: true,
|
||||||
|
hide: true,
|
||||||
|
span: 24,
|
||||||
|
order: 55,
|
||||||
|
rules: [{ required: true, message: '请选择所属组织', trigger: 'change' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '所属组织',
|
||||||
|
prop: 'deptName',
|
||||||
|
minWidth: 180,
|
||||||
|
overHidden: true,
|
||||||
|
addDisplay: false,
|
||||||
|
editDisplay: false,
|
||||||
|
viewDisplay: false,
|
||||||
|
order: 54,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '数据权限范围',
|
||||||
|
prop: 'dataScopeRange',
|
||||||
|
type: 'radio',
|
||||||
|
span: 24,
|
||||||
|
hide: true,
|
||||||
|
order: 53,
|
||||||
|
value: 1,
|
||||||
|
dicData: [
|
||||||
|
{ label: '仅所属组织', value: 1 },
|
||||||
|
{ label: '全部组织', value: 2 },
|
||||||
|
{ label: '自定义', value: 3 },
|
||||||
|
],
|
||||||
|
rules: [{ required: true, message: '请选择数据权限范围', trigger: 'change' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '数据层级范围',
|
||||||
|
prop: 'dataLevelRange',
|
||||||
|
type: 'radio',
|
||||||
|
span: 24,
|
||||||
|
hide: true,
|
||||||
|
order: 52,
|
||||||
|
value: 1,
|
||||||
|
dicData: [
|
||||||
|
{ label: '包含下级', value: 1 },
|
||||||
|
{ label: '仅本级', value: 2 },
|
||||||
|
],
|
||||||
|
rules: [{ required: true, message: '请选择数据层级范围', trigger: 'change' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '数据权限',
|
||||||
|
prop: 'dataScopeDeptIds',
|
||||||
|
formslot: true,
|
||||||
|
span: 24,
|
||||||
|
hide: true,
|
||||||
|
order: 51,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '性别',
|
label: '性别',
|
||||||
prop: 'sex',
|
prop: 'sex',
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -1591,8 +1591,8 @@
|
|||||||
<el-link type="primary" v-if="canCancel(row)" @click="handleAction('cancel', row)">
|
<el-link type="primary" v-if="canCancel(row)" @click="handleAction('cancel', row)">
|
||||||
取消
|
取消
|
||||||
</el-link>
|
</el-link>
|
||||||
<el-link type="primary" v-if="canComplete(row)" @click="handleAction('complete', row)">
|
<el-link type="primary" v-if="canComplete(row)" @click="handleCompleteAction(row)">
|
||||||
{{ completeActionLabel }}
|
{{ completeActionLabelFor(row) }}
|
||||||
</el-link>
|
</el-link>
|
||||||
<el-link type="primary" v-if="canMaintainMileage(row)" @click="openMileageDialog(row)">
|
<el-link type="primary" v-if="canMaintainMileage(row)" @click="openMileageDialog(row)">
|
||||||
维护里程
|
维护里程
|
||||||
@@ -5111,7 +5111,29 @@ export default {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const status = this.statusValue(row);
|
const status = this.statusValue(row);
|
||||||
return status === 'processing' || status === 'running';
|
return status === 'processing' || status === 'running' || status === 'pending_return';
|
||||||
|
},
|
||||||
|
completeActionLabelFor(row) {
|
||||||
|
return this.statusValue(row) === 'pending_return' ? '确认回单' : this.completeActionLabel;
|
||||||
|
},
|
||||||
|
handleCompleteAction(row) {
|
||||||
|
const isPendingReturn = this.statusValue(row) === 'pending_return';
|
||||||
|
const actionName = this.completeActionLabelFor(row);
|
||||||
|
this.$confirm(`确定${actionName}该${this.config.title}?`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
if (isPendingReturn && typeof this.api.confirmReturn === 'function') {
|
||||||
|
return this.api.confirmReturn(row.id);
|
||||||
|
}
|
||||||
|
return this.api.complete(row.id);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.$message.success(`${actionName}成功`);
|
||||||
|
this.onLoad(this.page, this.query);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
canMaintainMileage(row) {
|
canMaintainMileage(row) {
|
||||||
return (
|
return (
|
||||||
@@ -6543,6 +6565,9 @@ export default {
|
|||||||
if (status === 'completed') {
|
if (status === 'completed') {
|
||||||
return 'primary';
|
return 'primary';
|
||||||
}
|
}
|
||||||
|
if (status === 'pending_return') {
|
||||||
|
return 'warning';
|
||||||
|
}
|
||||||
return 'warning';
|
return 'warning';
|
||||||
},
|
},
|
||||||
normalizeRow(row, saveAsDraft = false) {
|
normalizeRow(row, saveAsDraft = false) {
|
||||||
@@ -10356,7 +10381,14 @@ export default {
|
|||||||
this.$message.warning('请选择至少一条数据');
|
this.$message.warning('请选择至少一条数据');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.$confirm('确定完成选中的运单?', '提示', {
|
const pendingReturnCount = this.selectionList.filter(
|
||||||
|
row => this.statusValue(row) === 'pending_return'
|
||||||
|
).length;
|
||||||
|
const tip =
|
||||||
|
pendingReturnCount > 0
|
||||||
|
? `确定完成选中的运单?其中 ${pendingReturnCount} 条为待确认回单,确认后将转为已完成。`
|
||||||
|
: '确定完成选中的运单?';
|
||||||
|
this.$confirm(tip, '提示', {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
@@ -10382,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 {
|
||||||
|
|||||||
@@ -8,11 +8,17 @@
|
|||||||
<el-form-item label="变更类型" class="contract-basic-section__change-type">
|
<el-form-item label="变更类型" class="contract-basic-section__change-type">
|
||||||
<el-radio-group v-model="form.changeType">
|
<el-radio-group v-model="form.changeType">
|
||||||
<el-radio label="合同信息变更" />
|
<el-radio label="合同信息变更" />
|
||||||
|
<el-radio label="结算规则变更" />
|
||||||
<el-radio label="终止合同" />
|
<el-radio label="终止合同" />
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="签约类型" prop="signType">
|
<el-form-item label="签约类型" prop="signType">
|
||||||
<el-select v-model="form.signType" placeholder="请选择签约类型" clearable>
|
<el-select
|
||||||
|
v-model="form.signType"
|
||||||
|
placeholder="请选择签约类型"
|
||||||
|
clearable
|
||||||
|
:disabled="isSettlementRuleChange"
|
||||||
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in signTypeOptions"
|
v-for="item in signTypeOptions"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
@@ -30,6 +36,7 @@
|
|||||||
maxlength="100"
|
maxlength="100"
|
||||||
show-word-limit
|
show-word-limit
|
||||||
placeholder="请输入合同名称"
|
placeholder="请输入合同名称"
|
||||||
|
:disabled="isSettlementRuleChange"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="合同类型">
|
<el-form-item label="合同类型">
|
||||||
@@ -80,6 +87,7 @@
|
|||||||
format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
||||||
value-format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
|
:disabled="isSettlementRuleChange"
|
||||||
/>
|
/>
|
||||||
<span>至</span>
|
<span>至</span>
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
@@ -88,6 +96,7 @@
|
|||||||
format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
||||||
value-format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
|
:disabled="isSettlementRuleChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -95,6 +104,7 @@
|
|||||||
<el-input
|
<el-input
|
||||||
v-model="form.paymentDays"
|
v-model="form.paymentDays"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
|
:disabled="isSettlementRuleChange"
|
||||||
@input="value => positiveIntegerInput('paymentDays', value)"
|
@input="value => positiveIntegerInput('paymentDays', value)"
|
||||||
>
|
>
|
||||||
<template #suffix>天</template>
|
<template #suffix>天</template>
|
||||||
@@ -105,22 +115,36 @@
|
|||||||
:model-value="contractAmountDisplay"
|
:model-value="contractAmountDisplay"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
clearable
|
clearable
|
||||||
|
:disabled="isSettlementRuleChange"
|
||||||
@input="handleContractAmountInput"
|
@input="handleContractAmountInput"
|
||||||
@blur="formatContractAmountField"
|
@blur="formatContractAmountField"
|
||||||
@clear="form.contractAmount = null"
|
@clear="form.contractAmount = null"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="是否范本">
|
<el-form-item label="是否范本">
|
||||||
<el-select v-model="form.templateFlag" placeholder="请选择">
|
<el-select
|
||||||
|
v-model="form.templateFlag"
|
||||||
|
placeholder="请选择"
|
||||||
|
:disabled="isSettlementRuleChange"
|
||||||
|
>
|
||||||
<el-option label="否" :value="0" />
|
<el-option label="否" :value="0" />
|
||||||
<el-option label="是" :value="1" />
|
<el-option label="是" :value="1" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="原件合同编号">
|
<el-form-item label="原件合同编号">
|
||||||
<el-input v-model="form.originalContractNo" maxlength="100" placeholder="请输入" />
|
<el-input
|
||||||
|
v-model="form.originalContractNo"
|
||||||
|
maxlength="100"
|
||||||
|
placeholder="请输入"
|
||||||
|
:disabled="isSettlementRuleChange"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="是否电子章">
|
<el-form-item label="是否电子章">
|
||||||
<el-select v-model="form.electronicSealFlag" placeholder="请选择">
|
<el-select
|
||||||
|
v-model="form.electronicSealFlag"
|
||||||
|
placeholder="请选择"
|
||||||
|
:disabled="isSettlementRuleChange"
|
||||||
|
>
|
||||||
<el-option label="否" :value="0" />
|
<el-option label="否" :value="0" />
|
||||||
<el-option label="是" :value="1" />
|
<el-option label="是" :value="1" />
|
||||||
</el-select>
|
</el-select>
|
||||||
@@ -129,13 +153,19 @@
|
|||||||
<el-input
|
<el-input
|
||||||
v-model="form.invoiceCycle"
|
v-model="form.invoiceCycle"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
|
:disabled="isSettlementRuleChange"
|
||||||
@input="value => positiveIntegerInput('invoiceCycle', value)"
|
@input="value => positiveIntegerInput('invoiceCycle', value)"
|
||||||
>
|
>
|
||||||
<template #suffix>天</template>
|
<template #suffix>天</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="结算币种" prop="settlementCurrency">
|
<el-form-item label="结算币种" prop="settlementCurrency">
|
||||||
<el-select v-model="form.settlementCurrency" placeholder="请选择结算币种" clearable>
|
<el-select
|
||||||
|
v-model="form.settlementCurrency"
|
||||||
|
placeholder="请选择结算币种"
|
||||||
|
clearable
|
||||||
|
:disabled="isSettlementRuleChange"
|
||||||
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in settlementCurrencyOptions"
|
v-for="item in settlementCurrencyOptions"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
@@ -145,7 +175,12 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="合同格式">
|
<el-form-item label="合同格式">
|
||||||
<el-select v-model="form.contractFormat" placeholder="请选择合同格式" clearable>
|
<el-select
|
||||||
|
v-model="form.contractFormat"
|
||||||
|
placeholder="请选择合同格式"
|
||||||
|
clearable
|
||||||
|
:disabled="isSettlementRuleChange"
|
||||||
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in contractFormatOptions"
|
v-for="item in contractFormatOptions"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
@@ -158,19 +193,30 @@
|
|||||||
<el-input
|
<el-input
|
||||||
v-model="form.copyCount"
|
v-model="form.copyCount"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
|
:disabled="isSettlementRuleChange"
|
||||||
@input="value => positiveIntegerInput('copyCount', value)"
|
@input="value => positiveIntegerInput('copyCount', value)"
|
||||||
>
|
>
|
||||||
<template #suffix>份</template>
|
<template #suffix>份</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="是否需要加盖法人章">
|
<el-form-item label="是否需要加盖法人章">
|
||||||
<el-select v-model="form.legalSealFlag" placeholder="请选择" clearable>
|
<el-select
|
||||||
|
v-model="form.legalSealFlag"
|
||||||
|
placeholder="请选择"
|
||||||
|
clearable
|
||||||
|
:disabled="isSettlementRuleChange"
|
||||||
|
>
|
||||||
<el-option label="否" :value="0" />
|
<el-option label="否" :value="0" />
|
||||||
<el-option label="是" :value="1" />
|
<el-option label="是" :value="1" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="结算方式">
|
<el-form-item label="结算方式">
|
||||||
<el-select v-model="form.settlementMode" placeholder="请选择结算方式" clearable>
|
<el-select
|
||||||
|
v-model="form.settlementMode"
|
||||||
|
placeholder="请选择结算方式"
|
||||||
|
clearable
|
||||||
|
:disabled="isSettlementRuleChange"
|
||||||
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in settlementModeOptions"
|
v-for="item in settlementModeOptions"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
@@ -191,6 +237,7 @@
|
|||||||
maxlength="1000"
|
maxlength="1000"
|
||||||
show-word-limit
|
show-word-limit
|
||||||
placeholder="请输入合同主要内容"
|
placeholder="请输入合同主要内容"
|
||||||
|
:disabled="isSettlementRuleChange"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="其他事项说明" class="contract-basic-section__remark">
|
<el-form-item label="其他事项说明" class="contract-basic-section__remark">
|
||||||
@@ -201,6 +248,7 @@
|
|||||||
maxlength="200"
|
maxlength="200"
|
||||||
show-word-limit
|
show-word-limit
|
||||||
placeholder="请输入其他事项说明"
|
placeholder="请输入其他事项说明"
|
||||||
|
:disabled="isSettlementRuleChange"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<!-- 风险客商提示:合同已推送合规并留痕高风险/黑名单时展示,变更提交同样要求特批附件 -->
|
<!-- 风险客商提示:合同已推送合规并留痕高风险/黑名单时展示,变更提交同样要求特批附件 -->
|
||||||
@@ -214,6 +262,7 @@
|
|||||||
title="用印材料"
|
title="用印材料"
|
||||||
description
|
description
|
||||||
attachment-type
|
attachment-type
|
||||||
|
:readonly="isSettlementRuleChange"
|
||||||
:attachment-location-options="changeAttachmentLocationOptions"
|
:attachment-location-options="changeAttachmentLocationOptions"
|
||||||
:rows="contractFileRows"
|
:rows="contractFileRows"
|
||||||
:preview="previewAttachment"
|
:preview="previewAttachment"
|
||||||
@@ -224,12 +273,36 @@
|
|||||||
<section class="change-section">
|
<section class="change-section">
|
||||||
<div class="section-head">
|
<div class="section-head">
|
||||||
<div class="dialog-section-title">计费信息</div>
|
<div class="dialog-section-title">计费信息</div>
|
||||||
<el-button v-if="feeGenerationMode !== 'manual'" type="primary" plain @click="addPlan"
|
<el-button
|
||||||
|
v-if="feeGenerationMode !== 'manual'"
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
@click="addPlan"
|
||||||
>添加</el-button
|
>添加</el-button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<el-radio-group v-model="feeGenerationMode"><el-radio label="system">系统生成</el-radio><el-radio label="manual">账单导入生成</el-radio></el-radio-group>
|
<el-radio-group v-model="feeGenerationMode"
|
||||||
<el-table :data="plans" border class="change-table"><el-table-column type="index" label="序号" width="70" /><el-table-column prop="planName" label="方案名称" min-width="220" /><el-table-column label="默认方案" min-width="120"><template #default="{ row }">{{ row.defaultPlan ? '是' : '否' }}</template></el-table-column><el-table-column prop="remark" label="备注" min-width="260" /><el-table-column label="操作" width="180"><template #default="{ row, $index }"><el-link type="primary" @click="editPlan(row, $index)">编辑</el-link><el-link type="danger" @click="plans.splice($index, 1)">删除</el-link></template></el-table-column></el-table>
|
><el-radio label="system">系统生成</el-radio
|
||||||
|
><el-radio label="manual">账单导入生成</el-radio></el-radio-group
|
||||||
|
>
|
||||||
|
<el-table :data="plans" border class="change-table"
|
||||||
|
><el-table-column type="index" label="序号" width="70" /><el-table-column
|
||||||
|
prop="planName"
|
||||||
|
label="方案名称"
|
||||||
|
min-width="220"
|
||||||
|
/><el-table-column label="默认方案" min-width="120"
|
||||||
|
><template #default="{ row }">{{
|
||||||
|
row.defaultPlan ? '是' : '否'
|
||||||
|
}}</template></el-table-column
|
||||||
|
><el-table-column prop="remark" label="备注" min-width="260" /><el-table-column
|
||||||
|
label="操作"
|
||||||
|
width="180"
|
||||||
|
><template #default="{ row, $index }"
|
||||||
|
><el-link type="primary" @click="editPlan(row, $index)">编辑</el-link
|
||||||
|
><el-link type="danger" @click="plans.splice($index, 1)">删除</el-link></template
|
||||||
|
></el-table-column
|
||||||
|
></el-table
|
||||||
|
>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="change-section">
|
<section class="change-section">
|
||||||
@@ -248,19 +321,106 @@
|
|||||||
<el-radio :label="0">关闭</el-radio>
|
<el-radio :label="0">关闭</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</div>
|
</div>
|
||||||
<el-form v-if="settlementRule.autoGenerate === 1" :model="settlementRule" label-position="right" label-width="auto" class="settlement-form">
|
<el-form
|
||||||
<el-form-item label="账单起始日" required><el-date-picker v-model="settlementRule.billStartDate" type="date" placeholder="请选择账单起始日" format="YYYY-MM-DD" value-format="YYYY-MM-DD" /></el-form-item>
|
v-if="settlementRule.autoGenerate === 1"
|
||||||
<el-form-item label="结算类型" required><el-select v-model="settlementRule.settlementType" placeholder="请选择结算类型" @change="handleSettlementTypeChange"><el-option v-for="item in settlementTypeOptions" :key="item" :label="item" :value="item" /></el-select></el-form-item>
|
:model="settlementRule"
|
||||||
<el-form-item v-if="showSettlementBillCycleType" label="结算周期" required><el-select v-model="settlementRule.billCycleType" placeholder="请选择结算周期" @change="handleCycleTypeChange"><el-option v-for="item in billCycleTypeOptions" :key="item" :label="item" :value="item" /></el-select></el-form-item>
|
label-position="right"
|
||||||
<el-form-item v-if="showSettlementBillCutoffDay" label="账单截单日" required><el-select v-model="settlementRule.billCutoffDay" placeholder="请选择账单截单日"><el-option v-for="item in billCutoffDayOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select></el-form-item>
|
label-width="auto"
|
||||||
<el-form-item v-if="showSettlementCycleDays" label="周期天数" required><el-select v-model="settlementRule.cycleDays" placeholder="请选择周期天数"><el-option v-for="item in cycleDayOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select></el-form-item>
|
class="settlement-form"
|
||||||
|
>
|
||||||
|
<el-form-item label="账单起始日" required
|
||||||
|
><el-date-picker
|
||||||
|
v-model="settlementRule.billStartDate"
|
||||||
|
type="date"
|
||||||
|
placeholder="请选择账单起始日"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
/></el-form-item>
|
||||||
|
<el-form-item label="结算类型" required
|
||||||
|
><el-select
|
||||||
|
v-model="settlementRule.settlementType"
|
||||||
|
placeholder="请选择结算类型"
|
||||||
|
@change="handleSettlementTypeChange"
|
||||||
|
><el-option
|
||||||
|
v-for="item in settlementTypeOptions"
|
||||||
|
:key="item"
|
||||||
|
:label="item"
|
||||||
|
:value="item"
|
||||||
|
/></el-select></el-form-item>
|
||||||
|
<el-form-item v-if="showSettlementBillCycleType" label="结算周期" required
|
||||||
|
><el-select
|
||||||
|
v-model="settlementRule.billCycleType"
|
||||||
|
placeholder="请选择结算周期"
|
||||||
|
@change="handleCycleTypeChange"
|
||||||
|
><el-option
|
||||||
|
v-for="item in billCycleTypeOptions"
|
||||||
|
:key="item"
|
||||||
|
:label="item"
|
||||||
|
:value="item"
|
||||||
|
/></el-select></el-form-item>
|
||||||
|
<el-form-item v-if="showSettlementBillCutoffDay" label="账单截单日" required
|
||||||
|
><el-select v-model="settlementRule.billCutoffDay" placeholder="请选择账单截单日"
|
||||||
|
><el-option
|
||||||
|
v-for="item in billCutoffDayOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/></el-select></el-form-item>
|
||||||
|
<el-form-item v-if="showSettlementCycleDays" label="周期天数" required
|
||||||
|
><el-select v-model="settlementRule.cycleDays" placeholder="请选择周期天数"
|
||||||
|
><el-option
|
||||||
|
v-for="item in cycleDayOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/></el-select></el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<el-table v-if="settlementRule.autoGenerate === 1 && showSettlementCustomPeriods" :data="settlementRule.customPeriods || []" border class="change-table custom-periods-table">
|
<el-table
|
||||||
|
v-if="settlementRule.autoGenerate === 1 && showSettlementCustomPeriods"
|
||||||
|
:data="settlementRule.customPeriods || []"
|
||||||
|
border
|
||||||
|
class="change-table custom-periods-table"
|
||||||
|
>
|
||||||
<el-table-column type="index" label="序号" width="70" />
|
<el-table-column type="index" label="序号" width="70" />
|
||||||
<el-table-column label="运单区间-开始日" min-width="180"><template #default="{ row, $index }"><el-select :model-value="row.startDay" placeholder="请选择" clearable @update:model-value="value => handleCustomPeriodStartDayChange($index, value)"><el-option v-for="item in billCutoffDayOptions" :key="`start-${item.value}`" :label="item.label" :value="item.value" /></el-select></template></el-table-column>
|
<el-table-column label="运单区间-开始日" min-width="180"
|
||||||
<el-table-column label="运单区间-结束日" min-width="200"><template #default="{ row, $index }"><el-select :model-value="row.endDay" placeholder="请选择" clearable @update:model-value="value => handleCustomPeriodEndDayChange($index, value)"><el-option v-for="item in customPeriodEndDayOptions(row)" :key="item.value" :label="item.label" :value="item.value" /></el-select></template></el-table-column>
|
><template #default="{ row, $index }"
|
||||||
<el-table-column label="操作" width="100"><template #default="{ $index }"><el-link v-if="$index === 0" type="primary" @click="addCustomPeriodRow">添加</el-link><el-link v-else type="danger" @click="removeCustomPeriodRow($index)">删除</el-link></template></el-table-column>
|
><el-select
|
||||||
<template #empty><el-link type="primary" @click="addCustomPeriodRow">添加</el-link></template>
|
:model-value="row.startDay"
|
||||||
|
placeholder="请选择"
|
||||||
|
clearable
|
||||||
|
@update:model-value="value => handleCustomPeriodStartDayChange($index, value)"
|
||||||
|
><el-option
|
||||||
|
v-for="item in billCutoffDayOptions"
|
||||||
|
:key="`start-${item.value}`"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/></el-select></template
|
||||||
|
></el-table-column>
|
||||||
|
<el-table-column label="运单区间-结束日" min-width="200"
|
||||||
|
><template #default="{ row, $index }"
|
||||||
|
><el-select
|
||||||
|
:model-value="row.endDay"
|
||||||
|
placeholder="请选择"
|
||||||
|
clearable
|
||||||
|
@update:model-value="value => handleCustomPeriodEndDayChange($index, value)"
|
||||||
|
><el-option
|
||||||
|
v-for="item in customPeriodEndDayOptions(row)"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/></el-select></template
|
||||||
|
></el-table-column>
|
||||||
|
<el-table-column label="操作" width="100"
|
||||||
|
><template #default="{ $index }"
|
||||||
|
><el-link v-if="$index === 0" type="primary" @click="addCustomPeriodRow">添加</el-link
|
||||||
|
><el-link v-else type="danger" @click="removeCustomPeriodRow($index)"
|
||||||
|
>删除</el-link
|
||||||
|
></template
|
||||||
|
></el-table-column
|
||||||
|
>
|
||||||
|
<template #empty
|
||||||
|
><el-link type="primary" @click="addCustomPeriodRow">添加</el-link></template
|
||||||
|
>
|
||||||
</el-table>
|
</el-table>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -268,17 +428,48 @@
|
|||||||
<div class="dialog-section-title">
|
<div class="dialog-section-title">
|
||||||
付款比例设置
|
付款比例设置
|
||||||
<el-tooltip content="非必填;配置付款比例时,合计必须等于100%。" placement="top">
|
<el-tooltip content="非必填;配置付款比例时,合计必须等于100%。" placement="top">
|
||||||
<el-icon style="margin-left: 4px; color: #909399; cursor: pointer"><el-icon-question-filled /></el-icon>
|
<el-icon style="margin-left: 4px; color: #909399; cursor: pointer"
|
||||||
|
><el-icon-question-filled
|
||||||
|
/></el-icon>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<el-table :data="paymentRatioRows" border class="change-table">
|
<el-table :data="paymentRatioRows" border class="change-table">
|
||||||
<el-table-column type="index" label="序号" width="70" />
|
<el-table-column type="index" label="序号" width="70" />
|
||||||
<el-table-column prop="paymentTerm" label="付款笔数" min-width="180" />
|
<el-table-column prop="paymentTerm" label="付款笔数" min-width="180" />
|
||||||
<el-table-column label="付款比例上限(%)" min-width="220"><template #default="{ row }"><el-input-number v-model="row.ratioLimit" :min="0" :max="100" :precision="2" controls-position="right" /></template></el-table-column>
|
<el-table-column label="付款比例上限(%)" min-width="220"
|
||||||
<el-table-column label="备注" min-width="220"><template #default="{ row }"><el-input v-model="row.remark" maxlength="200" /></template></el-table-column>
|
><template #default="{ row }"
|
||||||
<el-table-column label="操作" width="100"><template #default="{ $index }"><el-link type="danger" @click="paymentRatioRows.splice($index, 1)">删除</el-link></template></el-table-column>
|
><el-input-number
|
||||||
|
v-model="row.ratioLimit"
|
||||||
|
:min="0"
|
||||||
|
:max="100"
|
||||||
|
:precision="2"
|
||||||
|
controls-position="right"
|
||||||
|
:disabled="isSettlementRuleChange"
|
||||||
|
/></template
|
||||||
|
></el-table-column>
|
||||||
|
<el-table-column label="备注" min-width="220"
|
||||||
|
><template #default="{ row }"
|
||||||
|
><el-input
|
||||||
|
v-model="row.remark"
|
||||||
|
maxlength="200"
|
||||||
|
:disabled="isSettlementRuleChange"
|
||||||
|
/></template
|
||||||
|
></el-table-column>
|
||||||
|
<el-table-column v-if="!isSettlementRuleChange" label="操作" width="100"
|
||||||
|
><template #default="{ $index }"
|
||||||
|
><el-link type="danger" @click="paymentRatioRows.splice($index, 1)"
|
||||||
|
>删除</el-link
|
||||||
|
></template
|
||||||
|
></el-table-column
|
||||||
|
>
|
||||||
</el-table>
|
</el-table>
|
||||||
<el-button type="primary" plain @click="addPaymentRatioRow">添加</el-button>
|
<el-button
|
||||||
|
v-if="!isSettlementRuleChange"
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
@click="addPaymentRatioRow"
|
||||||
|
>添加</el-button
|
||||||
|
>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<billing-plan-editor
|
<billing-plan-editor
|
||||||
@@ -295,6 +486,7 @@
|
|||||||
attachment-type
|
attachment-type
|
||||||
attachment-location="附件(佐证材料等)"
|
attachment-location="附件(佐证材料等)"
|
||||||
attachment-type-dict-code="contract_supporting_attachment_type"
|
attachment-type-dict-code="contract_supporting_attachment_type"
|
||||||
|
:readonly="isSettlementRuleChange"
|
||||||
:attachment-location-options="changeAttachmentLocationOptions"
|
:attachment-location-options="changeAttachmentLocationOptions"
|
||||||
:rows="attachments"
|
:rows="attachments"
|
||||||
:preview="previewAttachment"
|
:preview="previewAttachment"
|
||||||
@@ -510,7 +702,13 @@ export default {
|
|||||||
.join('、');
|
.join('、');
|
||||||
return `【${parties || '相对方'}】:被列入外部黑名单;涉贪污受贿裁判相关提及方(3年以上及其他);被执行人信息;买卖合同纠纷;劳动纠纷;重大纠纷。提交变更前须上传「特批附件」类型附件`;
|
return `【${parties || '相对方'}】:被列入外部黑名单;涉贪污受贿裁判相关提及方(3年以上及其他);被执行人信息;买卖合同纠纷;劳动纠纷;重大纠纷。提交变更前须上传「特批附件」类型附件`;
|
||||||
},
|
},
|
||||||
|
isSettlementRuleChange() {
|
||||||
|
return this.form.changeType === '结算规则变更';
|
||||||
|
},
|
||||||
changeAttachmentLocationOptions() {
|
changeAttachmentLocationOptions() {
|
||||||
|
if (this.isSettlementRuleChange) {
|
||||||
|
return ['变更材料'];
|
||||||
|
}
|
||||||
return ['用印材料', '附件(佐证材料等)', '变更材料'];
|
return ['用印材料', '附件(佐证材料等)', '变更材料'];
|
||||||
},
|
},
|
||||||
contractAmountDisplay() {
|
contractAmountDisplay() {
|
||||||
@@ -736,6 +934,12 @@ export default {
|
|||||||
previewAttachment(row, rows = this.attachments) { const url = this.attachmentUrl(row); if (!url) { this.$message.warning('附件地址为空,无法预览'); return; } if (this.isAttachmentImage(row)) { this.imagePreviewUrls = (rows || []).filter(item => this.isAttachmentImage(item) && this.attachmentUrl(item)).map(item => this.attachmentUrl(item)); this.imagePreviewIndex = Math.max(this.imagePreviewUrls.indexOf(url), 0); this.imagePreviewVisible = true; return; } this.previewFile = { name: this.attachmentName(row), url, mimeType: row.mimeType || row.contentType || '' }; this.documentPreviewVisible = true; },
|
previewAttachment(row, rows = this.attachments) { const url = this.attachmentUrl(row); if (!url) { this.$message.warning('附件地址为空,无法预览'); return; } if (this.isAttachmentImage(row)) { this.imagePreviewUrls = (rows || []).filter(item => this.isAttachmentImage(item) && this.attachmentUrl(item)).map(item => this.attachmentUrl(item)); this.imagePreviewIndex = Math.max(this.imagePreviewUrls.indexOf(url), 0); this.imagePreviewVisible = true; return; } this.previewFile = { name: this.attachmentName(row), url, mimeType: row.mimeType || row.contentType || '' }; this.documentPreviewVisible = true; },
|
||||||
handleAttachmentUploadToLocation({ location, rows = [] }) {
|
handleAttachmentUploadToLocation({ location, rows = [] }) {
|
||||||
if (!rows.length) return;
|
if (!rows.length) return;
|
||||||
|
if (this.isSettlementRuleChange) {
|
||||||
|
if (location === '变更材料') {
|
||||||
|
this.changeMaterials = [...(this.changeMaterials || []), ...rows];
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (isContractSealMaterialLocation(location)) {
|
if (isContractSealMaterialLocation(location)) {
|
||||||
this.contractFileRows = [...(this.contractFileRows || []), ...rows];
|
this.contractFileRows = [...(this.contractFileRows || []), ...rows];
|
||||||
return;
|
return;
|
||||||
@@ -759,7 +963,81 @@ export default {
|
|||||||
removeCustomPeriodRow(index) { if (index <= 0) return; const periods = [...(this.settlementRule.customPeriods || [])]; periods.splice(index, 1); this.settlementRule.customPeriods = normalizeCustomPeriods(periods); },
|
removeCustomPeriodRow(index) { if (index <= 0) return; const periods = [...(this.settlementRule.customPeriods || [])]; periods.splice(index, 1); this.settlementRule.customPeriods = normalizeCustomPeriods(periods); },
|
||||||
validateCustomPeriods(periods = [], label) { const rows = normalizeCustomPeriods(periods); if (!rows.length) { this.$message.warning(`${label}:请至少配置一段自定义周期`); return false; } for (let index = 0; index < rows.length; index += 1) { const row = rows[index]; const startDay = Number(row.startDay); const endDay = Number(row.endDay); if (!Number.isFinite(startDay) || startDay < 1 || startDay > 31) { this.$message.warning(`${label}:请选择第${index + 1}行运单区间开始日`); return false; } if (!Number.isFinite(endDay) || endDay < 1 || endDay > 31) { this.$message.warning(`${label}:请选择第${index + 1}行运单区间结束日`); return false; } if (endDay < startDay) { this.$message.warning(`${label}:第${index + 1}行结束日不能早于开始日`); return false; } if (index > 0 && startDay !== Number(rows[index - 1].endDay) + 1) { this.$message.warning(`${label}:自定义多周期区间必须连续,不允许重叠或存在日期缺口`); return false; } } return true; },
|
validateCustomPeriods(periods = [], label) { const rows = normalizeCustomPeriods(periods); if (!rows.length) { this.$message.warning(`${label}:请至少配置一段自定义周期`); return false; } for (let index = 0; index < rows.length; index += 1) { const row = rows[index]; const startDay = Number(row.startDay); const endDay = Number(row.endDay); if (!Number.isFinite(startDay) || startDay < 1 || startDay > 31) { this.$message.warning(`${label}:请选择第${index + 1}行运单区间开始日`); return false; } if (!Number.isFinite(endDay) || endDay < 1 || endDay > 31) { this.$message.warning(`${label}:请选择第${index + 1}行运单区间结束日`); return false; } if (endDay < startDay) { this.$message.warning(`${label}:第${index + 1}行结束日不能早于开始日`); return false; } if (index > 0 && startDay !== Number(rows[index - 1].endDay) + 1) { this.$message.warning(`${label}:自定义多周期区间必须连续,不允许重叠或存在日期缺口`); return false; } } return true; },
|
||||||
validateSettlementRule(rule, label) { if (Number(rule.autoGenerate) !== 1) return true; if (!rule.billStartDate || !rule.settlementType) { this.$message.warning(`${label}:请完整填写账单起始日和结算类型`); return false; } if (rule.settlementType === '月结' && !rule.billCycleType) { this.$message.warning(`${label}:请选择结算周期`); return false; } if (rule.settlementType === '月结' && rule.billCycleType === '固定截单日' && !rule.billCutoffDay) { this.$message.warning(`${label}:请选择账单截单日`); return false; } if (rule.settlementType === '月结' && rule.billCycleType === '自定义多周期') return this.validateCustomPeriods(rule.customPeriods, label); if (rule.settlementType === '固定天数周期结算' && !rule.cycleDays) { this.$message.warning(`${label}:请选择周期天数`); return false; } return true; },
|
validateSettlementRule(rule, label) { if (Number(rule.autoGenerate) !== 1) return true; if (!rule.billStartDate || !rule.settlementType) { this.$message.warning(`${label}:请完整填写账单起始日和结算类型`); return false; } if (rule.settlementType === '月结' && !rule.billCycleType) { this.$message.warning(`${label}:请选择结算周期`); return false; } if (rule.settlementType === '月结' && rule.billCycleType === '固定截单日' && !rule.billCutoffDay) { this.$message.warning(`${label}:请选择账单截单日`); return false; } if (rule.settlementType === '月结' && rule.billCycleType === '自定义多周期') return this.validateCustomPeriods(rule.customPeriods, label); if (rule.settlementType === '固定天数周期结算' && !rule.cycleDays) { this.$message.warning(`${label}:请选择周期天数`); return false; } return true; },
|
||||||
async submit() { await this.$refs.formRef.validate(); const total = this.paymentRatioRows.reduce((sum, row) => sum + Number(row.ratioLimit || 0), 0); if (this.paymentRatioRows.length && Math.abs(total - 100) > 0.0001) { this.$message.warning('付款比例上限合计必须等于100%'); return; } if (this.settlementConfigTab === 'pre') this.preSettlementConfig = { ...this.settlementRule }; else this.formalSettlementConfig = { ...this.settlementRule }; if (!this.validateSettlementRule(this.preSettlementConfig, '预结算配置') || !this.validateSettlementRule(this.formalSettlementConfig, '正式结算配置')) return; const settlementRule = { preSettlementConfig: this.preSettlementConfig, formalSettlementConfig: this.formalSettlementConfig }; await api.submitChange({ ...this.form, settlementCurrency: String(this.form.settlementCurrency || '').trim() || 'RMB', copyCount: normalizeOptionalPositiveInteger(this.form.copyCount), invoiceCycle: normalizeOptionalPositiveInteger(this.form.invoiceCycle), paymentDays: normalizeOptionalPositiveInteger(this.form.paymentDays), contractAmount: normalizeOptionalAmount(this.form.contractAmount), startDate: this.period[0], endDate: this.period[1], feeGenerationMode: this.feeGenerationMode, billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0, billingPlanJson: JSON.stringify(this.plans), settlementRuleJson: JSON.stringify(settlementRule), preSettlementConfigJson: JSON.stringify(this.preSettlementConfig), formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig), paymentRatioJson: JSON.stringify(this.paymentRatioRows), contractFileJson: JSON.stringify(this.contractFileRows), attachmentsJson: JSON.stringify(this.attachments), changeContent: this.form.changeContent, changeReason: this.form.changeReason, changeAttachmentsJson: JSON.stringify(this.changeMaterials) }); await submitMkApprovalFlow({ bizType: 'contract-manage', bizId: this.form.id, subjectName: this.form.contractName || '', approvalStatus: this.form.approvalStatus || 'change_rejected', mkEnabled: await loadMkEnabled().catch(() => false) }); this.$message.success('变更已提交'); this.$router.back(); },
|
async submit() {
|
||||||
|
if (this.form.changeType === '终止合同') {
|
||||||
|
await api.terminate(this.form.id, this.form.changeReason);
|
||||||
|
this.$message.success('合同已终止');
|
||||||
|
this.$router.back();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.$refs.formRef.validate();
|
||||||
|
if (!this.isSettlementRuleChange) {
|
||||||
|
const total = this.paymentRatioRows.reduce((sum, row) => sum + Number(row.ratioLimit || 0), 0);
|
||||||
|
if (this.paymentRatioRows.length && Math.abs(total - 100) > 0.0001) {
|
||||||
|
this.$message.warning('付款比例上限合计必须等于100%');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.settlementConfigTab === 'pre') this.preSettlementConfig = { ...this.settlementRule };
|
||||||
|
else this.formalSettlementConfig = { ...this.settlementRule };
|
||||||
|
if (
|
||||||
|
!this.validateSettlementRule(this.preSettlementConfig, '预结算配置') ||
|
||||||
|
!this.validateSettlementRule(this.formalSettlementConfig, '正式结算配置')
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const settlementRule = {
|
||||||
|
preSettlementConfig: this.preSettlementConfig,
|
||||||
|
formalSettlementConfig: this.formalSettlementConfig,
|
||||||
|
};
|
||||||
|
const payload = this.isSettlementRuleChange
|
||||||
|
? {
|
||||||
|
id: this.form.id,
|
||||||
|
changeType: this.form.changeType,
|
||||||
|
changeContent: this.form.changeContent,
|
||||||
|
changeReason: this.form.changeReason,
|
||||||
|
feeGenerationMode: this.feeGenerationMode,
|
||||||
|
billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0,
|
||||||
|
billingPlanJson: JSON.stringify(this.plans),
|
||||||
|
settlementRuleJson: JSON.stringify(settlementRule),
|
||||||
|
preSettlementConfigJson: JSON.stringify(this.preSettlementConfig),
|
||||||
|
formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig),
|
||||||
|
changeAttachmentsJson: JSON.stringify(this.changeMaterials),
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
...this.form,
|
||||||
|
changeType: this.form.changeType || '合同信息变更',
|
||||||
|
settlementCurrency: String(this.form.settlementCurrency || '').trim() || 'RMB',
|
||||||
|
copyCount: normalizeOptionalPositiveInteger(this.form.copyCount),
|
||||||
|
invoiceCycle: normalizeOptionalPositiveInteger(this.form.invoiceCycle),
|
||||||
|
paymentDays: normalizeOptionalPositiveInteger(this.form.paymentDays),
|
||||||
|
contractAmount: normalizeOptionalAmount(this.form.contractAmount),
|
||||||
|
startDate: this.period[0],
|
||||||
|
endDate: this.period[1],
|
||||||
|
feeGenerationMode: this.feeGenerationMode,
|
||||||
|
billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0,
|
||||||
|
billingPlanJson: JSON.stringify(this.plans),
|
||||||
|
settlementRuleJson: JSON.stringify(settlementRule),
|
||||||
|
preSettlementConfigJson: JSON.stringify(this.preSettlementConfig),
|
||||||
|
formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig),
|
||||||
|
paymentRatioJson: JSON.stringify(this.paymentRatioRows),
|
||||||
|
contractFileJson: JSON.stringify(this.contractFileRows),
|
||||||
|
attachmentsJson: JSON.stringify(this.attachments),
|
||||||
|
changeContent: this.form.changeContent,
|
||||||
|
changeReason: this.form.changeReason,
|
||||||
|
changeAttachmentsJson: JSON.stringify(this.changeMaterials),
|
||||||
|
};
|
||||||
|
await api.submitChange(payload);
|
||||||
|
await submitMkApprovalFlow({
|
||||||
|
bizType: 'contract-manage',
|
||||||
|
bizId: this.form.id,
|
||||||
|
subjectName: this.form.contractName || '',
|
||||||
|
approvalStatus: this.form.approvalStatus || 'change_rejected',
|
||||||
|
mkEnabled: await loadMkEnabled().catch(() => false),
|
||||||
|
});
|
||||||
|
this.$message.success('变更已提交');
|
||||||
|
this.$router.back();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -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">
|
||||||
@@ -1357,19 +1368,29 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="110" fixed="right">
|
<el-table-column label="操作" width="110" fixed="right">
|
||||||
<template #default="{ $index }">
|
<template #default="{ row, $index }">
|
||||||
<el-link
|
<el-link
|
||||||
v-if="!dialogReadonly"
|
v-if="!dialogReadonly && !isSelectedWaybillRemoveLocked(row)"
|
||||||
type="danger"
|
type="danger"
|
||||||
@click="removeSelectedWaybill($index)"
|
@click="removeSelectedWaybill($index)"
|
||||||
>删除</el-link
|
>删除</el-link
|
||||||
>
|
>
|
||||||
|
<span
|
||||||
|
v-else-if="!dialogReadonly && isSelectedWaybillRemoveLocked(row)"
|
||||||
|
class="loading-manage-dialog__locked-tip"
|
||||||
|
>已打卡</span
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<section-card title="线路编辑">
|
<section-card title="线路编辑">
|
||||||
|
<template v-if="isRunningEdit" #extra>
|
||||||
|
<span class="loading-manage-dialog__route-hint"
|
||||||
|
>已打卡地点置灰不可拖拽,新增地址仅可在未打卡节点间调整</span
|
||||||
|
>
|
||||||
|
</template>
|
||||||
<div class="loading-manage-dialog__route-list" v-if="routeNodes.length">
|
<div class="loading-manage-dialog__route-list" v-if="routeNodes.length">
|
||||||
<div
|
<div
|
||||||
v-for="(node, index) in routeNodes"
|
v-for="(node, index) in routeNodes"
|
||||||
@@ -1415,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" />
|
||||||
@@ -1436,7 +1458,7 @@
|
|||||||
<el-segmented
|
<el-segmented
|
||||||
v-model="dialogForm.carrierType"
|
v-model="dialogForm.carrierType"
|
||||||
:options="carrierTypeOptions"
|
:options="carrierTypeOptions"
|
||||||
:disabled="dialogReadonly"
|
:disabled="dialogReadonly || isRunningTaskRequiredLocked"
|
||||||
@change="handleCarrierTypeChange"
|
@change="handleCarrierTypeChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -1450,7 +1472,11 @@
|
|||||||
"
|
"
|
||||||
clearable
|
clearable
|
||||||
filterable
|
filterable
|
||||||
:disabled="dialogReadonly || !selectedWaybillRows.length"
|
:disabled="
|
||||||
|
dialogReadonly ||
|
||||||
|
isRunningTaskRequiredLocked ||
|
||||||
|
!selectedWaybillRows.length
|
||||||
|
"
|
||||||
:loading="taskCarrierLoading"
|
:loading="taskCarrierLoading"
|
||||||
:placeholder="taskCarrierPlaceholder"
|
:placeholder="taskCarrierPlaceholder"
|
||||||
@change="handleTaskCarrierChange"
|
@change="handleTaskCarrierChange"
|
||||||
@@ -1483,6 +1509,7 @@
|
|||||||
:fetch-suggestions="fetchDriverSuggestions"
|
:fetch-suggestions="fetchDriverSuggestions"
|
||||||
:loading="driverLoading"
|
:loading="driverLoading"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
|
:disabled="dialogReadonly || isRunningSelfDriverLocked"
|
||||||
@select="item => handleDriverSuggestionSelect('dialog', item)"
|
@select="item => handleDriverSuggestionSelect('dialog', item)"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -1490,13 +1517,28 @@
|
|||||||
label="手机号"
|
label="手机号"
|
||||||
:required="dialogForm.carrierType === '自运'"
|
:required="dialogForm.carrierType === '自运'"
|
||||||
>
|
>
|
||||||
<el-input v-model="dialogForm.driverPhone" clearable placeholder="请输入" />
|
<el-input
|
||||||
|
v-model="dialogForm.driverPhone"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入"
|
||||||
|
:disabled="dialogReadonly || isRunningSelfDriverLocked"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="车牌号" required>
|
<el-form-item label="车牌号" required>
|
||||||
<el-input v-model="dialogForm.vehicleNo" clearable placeholder="请输入" />
|
<el-input
|
||||||
|
v-model="dialogForm.vehicleNo"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入"
|
||||||
|
:disabled="dialogReadonly || isRunningTaskRequiredLocked"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="dialogForm.carrierType !== '承运商'" label="挂车车牌号">
|
<el-form-item v-if="dialogForm.carrierType !== '承运商'" label="挂车车牌号">
|
||||||
<el-input v-model="dialogForm.trailerVehicleNo" clearable placeholder="请输入" />
|
<el-input
|
||||||
|
v-model="dialogForm.trailerVehicleNo"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入"
|
||||||
|
:disabled="dialogReadonly"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="dialogForm.carrierType !== '承运商'" label="押运人">
|
<el-form-item v-if="dialogForm.carrierType !== '承运商'" label="押运人">
|
||||||
<el-autocomplete
|
<el-autocomplete
|
||||||
@@ -1506,11 +1548,17 @@
|
|||||||
:fetch-suggestions="fetchEscortSuggestions"
|
:fetch-suggestions="fetchEscortSuggestions"
|
||||||
:loading="escortLoading"
|
:loading="escortLoading"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
|
:disabled="dialogReadonly"
|
||||||
@select="item => handleEscortSuggestionSelect('dialog', item)"
|
@select="item => handleEscortSuggestionSelect('dialog', item)"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="dialogForm.carrierType !== '承运商'" label="押运人手机号">
|
<el-form-item v-if="dialogForm.carrierType !== '承运商'" label="押运人手机号">
|
||||||
<el-input v-model="dialogForm.escortPhone" clearable placeholder="请输入" />
|
<el-input
|
||||||
|
v-model="dialogForm.escortPhone"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入"
|
||||||
|
:disabled="dialogReadonly"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="里程(km)">
|
<el-form-item label="里程(km)">
|
||||||
<el-input
|
<el-input
|
||||||
@@ -1528,6 +1576,7 @@
|
|||||||
format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
||||||
value-format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
||||||
placeholder="请选择"
|
placeholder="请选择"
|
||||||
|
:disabled="dialogReadonly"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="预计完成日期">
|
<el-form-item label="预计完成日期">
|
||||||
@@ -1537,6 +1586,7 @@
|
|||||||
format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
||||||
value-format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
||||||
placeholder="请选择"
|
placeholder="请选择"
|
||||||
|
:disabled="dialogReadonly"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="备注" class="loading-manage-dialog__span-2">
|
<el-form-item label="备注" class="loading-manage-dialog__span-2">
|
||||||
@@ -1547,6 +1597,7 @@
|
|||||||
maxlength="500"
|
maxlength="500"
|
||||||
show-word-limit
|
show-word-limit
|
||||||
placeholder="请输入,500字以内"
|
placeholder="请输入,500字以内"
|
||||||
|
:disabled="dialogReadonly"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
@@ -1561,11 +1612,17 @@
|
|||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<el-button @click="closeLoadingDialog">关闭</el-button>
|
<el-button @click="closeLoadingDialog">关闭</el-button>
|
||||||
<el-button type="primary" plain :loading="dialogSaving === 'draft'" @click="saveDraft">
|
<el-button
|
||||||
|
v-if="!isRunningEdit"
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
:loading="dialogSaving === 'draft'"
|
||||||
|
@click="saveDraft"
|
||||||
|
>
|
||||||
暂存
|
暂存
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="primary" :loading="dialogSaving === 'submit'" @click="submitLoading">
|
<el-button type="primary" :loading="dialogSaving === 'submit'" @click="submitLoading">
|
||||||
{{ dialogMode === 'reassign' ? '确认派单' : '确认' }}
|
{{ dialogMode === 'reassign' ? '确认派单' : isRunningEdit ? '保存' : '确认' }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -2107,6 +2164,8 @@ export default {
|
|||||||
routeNodes: [],
|
routeNodes: [],
|
||||||
cargoRows: [],
|
cargoRows: [],
|
||||||
routeDragIndex: -1,
|
routeDragIndex: -1,
|
||||||
|
/** 进行中编辑:子运单打卡记录映射 waybillId -> records[] */
|
||||||
|
waybillPunchMap: {},
|
||||||
transportTypeOptions: [],
|
transportTypeOptions: [],
|
||||||
projectOptions: [],
|
projectOptions: [],
|
||||||
customerOptions: [],
|
customerOptions: [],
|
||||||
@@ -2170,6 +2229,23 @@ export default {
|
|||||||
dialogReadonly() {
|
dialogReadonly() {
|
||||||
return this.dialogMode === 'view';
|
return this.dialogMode === 'view';
|
||||||
},
|
},
|
||||||
|
/** 进行中配载编辑:可加子单/调未打卡路线,但任务必填字段锁定 */
|
||||||
|
isRunningEdit() {
|
||||||
|
return (
|
||||||
|
this.dialogMode === 'edit' &&
|
||||||
|
String(this.dialogForm.businessStatus || '') === 'running'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
/** 进行中任务必填字段(已派车)不可改 */
|
||||||
|
isRunningTaskRequiredLocked() {
|
||||||
|
return this.isRunningEdit;
|
||||||
|
},
|
||||||
|
/** 自运进行中:司机/手机号同属必填派车信息,不可改 */
|
||||||
|
isRunningSelfDriverLocked() {
|
||||||
|
return (
|
||||||
|
this.isRunningEdit && String(this.dialogForm.carrierType || '').trim() === '自运'
|
||||||
|
);
|
||||||
|
},
|
||||||
// 打卡时间轴:已打卡按时间升序,未打卡保持原节点顺序排在其后
|
// 打卡时间轴:已打卡按时间升序,未打卡保持原节点顺序排在其后
|
||||||
orderedPunchRecords() {
|
orderedPunchRecords() {
|
||||||
const records = Array.isArray(this.punchRecords) ? this.punchRecords : [];
|
const records = Array.isArray(this.punchRecords) ? this.punchRecords : [];
|
||||||
@@ -2913,6 +2989,7 @@ export default {
|
|||||||
if (status === 'running') {
|
if (status === 'running') {
|
||||||
return [
|
return [
|
||||||
{ type: 'view', label: '详情' },
|
{ type: 'view', label: '详情' },
|
||||||
|
{ type: 'edit', label: '编辑' },
|
||||||
{ type: 'complete', label: '完成' },
|
{ type: 'complete', label: '完成' },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -3014,7 +3091,12 @@ export default {
|
|||||||
query: {
|
query: {
|
||||||
mode,
|
mode,
|
||||||
...(mode === 'edit' && row?.id ? { id: row.id } : {}),
|
...(mode === 'edit' && row?.id ? { id: row.id } : {}),
|
||||||
name: mode === 'edit' ? '编辑配载管理' : '新增配载管理',
|
name:
|
||||||
|
mode === 'edit'
|
||||||
|
? row?.businessStatus === 'running'
|
||||||
|
? '编辑进行中配载'
|
||||||
|
: '编辑配载管理'
|
||||||
|
: '新增配载管理',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -3037,6 +3119,9 @@ export default {
|
|||||||
this.dialogForm.loadingSubNos
|
this.dialogForm.loadingSubNos
|
||||||
);
|
);
|
||||||
this.syncProcessWaybillOptions();
|
this.syncProcessWaybillOptions();
|
||||||
|
if (this.isRunningEdit) {
|
||||||
|
await this.refreshWaybillPunchMap();
|
||||||
|
}
|
||||||
this.restoreRouteAndCargo();
|
this.restoreRouteAndCargo();
|
||||||
this.restoreRouteChangeRecords();
|
this.restoreRouteChangeRecords();
|
||||||
this.loadPunchRecords();
|
this.loadPunchRecords();
|
||||||
@@ -3071,6 +3156,8 @@ export default {
|
|||||||
this.taskCarrierLoading = false;
|
this.taskCarrierLoading = false;
|
||||||
this.routeNodes = [];
|
this.routeNodes = [];
|
||||||
this.cargoRows = [];
|
this.cargoRows = [];
|
||||||
|
this.routeDragIndex = -1;
|
||||||
|
this.waybillPunchMap = {};
|
||||||
this.candidatePage.currentPage = 1;
|
this.candidatePage.currentPage = 1;
|
||||||
this.candidatePage.total = 0;
|
this.candidatePage.total = 0;
|
||||||
this.candidateSearchVisible = false;
|
this.candidateSearchVisible = false;
|
||||||
@@ -3435,20 +3522,144 @@ export default {
|
|||||||
const statusName = String(row.businessStatusName || row.statusName || '');
|
const statusName = String(row.businessStatusName || row.statusName || '');
|
||||||
return statusName.includes('已完成');
|
return statusName.includes('已完成');
|
||||||
},
|
},
|
||||||
|
isPunchRecordDone(record = {}) {
|
||||||
|
if (record.punched === false || record.statusName === '未打卡') return false;
|
||||||
|
return Boolean(record.punched || record.punchTime || record.punchAt || record.punchDateTime);
|
||||||
|
},
|
||||||
|
isLoadPunchRecord(record = {}) {
|
||||||
|
if (!this.isPunchRecordDone(record)) return false;
|
||||||
|
const code = String(record.nodeCode || '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
const name = String(record.nodeName || '').trim();
|
||||||
|
return code === 'load' || name.includes('装货');
|
||||||
|
},
|
||||||
|
isUnloadPunchRecord(record = {}) {
|
||||||
|
if (!this.isPunchRecordDone(record)) return false;
|
||||||
|
const code = String(record.nodeCode || '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
const name = String(record.nodeName || '').trim();
|
||||||
|
return (
|
||||||
|
code === 'unload' ||
|
||||||
|
name.includes('卸货') ||
|
||||||
|
code === 'arrival' ||
|
||||||
|
name.includes('到货') ||
|
||||||
|
code === 'sign' ||
|
||||||
|
name.includes('签收')
|
||||||
|
);
|
||||||
|
},
|
||||||
|
waybillPunchRecords(row = {}) {
|
||||||
|
const key = String(row?.id || '');
|
||||||
|
if (!key) return [];
|
||||||
|
const records = this.waybillPunchMap[key];
|
||||||
|
return Array.isArray(records) ? records : [];
|
||||||
|
},
|
||||||
|
isWaybillLoadPunched(row = {}) {
|
||||||
|
return this.waybillPunchRecords(row).some(record => this.isLoadPunchRecord(record));
|
||||||
|
},
|
||||||
|
isWaybillUnloadPunched(row = {}) {
|
||||||
|
return this.waybillPunchRecords(row).some(record => this.isUnloadPunchRecord(record));
|
||||||
|
},
|
||||||
|
isWaybillPunchLocked(row = {}) {
|
||||||
|
return this.isWaybillLoadPunched(row) || this.isWaybillUnloadPunched(row);
|
||||||
|
},
|
||||||
|
isSelectedWaybillRemoveLocked(row = {}) {
|
||||||
|
if (this.isWaybillCompleted(row)) return true;
|
||||||
|
return this.isRunningEdit && this.isWaybillPunchLocked(row);
|
||||||
|
},
|
||||||
|
isRouteNodeTagLocked(tag, rows = this.selectedWaybillRows) {
|
||||||
|
const match = String(tag || '').match(/^(装|卸)(\d+)$/);
|
||||||
|
if (!match) return false;
|
||||||
|
const row = rows[Number(match[2]) - 1];
|
||||||
|
if (!row) return false;
|
||||||
|
if (this.isRunningEdit) {
|
||||||
|
return match[1] === '装' ? this.isWaybillLoadPunched(row) : this.isWaybillUnloadPunched(row);
|
||||||
|
}
|
||||||
|
return this.isWaybillCompleted(row);
|
||||||
|
},
|
||||||
isRouteChangeNodeLocked(node = {}) {
|
isRouteChangeNodeLocked(node = {}) {
|
||||||
return Boolean(node?.locked);
|
return Boolean(node?.locked);
|
||||||
},
|
},
|
||||||
syncRouteNodeLockedFlags(nodes = [], rows = this.selectedWaybillRows) {
|
syncRouteNodeLockedFlags(nodes = [], rows = this.selectedWaybillRows) {
|
||||||
return (nodes || []).map(node => {
|
return (nodes || []).map(node => {
|
||||||
const locked = (node.tags || []).some(tag => {
|
const locked = (node.tags || []).some(tag => this.isRouteNodeTagLocked(tag, rows));
|
||||||
const match = String(tag).match(/^(装|卸)(\d+)$/);
|
|
||||||
if (!match) return false;
|
|
||||||
const row = rows[Number(match[2]) - 1];
|
|
||||||
return row ? this.isWaybillCompleted(row) : false;
|
|
||||||
});
|
|
||||||
return { ...node, locked };
|
return { ...node, locked };
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
async refreshWaybillPunchMap() {
|
||||||
|
const rows = (this.selectedWaybillRows || []).filter(row => row?.id);
|
||||||
|
if (!rows.length) {
|
||||||
|
this.waybillPunchMap = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const entries = await Promise.all(
|
||||||
|
rows.map(async row => {
|
||||||
|
try {
|
||||||
|
const res = await getWaybillPunchRecords(row.id);
|
||||||
|
const data = res?.data?.data || res?.data || {};
|
||||||
|
return [String(row.id), Array.isArray(data.records) ? data.records : []];
|
||||||
|
} catch (error) {
|
||||||
|
return [String(row.id), []];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
this.waybillPunchMap = Object.fromEntries(entries);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 进行中编辑:已打卡地点保持在前且不可插入,新增地址只能排在已打卡节点之后。
|
||||||
|
*/
|
||||||
|
mergeRouteNodesKeepingPunched(prevNodes = [], builtNodes = []) {
|
||||||
|
const prev = Array.isArray(prevNodes) ? prevNodes : [];
|
||||||
|
const built = Array.isArray(builtNodes) ? builtNodes : [];
|
||||||
|
if (!this.isRunningEdit) {
|
||||||
|
return built;
|
||||||
|
}
|
||||||
|
const builtByAddress = new Map();
|
||||||
|
built.forEach(node => {
|
||||||
|
if (node?.address && !builtByAddress.has(node.address)) {
|
||||||
|
builtByAddress.set(node.address, node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const lockedAddresses = new Set(
|
||||||
|
built.filter(node => node?.locked && node.address).map(node => node.address)
|
||||||
|
);
|
||||||
|
const used = new Set();
|
||||||
|
const lockedResult = [];
|
||||||
|
prev.forEach(node => {
|
||||||
|
if (!node?.address || !lockedAddresses.has(node.address) || used.has(node.address)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const latest = builtByAddress.get(node.address);
|
||||||
|
if (!latest) return;
|
||||||
|
lockedResult.push({ ...latest, locked: true });
|
||||||
|
used.add(node.address);
|
||||||
|
});
|
||||||
|
built.forEach(node => {
|
||||||
|
if (!node?.locked || !node.address || used.has(node.address)) return;
|
||||||
|
lockedResult.push({ ...node, locked: true });
|
||||||
|
used.add(node.address);
|
||||||
|
});
|
||||||
|
const unlockedResult = [];
|
||||||
|
prev.forEach(node => {
|
||||||
|
if (!node?.address || lockedAddresses.has(node.address) || used.has(node.address)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const latest = builtByAddress.get(node.address);
|
||||||
|
if (!latest) return;
|
||||||
|
unlockedResult.push({ ...latest, locked: false });
|
||||||
|
used.add(node.address);
|
||||||
|
});
|
||||||
|
built.forEach(node => {
|
||||||
|
if (node?.locked || !node?.address || used.has(node.address)) return;
|
||||||
|
unlockedResult.push({ ...node, locked: false });
|
||||||
|
used.add(node.address);
|
||||||
|
});
|
||||||
|
return [...lockedResult, ...unlockedResult].map((node, index) => ({
|
||||||
|
...node,
|
||||||
|
key: `${node.address}-${index}`,
|
||||||
|
}));
|
||||||
|
},
|
||||||
openRouteChangeMapDialog(row, field) {
|
openRouteChangeMapDialog(row, field) {
|
||||||
if (this.isWaybillCompleted(row)) return;
|
if (this.isWaybillCompleted(row)) return;
|
||||||
this.routeChangeMapTarget = { row, field };
|
this.routeChangeMapTarget = { row, field };
|
||||||
@@ -3701,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(
|
||||||
@@ -3766,6 +3975,33 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
if (this.isRunningEdit && String(this.dialogForm.carrierType || '').trim() === '承运商') {
|
||||||
|
const contractId = this.dialogForm.carrierContractId;
|
||||||
|
if (contractId) {
|
||||||
|
const projectIds = [
|
||||||
|
...new Set(
|
||||||
|
[...this.selectedWaybillRows, ...selectedRows]
|
||||||
|
.map(row => row.projectId)
|
||||||
|
.filter(Boolean)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
if (projectIds.length) {
|
||||||
|
try {
|
||||||
|
const response = await loadingApi.getCarrierContracts(projectIds);
|
||||||
|
const contracts = response?.data?.data || response?.data || [];
|
||||||
|
const stillAllowed = (Array.isArray(contracts) ? contracts : []).some(
|
||||||
|
item => String(item.id) === String(contractId)
|
||||||
|
);
|
||||||
|
if (!stillAllowed) {
|
||||||
|
ElMessage.warning('新增子运单须兼容当前配载单已选定的承运商合同');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 合同校验失败时仍允许添加,最终由后端确认拦截
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
const exists = new Set(
|
const exists = new Set(
|
||||||
this.selectedWaybillRows.map(row => String(row.id || row.waybillNo))
|
this.selectedWaybillRows.map(row => String(row.id || row.waybillNo))
|
||||||
);
|
);
|
||||||
@@ -3777,8 +4013,12 @@ export default {
|
|||||||
exists.add(key);
|
exists.add(key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const prevRouteNodes = [...this.routeNodes];
|
||||||
this.selectedWaybillRows = nextRows;
|
this.selectedWaybillRows = nextRows;
|
||||||
this.rebuildSummaryFromWaybills();
|
if (this.isRunningEdit) {
|
||||||
|
await this.refreshWaybillPunchMap();
|
||||||
|
}
|
||||||
|
this.rebuildSummaryFromWaybills(true, prevRouteNodes);
|
||||||
await this.loadTaskCarrierOptions();
|
await this.loadTaskCarrierOptions();
|
||||||
this.$refs.candidateTableRef?.clearSelection?.();
|
this.$refs.candidateTableRef?.clearSelection?.();
|
||||||
this.candidateSelection = [];
|
this.candidateSelection = [];
|
||||||
@@ -3790,11 +4030,23 @@ export default {
|
|||||||
await this.addWaybillRows(this.candidateSelection);
|
await this.addWaybillRows(this.candidateSelection);
|
||||||
},
|
},
|
||||||
removeSelectedWaybill(index) {
|
removeSelectedWaybill(index) {
|
||||||
|
const row = this.selectedWaybillRows[index];
|
||||||
|
if (this.isSelectedWaybillRemoveLocked(row)) {
|
||||||
|
ElMessage.warning('已打卡的子运单不可移除');
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.selectedWaybillRows.splice(index, 1);
|
this.selectedWaybillRows.splice(index, 1);
|
||||||
|
if (this.isRunningEdit) {
|
||||||
|
this.refreshWaybillPunchMap().then(() => {
|
||||||
|
this.rebuildSummaryFromWaybills();
|
||||||
|
this.loadTaskCarrierOptions();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.rebuildSummaryFromWaybills();
|
this.rebuildSummaryFromWaybills();
|
||||||
this.loadTaskCarrierOptions();
|
this.loadTaskCarrierOptions();
|
||||||
},
|
},
|
||||||
rebuildSummaryFromWaybills(rebuildRoute = true) {
|
rebuildSummaryFromWaybills(rebuildRoute = true, prevRouteNodes = null) {
|
||||||
const rows = this.selectedWaybillRows;
|
const rows = this.selectedWaybillRows;
|
||||||
this.dialogForm.loadingSubNos = rows
|
this.dialogForm.loadingSubNos = rows
|
||||||
.map(row => row.waybillNo)
|
.map(row => row.waybillNo)
|
||||||
@@ -3819,7 +4071,13 @@ export default {
|
|||||||
this.dialogForm.transportType = rows.find(row => row.transportType)?.transportType || '';
|
this.dialogForm.transportType = rows.find(row => row.transportType)?.transportType || '';
|
||||||
}
|
}
|
||||||
if (rebuildRoute) {
|
if (rebuildRoute) {
|
||||||
this.routeNodes = this.buildRouteNodes(rows);
|
const builtNodes = this.buildRouteNodes(rows);
|
||||||
|
this.routeNodes = this.isRunningEdit
|
||||||
|
? this.mergeRouteNodesKeepingPunched(
|
||||||
|
prevRouteNodes || this.routeNodes,
|
||||||
|
builtNodes
|
||||||
|
)
|
||||||
|
: builtNodes;
|
||||||
}
|
}
|
||||||
this.cargoRows = this.buildCargoRows(rows);
|
this.cargoRows = this.buildCargoRows(rows);
|
||||||
// 编辑回填阶段 routeNodes 尚未恢复时跳过,避免清空已有发到货地
|
// 编辑回填阶段 routeNodes 尚未恢复时跳过,避免清空已有发到货地
|
||||||
@@ -3839,7 +4097,9 @@ export default {
|
|||||||
if (locked) node.locked = true;
|
if (locked) node.locked = true;
|
||||||
};
|
};
|
||||||
rows.forEach((row, rowIndex) => {
|
rows.forEach((row, rowIndex) => {
|
||||||
const locked = this.isWaybillCompleted(row);
|
const locked = this.isRunningEdit
|
||||||
|
? this.isWaybillLoadPunched(row)
|
||||||
|
: this.isWaybillCompleted(row);
|
||||||
const departure = row.departureAddress || row.departureName;
|
const departure = row.departureAddress || row.departureName;
|
||||||
appendNode(
|
appendNode(
|
||||||
departure,
|
departure,
|
||||||
@@ -3849,7 +4109,9 @@ export default {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
rows.forEach((row, rowIndex) => {
|
rows.forEach((row, rowIndex) => {
|
||||||
const locked = this.isWaybillCompleted(row);
|
const locked = this.isRunningEdit
|
||||||
|
? this.isWaybillUnloadPunched(row)
|
||||||
|
: this.isWaybillCompleted(row);
|
||||||
const arrival = row.arrivalAddress || row.arrivalName;
|
const arrival = row.arrivalAddress || row.arrivalName;
|
||||||
appendNode(
|
appendNode(
|
||||||
arrival,
|
arrival,
|
||||||
@@ -3901,7 +4163,9 @@ export default {
|
|||||||
if (
|
if (
|
||||||
originalLockedKeys.some((key, lockedIndex) => key && key !== nextLockedKeys[lockedIndex])
|
originalLockedKeys.some((key, lockedIndex) => key && key !== nextLockedKeys[lockedIndex])
|
||||||
) {
|
) {
|
||||||
ElMessage.warning('已完成运单对应的路线不可调整');
|
ElMessage.warning(
|
||||||
|
this.isRunningEdit ? '已打卡地点不可调整,新地址只能在未打卡节点间拖拽' : '已完成运单对应的路线不可调整'
|
||||||
|
);
|
||||||
this.routeDragIndex = -1;
|
this.routeDragIndex = -1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3941,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 ||
|
||||||
@@ -4145,15 +4423,15 @@ export default {
|
|||||||
try {
|
try {
|
||||||
const payload = {
|
const payload = {
|
||||||
...this.normalizePayload(),
|
...this.normalizePayload(),
|
||||||
// 确认生成正式配载单(待执行),覆盖草稿状态
|
// 进行中编辑保持 running;新建/草稿确认由后端按项目是否需接单写入 pending/running
|
||||||
businessStatus: 'pending',
|
...(this.isRunningEdit ? { businessStatus: 'running' } : {}),
|
||||||
};
|
};
|
||||||
if (this.dialogMode === 'reassign') {
|
if (this.dialogMode === 'reassign') {
|
||||||
await loadingApi.reassign(payload);
|
await loadingApi.reassign(payload);
|
||||||
ElMessage.success('派单成功');
|
ElMessage.success('派单成功');
|
||||||
} else {
|
} else {
|
||||||
await loadingApi.submit(payload);
|
await loadingApi.submit(payload);
|
||||||
ElMessage.success('确认成功');
|
ElMessage.success(this.isRunningEdit ? '保存成功' : '确认成功');
|
||||||
}
|
}
|
||||||
sessionStorage.setItem(loadingManageListRefreshKey, '1');
|
sessionStorage.setItem(loadingManageListRefreshKey, '1');
|
||||||
this.closeLoadingDialog();
|
this.closeLoadingDialog();
|
||||||
@@ -4715,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;
|
||||||
@@ -4787,6 +5089,16 @@ export default {
|
|||||||
color: #909399;
|
color: #909399;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading-manage-dialog__route-hint {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-manage-dialog__locked-tip {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.loading-manage-dialog__route-type {
|
.loading-manage-dialog__route-type {
|
||||||
width: 28px;
|
width: 28px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
|
|||||||
@@ -955,7 +955,7 @@ import {
|
|||||||
getList as getCustomerArchiveList,
|
getList as getCustomerArchiveList,
|
||||||
getDetail as getCustomerArchiveDetail,
|
getDetail as getCustomerArchiveDetail,
|
||||||
} from '@/api/vehicle/customer-archive';
|
} from '@/api/vehicle/customer-archive';
|
||||||
import { getDeptTree, getPlatformCompanySelect } from '@/api/system/dept';
|
import { getBusinessDeptSelect, getDeptTree, getPlatformCompanySelect } from '@/api/system/dept';
|
||||||
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
||||||
import { getDictionary as getBizDictionary } from '@/api/system/dictbiz';
|
import { getDictionary as getBizDictionary } from '@/api/system/dictbiz';
|
||||||
import { getList as getUserList } from '@/api/system/user';
|
import { getList as getUserList } from '@/api/system/user';
|
||||||
@@ -1596,6 +1596,7 @@ export default {
|
|||||||
projectType: type === 'majorSupplement' ? '重大项目' : '普通项目',
|
projectType: type === 'majorSupplement' ? '重大项目' : '普通项目',
|
||||||
});
|
});
|
||||||
this.fillDefaultUsers();
|
this.fillDefaultUsers();
|
||||||
|
this.fillDefaultBusinessDept();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
@@ -2032,6 +2033,7 @@ export default {
|
|||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
this.syncSelectedCustomerOptions();
|
this.syncSelectedCustomerOptions();
|
||||||
this.ensureCurrentPlatformOption();
|
this.ensureCurrentPlatformOption();
|
||||||
|
this.ensureCurrentBusinessDeptOption();
|
||||||
},
|
},
|
||||||
fillDefaultUsers() {
|
fillDefaultUsers() {
|
||||||
const userId = this.userInfo?.userId || this.userInfo?.id || '';
|
const userId = this.userInfo?.userId || this.userInfo?.id || '';
|
||||||
@@ -2050,6 +2052,25 @@ export default {
|
|||||||
this.form.principalUserName = userName;
|
this.form.principalUserName = userName;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 新增/重大补录:业务部门默认取当前用户所属部门中第一个「是否业务部门=是」的组织。
|
||||||
|
*/
|
||||||
|
fillDefaultBusinessDept() {
|
||||||
|
if (!['add', 'majorSupplement'].includes(this.dialogType)) return;
|
||||||
|
if (this.form.businessDeptId) return;
|
||||||
|
const raw = this.userInfo?.deptId || this.userInfo?.dept_id || '';
|
||||||
|
const userDeptIds = String(raw)
|
||||||
|
.split(/[,,]/)
|
||||||
|
.map(item => String(item || '').trim())
|
||||||
|
.filter(item => item && item !== '-1');
|
||||||
|
if (!userDeptIds.length || !this.deptOptions.length) return;
|
||||||
|
const matched = userDeptIds.find(id =>
|
||||||
|
this.deptOptions.some(item => String(item.value) === String(id))
|
||||||
|
);
|
||||||
|
if (!matched) return;
|
||||||
|
this.form.businessDeptId = matched;
|
||||||
|
this.handleBusinessDeptChange(matched);
|
||||||
|
},
|
||||||
resolveUserRealName(row = {}, role) {
|
resolveUserRealName(row = {}, role) {
|
||||||
return (
|
return (
|
||||||
row[`${role}RealName`] ||
|
row[`${role}RealName`] ||
|
||||||
@@ -2632,17 +2653,61 @@ export default {
|
|||||||
this.$refs.projectForm?.validateField('projectType');
|
this.$refs.projectForm?.validateField('projectType');
|
||||||
},
|
},
|
||||||
loadDeptOptions() {
|
loadDeptOptions() {
|
||||||
getDeptTree().then(res => {
|
Promise.all([getDeptTree(), getBusinessDeptSelect()])
|
||||||
const deptTree = res.data.data || [];
|
.then(([treeRes, bizRes]) => {
|
||||||
// 新增/编辑/重大补录/项目变更均可选择业务部门,排除外部组织
|
const deptTree = this.excludeExternalOrganization(treeRes.data.data || []);
|
||||||
const businessDeptTree = ['add', 'edit', 'majorSupplement', 'change'].includes(this.dialogType)
|
const bizList = bizRes.data.data || [];
|
||||||
? this.excludeExternalOrganization(deptTree)
|
const bizIds = new Set(
|
||||||
: deptTree;
|
bizList
|
||||||
this.deptOptions = this.flattenDept(businessDeptTree);
|
.map(item => (item.id == null || item.id === '' ? '' : String(item.id)))
|
||||||
this.businessDeptTreeOptions = this.toDeptCascaderOptions(businessDeptTree);
|
.filter(Boolean)
|
||||||
});
|
);
|
||||||
|
// 仅保留「是否业务部门=是」的节点及其祖先,祖先不可选
|
||||||
|
const businessDeptTree = this.filterBusinessDeptTree(deptTree, bizIds);
|
||||||
|
this.deptOptions = this.flattenDept(businessDeptTree).filter(item =>
|
||||||
|
bizIds.has(String(item.value))
|
||||||
|
);
|
||||||
|
this.businessDeptTreeOptions = this.toDeptCascaderOptions(businessDeptTree, bizIds);
|
||||||
|
this.fillDefaultBusinessDept();
|
||||||
|
this.ensureCurrentBusinessDeptOption();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.deptOptions = [];
|
||||||
|
this.businessDeptTreeOptions = [];
|
||||||
|
});
|
||||||
this.loadPlatformCompanyOptions();
|
this.loadPlatformCompanyOptions();
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 剪枝组织树:只保留业务部门节点及其祖先路径。
|
||||||
|
*/
|
||||||
|
filterBusinessDeptTree(list = [], bizIds = new Set()) {
|
||||||
|
return (list || []).reduce((result, item) => {
|
||||||
|
const children = item.children?.length
|
||||||
|
? this.filterBusinessDeptTree(item.children, bizIds)
|
||||||
|
: [];
|
||||||
|
const isBiz = bizIds.has(String(item.id));
|
||||||
|
if (!isBiz && !children.length) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.push({
|
||||||
|
...item,
|
||||||
|
children: children.length ? children : undefined,
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}, []);
|
||||||
|
},
|
||||||
|
ensureCurrentBusinessDeptOption() {
|
||||||
|
const id = this.form.businessDeptId;
|
||||||
|
if (!id) return;
|
||||||
|
const exists = this.deptOptions.some(item => String(item.value) === String(id));
|
||||||
|
if (exists) return;
|
||||||
|
const label = this.form.businessDeptName || String(id);
|
||||||
|
this.deptOptions.unshift({ label, rawLabel: label, value: id });
|
||||||
|
const pathExists = this.findDeptPath(id, this.businessDeptTreeOptions);
|
||||||
|
if (!pathExists) {
|
||||||
|
this.businessDeptTreeOptions.unshift({ label, value: id });
|
||||||
|
}
|
||||||
|
},
|
||||||
loadPlatformCompanyOptions() {
|
loadPlatformCompanyOptions() {
|
||||||
getPlatformCompanySelect().then(res => {
|
getPlatformCompanySelect().then(res => {
|
||||||
const list = res.data.data || [];
|
const list = res.data.data || [];
|
||||||
@@ -2686,12 +2751,22 @@ export default {
|
|||||||
return result;
|
return result;
|
||||||
}, []);
|
}, []);
|
||||||
},
|
},
|
||||||
toDeptCascaderOptions(list = []) {
|
toDeptCascaderOptions(list = [], bizIds = null) {
|
||||||
return list.map(item => ({
|
return list.map(item => {
|
||||||
label: item.title || item.deptName || item.name,
|
const value = item.id == null || item.id === '' ? item.value : item.id;
|
||||||
value: item.id,
|
const option = {
|
||||||
children: item.children?.length ? this.toDeptCascaderOptions(item.children) : undefined,
|
label: item.title || item.deptName || item.name || item.label,
|
||||||
}));
|
value: value == null || value === '' ? '' : String(value),
|
||||||
|
children: item.children?.length
|
||||||
|
? this.toDeptCascaderOptions(item.children, bizIds)
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
// 仅业务部门可选;祖先节点保留路径但禁用
|
||||||
|
if (bizIds && !bizIds.has(String(option.value))) {
|
||||||
|
option.disabled = true;
|
||||||
|
}
|
||||||
|
return option;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
flattenDept(list = [], level = 0) {
|
flattenDept(list = [], level = 0) {
|
||||||
return list.reduce((result, item) => {
|
return list.reduce((result, item) => {
|
||||||
|
|||||||
@@ -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 = '';
|
||||||
|
|||||||
@@ -271,7 +271,7 @@ export default {
|
|||||||
this.unwrapData(
|
this.unwrapData(
|
||||||
await billLedgerApi.getAvailableOptions(
|
await billLedgerApi.getAvailableOptions(
|
||||||
this.billDialog.keyword,
|
this.billDialog.keyword,
|
||||||
this.form.deptId,
|
undefined,
|
||||||
this.form.billLedgerId
|
this.form.billLedgerId
|
||||||
)
|
)
|
||||||
) || [];
|
) || [];
|
||||||
|
|||||||
@@ -249,7 +249,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)"
|
||||||
@@ -267,7 +267,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)"
|
||||||
@@ -1025,62 +1025,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() {
|
||||||
// 收款方 C101-1 高风险/黑名单,或客商等级 D/E/F(本地评级口径),命中须上传特批附件
|
// 收款方 C101-1 高风险/黑名单,或客商等级 D/E/F(本地评级口径),命中须上传特批附件
|
||||||
@@ -1149,11 +1124,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)
|
||||||
@@ -1251,14 +1227,6 @@ export default {
|
|||||||
callback();
|
callback();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
selected.billType &&
|
|
||||||
selected.billType !== 'received' &&
|
|
||||||
!this.billMatchesSettlementParties(selected)
|
|
||||||
) {
|
|
||||||
callback(new Error('汇票付款仅可选择收票类型,或出票单位与收票单位均为当前结算收付款方的汇票'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (selected.maturityDate) {
|
if (selected.maturityDate) {
|
||||||
const maturity = this.$dayjs(selected.maturityDate).startOf('day');
|
const maturity = this.$dayjs(selected.maturityDate).startOf('day');
|
||||||
const today = this.$dayjs().startOf('day');
|
const today = this.$dayjs().startOf('day');
|
||||||
@@ -1562,8 +1530,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);
|
||||||
@@ -1575,27 +1541,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;
|
||||||
@@ -1622,8 +1567,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)
|
||||||
),
|
),
|
||||||
@@ -1679,7 +1623,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(
|
||||||
(
|
(
|
||||||
@@ -1688,11 +1632,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];
|
||||||
@@ -1726,18 +1669,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;
|
||||||
@@ -1880,21 +1832,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, {
|
||||||
@@ -1920,8 +1857,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 = [];
|
||||||
@@ -2483,8 +2419,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
payeeReceiptLabel(item) {
|
payeeReceiptLabel(item) {
|
||||||
const name = item.accountName || item.accountHolderName || '未命名收款方';
|
return item.accountName || item.accountHolderName || '未命名收款方';
|
||||||
return item.bankAccount ? `${name}(${item.bankAccount})` : name;
|
|
||||||
},
|
},
|
||||||
receiptAccountLabel(item) {
|
receiptAccountLabel(item) {
|
||||||
return [item.accountName || item.accountHolderName, item.bankName, item.bankAccount]
|
return [item.accountName || item.accountHolderName, item.bankName, item.bankAccount]
|
||||||
@@ -2642,7 +2577,7 @@ export default {
|
|||||||
this.unwrapData(
|
this.unwrapData(
|
||||||
await billLedgerApi.getAvailableOptions(
|
await billLedgerApi.getAvailableOptions(
|
||||||
keyword,
|
keyword,
|
||||||
this.form.deptId,
|
undefined,
|
||||||
selectedId,
|
selectedId,
|
||||||
this.settlementPayerName,
|
this.settlementPayerName,
|
||||||
this.settlementPayeeName
|
this.settlementPayeeName
|
||||||
@@ -2667,7 +2602,7 @@ export default {
|
|||||||
this.billPage.current,
|
this.billPage.current,
|
||||||
this.billPage.size,
|
this.billPage.size,
|
||||||
String(this.billQuery.keyword || '').trim() || undefined,
|
String(this.billQuery.keyword || '').trim() || undefined,
|
||||||
this.form.deptId,
|
undefined,
|
||||||
this.form.billLedgerId,
|
this.form.billLedgerId,
|
||||||
this.settlementPayerName,
|
this.settlementPayerName,
|
||||||
this.settlementPayeeName
|
this.settlementPayeeName
|
||||||
@@ -2706,14 +2641,6 @@ export default {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
selectBill(row) {
|
selectBill(row) {
|
||||||
if (
|
|
||||||
row?.billType &&
|
|
||||||
row.billType !== 'received' &&
|
|
||||||
!this.billMatchesSettlementParties(row)
|
|
||||||
) {
|
|
||||||
ElMessage.warning('汇票付款仅可选择收票类型,或出票单位与收票单位均为当前结算收付款方的汇票');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (row?.maturityDate) {
|
if (row?.maturityDate) {
|
||||||
const maturity = this.$dayjs(row.maturityDate).startOf('day');
|
const maturity = this.$dayjs(row.maturityDate).startOf('day');
|
||||||
const today = this.$dayjs().startOf('day');
|
const today = this.$dayjs().startOf('day');
|
||||||
@@ -3136,22 +3063,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 =>
|
||||||
@@ -3160,55 +3119,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() {
|
||||||
@@ -3317,8 +3240,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,
|
||||||
@@ -3379,21 +3302,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) {
|
||||||
|
|||||||
@@ -425,6 +425,9 @@ export default {
|
|||||||
minWidth: 140,
|
minWidth: 140,
|
||||||
order: 61,
|
order: 61,
|
||||||
placeholder: '如 1',
|
placeholder: '如 1',
|
||||||
|
// -1 为空占位,列表展示为空
|
||||||
|
formatter: row =>
|
||||||
|
Number(row.contractInitialSerial) === -1 ? '' : row.contractInitialSerial,
|
||||||
tip: '该部门当年首个合同时从此流水号起编,后续递增;范围1-999,不填默认从1开始',
|
tip: '该部门当年首个合同时从此流水号起编,后续递增;范围1-999,不填默认从1开始',
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
@@ -532,6 +535,27 @@ export default {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '是否业务部门',
|
||||||
|
prop: 'isBusinessDept',
|
||||||
|
type: 'select',
|
||||||
|
dicData: [
|
||||||
|
{ label: '否', value: 0 },
|
||||||
|
{ label: '是', value: 1 },
|
||||||
|
],
|
||||||
|
dataType: 'number',
|
||||||
|
value: 0,
|
||||||
|
width: 120,
|
||||||
|
order: 34,
|
||||||
|
formatter: row => (Number(row.isBusinessDept) === 1 ? '是' : '否'),
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请选择是否业务部门',
|
||||||
|
trigger: 'change',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '排序',
|
label: '排序',
|
||||||
prop: 'sort',
|
prop: 'sort',
|
||||||
@@ -1520,6 +1544,9 @@ export default {
|
|||||||
if (this.form.isPlatformCompany === undefined || this.form.isPlatformCompany === null) {
|
if (this.form.isPlatformCompany === undefined || this.form.isPlatformCompany === null) {
|
||||||
this.form.isPlatformCompany = 0;
|
this.form.isPlatformCompany = 0;
|
||||||
}
|
}
|
||||||
|
if (this.form.isBusinessDept === undefined || this.form.isBusinessDept === null) {
|
||||||
|
this.form.isBusinessDept = 0;
|
||||||
|
}
|
||||||
this.clearEmptyContractInitialSerial(this.form);
|
this.clearEmptyContractInitialSerial(this.form);
|
||||||
this.initData(this.form.tenantId);
|
this.initData(this.form.tenantId);
|
||||||
this.loadParentDeptCode(this.form.parentId);
|
this.loadParentDeptCode(this.form.parentId);
|
||||||
|
|||||||
+185
-39
@@ -20,33 +20,32 @@
|
|||||||
@on-load="onLoad"
|
@on-load="onLoad"
|
||||||
>
|
>
|
||||||
<template #menu-left>
|
<template #menu-left>
|
||||||
<el-button
|
<el-button type="primary" v-if="permission.mp_user_add" @click="$refs.crud.rowAdd()">
|
||||||
type="primary"
|
|
||||||
v-if="permission.mp_user_add"
|
|
||||||
@click="$refs.crud.rowAdd()"
|
|
||||||
>
|
|
||||||
新增
|
新增
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button type="primary" plain v-if="permission.mp_user_reset" @click="handleReset">
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
v-if="permission.mp_user_reset"
|
|
||||||
@click="handleReset"
|
|
||||||
>
|
|
||||||
密码重置
|
密码重置
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button type="danger" plain v-if="permission.mp_user_delete" @click="handleDelete">
|
||||||
type="danger"
|
|
||||||
plain
|
|
||||||
v-if="permission.mp_user_delete"
|
|
||||||
@click="handleDelete"
|
|
||||||
>
|
|
||||||
批量删除
|
批量删除
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
<template #identities="{ row }">
|
<template #identities="{ row }">
|
||||||
{{ row.identitiesName || formatIdentities(row.identities) }}
|
{{ row.identitiesName || formatIdentities(row.identities) }}
|
||||||
</template>
|
</template>
|
||||||
|
<template #deptName="{ row }">
|
||||||
|
<template v-if="formatDeptNames(row.deptName).length">
|
||||||
|
<el-tag
|
||||||
|
v-for="name in formatDeptNames(row.deptName)"
|
||||||
|
:key="name"
|
||||||
|
size="small"
|
||||||
|
class="dept-tag"
|
||||||
|
>
|
||||||
|
{{ name }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
<template #status="{ row }">
|
<template #status="{ row }">
|
||||||
<el-tag :type="row.status === 1 ? 'primary' : 'info'" class="status-text">
|
<el-tag :type="row.status === 1 ? 'primary' : 'info'" class="status-text">
|
||||||
{{ row.status === 1 ? '启用' : '停用' }}
|
{{ row.status === 1 ? '启用' : '停用' }}
|
||||||
@@ -55,6 +54,33 @@
|
|||||||
<template #updateUserName="{ row }">
|
<template #updateUserName="{ row }">
|
||||||
{{ formatUpdateUserName(row) }}
|
{{ formatUpdateUserName(row) }}
|
||||||
</template>
|
</template>
|
||||||
|
<template #deptId-form>
|
||||||
|
<el-cascader
|
||||||
|
v-model="deptCascaderValue"
|
||||||
|
:options="permissionDeptTree"
|
||||||
|
:props="deptCascaderProps"
|
||||||
|
:disabled="isViewMode"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
collapse-tags
|
||||||
|
collapse-tags-tooltip
|
||||||
|
placeholder="请选择所属组织(可多选)"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #dataScopeDeptIds-form>
|
||||||
|
<el-tree
|
||||||
|
v-if="Number(form.dataScopeRange) === 3"
|
||||||
|
ref="dataScopeTree"
|
||||||
|
:data="permissionDeptTree"
|
||||||
|
show-checkbox
|
||||||
|
node-key="id"
|
||||||
|
:default-checked-keys="form.dataScopeDeptIds || []"
|
||||||
|
:props="{ label: 'title', children: 'children' }"
|
||||||
|
@check="handleDataScopeCheck"
|
||||||
|
/>
|
||||||
|
<span v-else class="form-tip">选择「自定义」后可勾选数据权限组织</span>
|
||||||
|
</template>
|
||||||
<template #menu="{ row, index }">
|
<template #menu="{ row, index }">
|
||||||
<el-link
|
<el-link
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -70,25 +96,13 @@
|
|||||||
>
|
>
|
||||||
编辑
|
编辑
|
||||||
</el-link>
|
</el-link>
|
||||||
<el-link
|
<el-link type="primary" v-if="permission.mp_user_status" @click="handleStatus(row)">
|
||||||
type="primary"
|
|
||||||
v-if="permission.mp_user_status"
|
|
||||||
@click="handleStatus(row)"
|
|
||||||
>
|
|
||||||
{{ row.status === 1 ? '停用' : '启用' }}
|
{{ row.status === 1 ? '停用' : '启用' }}
|
||||||
</el-link>
|
</el-link>
|
||||||
<el-link
|
<el-link type="primary" v-if="permission.mp_user_reset" @click="handleResetForRow(row)">
|
||||||
type="primary"
|
|
||||||
v-if="permission.mp_user_reset"
|
|
||||||
@click="handleResetForRow(row)"
|
|
||||||
>
|
|
||||||
重置密码
|
重置密码
|
||||||
</el-link>
|
</el-link>
|
||||||
<el-link
|
<el-link type="danger" v-if="permission.mp_user_delete" @click="rowDel(row)">
|
||||||
type="danger"
|
|
||||||
v-if="permission.mp_user_delete"
|
|
||||||
@click="rowDel(row)"
|
|
||||||
>
|
|
||||||
删除
|
删除
|
||||||
</el-link>
|
</el-link>
|
||||||
</template>
|
</template>
|
||||||
@@ -108,9 +122,12 @@
|
|||||||
<script>
|
<script>
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
import { getList, getDetail, submit, remove, changeStatus, resetPassword } from '@/api/system/mpUser';
|
import { getList, getDetail, submit, remove, changeStatus, resetPassword } from '@/api/system/mpUser';
|
||||||
|
import { getDeptTree } from '@/api/system/dept';
|
||||||
import option from '@/option/system/mpUser';
|
import option from '@/option/system/mpUser';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { formatUpdateUserName } from '@/utils/audit';
|
import { formatUpdateUserName } from '@/utils/audit';
|
||||||
|
import { sensitive } from '@/utils/sensitive';
|
||||||
|
import func from '@/utils/func';
|
||||||
|
|
||||||
const IDENTITY_LABEL = {
|
const IDENTITY_LABEL = {
|
||||||
driver: '司机',
|
driver: '司机',
|
||||||
@@ -132,10 +149,12 @@ export default {
|
|||||||
selectionList: [],
|
selectionList: [],
|
||||||
option,
|
option,
|
||||||
data: [],
|
data: [],
|
||||||
|
permissionDeptTree: [],
|
||||||
|
sensitiveManager: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(['permission']),
|
...mapGetters(['permission', 'userInfo']),
|
||||||
permissionList() {
|
permissionList() {
|
||||||
return {
|
return {
|
||||||
addBtn: this.validData(this.permission.mp_user_add, false),
|
addBtn: this.validData(this.permission.mp_user_add, false),
|
||||||
@@ -147,6 +166,46 @@ export default {
|
|||||||
ids() {
|
ids() {
|
||||||
return this.selectionList.map(item => item.id).join(',');
|
return this.selectionList.map(item => item.id).join(',');
|
||||||
},
|
},
|
||||||
|
isViewMode() {
|
||||||
|
return this.dialogType === 'view';
|
||||||
|
},
|
||||||
|
deptCascaderProps() {
|
||||||
|
return {
|
||||||
|
label: 'title',
|
||||||
|
value: 'id',
|
||||||
|
children: 'children',
|
||||||
|
emitPath: false,
|
||||||
|
multiple: true,
|
||||||
|
checkStrictly: true,
|
||||||
|
expandTrigger: 'click',
|
||||||
|
disabled: 'disabled',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
deptCascaderValue: {
|
||||||
|
get() {
|
||||||
|
const ids = Array.isArray(this.form.deptId)
|
||||||
|
? this.form.deptId
|
||||||
|
: this.form.deptId
|
||||||
|
? String(this.form.deptId).split(',').filter(Boolean)
|
||||||
|
: [];
|
||||||
|
return ids
|
||||||
|
.filter(item => item !== undefined && item !== null && item !== '')
|
||||||
|
.map(item => (typeof item === 'number' ? item : String(item)));
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.form.deptId = Array.isArray(value)
|
||||||
|
? value.filter(item => item !== undefined && item !== null && item !== '')
|
||||||
|
: value
|
||||||
|
? [value]
|
||||||
|
: [];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.sensitiveManager = sensitive.create({
|
||||||
|
fields: ['phone', 'email'],
|
||||||
|
});
|
||||||
|
this.loadDeptTree();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
formatUpdateUserName,
|
formatUpdateUserName,
|
||||||
@@ -158,19 +217,59 @@ export default {
|
|||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(',');
|
.join(',');
|
||||||
},
|
},
|
||||||
normalizeSubmitRow(row = {}) {
|
formatDeptNames(deptName) {
|
||||||
const payload = { ...row };
|
if (!deptName) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return String(deptName)
|
||||||
|
.split(',')
|
||||||
|
.map(item => item.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
},
|
||||||
|
loadDeptTree() {
|
||||||
|
const tenantId = this.userInfo?.tenantId;
|
||||||
|
if (!tenantId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
getDeptTree(tenantId).then(res => {
|
||||||
|
this.permissionDeptTree = res.data.data || [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleDataScopeCheck(_, checked) {
|
||||||
|
if (this.isViewMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.form.dataScopeDeptIds = checked.checkedKeys || [];
|
||||||
|
},
|
||||||
|
normalizeSubmitRow(row = {}, isUpdate = false) {
|
||||||
|
let payload = { ...row };
|
||||||
|
// 编辑时剔除未改动的脱敏字段,避免把 138****8000 写回库
|
||||||
|
if (isUpdate && this.sensitiveManager) {
|
||||||
|
payload = this.sensitiveManager.getSubmitData(payload);
|
||||||
|
}
|
||||||
if (Array.isArray(payload.identities)) {
|
if (Array.isArray(payload.identities)) {
|
||||||
payload.identities = payload.identities.filter(Boolean).join(',');
|
payload.identities = payload.identities.filter(Boolean).join(',');
|
||||||
}
|
}
|
||||||
|
payload.deptId = func.join(payload.deptId);
|
||||||
if (payload.phone) {
|
if (payload.phone) {
|
||||||
payload.phone = String(payload.phone).trim();
|
payload.phone = String(payload.phone).trim();
|
||||||
}
|
}
|
||||||
if (!payload.account && payload.phone) {
|
// 脱敏手机号不能作为账号默认值
|
||||||
|
if (!payload.account && payload.phone && !String(payload.phone).includes('*')) {
|
||||||
payload.account = payload.phone;
|
payload.account = payload.phone;
|
||||||
}
|
}
|
||||||
|
payload.dataScopeRange = Number(payload.dataScopeRange || 1);
|
||||||
|
payload.dataLevelRange = Number(payload.dataLevelRange || 1);
|
||||||
|
if (payload.dataScopeRange === 3) {
|
||||||
|
payload.dataScopeDeptIds = Array.isArray(payload.dataScopeDeptIds)
|
||||||
|
? payload.dataScopeDeptIds
|
||||||
|
: [];
|
||||||
|
} else {
|
||||||
|
payload.dataScopeDeptIds = [];
|
||||||
|
}
|
||||||
delete payload.password;
|
delete payload.password;
|
||||||
delete payload.identitiesName;
|
delete payload.identitiesName;
|
||||||
|
delete payload.deptName;
|
||||||
delete payload.sexName;
|
delete payload.sexName;
|
||||||
delete payload.createUserName;
|
delete payload.createUserName;
|
||||||
delete payload.updateUserName;
|
delete payload.updateUserName;
|
||||||
@@ -178,13 +277,25 @@ export default {
|
|||||||
},
|
},
|
||||||
beforeOpen(done, type) {
|
beforeOpen(done, type) {
|
||||||
this.dialogType = type;
|
this.dialogType = type;
|
||||||
|
this.loadDeptTree();
|
||||||
if (['edit', 'view'].includes(type) && this.form.id) {
|
if (['edit', 'view'].includes(type) && this.form.id) {
|
||||||
getDetail(this.form.id).then(res => {
|
getDetail(this.form.id).then(res => {
|
||||||
const detail = res.data.data || {};
|
const detail = res.data.data || {};
|
||||||
if (detail.identities && typeof detail.identities === 'string') {
|
if (detail.identities && typeof detail.identities === 'string') {
|
||||||
detail.identities = detail.identities.split(',').filter(Boolean);
|
detail.identities = detail.identities.split(',').filter(Boolean);
|
||||||
}
|
}
|
||||||
this.form = detail;
|
const deptIds = func.split(detail.deptId || '');
|
||||||
|
this.form = {
|
||||||
|
...detail,
|
||||||
|
deptId: Array.isArray(deptIds) ? deptIds : [],
|
||||||
|
dataScopeRange: detail.dataScopeRange || 1,
|
||||||
|
dataLevelRange: detail.dataLevelRange || 1,
|
||||||
|
dataScopeDeptIds: detail.dataScopeDeptIds || [],
|
||||||
|
};
|
||||||
|
this.sensitiveManager?.saveInitialData(this.form);
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.dataScopeTree?.setCheckedKeys?.(this.form.dataScopeDeptIds || []);
|
||||||
|
});
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -193,12 +304,28 @@ export default {
|
|||||||
this.form = {
|
this.form = {
|
||||||
status: 1,
|
status: 1,
|
||||||
identities: ['driver'],
|
identities: ['driver'],
|
||||||
|
deptId: [],
|
||||||
|
dataScopeRange: 1,
|
||||||
|
dataLevelRange: 1,
|
||||||
|
dataScopeDeptIds: [],
|
||||||
};
|
};
|
||||||
|
this.sensitiveManager?.saveInitialData({});
|
||||||
}
|
}
|
||||||
done();
|
done();
|
||||||
},
|
},
|
||||||
rowSave(row, done, loading) {
|
rowSave(row, done, loading) {
|
||||||
submit(this.normalizeSubmitRow(row)).then(
|
const payload = this.normalizeSubmitRow(row, false);
|
||||||
|
if (!payload.deptId) {
|
||||||
|
this.$message.warning('请选择所属组织');
|
||||||
|
loading();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (payload.dataScopeRange === 3 && (!payload.dataScopeDeptIds || !payload.dataScopeDeptIds.length)) {
|
||||||
|
this.$message.warning('请选择自定义数据权限组织');
|
||||||
|
loading();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
submit(payload).then(
|
||||||
() => {
|
() => {
|
||||||
this.onLoad(this.page);
|
this.onLoad(this.page);
|
||||||
this.$message.success('操作成功');
|
this.$message.success('操作成功');
|
||||||
@@ -208,7 +335,18 @@ export default {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
rowUpdate(row, index, done, loading) {
|
rowUpdate(row, index, done, loading) {
|
||||||
submit(this.normalizeSubmitRow(row)).then(
|
const payload = this.normalizeSubmitRow(row, true);
|
||||||
|
if (!payload.deptId) {
|
||||||
|
this.$message.warning('请选择所属组织');
|
||||||
|
loading();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (payload.dataScopeRange === 3 && (!payload.dataScopeDeptIds || !payload.dataScopeDeptIds.length)) {
|
||||||
|
this.$message.warning('请选择自定义数据权限组织');
|
||||||
|
loading();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
submit(payload).then(
|
||||||
() => {
|
() => {
|
||||||
this.onLoad(this.page);
|
this.onLoad(this.page);
|
||||||
this.$message.success('操作成功');
|
this.$message.success('操作成功');
|
||||||
@@ -351,5 +489,13 @@ export default {
|
|||||||
:deep(.avue-crud__menu) {
|
:deep(.avue-crud__menu) {
|
||||||
min-height: auto;
|
min-height: auto;
|
||||||
}
|
}
|
||||||
|
.dept-tag {
|
||||||
|
margin-right: 4px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
.form-tip {
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+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个基础得分项目');
|
||||||
|
|||||||
@@ -398,47 +398,6 @@
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="6">
|
|
||||||
<el-form-item label="经营范围" prop="businessScope">
|
|
||||||
<el-select v-model="archiveForm.businessScope" multiple filterable clearable>
|
|
||||||
<el-option
|
|
||||||
v-for="item in businessScopeOptions"
|
|
||||||
:key="item.value"
|
|
||||||
:label="item.label"
|
|
||||||
:value="item.value"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-form-item
|
|
||||||
label="营业期限"
|
|
||||||
prop="businessEndDate"
|
|
||||||
:required="archiveForm.businessTermType === '固定期限'"
|
|
||||||
:rules="businessEndDateRules"
|
|
||||||
>
|
|
||||||
<div class="business-term-field">
|
|
||||||
<el-date-picker
|
|
||||||
v-model="archiveForm.businessEndDate"
|
|
||||||
type="date"
|
|
||||||
value-format="YYYY-MM-DD"
|
|
||||||
:disabled="archiveForm.businessTermType === '长期'"
|
|
||||||
@change="handleBusinessEndDateChange"
|
|
||||||
/>
|
|
||||||
<el-checkbox
|
|
||||||
:model-value="archiveForm.businessTermType === '长期'"
|
|
||||||
@change="handleNoFixedTermChange"
|
|
||||||
>
|
|
||||||
无固定期限
|
|
||||||
</el-checkbox>
|
|
||||||
</div>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-form-item label="助记码" prop="mnemonicCode">
|
|
||||||
<el-input v-model="archiveForm.mnemonicCode" maxlength="15" show-word-limit />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="客户简称" prop="shortName">
|
<el-form-item label="客户简称" prop="shortName">
|
||||||
<el-input
|
<el-input
|
||||||
@@ -499,38 +458,38 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
<el-col :span="12">
|
||||||
</section-card>
|
<el-form-item
|
||||||
|
label="营业期限"
|
||||||
<section-card title="联系信息">
|
prop="businessEndDate"
|
||||||
<el-row :gutter="18">
|
:required="archiveForm.businessTermType === '固定期限'"
|
||||||
<el-col :span="6">
|
:rules="businessEndDateRules"
|
||||||
<el-form-item label="客商负责人" prop="principal">
|
>
|
||||||
<el-input v-model="archiveForm.principal" maxlength="10" show-word-limit />
|
<div class="business-term-field">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="archiveForm.businessStartDate"
|
||||||
|
type="date"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
placeholder="开始日期"
|
||||||
|
/>
|
||||||
|
<span class="business-term-separator">至</span>
|
||||||
|
<el-date-picker
|
||||||
|
v-model="archiveForm.businessEndDate"
|
||||||
|
type="date"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
placeholder="截止日期"
|
||||||
|
:disabled="archiveForm.businessTermType === '长期'"
|
||||||
|
@change="handleBusinessEndDateChange"
|
||||||
|
/>
|
||||||
|
<el-checkbox
|
||||||
|
:model-value="archiveForm.businessTermType === '长期'"
|
||||||
|
@change="handleNoFixedTermChange"
|
||||||
|
>
|
||||||
|
无固定期限
|
||||||
|
</el-checkbox>
|
||||||
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="6">
|
|
||||||
<el-form-item label="联系电话" prop="contactPhone">
|
|
||||||
<el-input v-model="archiveForm.contactPhone" maxlength="20" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-form-item label="所属组织" prop="deptName">
|
|
||||||
<el-cascader
|
|
||||||
v-model="deptCascaderValue"
|
|
||||||
:options="deptTree"
|
|
||||||
:props="deptCascaderProps"
|
|
||||||
disabled
|
|
||||||
placeholder="广西现代物流集团有限公司"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6"></el-col>
|
|
||||||
</el-row>
|
|
||||||
</section-card>
|
|
||||||
|
|
||||||
<section-card title="财务/信用信息">
|
|
||||||
<el-row :gutter="18">
|
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="注册资金(万元)" prop="registeredCapital">
|
<el-form-item label="注册资金(万元)" prop="registeredCapital">
|
||||||
<el-input
|
<el-input
|
||||||
@@ -542,11 +501,12 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="开票税点(%)" prop="invoiceTaxRate">
|
<el-form-item label="实缴资本(万元)" prop="paidInCapital">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="archiveForm.invoiceTaxRate"
|
v-model="archiveForm.paidInCapital"
|
||||||
maxlength="5"
|
maxlength="18"
|
||||||
@input="value => handleDecimalInput('invoiceTaxRate', value, 13)"
|
:disabled="kingdeeLocked"
|
||||||
|
@input="value => handleDecimalInput('paidInCapital', value)"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
@@ -570,6 +530,29 @@
|
|||||||
<el-input v-model="archiveForm.applyCreditLimit" maxlength="18" disabled />
|
<el-input v-model="archiveForm.applyCreditLimit" maxlength="18" disabled />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-form-item label="已使用额度(万元)" prop="usedCreditLimit">
|
||||||
|
<el-input
|
||||||
|
v-model="archiveForm.usedCreditLimit"
|
||||||
|
maxlength="18"
|
||||||
|
:disabled="readonly || Boolean(archiveForm.id)"
|
||||||
|
placeholder="新建可录历史已使用额度"
|
||||||
|
@input="value => handleDecimalInput('usedCreditLimit', value)"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="经营范围" prop="businessScope" class="archive-form__remark">
|
||||||
|
<el-input
|
||||||
|
v-model="archiveForm.businessScope"
|
||||||
|
type="textarea"
|
||||||
|
:rows="4"
|
||||||
|
maxlength="2000"
|
||||||
|
show-word-limit
|
||||||
|
placeholder="可通过营业执照 OCR 自动识别,也可手动填写"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
<el-form-item label="备注" prop="remark" class="archive-form__remark">
|
<el-form-item label="备注" prop="remark" class="archive-form__remark">
|
||||||
<el-input
|
<el-input
|
||||||
@@ -644,18 +627,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>
|
||||||
@@ -1155,7 +1147,14 @@
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="开户行名称" prop="bankName">
|
<el-form-item label="开户行名称" prop="bankName">
|
||||||
<el-input v-model="invoiceForm.bankName" maxlength="100" />
|
<el-autocomplete
|
||||||
|
v-model="invoiceForm.bankName"
|
||||||
|
:fetch-suggestions="queryBankNameSuggestions"
|
||||||
|
clearable
|
||||||
|
maxlength="100"
|
||||||
|
placeholder="请选择或输入开户行名称"
|
||||||
|
class="invoice-bank-autocomplete"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
@@ -1656,8 +1655,10 @@ export default {
|
|||||||
const nonNegativeLabelMap = {
|
const nonNegativeLabelMap = {
|
||||||
invoiceTaxRate: '开票税点',
|
invoiceTaxRate: '开票税点',
|
||||||
registeredCapital: '注册资金',
|
registeredCapital: '注册资金',
|
||||||
|
paidInCapital: '实缴资本',
|
||||||
maxCreditLimit: '最大资金使用额度',
|
maxCreditLimit: '最大资金使用额度',
|
||||||
applyCreditLimit: '申请总资金使用额度',
|
applyCreditLimit: '申请总资金使用额度',
|
||||||
|
usedCreditLimit: '已使用额度',
|
||||||
};
|
};
|
||||||
const validateNonNegative = (rule, value, callback) => {
|
const validateNonNegative = (rule, value, callback) => {
|
||||||
if (value === undefined || value === null || value === '') {
|
if (value === undefined || value === null || value === '') {
|
||||||
@@ -1668,15 +1669,6 @@ export default {
|
|||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const validateInvoiceTaxRate = (rule, value, callback) => {
|
|
||||||
if (value === undefined || value === null || value === '') {
|
|
||||||
callback();
|
|
||||||
} else if (Number(value) < 0 || Number(value) > 13) {
|
|
||||||
callback(new Error('开票税点必须在0到13之间'));
|
|
||||||
} else {
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const validateUnifiedCreditCode = (rule, value, callback) => {
|
const validateUnifiedCreditCode = (rule, value, callback) => {
|
||||||
const code = String(value || '')
|
const code = String(value || '')
|
||||||
.trim()
|
.trim()
|
||||||
@@ -1806,7 +1798,7 @@ export default {
|
|||||||
{ label: '是', value: 1 },
|
{ label: '是', value: 1 },
|
||||||
{ label: '否', value: 0 },
|
{ label: '否', value: 0 },
|
||||||
],
|
],
|
||||||
businessScopeOptions: [],
|
bankListOptions: [],
|
||||||
qualificationTypeOptions: [],
|
qualificationTypeOptions: [],
|
||||||
creditLevelOptions: ['A', 'B', 'C', 'D', 'E', 'F'].map(item => ({
|
creditLevelOptions: ['A', 'B', 'C', 'D', 'E', 'F'].map(item => ({
|
||||||
label: `${item}级`,
|
label: `${item}级`,
|
||||||
@@ -1845,15 +1837,12 @@ export default {
|
|||||||
],
|
],
|
||||||
customerType: [{ required: true, message: '请选择客商分类', trigger: 'change' }],
|
customerType: [{ required: true, message: '请选择客商分类', trigger: 'change' }],
|
||||||
legalPerson: [{ required: true, message: '请输入企业法人', trigger: 'blur' }],
|
legalPerson: [{ required: true, message: '请输入企业法人', trigger: 'blur' }],
|
||||||
contactPhone: [
|
|
||||||
{ required: true, message: '请输入联系电话', trigger: 'blur' },
|
|
||||||
{ pattern: /^1\d{10}$/, message: '联系电话必须为11位手机号', trigger: 'blur' },
|
|
||||||
],
|
|
||||||
deptName: [{ required: true, message: '请选择所属组织', trigger: 'change' }],
|
|
||||||
invoiceTaxRate: [{ validator: validateInvoiceTaxRate, trigger: 'blur' }],
|
|
||||||
registeredCapital: [{ validator: validateNonNegative, trigger: 'blur' }],
|
registeredCapital: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||||||
|
paidInCapital: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||||||
maxCreditLimit: [{ validator: validateNonNegative, trigger: 'blur' }],
|
maxCreditLimit: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||||||
applyCreditLimit: [{ validator: validateNonNegative, trigger: 'blur' }],
|
applyCreditLimit: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||||||
|
usedCreditLimit: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||||||
|
businessScope: [{ max: 2000, message: '经营范围最多2000个字符', trigger: 'blur' }],
|
||||||
registeredRegionName: [{ required: true, message: '请选择省市区', trigger: 'change' }],
|
registeredRegionName: [{ required: true, message: '请选择省市区', trigger: 'change' }],
|
||||||
registeredDetailAddress: [
|
registeredDetailAddress: [
|
||||||
{ required: true, message: '请输入详细地址', trigger: 'blur' },
|
{ required: true, message: '请输入详细地址', trigger: 'blur' },
|
||||||
@@ -2296,7 +2285,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;
|
||||||
@@ -2346,10 +2336,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,
|
||||||
@@ -2406,7 +2403,6 @@ export default {
|
|||||||
ensurePublicViewOptions(archive = {}) {
|
ensurePublicViewOptions(archive = {}) {
|
||||||
this.mergeSelectOptions('customerNatureOptions', archive.customerNature);
|
this.mergeSelectOptions('customerNatureOptions', archive.customerNature);
|
||||||
this.mergeSelectOptions('customerTypeOptions', archive.customerType);
|
this.mergeSelectOptions('customerTypeOptions', archive.customerType);
|
||||||
this.mergeSelectOptions('businessScopeOptions', archive.businessScope);
|
|
||||||
this.mergeSelectOptions(
|
this.mergeSelectOptions(
|
||||||
'qualificationTypeOptions',
|
'qualificationTypeOptions',
|
||||||
(this.qualificationFiles || []).map(item => item.type)
|
(this.qualificationFiles || []).map(item => item.type)
|
||||||
@@ -2464,9 +2460,11 @@ export default {
|
|||||||
form.fullName = '';
|
form.fullName = '';
|
||||||
form.legalPerson = '';
|
form.legalPerson = '';
|
||||||
form.registeredCapital = '';
|
form.registeredCapital = '';
|
||||||
|
form.paidInCapital = '';
|
||||||
form.registeredDetailAddress = '';
|
form.registeredDetailAddress = '';
|
||||||
form.businessScope = [];
|
form.businessScope = '';
|
||||||
form.businessTermType = '固定期限';
|
form.businessTermType = '固定期限';
|
||||||
|
form.businessStartDate = '';
|
||||||
form.businessEndDate = '';
|
form.businessEndDate = '';
|
||||||
},
|
},
|
||||||
// 新增态下统一社会信用代码录入完成后自动查询金蝶(A902-1),防抖 500ms;
|
// 新增态下统一社会信用代码录入完成后自动查询金蝶(A902-1),防抖 500ms;
|
||||||
@@ -2525,14 +2523,7 @@ export default {
|
|||||||
}
|
}
|
||||||
if (customer.businessterm) this.applyBusinessTermRecognition(customer.businessterm);
|
if (customer.businessterm) this.applyBusinessTermRecognition(customer.businessterm);
|
||||||
if (customer.businessscope) {
|
if (customer.businessscope) {
|
||||||
const matchedScopes = this.businessScopeOptions
|
form.businessScope = String(customer.businessscope).trim();
|
||||||
.filter(
|
|
||||||
item =>
|
|
||||||
customer.businessscope.includes(item.label) ||
|
|
||||||
customer.businessscope.includes(item.value)
|
|
||||||
)
|
|
||||||
.map(item => item.value);
|
|
||||||
if (matchedScopes.length) form.businessScope = matchedScopes;
|
|
||||||
}
|
}
|
||||||
if (!form.shortName && customer.simplename) form.shortName = customer.simplename;
|
if (!form.shortName && customer.simplename) form.shortName = customer.simplename;
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
@@ -2541,6 +2532,8 @@ export default {
|
|||||||
'fullName',
|
'fullName',
|
||||||
'legalPerson',
|
'legalPerson',
|
||||||
'registeredCapital',
|
'registeredCapital',
|
||||||
|
'paidInCapital',
|
||||||
|
'businessStartDate',
|
||||||
'businessEndDate',
|
'businessEndDate',
|
||||||
'businessScope',
|
'businessScope',
|
||||||
'registeredDetailAddress',
|
'registeredDetailAddress',
|
||||||
@@ -2563,8 +2556,13 @@ export default {
|
|||||||
deptIds: '',
|
deptIds: '',
|
||||||
deptName: '',
|
deptName: '',
|
||||||
customerType: [],
|
customerType: [],
|
||||||
businessScope: [],
|
businessScope: '',
|
||||||
businessTermType: '固定期限',
|
businessTermType: '固定期限',
|
||||||
|
businessStartDate: '',
|
||||||
|
businessEndDate: '',
|
||||||
|
registeredCapital: '',
|
||||||
|
paidInCapital: '',
|
||||||
|
usedCreditLimit: '',
|
||||||
networkFreightPlatform: null,
|
networkFreightPlatform: null,
|
||||||
guangxiTop100: null,
|
guangxiTop100: null,
|
||||||
registeredRegionPath: [],
|
registeredRegionPath: [],
|
||||||
@@ -2699,6 +2697,12 @@ export default {
|
|||||||
approvedTime: '审核通过时间',
|
approvedTime: '审核通过时间',
|
||||||
status: '状态',
|
status: '状态',
|
||||||
businessTermType: '营业期限类型',
|
businessTermType: '营业期限类型',
|
||||||
|
businessStartDate: '营业期限开始日',
|
||||||
|
businessEndDate: '营业期限截止日',
|
||||||
|
registeredCapital: '注册资金(万元)',
|
||||||
|
paidInCapital: '实缴资本(万元)',
|
||||||
|
usedCreditLimit: '已使用额度(万元)',
|
||||||
|
businessScope: '经营范围',
|
||||||
networkFreightPlatform: '网络货运平台',
|
networkFreightPlatform: '网络货运平台',
|
||||||
registeredRegionName: '注册地址',
|
registeredRegionName: '注册地址',
|
||||||
registeredDetailAddress: '详细地址',
|
registeredDetailAddress: '详细地址',
|
||||||
@@ -3147,11 +3151,21 @@ export default {
|
|||||||
const customerTypeColumn = this.findColumn(this.option.column, 'customerType');
|
const customerTypeColumn = this.findColumn(this.option.column, 'customerType');
|
||||||
customerTypeColumn.dicData = [{ label: '全部', value: '' }, ...options];
|
customerTypeColumn.dicData = [{ label: '全部', value: '' }, ...options];
|
||||||
});
|
});
|
||||||
this.loadDictOptions('scope_of_business', 'businessScopeOptions');
|
this.loadDictOptions('bank_list', 'bankListOptions');
|
||||||
this.loadDictOptions('customer_attachment_types', 'qualificationTypeOptions', [], () => {
|
this.loadDictOptions('customer_attachment_types', 'qualificationTypeOptions', [], () => {
|
||||||
this.sortQualificationFiles();
|
this.sortQualificationFiles();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
queryBankNameSuggestions(queryString, callback) {
|
||||||
|
const query = String(queryString || '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
const options = Array.isArray(this.bankListOptions) ? this.bankListOptions : [];
|
||||||
|
const matched = query
|
||||||
|
? options.filter(item => String(item.value || '').toLowerCase().includes(query))
|
||||||
|
: options;
|
||||||
|
callback(matched.map(item => ({ value: item.value })));
|
||||||
|
},
|
||||||
initMkEnabled() {
|
initMkEnabled() {
|
||||||
this._mkEnabledResolved = false;
|
this._mkEnabledResolved = false;
|
||||||
loadMkEnabled()
|
loadMkEnabled()
|
||||||
@@ -3405,8 +3419,15 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
formatBusinessLicenseValidPeriod(row = {}) {
|
formatBusinessLicenseValidPeriod(row = {}) {
|
||||||
if (row.businessTermType === '长期') return '无固定期限';
|
if (row.businessTermType === '长期') {
|
||||||
return row.businessEndDate || '';
|
return row.businessStartDate
|
||||||
|
? `${row.businessStartDate} 至 无固定期限`
|
||||||
|
: '无固定期限';
|
||||||
|
}
|
||||||
|
if (row.businessStartDate && row.businessEndDate) {
|
||||||
|
return `${row.businessStartDate} 至 ${row.businessEndDate}`;
|
||||||
|
}
|
||||||
|
return row.businessEndDate || row.businessStartDate || '';
|
||||||
},
|
},
|
||||||
formatFundUseRate(row = {}) {
|
formatFundUseRate(row = {}) {
|
||||||
if (row.fundUseRate === undefined || row.fundUseRate === null || row.fundUseRate === '') {
|
if (row.fundUseRate === undefined || row.fundUseRate === null || row.fundUseRate === '') {
|
||||||
@@ -3939,8 +3960,13 @@ export default {
|
|||||||
const legalPerson = this.getBusinessLicenseOcrValue(result, ['法定代表人', '法人']);
|
const legalPerson = this.getBusinessLicenseOcrValue(result, ['法定代表人', '法人']);
|
||||||
const address = this.getBusinessLicenseOcrValue(result, ['地址', '住所']);
|
const address = this.getBusinessLicenseOcrValue(result, ['地址', '住所']);
|
||||||
const capital = this.getBusinessLicenseOcrValue(result, ['注册资本']);
|
const capital = this.getBusinessLicenseOcrValue(result, ['注册资本']);
|
||||||
|
const paidInCapital = this.getBusinessLicenseOcrValue(result, ['实缴资本', '实收资本']);
|
||||||
const term = this.getBusinessLicenseOcrValue(result, ['营业期限', '有效期']);
|
const term = this.getBusinessLicenseOcrValue(result, ['营业期限', '有效期']);
|
||||||
const termStartDate = this.getBusinessLicenseOcrValue(result, ['有效期起始日期']);
|
const termStartDate = this.getBusinessLicenseOcrValue(result, [
|
||||||
|
'有效期起始日期',
|
||||||
|
'成立日期',
|
||||||
|
'核准日期',
|
||||||
|
]);
|
||||||
const scope = this.getBusinessLicenseOcrValue(result, ['经营范围']);
|
const scope = this.getBusinessLicenseOcrValue(result, ['经营范围']);
|
||||||
if (code) this.archiveForm.unifiedCreditCode = code.replace(/\s/g, '').toUpperCase();
|
if (code) this.archiveForm.unifiedCreditCode = code.replace(/\s/g, '').toUpperCase();
|
||||||
if (name) this.archiveForm.fullName = name;
|
if (name) this.archiveForm.fullName = name;
|
||||||
@@ -3955,15 +3981,16 @@ export default {
|
|||||||
const normalizedCapital = capital.replace(/,/g, '').match(/\d+(?:\.\d+)?/);
|
const normalizedCapital = capital.replace(/,/g, '').match(/\d+(?:\.\d+)?/);
|
||||||
if (normalizedCapital) this.archiveForm.registeredCapital = normalizedCapital[0];
|
if (normalizedCapital) this.archiveForm.registeredCapital = normalizedCapital[0];
|
||||||
}
|
}
|
||||||
this.applyBusinessTermRecognition(term);
|
if (paidInCapital) {
|
||||||
|
const normalizedPaidIn = paidInCapital.replace(/,/g, '').match(/\d+(?:\.\d+)?/);
|
||||||
|
if (normalizedPaidIn) this.archiveForm.paidInCapital = normalizedPaidIn[0];
|
||||||
|
}
|
||||||
|
this.applyBusinessTermRecognition(term, termStartDate);
|
||||||
if (this.isNoFixedBusinessTerm(term) || this.isNoFixedBusinessTerm(termStartDate)) {
|
if (this.isNoFixedBusinessTerm(term) || this.isNoFixedBusinessTerm(termStartDate)) {
|
||||||
this.handleNoFixedTermChange(true);
|
this.handleNoFixedTermChange(true);
|
||||||
}
|
}
|
||||||
if (scope) {
|
if (scope) {
|
||||||
const matchedScopes = this.businessScopeOptions
|
this.archiveForm.businessScope = scope;
|
||||||
.filter(item => scope.includes(item.label) || scope.includes(item.value))
|
|
||||||
.map(item => item.value);
|
|
||||||
if (matchedScopes.length) this.archiveForm.businessScope = matchedScopes;
|
|
||||||
}
|
}
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
[
|
[
|
||||||
@@ -3971,27 +3998,49 @@ export default {
|
|||||||
'fullName',
|
'fullName',
|
||||||
'legalPerson',
|
'legalPerson',
|
||||||
'registeredCapital',
|
'registeredCapital',
|
||||||
|
'paidInCapital',
|
||||||
|
'businessStartDate',
|
||||||
'businessEndDate',
|
'businessEndDate',
|
||||||
'businessScope',
|
'businessScope',
|
||||||
'registeredDetailAddress',
|
'registeredDetailAddress',
|
||||||
].forEach(prop => this.$refs.archiveForm?.validateField(prop));
|
].forEach(prop => this.$refs.archiveForm?.validateField(prop));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
applyBusinessTermRecognition(term) {
|
normalizeOcrDate(value) {
|
||||||
|
if (!value) return '';
|
||||||
|
const match = String(value).match(/(\d{4})[年./-](\d{1,2})[月./-](\d{1,2})/);
|
||||||
|
if (!match) return '';
|
||||||
|
const month = String(match[2]).padStart(2, '0');
|
||||||
|
const day = String(match[3]).padStart(2, '0');
|
||||||
|
return `${match[1]}-${month}-${day}`;
|
||||||
|
},
|
||||||
|
applyBusinessTermRecognition(term, termStartDate = '') {
|
||||||
|
const startFromField = this.normalizeOcrDate(termStartDate);
|
||||||
|
if (startFromField) {
|
||||||
|
this.archiveForm.businessStartDate = startFromField;
|
||||||
|
}
|
||||||
if (!term) return;
|
if (!term) return;
|
||||||
if (/长期|永久/.test(term)) {
|
if (/长期|永久|无固定/.test(term) || this.isNoFixedBusinessTerm(term)) {
|
||||||
this.archiveForm.businessTermType = '长期';
|
this.archiveForm.businessTermType = '长期';
|
||||||
this.archiveForm.businessEndDate = '';
|
this.archiveForm.businessEndDate = '';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const match = term.match(/(\d{4}[年./-]\d{1,2}[月./-]\d{1,2})/);
|
const dateMatches = String(term).match(/\d{4}[年./-]\d{1,2}[月./-]\d{1,2}/g) || [];
|
||||||
if (!match) return;
|
const startDate = this.normalizeOcrDate(dateMatches[0]) || startFromField;
|
||||||
const date = match[1]
|
const endDate = this.normalizeOcrDate(dateMatches[1]);
|
||||||
.replace(/[年./]/g, '-')
|
if (startDate) {
|
||||||
.replace('月', '-')
|
this.archiveForm.businessStartDate = startDate;
|
||||||
.replace('日', '');
|
}
|
||||||
this.archiveForm.businessTermType = '固定期限';
|
if (endDate) {
|
||||||
this.archiveForm.businessEndDate = date;
|
this.archiveForm.businessTermType = '固定期限';
|
||||||
|
this.archiveForm.businessEndDate = endDate;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (startDate && dateMatches.length === 1 && !/至|到|-|—|~/.test(term)) {
|
||||||
|
// 仅识别到一个日期且无区间分隔时,按截止日处理(兼容旧 OCR 结果)
|
||||||
|
this.archiveForm.businessTermType = '固定期限';
|
||||||
|
this.archiveForm.businessEndDate = startDate;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
getQualificationFileUrl(file = {}) {
|
getQualificationFileUrl(file = {}) {
|
||||||
return file.url || file.link || file.domain || '';
|
return file.url || file.link || file.domain || '';
|
||||||
@@ -4159,7 +4208,6 @@ export default {
|
|||||||
return value;
|
return value;
|
||||||
},
|
},
|
||||||
normalizeDetail(detail) {
|
normalizeDetail(detail) {
|
||||||
const businessScope = this.splitValue(detail.businessScope);
|
|
||||||
const registeredDetailAddress =
|
const registeredDetailAddress =
|
||||||
detail.registeredDetailAddress ||
|
detail.registeredDetailAddress ||
|
||||||
(detail.registeredRegionName ? '' : detail.registeredAddress) ||
|
(detail.registeredRegionName ? '' : detail.registeredAddress) ||
|
||||||
@@ -4172,10 +4220,14 @@ export default {
|
|||||||
customerKind: detail.customerKind || 'external',
|
customerKind: detail.customerKind || 'external',
|
||||||
invoiceTaxRate: this.normalizeOptionalAmount(detail.invoiceTaxRate),
|
invoiceTaxRate: this.normalizeOptionalAmount(detail.invoiceTaxRate),
|
||||||
registeredCapital: this.normalizeOptionalAmount(detail.registeredCapital),
|
registeredCapital: this.normalizeOptionalAmount(detail.registeredCapital),
|
||||||
|
paidInCapital: this.normalizeOptionalAmount(detail.paidInCapital),
|
||||||
maxCreditLimit: this.normalizeOptionalAmount(detail.maxCreditLimit),
|
maxCreditLimit: this.normalizeOptionalAmount(detail.maxCreditLimit),
|
||||||
applyCreditLimit: this.normalizeOptionalAmount(detail.applyCreditLimit),
|
applyCreditLimit: this.normalizeOptionalAmount(detail.applyCreditLimit),
|
||||||
businessScope,
|
usedCreditLimit: this.normalizeOptionalAmount(detail.usedCreditLimit),
|
||||||
|
businessScope: detail.businessScope || '',
|
||||||
businessTermType: detail.businessTermType || '固定期限',
|
businessTermType: detail.businessTermType || '固定期限',
|
||||||
|
businessStartDate: detail.businessStartDate || '',
|
||||||
|
businessEndDate: detail.businessEndDate || '',
|
||||||
networkFreightPlatform:
|
networkFreightPlatform:
|
||||||
detail.networkFreightPlatform === 0 || detail.networkFreightPlatform === 1
|
detail.networkFreightPlatform === 0 || detail.networkFreightPlatform === 1
|
||||||
? detail.networkFreightPlatform
|
? detail.networkFreightPlatform
|
||||||
@@ -4211,6 +4263,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: '',
|
||||||
@@ -4230,6 +4283,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 => ({
|
||||||
@@ -4268,7 +4325,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 || '';
|
||||||
@@ -4397,16 +4461,21 @@ export default {
|
|||||||
if (archive.businessTermType === '长期') {
|
if (archive.businessTermType === '长期') {
|
||||||
archive.businessEndDate = null;
|
archive.businessEndDate = null;
|
||||||
}
|
}
|
||||||
|
if (!archive.businessStartDate) {
|
||||||
|
archive.businessStartDate = null;
|
||||||
|
}
|
||||||
const invoiceTaxRate = this.normalizeOptionalAmount(archive.invoiceTaxRate);
|
const invoiceTaxRate = this.normalizeOptionalAmount(archive.invoiceTaxRate);
|
||||||
const registeredCapital = this.normalizeOptionalAmount(archive.registeredCapital);
|
const registeredCapital = this.normalizeOptionalAmount(archive.registeredCapital);
|
||||||
|
const paidInCapital = this.normalizeOptionalAmount(archive.paidInCapital);
|
||||||
|
const usedCreditLimit = this.normalizeOptionalAmount(archive.usedCreditLimit);
|
||||||
archive.invoiceTaxRate = invoiceTaxRate === '' ? null : invoiceTaxRate;
|
archive.invoiceTaxRate = invoiceTaxRate === '' ? null : invoiceTaxRate;
|
||||||
archive.registeredCapital = registeredCapital === '' ? null : registeredCapital;
|
archive.registeredCapital = registeredCapital === '' ? null : registeredCapital;
|
||||||
|
archive.paidInCapital = paidInCapital === '' ? null : paidInCapital;
|
||||||
|
archive.usedCreditLimit = usedCreditLimit === '' ? null : usedCreditLimit;
|
||||||
archive.customerType = Array.isArray(archive.customerType)
|
archive.customerType = Array.isArray(archive.customerType)
|
||||||
? archive.customerType.join(',')
|
? archive.customerType.join(',')
|
||||||
: archive.customerType;
|
: archive.customerType;
|
||||||
archive.businessScope = Array.isArray(archive.businessScope)
|
archive.businessScope = String(archive.businessScope || '').trim();
|
||||||
? archive.businessScope.join(',')
|
|
||||||
: archive.businessScope;
|
|
||||||
archive.networkFreightPlatform =
|
archive.networkFreightPlatform =
|
||||||
archive.networkFreightPlatform === 0 || archive.networkFreightPlatform === 1
|
archive.networkFreightPlatform === 0 || archive.networkFreightPlatform === 1
|
||||||
? archive.networkFreightPlatform
|
? archive.networkFreightPlatform
|
||||||
@@ -4437,6 +4506,11 @@ export default {
|
|||||||
return contact;
|
return contact;
|
||||||
})
|
})
|
||||||
.filter(item => item.contactName || item.contactPhone);
|
.filter(item => item.contactName || item.contactPhone);
|
||||||
|
// 主表联系电话改由联系人维护,取第一条有效手机号回填
|
||||||
|
const firstContactPhone = (archive.contacts || [])
|
||||||
|
.map(item => String(item.contactPhone || '').trim())
|
||||||
|
.find(Boolean);
|
||||||
|
archive.contactPhone = firstContactPhone || null;
|
||||||
archive.receiptAccounts = (archive.receiptAccounts || [])
|
archive.receiptAccounts = (archive.receiptAccounts || [])
|
||||||
.map(item => this.normalizeReceipt(item))
|
.map(item => this.normalizeReceipt(item))
|
||||||
.filter(item => item.accountName || item.accountHolderName || item.bankAccount || item.cnapsCode);
|
.filter(item => item.accountName || item.accountHolderName || item.bankAccount || item.cnapsCode);
|
||||||
@@ -4475,16 +4549,29 @@ export default {
|
|||||||
archive.applyCreditLimit = applyCreditLimit === '' ? null : applyCreditLimit;
|
archive.applyCreditLimit = applyCreditLimit === '' ? null : applyCreditLimit;
|
||||||
return archive;
|
return archive;
|
||||||
},
|
},
|
||||||
|
validateContactsRequired(archive = this.archiveForm) {
|
||||||
|
const contacts = (archive.contacts || []).filter(
|
||||||
|
item => String(item.contactName || '').trim() || String(item.contactPhone || '').trim()
|
||||||
|
);
|
||||||
|
if (!contacts.length) {
|
||||||
|
this.$message.warning('请至少维护一条联系人');
|
||||||
|
this.activeTab = 'contacts';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
saveArchive() {
|
saveArchive() {
|
||||||
if (this.kingdeeBlocked) {
|
if (this.kingdeeBlocked) {
|
||||||
this.$message.warning('金蝶审核中,请稍后重新添加');
|
this.$message.warning('金蝶审核中,请稍后重新添加');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.applyFixedOwnerDept();
|
||||||
this.$refs.archiveForm.validate(valid => {
|
this.$refs.archiveForm.validate(valid => {
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
this.$message.warning('请先完整填写必填项');
|
this.$message.warning('请先完整填写必填项');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!this.validateContactsRequired()) return;
|
||||||
submit(this.normalizeArchive()).then(() => {
|
submit(this.normalizeArchive()).then(() => {
|
||||||
this.closeArchive();
|
this.closeArchive();
|
||||||
this.onLoad(this.page);
|
this.onLoad(this.page);
|
||||||
@@ -4497,11 +4584,13 @@ export default {
|
|||||||
this.$message.warning('金蝶审核中,请稍后重新添加');
|
this.$message.warning('金蝶审核中,请稍后重新添加');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.applyFixedOwnerDept();
|
||||||
this.$refs.archiveForm.validate(valid => {
|
this.$refs.archiveForm.validate(valid => {
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
this.$message.warning('请先完整填写必填项');
|
this.$message.warning('请先完整填写必填项');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!this.validateContactsRequired()) return;
|
||||||
const archive = this.normalizeArchive();
|
const archive = this.normalizeArchive();
|
||||||
// 仅提交时落变更记录;保存不记录
|
// 仅提交时落变更记录;保存不记录
|
||||||
archive.recordChange = true;
|
archive.recordChange = true;
|
||||||
@@ -4577,9 +4666,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;
|
||||||
@@ -4600,9 +4711,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;
|
||||||
@@ -4637,6 +4748,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,
|
||||||
@@ -4653,6 +4769,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 => ({
|
||||||
@@ -4797,13 +4924,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 ?? '')
|
||||||
@@ -4984,14 +5122,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)
|
||||||
);
|
);
|
||||||
@@ -5065,7 +5206,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',
|
||||||
@@ -5078,16 +5223,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(() => {});
|
||||||
},
|
},
|
||||||
@@ -5223,6 +5375,7 @@ export default {
|
|||||||
getDetail(row.id).then(res => {
|
getDetail(row.id).then(res => {
|
||||||
const archive = this.normalizeDetail(res.data.data);
|
const archive = this.normalizeDetail(res.data.data);
|
||||||
archive.qualificationAttachments = archive.qualificationAttachments || '';
|
archive.qualificationAttachments = archive.qualificationAttachments || '';
|
||||||
|
if (!this.validateContactsRequired(archive)) return;
|
||||||
if (!this.validateScoreForApproval(archive)) return;
|
if (!this.validateScoreForApproval(archive)) return;
|
||||||
this.$confirm('是否提交客商准入审批?', '提示', {
|
this.$confirm('是否提交客商准入审批?', '提示', {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
@@ -5604,7 +5757,8 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: wrap;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
:deep(.el-date-editor) {
|
:deep(.el-date-editor) {
|
||||||
flex: 0 0 140px;
|
flex: 0 0 140px;
|
||||||
@@ -5612,6 +5766,11 @@ export default {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.business-term-separator {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.el-checkbox) {
|
:deep(.el-checkbox) {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
@@ -5619,6 +5778,10 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.invoice-bank-autocomplete {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.archive-form__address {
|
.archive-form__address {
|
||||||
:deep(.el-cascader) {
|
:deep(.el-cascader) {
|
||||||
flex: 0 0 240px;
|
flex: 0 0 240px;
|
||||||
@@ -5827,6 +5990,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 {
|
||||||
@@ -5846,11 +6017,13 @@ export default {
|
|||||||
|
|
||||||
:deep(.el-input),
|
:deep(.el-input),
|
||||||
:deep(.el-cascader),
|
:deep(.el-cascader),
|
||||||
:deep(.el-select) {
|
:deep(.el-select),
|
||||||
|
:deep(.el-autocomplete) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-cascader .el-input) {
|
:deep(.el-cascader .el-input),
|
||||||
|
:deep(.el-autocomplete .el-input) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+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