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:
@@ -13824,13 +13824,13 @@ export default {
|
||||
}
|
||||
const unavailableWaybills = this.selectionList.filter(
|
||||
row =>
|
||||
this.statusValue(row) !== 'pending' ||
|
||||
!['pending', 'processing', 'running'].includes(this.statusValue(row)) ||
|
||||
row.loadingId ||
|
||||
row.loadingManageId ||
|
||||
row.loadingNo
|
||||
);
|
||||
if (unavailableWaybills.length) {
|
||||
this.$message.warning('公路配载仅支持进行中且未配载的运单');
|
||||
this.$message.warning('公路配载仅支持待执行/进行中且未关联有效配载单的运单');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
<div class="route-node-form">
|
||||
<el-form-item label="途经地" required class="route-address-form-item"
|
||||
><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 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>
|
||||
@@ -111,7 +111,7 @@
|
||||
<div class="route-node-form">
|
||||
<el-form-item label="收货地" required class="route-address-form-item"
|
||||
><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 label="联系人" class="route-contact-form-item"
|
||||
><el-input v-model="form.arrivalContact" :disabled="isBasicRouteReadonly" placeholder="请输入" /></el-form-item>
|
||||
@@ -714,7 +714,7 @@ export default {
|
||||
'msg',
|
||||
'zip',
|
||||
],
|
||||
transports: ['公路运输', '铁路运输', '水路运输', '航空运输'],
|
||||
transports: ['铁路运输', '水路运输', '航空运输', '公路运输'],
|
||||
quantityUnitOptions: [],
|
||||
quantityUnitLoading: false,
|
||||
form: {
|
||||
@@ -798,6 +798,7 @@ export default {
|
||||
methods: {
|
||||
formatContractOptionLabel,
|
||||
defaultQuantityUnit() {
|
||||
if (this.quantityUnitOptions.includes('吨')) return '吨';
|
||||
return this.quantityUnitOptions[0] || '吨';
|
||||
},
|
||||
loadQuantityUnitOptions() {
|
||||
@@ -1164,22 +1165,30 @@ export default {
|
||||
const field = this.addressModel(target);
|
||||
if (!field) return false;
|
||||
const siteCode = String(field.model[field.siteCode] || '').trim();
|
||||
// 地址展示模式由实际选择的数据决定:有站点编码表示非公路站点名称,
|
||||
// 没有站点编码则保留省市区级联,避免切换运输方式时改写已有地址。
|
||||
// 展示模式由已选数据决定:有站点编码则显示站点名,否则省市区;暂不按运输方式强制站点限制。
|
||||
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 = {}) {
|
||||
return row.regionCode || row.districtCode || row.areaCode || row.adcode || '';
|
||||
},
|
||||
openCommonAddress(target) {
|
||||
if (this.isBasicRouteReadonly) return;
|
||||
this.tipEmptyAddressDetail(target);
|
||||
this.addressTarget = target;
|
||||
if (this.addressTransportMode(target) !== 'road') {
|
||||
this.stationQuery = {};
|
||||
this.stationPage.current = 1;
|
||||
this.stationDialogVisible = true;
|
||||
return;
|
||||
}
|
||||
// 暂不按运输方式限制站点:统一走常用地址选择。
|
||||
this.addressQuery = {};
|
||||
this.addressPage.current = 1;
|
||||
this.addressDialogVisible = true;
|
||||
@@ -1279,18 +1288,11 @@ export default {
|
||||
},
|
||||
handleAddressDetailClick(target) {
|
||||
if (this.isBasicRouteReadonly) return;
|
||||
if (this.addressTransportMode(target) !== 'road') {
|
||||
this.openCommonAddress(target);
|
||||
return;
|
||||
}
|
||||
this.tipEmptyAddressDetail(target);
|
||||
this.openAddressMap(target);
|
||||
},
|
||||
openAddressMap(target) {
|
||||
if (this.isBasicRouteReadonly) return;
|
||||
if (this.addressTransportMode(target) !== 'road') {
|
||||
this.$message.info('非公路运输地址不能使用地图选择');
|
||||
return;
|
||||
}
|
||||
const field = this.addressModel(target);
|
||||
if (!field) return;
|
||||
this.mapTarget = target;
|
||||
@@ -1690,7 +1692,11 @@ export default {
|
||||
'',
|
||||
cargoTypePath,
|
||||
quantity: row.quantity || '',
|
||||
quantityUnit: row.quantityUnit || this.defaultQuantityUnit(),
|
||||
// 常用货物单位若为空或「个」,新建总单统一默认「吨」
|
||||
quantityUnit:
|
||||
!row.quantityUnit || String(row.quantityUnit).trim() === '个'
|
||||
? this.defaultQuantityUnit()
|
||||
: row.quantityUnit,
|
||||
packageType: row.packageType || row.package || '',
|
||||
brand: row.brand || '',
|
||||
specification: row.specification || row.spec || '',
|
||||
@@ -1775,17 +1781,21 @@ export default {
|
||||
return formatUpdateUserName(row);
|
||||
},
|
||||
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.departureAddress = route.departureAddress || '';
|
||||
this.form.departureContact = route.departureContact || '';
|
||||
this.form.departurePhone = route.departurePhone || '';
|
||||
this.form.departureSiteCode = '';
|
||||
this.form.arrivalName = route.arrivalName || '';
|
||||
this.form.arrivalAddress = route.arrivalAddress || '';
|
||||
this.form.arrivalContact = route.arrivalContact || '';
|
||||
this.form.arrivalPhone = route.arrivalPhone || '';
|
||||
this.form.arrivalSiteCode = '';
|
||||
this.syncAddressRegionPaths();
|
||||
this.form.finalTransportType = '公路运输';
|
||||
if (!this.form.routes.length) this.addRoute();
|
||||
this.routeDialogVisible = false;
|
||||
},
|
||||
createRoute(index = this.form.routes.length) {
|
||||
|
||||
@@ -1591,8 +1591,8 @@
|
||||
<el-link type="primary" v-if="canCancel(row)" @click="handleAction('cancel', row)">
|
||||
取消
|
||||
</el-link>
|
||||
<el-link type="primary" v-if="canComplete(row)" @click="handleAction('complete', row)">
|
||||
{{ completeActionLabel }}
|
||||
<el-link type="primary" v-if="canComplete(row)" @click="handleCompleteAction(row)">
|
||||
{{ completeActionLabelFor(row) }}
|
||||
</el-link>
|
||||
<el-link type="primary" v-if="canMaintainMileage(row)" @click="openMileageDialog(row)">
|
||||
维护里程
|
||||
@@ -5111,7 +5111,29 @@ export default {
|
||||
return false;
|
||||
}
|
||||
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) {
|
||||
return (
|
||||
@@ -6543,6 +6565,9 @@ export default {
|
||||
if (status === 'completed') {
|
||||
return 'primary';
|
||||
}
|
||||
if (status === 'pending_return') {
|
||||
return 'warning';
|
||||
}
|
||||
return 'warning';
|
||||
},
|
||||
normalizeRow(row, saveAsDraft = false) {
|
||||
@@ -10356,7 +10381,14 @@ export default {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定完成选中的运单?', '提示', {
|
||||
const pendingReturnCount = this.selectionList.filter(
|
||||
row => this.statusValue(row) === 'pending_return'
|
||||
).length;
|
||||
const tip =
|
||||
pendingReturnCount > 0
|
||||
? `确定完成选中的运单?其中 ${pendingReturnCount} 条为待确认回单,确认后将转为已完成。`
|
||||
: '确定完成选中的运单?';
|
||||
this.$confirm(tip, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
@@ -10382,13 +10414,13 @@ export default {
|
||||
}
|
||||
const unavailableWaybills = this.selectionList.filter(
|
||||
row =>
|
||||
!['processing', 'running'].includes(this.statusValue(row)) ||
|
||||
!['pending', 'processing', 'running'].includes(this.statusValue(row)) ||
|
||||
row.loadingId ||
|
||||
row.loadingManageId ||
|
||||
row.loadingNo
|
||||
);
|
||||
if (unavailableWaybills.length) {
|
||||
this.$message.warning('公路配载仅支持进行中且未配载的运单');
|
||||
this.$message.warning('公路配载仅支持待执行/进行中且未关联有效配载单的运单');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
||||
@@ -8,11 +8,17 @@
|
||||
<el-form-item label="变更类型" class="contract-basic-section__change-type">
|
||||
<el-radio-group v-model="form.changeType">
|
||||
<el-radio label="合同信息变更" />
|
||||
<el-radio label="结算规则变更" />
|
||||
<el-radio label="终止合同" />
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<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
|
||||
v-for="item in signTypeOptions"
|
||||
:key="item.value"
|
||||
@@ -30,6 +36,7 @@
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
placeholder="请输入合同名称"
|
||||
:disabled="isSettlementRuleChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="合同类型">
|
||||
@@ -80,6 +87,7 @@
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请输入"
|
||||
:disabled="isSettlementRuleChange"
|
||||
/>
|
||||
<span>至</span>
|
||||
<el-date-picker
|
||||
@@ -88,6 +96,7 @@
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请输入"
|
||||
:disabled="isSettlementRuleChange"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
@@ -95,6 +104,7 @@
|
||||
<el-input
|
||||
v-model="form.paymentDays"
|
||||
placeholder="请输入"
|
||||
:disabled="isSettlementRuleChange"
|
||||
@input="value => positiveIntegerInput('paymentDays', value)"
|
||||
>
|
||||
<template #suffix>天</template>
|
||||
@@ -105,22 +115,36 @@
|
||||
:model-value="contractAmountDisplay"
|
||||
placeholder="请输入"
|
||||
clearable
|
||||
:disabled="isSettlementRuleChange"
|
||||
@input="handleContractAmountInput"
|
||||
@blur="formatContractAmountField"
|
||||
@clear="form.contractAmount = null"
|
||||
/>
|
||||
</el-form-item>
|
||||
<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="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<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 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="1" />
|
||||
</el-select>
|
||||
@@ -129,13 +153,19 @@
|
||||
<el-input
|
||||
v-model="form.invoiceCycle"
|
||||
placeholder="请输入"
|
||||
:disabled="isSettlementRuleChange"
|
||||
@input="value => positiveIntegerInput('invoiceCycle', value)"
|
||||
>
|
||||
<template #suffix>天</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<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
|
||||
v-for="item in settlementCurrencyOptions"
|
||||
:key="item.value"
|
||||
@@ -145,7 +175,12 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="合同格式">
|
||||
<el-select v-model="form.contractFormat" placeholder="请选择合同格式" clearable>
|
||||
<el-select
|
||||
v-model="form.contractFormat"
|
||||
placeholder="请选择合同格式"
|
||||
clearable
|
||||
:disabled="isSettlementRuleChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in contractFormatOptions"
|
||||
:key="item.value"
|
||||
@@ -158,19 +193,30 @@
|
||||
<el-input
|
||||
v-model="form.copyCount"
|
||||
placeholder="请输入"
|
||||
:disabled="isSettlementRuleChange"
|
||||
@input="value => positiveIntegerInput('copyCount', value)"
|
||||
>
|
||||
<template #suffix>份</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<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="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="结算方式">
|
||||
<el-select v-model="form.settlementMode" placeholder="请选择结算方式" clearable>
|
||||
<el-select
|
||||
v-model="form.settlementMode"
|
||||
placeholder="请选择结算方式"
|
||||
clearable
|
||||
:disabled="isSettlementRuleChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in settlementModeOptions"
|
||||
:key="item.value"
|
||||
@@ -191,6 +237,7 @@
|
||||
maxlength="1000"
|
||||
show-word-limit
|
||||
placeholder="请输入合同主要内容"
|
||||
:disabled="isSettlementRuleChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="其他事项说明" class="contract-basic-section__remark">
|
||||
@@ -201,6 +248,7 @@
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
placeholder="请输入其他事项说明"
|
||||
:disabled="isSettlementRuleChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<!-- 风险客商提示:合同已推送合规并留痕高风险/黑名单时展示,变更提交同样要求特批附件 -->
|
||||
@@ -214,6 +262,7 @@
|
||||
title="用印材料"
|
||||
description
|
||||
attachment-type
|
||||
:readonly="isSettlementRuleChange"
|
||||
:attachment-location-options="changeAttachmentLocationOptions"
|
||||
:rows="contractFileRows"
|
||||
:preview="previewAttachment"
|
||||
@@ -224,12 +273,36 @@
|
||||
<section class="change-section">
|
||||
<div class="section-head">
|
||||
<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
|
||||
>
|
||||
</div>
|
||||
<el-radio-group v-model="feeGenerationMode"><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>
|
||||
<el-radio-group v-model="feeGenerationMode"
|
||||
><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 class="change-section">
|
||||
@@ -248,19 +321,106 @@
|
||||
<el-radio :label="0">关闭</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<el-form v-if="settlementRule.autoGenerate === 1" :model="settlementRule" label-position="right" label-width="auto" 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
|
||||
v-if="settlementRule.autoGenerate === 1"
|
||||
:model="settlementRule"
|
||||
label-position="right"
|
||||
label-width="auto"
|
||||
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-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 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="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-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="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>
|
||||
</section>
|
||||
|
||||
@@ -268,17 +428,48 @@
|
||||
<div class="dialog-section-title">
|
||||
付款比例设置
|
||||
<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>
|
||||
</div>
|
||||
<el-table :data="paymentRatioRows" border class="change-table">
|
||||
<el-table-column type="index" label="序号" width="70" />
|
||||
<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"><template #default="{ row }"><el-input v-model="row.remark" maxlength="200" /></template></el-table-column>
|
||||
<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-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"
|
||||
: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-button type="primary" plain @click="addPaymentRatioRow">添加</el-button>
|
||||
<el-button
|
||||
v-if="!isSettlementRuleChange"
|
||||
type="primary"
|
||||
plain
|
||||
@click="addPaymentRatioRow"
|
||||
>添加</el-button
|
||||
>
|
||||
</section>
|
||||
|
||||
<billing-plan-editor
|
||||
@@ -295,6 +486,7 @@
|
||||
attachment-type
|
||||
attachment-location="附件(佐证材料等)"
|
||||
attachment-type-dict-code="contract_supporting_attachment_type"
|
||||
:readonly="isSettlementRuleChange"
|
||||
:attachment-location-options="changeAttachmentLocationOptions"
|
||||
:rows="attachments"
|
||||
:preview="previewAttachment"
|
||||
@@ -510,7 +702,13 @@ export default {
|
||||
.join('、');
|
||||
return `【${parties || '相对方'}】:被列入外部黑名单;涉贪污受贿裁判相关提及方(3年以上及其他);被执行人信息;买卖合同纠纷;劳动纠纷;重大纠纷。提交变更前须上传「特批附件」类型附件`;
|
||||
},
|
||||
isSettlementRuleChange() {
|
||||
return this.form.changeType === '结算规则变更';
|
||||
},
|
||||
changeAttachmentLocationOptions() {
|
||||
if (this.isSettlementRuleChange) {
|
||||
return ['变更材料'];
|
||||
}
|
||||
return ['用印材料', '附件(佐证材料等)', '变更材料'];
|
||||
},
|
||||
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; },
|
||||
handleAttachmentUploadToLocation({ location, rows = [] }) {
|
||||
if (!rows.length) return;
|
||||
if (this.isSettlementRuleChange) {
|
||||
if (location === '变更材料') {
|
||||
this.changeMaterials = [...(this.changeMaterials || []), ...rows];
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (isContractSealMaterialLocation(location)) {
|
||||
this.contractFileRows = [...(this.contractFileRows || []), ...rows];
|
||||
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); },
|
||||
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; },
|
||||
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>
|
||||
|
||||
@@ -635,10 +635,11 @@
|
||||
min-width="100" /><el-table-column
|
||||
prop="quantity"
|
||||
label="数量"
|
||||
min-width="90" /><el-table-column
|
||||
prop="quantityUnit"
|
||||
label="数量单位"
|
||||
min-width="100" /><el-table-column
|
||||
min-width="120"
|
||||
><template #default="{ row }">{{
|
||||
formatCargoQuantityDisplay(row)
|
||||
}}</template></el-table-column
|
||||
><el-table-column
|
||||
prop="materialCode"
|
||||
label="物料编码"
|
||||
min-width="120" /><el-table-column
|
||||
@@ -897,6 +898,16 @@
|
||||
>
|
||||
<div title="配载信息" class="loading-manage-dialog__load-info-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">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="6">
|
||||
@@ -1357,19 +1368,29 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="110" fixed="right">
|
||||
<template #default="{ $index }">
|
||||
<template #default="{ row, $index }">
|
||||
<el-link
|
||||
v-if="!dialogReadonly"
|
||||
v-if="!dialogReadonly && !isSelectedWaybillRemoveLocked(row)"
|
||||
type="danger"
|
||||
@click="removeSelectedWaybill($index)"
|
||||
>删除</el-link
|
||||
>
|
||||
<span
|
||||
v-else-if="!dialogReadonly && isSelectedWaybillRemoveLocked(row)"
|
||||
class="loading-manage-dialog__locked-tip"
|
||||
>已打卡</span
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section-card>
|
||||
|
||||
<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
|
||||
v-for="(node, index) in routeNodes"
|
||||
@@ -1415,8 +1436,9 @@
|
||||
<el-table-column prop="cargoName" 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="quantity" label="数量" min-width="100" />
|
||||
<el-table-column prop="quantityUnit" label="数量单位" min-width="100" />
|
||||
<el-table-column prop="quantity" label="数量" min-width="120">
|
||||
<template #default="{ row }">{{ formatCargoQuantityDisplay(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="materialCode" label="物料编码" min-width="130" />
|
||||
<el-table-column prop="equipmentCode" label="设备编码" min-width="130" />
|
||||
<el-table-column prop="brand" label="品牌" min-width="120" />
|
||||
@@ -1436,7 +1458,7 @@
|
||||
<el-segmented
|
||||
v-model="dialogForm.carrierType"
|
||||
:options="carrierTypeOptions"
|
||||
:disabled="dialogReadonly"
|
||||
:disabled="dialogReadonly || isRunningTaskRequiredLocked"
|
||||
@change="handleCarrierTypeChange"
|
||||
/>
|
||||
</div>
|
||||
@@ -1450,7 +1472,11 @@
|
||||
"
|
||||
clearable
|
||||
filterable
|
||||
:disabled="dialogReadonly || !selectedWaybillRows.length"
|
||||
:disabled="
|
||||
dialogReadonly ||
|
||||
isRunningTaskRequiredLocked ||
|
||||
!selectedWaybillRows.length
|
||||
"
|
||||
:loading="taskCarrierLoading"
|
||||
:placeholder="taskCarrierPlaceholder"
|
||||
@change="handleTaskCarrierChange"
|
||||
@@ -1483,6 +1509,7 @@
|
||||
:fetch-suggestions="fetchDriverSuggestions"
|
||||
:loading="driverLoading"
|
||||
placeholder="请输入"
|
||||
:disabled="dialogReadonly || isRunningSelfDriverLocked"
|
||||
@select="item => handleDriverSuggestionSelect('dialog', item)"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -1490,13 +1517,28 @@
|
||||
label="手机号"
|
||||
: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 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 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 v-if="dialogForm.carrierType !== '承运商'" label="押运人">
|
||||
<el-autocomplete
|
||||
@@ -1506,11 +1548,17 @@
|
||||
:fetch-suggestions="fetchEscortSuggestions"
|
||||
:loading="escortLoading"
|
||||
placeholder="请输入"
|
||||
:disabled="dialogReadonly"
|
||||
@select="item => handleEscortSuggestionSelect('dialog', item)"
|
||||
/>
|
||||
</el-form-item>
|
||||
<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 label="里程(km)">
|
||||
<el-input
|
||||
@@ -1528,6 +1576,7 @@
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请选择"
|
||||
:disabled="dialogReadonly"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="预计完成日期">
|
||||
@@ -1537,6 +1586,7 @@
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请选择"
|
||||
:disabled="dialogReadonly"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" class="loading-manage-dialog__span-2">
|
||||
@@ -1547,6 +1597,7 @@
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
placeholder="请输入,500字以内"
|
||||
:disabled="dialogReadonly"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
@@ -1561,11 +1612,17 @@
|
||||
</template>
|
||||
<template v-else>
|
||||
<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 type="primary" :loading="dialogSaving === 'submit'" @click="submitLoading">
|
||||
{{ dialogMode === 'reassign' ? '确认派单' : '确认' }}
|
||||
{{ dialogMode === 'reassign' ? '确认派单' : isRunningEdit ? '保存' : '确认' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</div>
|
||||
@@ -2107,6 +2164,8 @@ export default {
|
||||
routeNodes: [],
|
||||
cargoRows: [],
|
||||
routeDragIndex: -1,
|
||||
/** 进行中编辑:子运单打卡记录映射 waybillId -> records[] */
|
||||
waybillPunchMap: {},
|
||||
transportTypeOptions: [],
|
||||
projectOptions: [],
|
||||
customerOptions: [],
|
||||
@@ -2170,6 +2229,23 @@ export default {
|
||||
dialogReadonly() {
|
||||
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() {
|
||||
const records = Array.isArray(this.punchRecords) ? this.punchRecords : [];
|
||||
@@ -2913,6 +2989,7 @@ export default {
|
||||
if (status === 'running') {
|
||||
return [
|
||||
{ type: 'view', label: '详情' },
|
||||
{ type: 'edit', label: '编辑' },
|
||||
{ type: 'complete', label: '完成' },
|
||||
];
|
||||
}
|
||||
@@ -3014,7 +3091,12 @@ export default {
|
||||
query: {
|
||||
mode,
|
||||
...(mode === 'edit' && row?.id ? { id: row.id } : {}),
|
||||
name: mode === 'edit' ? '编辑配载管理' : '新增配载管理',
|
||||
name:
|
||||
mode === 'edit'
|
||||
? row?.businessStatus === 'running'
|
||||
? '编辑进行中配载'
|
||||
: '编辑配载管理'
|
||||
: '新增配载管理',
|
||||
},
|
||||
});
|
||||
return;
|
||||
@@ -3037,6 +3119,9 @@ export default {
|
||||
this.dialogForm.loadingSubNos
|
||||
);
|
||||
this.syncProcessWaybillOptions();
|
||||
if (this.isRunningEdit) {
|
||||
await this.refreshWaybillPunchMap();
|
||||
}
|
||||
this.restoreRouteAndCargo();
|
||||
this.restoreRouteChangeRecords();
|
||||
this.loadPunchRecords();
|
||||
@@ -3071,6 +3156,8 @@ export default {
|
||||
this.taskCarrierLoading = false;
|
||||
this.routeNodes = [];
|
||||
this.cargoRows = [];
|
||||
this.routeDragIndex = -1;
|
||||
this.waybillPunchMap = {};
|
||||
this.candidatePage.currentPage = 1;
|
||||
this.candidatePage.total = 0;
|
||||
this.candidateSearchVisible = false;
|
||||
@@ -3435,20 +3522,144 @@ export default {
|
||||
const statusName = String(row.businessStatusName || row.statusName || '');
|
||||
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 = {}) {
|
||||
return Boolean(node?.locked);
|
||||
},
|
||||
syncRouteNodeLockedFlags(nodes = [], rows = this.selectedWaybillRows) {
|
||||
return (nodes || []).map(node => {
|
||||
const locked = (node.tags || []).some(tag => {
|
||||
const match = String(tag).match(/^(装|卸)(\d+)$/);
|
||||
if (!match) return false;
|
||||
const row = rows[Number(match[2]) - 1];
|
||||
return row ? this.isWaybillCompleted(row) : false;
|
||||
});
|
||||
const locked = (node.tags || []).some(tag => this.isRouteNodeTagLocked(tag, rows));
|
||||
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) {
|
||||
if (this.isWaybillCompleted(row)) return;
|
||||
this.routeChangeMapTarget = { row, field };
|
||||
@@ -3701,12 +3912,10 @@ export default {
|
||||
try {
|
||||
const query = {
|
||||
...this.buildQueryParams(this.candidateQuery),
|
||||
// 待执行(需接单未接)与进行中(无需接单或已接单)均可配载
|
||||
businessStatus: 'pending,running',
|
||||
onlyUnassignedLoading: 1,
|
||||
// 与后端 LoadableAdmissionSupport 一致:公路 + 待执行/进行中 + 未关联有效配载单
|
||||
loadableOnly: 1,
|
||||
// 仅查当前账户所属组织及子组织下的运单
|
||||
allDept: 0,
|
||||
...(this.dialogMode === 'add' ? { transportType: 'road' } : {}),
|
||||
};
|
||||
const transportType = this.getCandidateTransportTypeQuery(query.transportType);
|
||||
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(
|
||||
this.selectedWaybillRows.map(row => String(row.id || row.waybillNo))
|
||||
);
|
||||
@@ -3777,8 +4013,12 @@ export default {
|
||||
exists.add(key);
|
||||
}
|
||||
});
|
||||
const prevRouteNodes = [...this.routeNodes];
|
||||
this.selectedWaybillRows = nextRows;
|
||||
this.rebuildSummaryFromWaybills();
|
||||
if (this.isRunningEdit) {
|
||||
await this.refreshWaybillPunchMap();
|
||||
}
|
||||
this.rebuildSummaryFromWaybills(true, prevRouteNodes);
|
||||
await this.loadTaskCarrierOptions();
|
||||
this.$refs.candidateTableRef?.clearSelection?.();
|
||||
this.candidateSelection = [];
|
||||
@@ -3790,11 +4030,23 @@ export default {
|
||||
await this.addWaybillRows(this.candidateSelection);
|
||||
},
|
||||
removeSelectedWaybill(index) {
|
||||
const row = this.selectedWaybillRows[index];
|
||||
if (this.isSelectedWaybillRemoveLocked(row)) {
|
||||
ElMessage.warning('已打卡的子运单不可移除');
|
||||
return;
|
||||
}
|
||||
this.selectedWaybillRows.splice(index, 1);
|
||||
if (this.isRunningEdit) {
|
||||
this.refreshWaybillPunchMap().then(() => {
|
||||
this.rebuildSummaryFromWaybills();
|
||||
this.loadTaskCarrierOptions();
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.rebuildSummaryFromWaybills();
|
||||
this.loadTaskCarrierOptions();
|
||||
},
|
||||
rebuildSummaryFromWaybills(rebuildRoute = true) {
|
||||
rebuildSummaryFromWaybills(rebuildRoute = true, prevRouteNodes = null) {
|
||||
const rows = this.selectedWaybillRows;
|
||||
this.dialogForm.loadingSubNos = rows
|
||||
.map(row => row.waybillNo)
|
||||
@@ -3819,7 +4071,13 @@ export default {
|
||||
this.dialogForm.transportType = rows.find(row => row.transportType)?.transportType || '';
|
||||
}
|
||||
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);
|
||||
// 编辑回填阶段 routeNodes 尚未恢复时跳过,避免清空已有发到货地
|
||||
@@ -3839,7 +4097,9 @@ export default {
|
||||
if (locked) node.locked = true;
|
||||
};
|
||||
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;
|
||||
appendNode(
|
||||
departure,
|
||||
@@ -3849,7 +4109,9 @@ export default {
|
||||
);
|
||||
});
|
||||
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;
|
||||
appendNode(
|
||||
arrival,
|
||||
@@ -3901,7 +4163,9 @@ export default {
|
||||
if (
|
||||
originalLockedKeys.some((key, lockedIndex) => key && key !== nextLockedKeys[lockedIndex])
|
||||
) {
|
||||
ElMessage.warning('已完成运单对应的路线不可调整');
|
||||
ElMessage.warning(
|
||||
this.isRunningEdit ? '已打卡地点不可调整,新地址只能在未打卡节点间拖拽' : '已完成运单对应的路线不可调整'
|
||||
);
|
||||
this.routeDragIndex = -1;
|
||||
return;
|
||||
}
|
||||
@@ -3941,14 +4205,28 @@ export default {
|
||||
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 = {}) {
|
||||
return sanitizeCargoRow({
|
||||
projectName: item.projectName || waybill.projectName || '',
|
||||
cargoName: item.cargoName || item.goodsName || waybill.cargoName || '',
|
||||
cargoType: item.cargoType || item.goodsType || waybill.cargoType || '',
|
||||
packageType: item.packageType || item.package || '',
|
||||
weight: item.weight || item.cargoWeight || '',
|
||||
volume: item.volume || item.cargoVolume || '',
|
||||
quantity: item.quantity || item.cargoQuantity || waybill.quantity || '',
|
||||
quantityUnit:
|
||||
item.quantityUnit ||
|
||||
@@ -4145,15 +4423,15 @@ export default {
|
||||
try {
|
||||
const payload = {
|
||||
...this.normalizePayload(),
|
||||
// 确认生成正式配载单(待执行),覆盖草稿状态
|
||||
businessStatus: 'pending',
|
||||
// 进行中编辑保持 running;新建/草稿确认由后端按项目是否需接单写入 pending/running
|
||||
...(this.isRunningEdit ? { businessStatus: 'running' } : {}),
|
||||
};
|
||||
if (this.dialogMode === 'reassign') {
|
||||
await loadingApi.reassign(payload);
|
||||
ElMessage.success('派单成功');
|
||||
} else {
|
||||
await loadingApi.submit(payload);
|
||||
ElMessage.success('确认成功');
|
||||
ElMessage.success(this.isRunningEdit ? '保存成功' : '确认成功');
|
||||
}
|
||||
sessionStorage.setItem(loadingManageListRefreshKey, '1');
|
||||
this.closeLoadingDialog();
|
||||
@@ -4715,6 +4993,30 @@ export default {
|
||||
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 {
|
||||
padding: 0;
|
||||
margin-bottom: 0;
|
||||
@@ -4787,6 +5089,16 @@ export default {
|
||||
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 {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
|
||||
@@ -955,7 +955,7 @@ import {
|
||||
getList as getCustomerArchiveList,
|
||||
getDetail as getCustomerArchiveDetail,
|
||||
} 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 getBizDictionary } from '@/api/system/dictbiz';
|
||||
import { getList as getUserList } from '@/api/system/user';
|
||||
@@ -1596,6 +1596,7 @@ export default {
|
||||
projectType: type === 'majorSupplement' ? '重大项目' : '普通项目',
|
||||
});
|
||||
this.fillDefaultUsers();
|
||||
this.fillDefaultBusinessDept();
|
||||
return;
|
||||
}
|
||||
if (!id) return;
|
||||
@@ -2032,6 +2033,7 @@ export default {
|
||||
.filter(Boolean);
|
||||
this.syncSelectedCustomerOptions();
|
||||
this.ensureCurrentPlatformOption();
|
||||
this.ensureCurrentBusinessDeptOption();
|
||||
},
|
||||
fillDefaultUsers() {
|
||||
const userId = this.userInfo?.userId || this.userInfo?.id || '';
|
||||
@@ -2050,6 +2052,25 @@ export default {
|
||||
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) {
|
||||
return (
|
||||
row[`${role}RealName`] ||
|
||||
@@ -2632,17 +2653,61 @@ export default {
|
||||
this.$refs.projectForm?.validateField('projectType');
|
||||
},
|
||||
loadDeptOptions() {
|
||||
getDeptTree().then(res => {
|
||||
const deptTree = res.data.data || [];
|
||||
// 新增/编辑/重大补录/项目变更均可选择业务部门,排除外部组织
|
||||
const businessDeptTree = ['add', 'edit', 'majorSupplement', 'change'].includes(this.dialogType)
|
||||
? this.excludeExternalOrganization(deptTree)
|
||||
: deptTree;
|
||||
this.deptOptions = this.flattenDept(businessDeptTree);
|
||||
this.businessDeptTreeOptions = this.toDeptCascaderOptions(businessDeptTree);
|
||||
});
|
||||
Promise.all([getDeptTree(), getBusinessDeptSelect()])
|
||||
.then(([treeRes, bizRes]) => {
|
||||
const deptTree = this.excludeExternalOrganization(treeRes.data.data || []);
|
||||
const bizList = bizRes.data.data || [];
|
||||
const bizIds = new Set(
|
||||
bizList
|
||||
.map(item => (item.id == null || item.id === '' ? '' : String(item.id)))
|
||||
.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();
|
||||
},
|
||||
/**
|
||||
* 剪枝组织树:只保留业务部门节点及其祖先路径。
|
||||
*/
|
||||
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() {
|
||||
getPlatformCompanySelect().then(res => {
|
||||
const list = res.data.data || [];
|
||||
@@ -2686,12 +2751,22 @@ export default {
|
||||
return result;
|
||||
}, []);
|
||||
},
|
||||
toDeptCascaderOptions(list = []) {
|
||||
return list.map(item => ({
|
||||
label: item.title || item.deptName || item.name,
|
||||
value: item.id,
|
||||
children: item.children?.length ? this.toDeptCascaderOptions(item.children) : undefined,
|
||||
}));
|
||||
toDeptCascaderOptions(list = [], bizIds = null) {
|
||||
return list.map(item => {
|
||||
const value = item.id == null || item.id === '' ? item.value : item.id;
|
||||
const option = {
|
||||
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) {
|
||||
return list.reduce((result, item) => {
|
||||
|
||||
@@ -203,7 +203,11 @@
|
||||
@click="openBatchDialog(row)"
|
||||
>更换运单批次</el-link
|
||||
><el-link
|
||||
v-if="(row.processStatus !== '处理完成' || row.auditStatus === '待审核') && row.auditStatus !== '审核驳回'"
|
||||
v-if="
|
||||
row.processStatus !== '处理失败' &&
|
||||
(row.processStatus !== '处理完成' || row.auditStatus === '待审核') &&
|
||||
row.auditStatus !== '审核驳回'
|
||||
"
|
||||
type="primary"
|
||||
@click="openBatchDialog(row)"
|
||||
>更换运单批次</el-link
|
||||
@@ -320,7 +324,7 @@
|
||||
<el-form-item label="执行凭证" required
|
||||
><el-upload
|
||||
action="#"
|
||||
accept=".zip,.7z"
|
||||
accept=".zip"
|
||||
:auto-upload="false"
|
||||
:limit="1"
|
||||
:on-change="selectVoucherFile"
|
||||
@@ -332,7 +336,7 @@
|
||||
>上传</el-button
|
||||
><template #tip
|
||||
><span class="voucher-manage-page__upload-tip"
|
||||
>请上传车辆的执行凭证压缩包,支持zip、7z格式。 上传附件大于50M时为超大附件,确认提交后可在“批量补录->上传任务”中查看上传进度。</span
|
||||
>请上传车辆的执行凭证压缩包,仅支持 zip 格式;无法解析的文件将标记为「处理失败」,可重新上传。上传附件大于50M时为超大附件,确认提交后可在“批量补录->上传任务”中查看上传进度。</span
|
||||
></template
|
||||
></el-upload
|
||||
><el-progress
|
||||
@@ -549,7 +553,7 @@
|
||||
ref="resumeFileInput"
|
||||
class="voucher-manage-page__hidden-file"
|
||||
type="file"
|
||||
accept=".zip,.7z"
|
||||
accept=".zip"
|
||||
@change="resumeFileSelected"
|
||||
/>
|
||||
</template>
|
||||
@@ -998,6 +1002,12 @@ const uploadVoucher = async file => {
|
||||
};
|
||||
const selectVoucherFile = file => {
|
||||
if (!file?.raw) return;
|
||||
const fileName = String(file.raw.name || '').toLowerCase();
|
||||
if (!fileName.endsWith('.zip')) {
|
||||
ElMessage.warning('仅支持 zip 压缩包,无法解析的文件将标记为处理失败');
|
||||
clearFile();
|
||||
return;
|
||||
}
|
||||
selectedVoucherFile.value = file.raw;
|
||||
editing.fileName = file.raw.name;
|
||||
editing.fileUrl = '';
|
||||
|
||||
Reference in New Issue
Block a user