✨ MK公开页、预结算体验优化、地图选址与认证密码规则完善
This commit is contained in:
@@ -162,9 +162,9 @@
|
||||
v-model="row.billingUnit"
|
||||
clearable
|
||||
filterable
|
||||
:disabled="!row.billingElement"
|
||||
:disabled="!canSelectBillingUnit(row)"
|
||||
:loading="unitLoading"
|
||||
:placeholder="row.billingElement ? '请选择' : '请先选择计费要素'"
|
||||
:placeholder="billingUnitPlaceholder(row)"
|
||||
><el-option
|
||||
v-for="item in unitOptionsFor(row)"
|
||||
:key="item.id || item.value"
|
||||
@@ -381,11 +381,18 @@ import { getDictionary } from '@/api/system/dictbiz';
|
||||
import SectionCard from '@/components/section-card/main.vue';
|
||||
|
||||
const clone = value => JSON.parse(JSON.stringify(value));
|
||||
/** 计费要素与计量单位维度的对应关系 */
|
||||
/** 计费要素与计量单位维度的对应关系(这些要素可选计费单位) */
|
||||
const BILLING_ELEMENT_DIMENSION_MAP = {
|
||||
按重量: '重量',
|
||||
按体积: '体积',
|
||||
按车辆: '数量',
|
||||
按数量: '数量',
|
||||
};
|
||||
const BILLING_UNIT_SELECTABLE_ELEMENTS = Object.keys(BILLING_ELEMENT_DIMENSION_MAP);
|
||||
/** 固定计费单位(不可编辑) */
|
||||
const FIXED_BILLING_UNIT_MAP = {
|
||||
按里程: '公里',
|
||||
'按吨·公里': '吨公里',
|
||||
'固定金额(整单一口价)': '单',
|
||||
};
|
||||
const defaultRule = () => ({
|
||||
feeType: '',
|
||||
@@ -436,7 +443,6 @@ export default {
|
||||
billingElements: [
|
||||
'按重量',
|
||||
'按体积',
|
||||
'按车辆',
|
||||
'按里程',
|
||||
'按吨·公里',
|
||||
'固定金额(整单一口价)',
|
||||
@@ -445,7 +451,6 @@ export default {
|
||||
typeMap: {
|
||||
按重量: ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
|
||||
按体积: ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
|
||||
按车辆: ['固定单价'],
|
||||
按里程: ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
|
||||
'按吨·公里': ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
|
||||
'固定金额(整单一口价)': ['固定一口价'],
|
||||
@@ -560,6 +565,7 @@ export default {
|
||||
next.limitRanges = next.limitRanges.map(item => ({ ...item, minimumBillingWeight: '' }));
|
||||
this.syncLegacyLimit(next);
|
||||
next.matchCondition = { ...defaultRule().matchCondition, ...(rule.matchCondition || {}) };
|
||||
this.syncBillingUnit(next);
|
||||
return next;
|
||||
},
|
||||
normalizeRanges(row) {
|
||||
@@ -651,7 +657,26 @@ export default {
|
||||
measurementDimension(billingElement) {
|
||||
return BILLING_ELEMENT_DIMENSION_MAP[billingElement] || '';
|
||||
},
|
||||
fixedBillingUnit(billingElement) {
|
||||
return FIXED_BILLING_UNIT_MAP[String(billingElement || '').trim()] || '';
|
||||
},
|
||||
canSelectBillingUnit(row) {
|
||||
return BILLING_UNIT_SELECTABLE_ELEMENTS.includes(String(row?.billingElement || '').trim());
|
||||
},
|
||||
billingUnitPlaceholder(row) {
|
||||
if (!row?.billingElement) return '请先选择计费要素';
|
||||
if (this.fixedBillingUnit(row.billingElement)) return this.fixedBillingUnit(row.billingElement);
|
||||
if (!this.canSelectBillingUnit(row)) return '当前计费要素无需选择';
|
||||
return '请选择';
|
||||
},
|
||||
unitOptionsFor(row) {
|
||||
const fixedUnit = this.fixedBillingUnit(row?.billingElement);
|
||||
if (fixedUnit) {
|
||||
return [{ id: fixedUnit, label: fixedUnit, value: fixedUnit }];
|
||||
}
|
||||
if (!this.canSelectBillingUnit(row)) {
|
||||
return [];
|
||||
}
|
||||
const dimension = this.measurementDimension(row?.billingElement);
|
||||
if (dimension) {
|
||||
return this.measurementUnits
|
||||
@@ -663,15 +688,18 @@ export default {
|
||||
}))
|
||||
.filter(item => item.value);
|
||||
}
|
||||
return (this.unitOptions || [])
|
||||
.map(item => ({
|
||||
id: item.id || item.dictKey || item.dictValue,
|
||||
label: item.dictValue,
|
||||
value: item.dictValue,
|
||||
}))
|
||||
.filter(item => item.value);
|
||||
return [];
|
||||
},
|
||||
syncBillingUnit(row) {
|
||||
const fixedUnit = this.fixedBillingUnit(row?.billingElement);
|
||||
if (fixedUnit) {
|
||||
row.billingUnit = fixedUnit;
|
||||
return;
|
||||
}
|
||||
if (!this.canSelectBillingUnit(row)) {
|
||||
row.billingUnit = '';
|
||||
return;
|
||||
}
|
||||
const options = this.unitOptionsFor(row);
|
||||
if (!options.some(item => String(item.value) === String(row.billingUnit || ''))) {
|
||||
row.billingUnit = '';
|
||||
@@ -892,7 +920,6 @@ export default {
|
||||
['taxRate', '税率'],
|
||||
['billingElement', '计费要素'],
|
||||
['billingType', '计费类型'],
|
||||
['billingUnit', '计费单位'],
|
||||
];
|
||||
for (const [i, row] of this.draft.rules.entries()) {
|
||||
const empty = required.find(
|
||||
@@ -902,6 +929,15 @@ export default {
|
||||
this.$message.warning(`第${i + 1}行${empty[1]}不能为空`);
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
this.canSelectBillingUnit(row) &&
|
||||
(row.billingUnit === undefined ||
|
||||
row.billingUnit === null ||
|
||||
String(row.billingUnit).trim() === '')
|
||||
) {
|
||||
this.$message.warning(`第${i + 1}行计费单位不能为空`);
|
||||
return false;
|
||||
}
|
||||
const feeItem = String(row.feeItem).trim();
|
||||
if (feeItemSet.has(feeItem)) {
|
||||
this.$message.warning(`费用项“${feeItem}”不能重复`);
|
||||
|
||||
@@ -1899,11 +1899,11 @@
|
||||
label-width="auto"
|
||||
class="business-crud-page__module-form business-crud-page__module-form--three"
|
||||
>
|
||||
<el-form-item label="账单起始日期" required :error="settlementRuleErrors.billStartDate">
|
||||
<el-form-item label="账单起始日" required :error="settlementRuleErrors.billStartDate">
|
||||
<el-date-picker
|
||||
v-model="settlementRuleForm.billStartDate"
|
||||
type="date"
|
||||
placeholder="请选择账单起始日期"
|
||||
placeholder="请选择账单起始日"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
:disabled="dialogReadonly"
|
||||
@@ -7784,8 +7784,8 @@ export default {
|
||||
const rule = this.normalizeSettlementRule(source);
|
||||
this.settlementRuleErrors = {};
|
||||
if (this.isBillingFieldEmpty(rule.billStartDate)) {
|
||||
this.settlementRuleErrors = { billStartDate: '请选择账单起始日期' };
|
||||
this.$message.warning(`${label}${label ? ':' : ''}请选择账单起始日期`);
|
||||
this.settlementRuleErrors = { billStartDate: '请选择账单起始日' };
|
||||
this.$message.warning(`${label}${label ? ':' : ''}请选择账单起始日`);
|
||||
return false;
|
||||
}
|
||||
if (this.isBillingFieldEmpty(rule.settlementType)) {
|
||||
|
||||
@@ -103,8 +103,13 @@
|
||||
<div class="contract-attachment-upload-dialog__body">
|
||||
<div class="contract-attachment-upload-dialog__field">
|
||||
<span class="contract-attachment-upload-dialog__label">附件位置</span>
|
||||
<el-select :model-value="attachmentLocation" disabled style="width: 100%">
|
||||
<el-option :label="attachmentLocation" :value="attachmentLocation" />
|
||||
<el-select v-model="uploadDialogLocation" placeholder="请选择附件位置" style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in resolvedLocationOptions"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="contract-attachment-upload-dialog__field">
|
||||
@@ -273,15 +278,17 @@ export default {
|
||||
markApprovedOnUpload: Boolean,
|
||||
useUploadDialog: Boolean,
|
||||
attachmentLocation: { type: String, default: '合同文件' },
|
||||
attachmentLocationOptions: { type: Array, default: null },
|
||||
preview: { type: Function, default: null },
|
||||
},
|
||||
emits: ['update:rows'],
|
||||
emits: ['update:rows', 'upload-to-location'],
|
||||
data() {
|
||||
return {
|
||||
selected: [],
|
||||
attachmentFileTypes,
|
||||
contractAttachmentTypeOptions: [...defaultContractAttachmentTypeOptions],
|
||||
uploadDialogVisible: false,
|
||||
uploadDialogLocation: '合同文件',
|
||||
uploadDialogType: resolveOtherAttachmentType(defaultContractAttachmentTypeOptions),
|
||||
uploadDialogFiles: [],
|
||||
uploadDialogFileList: [],
|
||||
@@ -300,6 +307,11 @@ export default {
|
||||
defaultAttachmentType() {
|
||||
return resolveOtherAttachmentType(this.contractAttachmentTypeOptions);
|
||||
},
|
||||
resolvedLocationOptions() {
|
||||
const options = (this.attachmentLocationOptions || []).filter(Boolean);
|
||||
if (options.length) return options;
|
||||
return ['合同文件', '其它附件'];
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.loadAttachmentTypeOptions();
|
||||
@@ -340,6 +352,10 @@ export default {
|
||||
this.update([...this.rows]);
|
||||
},
|
||||
openUploadDialog() {
|
||||
const options = this.resolvedLocationOptions;
|
||||
this.uploadDialogLocation = options.includes(this.attachmentLocation)
|
||||
? this.attachmentLocation
|
||||
: options[0] || this.attachmentLocation;
|
||||
this.uploadDialogType = this.defaultAttachmentType;
|
||||
this.uploadDialogFiles = [];
|
||||
this.uploadDialogFileList = [];
|
||||
@@ -348,6 +364,7 @@ export default {
|
||||
resetUploadDialog() {
|
||||
this.uploadDialogFiles = [];
|
||||
this.uploadDialogFileList = [];
|
||||
this.uploadDialogLocation = this.attachmentLocation;
|
||||
this.uploadDialogType = this.defaultAttachmentType;
|
||||
},
|
||||
beforeUpload(file) {
|
||||
@@ -416,6 +433,10 @@ export default {
|
||||
this.$message.warning('请先上传附件');
|
||||
return;
|
||||
}
|
||||
if (!this.uploadDialogLocation) {
|
||||
this.$message.warning('请选择附件位置');
|
||||
return;
|
||||
}
|
||||
if (!this.uploadDialogType) {
|
||||
this.$message.warning('请选择附件类型');
|
||||
return;
|
||||
@@ -430,7 +451,14 @@ export default {
|
||||
uploadTime: row.uploadTime || uploadTime,
|
||||
...(this.markApprovedOnUpload ? { approved: true } : {}),
|
||||
}));
|
||||
this.update([...(this.rows || []), ...appended]);
|
||||
if (this.uploadDialogLocation === this.attachmentLocation) {
|
||||
this.update([...(this.rows || []), ...appended]);
|
||||
} else {
|
||||
this.$emit('upload-to-location', {
|
||||
location: this.uploadDialogLocation,
|
||||
rows: appended,
|
||||
});
|
||||
}
|
||||
this.uploadDialogVisible = false;
|
||||
},
|
||||
handleChange(rows) {
|
||||
|
||||
@@ -1927,7 +1927,8 @@
|
||||
v-if="!waybillIsCarrier(detailRow)"
|
||||
class="waybill-manage-page__waybill-detail-field"
|
||||
>
|
||||
<span>里程(km)</span><strong>{{ detailRow.mileage || '-' }}</strong>
|
||||
<span>里程(km)</span
|
||||
><strong>{{ normalizeMileageValue(detailRow.mileage) || '-' }}</strong>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="waybillIsNonRoadTransport(detailRow)">
|
||||
@@ -2315,7 +2316,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="detailPageLocked" class="waybill-manage-page__detail-footer">
|
||||
<div v-if="detailPageLocked && !isPublicWaybillView" class="waybill-manage-page__detail-footer">
|
||||
<el-button type="primary" @click="closeDetail">关闭</el-button>
|
||||
</div>
|
||||
<template v-if="!detailPageLocked" #footer>
|
||||
@@ -3122,6 +3123,7 @@ import {
|
||||
getVoucherImages as getProcessConfigVoucherImages,
|
||||
} from '@/api/business/process-config';
|
||||
import { getPunchRecords as getWaybillPunchRecords, locateVehicle, trackVehicle } from '@/api/business/waybill-manage';
|
||||
import { getMkPublicDetail } from '@/api/mk-process';
|
||||
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
||||
import { getDictionary } from '@/api/system/dictbiz';
|
||||
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
||||
@@ -3646,6 +3648,7 @@ export default {
|
||||
this.formPageLocked = this.isStandaloneWaybillFormPage;
|
||||
this.detailPageLocked = this.isStandaloneWaybillDetailPage;
|
||||
this.formModeLocked = this.$route.query.mode || '';
|
||||
if (this.isPublicWaybillView) return;
|
||||
if (this.config.enableAllDept && this.isAdmin) {
|
||||
this.allDept = 1;
|
||||
}
|
||||
@@ -3709,8 +3712,14 @@ export default {
|
||||
isStandaloneWaybillFormPage() {
|
||||
return this.isStandaloneWaybillPage && ['add', 'edit'].includes(this.$route.query.mode);
|
||||
},
|
||||
isPublicWaybillView() {
|
||||
return this.$route.path === '/business/waybill-manage/public-view';
|
||||
},
|
||||
isStandaloneWaybillDetailPage() {
|
||||
return this.standaloneDetailPage && this.$route.path === standaloneWaybillDetailRoute;
|
||||
return (
|
||||
(this.standaloneDetailPage && this.$route.path === standaloneWaybillDetailRoute) ||
|
||||
this.isPublicWaybillView
|
||||
);
|
||||
},
|
||||
// 容器形态读实例创建时锁定的标志,不随 $route 翻转(详见 data 中 formPageLocked 的说明)
|
||||
// 当前路由是否仍属于本实例(tab.js 按 fullPath 建实例,一个实例只对应一个 path)。
|
||||
@@ -4591,14 +4600,19 @@ export default {
|
||||
this.waybillVoucherFolder = null;
|
||||
this.waybillVoucherFolderView = false;
|
||||
this.waybillVoucherImagesLoaded = false;
|
||||
const request =
|
||||
typeof this.api.getDetail === 'function'
|
||||
const request = this.isPublicWaybillView
|
||||
? getMkPublicDetail('waybill-manage', row.id)
|
||||
: typeof this.api.getDetail === 'function'
|
||||
? this.api.getDetail(row.id)
|
||||
: Promise.resolve({ data: { data: row } });
|
||||
request
|
||||
.then(res => {
|
||||
const detail = res?.data?.data || res?.data || row;
|
||||
this.detailRow = detail;
|
||||
if (this.isPublicWaybillView) {
|
||||
this.applyFormDetail(detail);
|
||||
return;
|
||||
}
|
||||
this.loadWaybillPunchRecords();
|
||||
this.loadWaybillVoucherImages();
|
||||
if (detail.contractId) {
|
||||
@@ -8560,7 +8574,7 @@ export default {
|
||||
};
|
||||
this.mileageForm = {
|
||||
id: row.id,
|
||||
mileage: row.mileage === null || row.mileage === undefined ? '' : String(row.mileage),
|
||||
mileage: this.normalizeMileageValue(row.mileage),
|
||||
mileageRemark: row.mileageRemark || '',
|
||||
};
|
||||
this.$nextTick(() => this.$refs.mileageFormRef?.clearValidate());
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
type="date"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="YYYY-MM-DD"
|
||||
placeholder="请输入"
|
||||
/>
|
||||
<span>至</span>
|
||||
<el-date-picker
|
||||
@@ -78,7 +78,7 @@
|
||||
type="date"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="YYYY-MM-DD"
|
||||
placeholder="请输入"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
@@ -92,12 +92,12 @@
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="合同金额">
|
||||
<el-input-number
|
||||
v-model="form.contractAmount"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
<el-input
|
||||
:model-value="contractAmountDisplay"
|
||||
placeholder="请输入"
|
||||
clearable
|
||||
@input="handleContractAmountInput"
|
||||
@clear="form.contractAmount = null"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否范本">
|
||||
@@ -190,9 +190,11 @@
|
||||
description
|
||||
attachment-type
|
||||
use-upload-dialog
|
||||
:attachment-location-options="changeAttachmentLocationOptions"
|
||||
:rows="contractFileRows"
|
||||
:preview="previewAttachment"
|
||||
@update:rows="contractFileRows = $event"
|
||||
@upload-to-location="handleAttachmentUploadToLocation"
|
||||
/>
|
||||
|
||||
<section class="change-section">
|
||||
@@ -218,7 +220,7 @@
|
||||
</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-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>
|
||||
@@ -258,9 +260,11 @@
|
||||
attachment-type
|
||||
use-upload-dialog
|
||||
attachment-location="其它附件"
|
||||
:attachment-location-options="changeAttachmentLocationOptions"
|
||||
:rows="attachments"
|
||||
:preview="previewAttachment"
|
||||
@update:rows="attachments = $event"
|
||||
@upload-to-location="handleAttachmentUploadToLocation"
|
||||
/>
|
||||
|
||||
<section class="change-section change-reason-section">
|
||||
@@ -283,9 +287,11 @@
|
||||
attachment-type
|
||||
use-upload-dialog
|
||||
attachment-location="变更材料"
|
||||
:attachment-location-options="changeAttachmentLocationOptions"
|
||||
:rows="changeMaterials"
|
||||
:preview="previewAttachment"
|
||||
@update:rows="changeMaterials = $event"
|
||||
@upload-to-location="handleAttachmentUploadToLocation"
|
||||
/>
|
||||
<div class="page-footer">
|
||||
<el-button @click="$router.back()">取消</el-button>
|
||||
@@ -343,7 +349,15 @@ const normalizeOptionalPositiveInteger = value => {
|
||||
return Number.isInteger(number) && number > 0 ? number : null;
|
||||
};
|
||||
const normalizeOptionalAmount = value => {
|
||||
if (value === undefined || value === null || value === '') return null;
|
||||
if (
|
||||
value === undefined ||
|
||||
value === null ||
|
||||
value === '' ||
|
||||
Number(value) === -1 ||
|
||||
String(value) === '-1'
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const number = Number(value);
|
||||
return Number.isFinite(number) && number >= 0 ? Number(number.toFixed(2)) : null;
|
||||
};
|
||||
@@ -421,6 +435,22 @@ export default {
|
||||
pageTitle() {
|
||||
return this.$route.query.name || '合同变更';
|
||||
},
|
||||
changeAttachmentLocationOptions() {
|
||||
return ['合同文件', '其它附件', '变更材料'];
|
||||
},
|
||||
contractAmountDisplay() {
|
||||
const value = this.form.contractAmount;
|
||||
if (
|
||||
value === undefined ||
|
||||
value === null ||
|
||||
value === '' ||
|
||||
Number(value) === -1 ||
|
||||
String(value) === '-1'
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
return value;
|
||||
},
|
||||
contractCategoryOptions() {
|
||||
return this.contractCategoryDictOptions.length
|
||||
? this.contractCategoryDictOptions
|
||||
@@ -464,6 +494,13 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleContractAmountInput(value) {
|
||||
const text = String(value || '').replace(/[^\d.]/g, '');
|
||||
const parts = text.split('.');
|
||||
const normalized =
|
||||
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
|
||||
this.form.contractAmount = normalized === '' ? null : normalized;
|
||||
},
|
||||
loadDictionaries() {
|
||||
Promise.all([
|
||||
getSystemDictionary({ code: 'currency_type' }),
|
||||
@@ -619,6 +656,20 @@ export default {
|
||||
attachmentExtension(row = {}) { const source = String(this.attachmentName(row) || this.attachmentUrl(row)).split('?')[0]; const index = source.lastIndexOf('.'); return index > -1 ? source.slice(index + 1).toLowerCase() : ''; },
|
||||
isAttachmentImage(row) { return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(this.attachmentExtension(row)); },
|
||||
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 (location === '合同文件') {
|
||||
this.contractFileRows = [...(this.contractFileRows || []), ...rows];
|
||||
return;
|
||||
}
|
||||
if (location === '其它附件') {
|
||||
this.attachments = [...(this.attachments || []), ...rows];
|
||||
return;
|
||||
}
|
||||
if (location === '变更材料') {
|
||||
this.changeMaterials = [...(this.changeMaterials || []), ...rows];
|
||||
}
|
||||
},
|
||||
handlePreviewUnsupported() { this.$message.warning('当前文件暂不支持在线预览'); },
|
||||
handlePreviewError() { this.$message.error('附件预览失败'); },
|
||||
formatFileSize(value) { const size = Number(value || 0); return size > 1024 * 1024 ? `${(size / 1024 / 1024).toFixed(2)}MB` : `${Math.max(1, Math.ceil(size / 1024))}KB`; },
|
||||
@@ -629,7 +680,7 @@ export default {
|
||||
addCustomPeriodRow() { const periods = normalizeCustomPeriods(this.settlementRule.customPeriods || []); periods.push({ startDay: '', endDay: '' }); this.settlementRule.customPeriods = 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; },
|
||||
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', formInstanceId: this.form.id, subjectName: this.form.contractName || '', approvalStatus: this.form.approvalStatus || 'change_rejected' }); this.$message.success('变更已提交'); this.$router.back(); },
|
||||
},
|
||||
};
|
||||
@@ -654,6 +705,14 @@ export default {
|
||||
.contract-basic-section :deep(.el-input),
|
||||
.contract-basic-section :deep(.el-select),
|
||||
.contract-basic-section :deep(.el-date-editor) { width: 360px; max-width: 100%; }
|
||||
.contract-basic-section :deep(.el-input-number) {
|
||||
width: 360px;
|
||||
max-width: 100%;
|
||||
|
||||
.el-input__inner {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
.dialog-section-title { margin-bottom: 18px; font-size: 16px; font-weight: 600; }
|
||||
.dialog-section-title::before { display: inline-block; width: 4px; height: 16px; margin-right: 8px; vertical-align: -2px; background: #409eff; content: ''; }
|
||||
.section-head { display: flex; align-items: center; justify-content: space-between; }
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<mk-public-shell biz-type="contract-manage" :get-form="getForm">
|
||||
<contract-manage ref="page" />
|
||||
</mk-public-shell>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MkPublicShell from '@/views/mk/mk-public-shell.vue';
|
||||
import ContractManage from '@/views/business/contract-manage.vue';
|
||||
|
||||
export default {
|
||||
name: 'ContractManagePublicView',
|
||||
components: { MkPublicShell, ContractManage },
|
||||
methods: {
|
||||
getForm() {
|
||||
const row = this.$refs.page?.detailRow || {};
|
||||
return { ...row, subject: row.contractName || row.contractNo || '' };
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -222,7 +222,7 @@
|
||||
Number(detailSettlementRule.autoGenerate) === 1 ? '开启' : '关闭'
|
||||
}}</el-descriptions-item>
|
||||
<template v-if="Number(detailSettlementRule.autoGenerate) === 1">
|
||||
<el-descriptions-item label="账单起始日期">{{
|
||||
<el-descriptions-item label="账单起始日">{{
|
||||
displayValue(detailSettlementRule.billStartDate)
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="结算类型">{{
|
||||
@@ -244,7 +244,7 @@
|
||||
}}</el-descriptions-item
|
||||
>
|
||||
<el-descriptions-item
|
||||
v-if="detailSettlementRule.settlementType === '固定天数周期结算'"
|
||||
v-if="isFixedCycleSettlementType(detailSettlementRule.settlementType)"
|
||||
label="周期天数"
|
||||
>{{
|
||||
detailObjectUnitValue(detailSettlementRule, 'cycleDays', '天')
|
||||
@@ -345,7 +345,7 @@
|
||||
</section>
|
||||
</div>
|
||||
<div class="contract-manage-page__footer">
|
||||
<el-button @click="closeDetail">关闭</el-button>
|
||||
<el-button v-if="!isPublicViewPage" @click="closeDetail">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -478,7 +478,7 @@
|
||||
type="date"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="YYYY-MM-DD"
|
||||
placeholder="请输入"
|
||||
/>
|
||||
<span>至</span>
|
||||
<el-date-picker
|
||||
@@ -486,7 +486,7 @@
|
||||
type="date"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="YYYY-MM-DD"
|
||||
placeholder="请输入"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
@@ -498,12 +498,12 @@
|
||||
><template #suffix>天</template></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="合同金额">
|
||||
<el-input-number
|
||||
v-model="form.contractAmount"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
<el-input
|
||||
:model-value="contractAmountDisplay"
|
||||
placeholder="请输入"
|
||||
clearable
|
||||
@input="handleContractAmountInput"
|
||||
@clear="form.contractAmount = null"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否范本">
|
||||
@@ -593,11 +593,13 @@
|
||||
description
|
||||
attachment-type
|
||||
use-upload-dialog
|
||||
:attachment-location-options="contractAttachmentLocationOptions"
|
||||
:lock-approved="isAttachmentUploadMode || hasApprovedContractFiles"
|
||||
:mark-approved-on-upload="isAttachmentUploadMode"
|
||||
:rows="contractFileRows"
|
||||
:preview="previewAttachment"
|
||||
@update:rows="contractFileRows = $event"
|
||||
@upload-to-location="handleAttachmentUploadToLocation"
|
||||
/>
|
||||
|
||||
<section class="contract-manage-form__section contract-manage-form__section--panel">
|
||||
@@ -646,7 +648,7 @@
|
||||
content="开启时,系统根据配置规则归集运单,定时生成结算单"
|
||||
placement="top"
|
||||
>
|
||||
<el-icon class="settlement-switch-tip"><QuestionFilled /></el-icon>
|
||||
<el-icon class="contract-manage-form__fee-mode-tip"><QuestionFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
<el-radio :label="0">关闭</el-radio>
|
||||
</el-radio-group>
|
||||
@@ -655,13 +657,13 @@
|
||||
v-if="settlementRuleEnabled"
|
||||
class="contract-manage-form__grid contract-manage-form__settlement-grid"
|
||||
>
|
||||
<el-form-item label="账单起始日期" required>
|
||||
<el-form-item label="账单起始日" required>
|
||||
<el-date-picker
|
||||
v-model="settlementRuleForm.billStartDate"
|
||||
type="date"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请选择账单起始日期"
|
||||
placeholder="请选择账单起始日"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="结算类型" required>
|
||||
@@ -813,10 +815,12 @@
|
||||
attachment-type
|
||||
use-upload-dialog
|
||||
attachment-location="其它附件"
|
||||
:attachment-location-options="contractAttachmentLocationOptions"
|
||||
:readonly="isAttachmentUploadMode"
|
||||
:rows="attachmentRows"
|
||||
:preview="previewAttachment"
|
||||
@update:rows="attachmentRows = $event"
|
||||
@upload-to-location="handleAttachmentUploadToLocation"
|
||||
/>
|
||||
|
||||
<section
|
||||
@@ -1027,6 +1031,7 @@ import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import * as api from '@/api/business/contract-manage';
|
||||
import { getMkPublicDetail } from '@/api/mk-process';
|
||||
import { getList as getProjectList } from '@/api/business/project-apply';
|
||||
import { getList as getCustomerArchiveList } from '@/api/vehicle/customer-archive';
|
||||
import { getDeptTree } from '@/api/system/dept';
|
||||
@@ -1058,7 +1063,15 @@ const normalizeOptionalInteger = (value, emptyValue = null) => {
|
||||
return Number.isFinite(number) ? Math.trunc(number) : emptyValue;
|
||||
};
|
||||
const normalizeOptionalAmount = (value, emptyValue = null) => {
|
||||
if (value === undefined || value === null || value === '') return emptyValue;
|
||||
if (
|
||||
value === undefined ||
|
||||
value === null ||
|
||||
value === '' ||
|
||||
Number(value) === -1 ||
|
||||
String(value) === '-1'
|
||||
) {
|
||||
return emptyValue;
|
||||
}
|
||||
const number = Number(value);
|
||||
return Number.isFinite(number) && number >= 0 ? Number(number.toFixed(2)) : emptyValue;
|
||||
};
|
||||
@@ -1100,7 +1113,7 @@ const defaultForm = () => ({
|
||||
settlementCurrency: '',
|
||||
invoiceCycle: '',
|
||||
paymentDays: '',
|
||||
contractAmount: '',
|
||||
contractAmount: null,
|
||||
templateFlag: 0,
|
||||
originalContractNo: '',
|
||||
electronicSealFlag: 0,
|
||||
@@ -1272,7 +1285,7 @@ export default {
|
||||
formalSettlementRuleForm: defaultSettlementRule(),
|
||||
paymentRatioRows: [],
|
||||
changeRecordRows: [],
|
||||
settlementTypeOptions: ['月结', '日结', '周结', '半月结', '固定天数周期结算'],
|
||||
settlementTypeOptions: ['现结', '日结', '周结', '月结', '按固定天数周期'],
|
||||
billCycleTypeOptions: ['固定截单日', '自然月', '自定义多周期'],
|
||||
detailLoading: false,
|
||||
detailRow: {},
|
||||
@@ -1316,7 +1329,12 @@ export default {
|
||||
return this.$route.path === '/business/contract-manage/form';
|
||||
},
|
||||
isDetailPage() {
|
||||
return this.$route.path === '/business/contract-manage/detail';
|
||||
return (
|
||||
this.$route.path === '/business/contract-manage/detail' || this.isPublicViewPage
|
||||
);
|
||||
},
|
||||
isPublicViewPage() {
|
||||
return this.$route.path === '/business/contract-manage/public-view';
|
||||
},
|
||||
formMode() {
|
||||
return this.$route.query.mode === 'edit' ? 'edit' : 'add';
|
||||
@@ -1331,6 +1349,23 @@ export default {
|
||||
isAttachmentUploadMode() {
|
||||
return this.$route.query.attachmentUpload === '1';
|
||||
},
|
||||
contractAttachmentLocationOptions() {
|
||||
if (this.isAttachmentUploadMode) return ['合同文件'];
|
||||
return ['合同文件', '其它附件'];
|
||||
},
|
||||
contractAmountDisplay() {
|
||||
const value = this.form.contractAmount;
|
||||
if (
|
||||
value === undefined ||
|
||||
value === null ||
|
||||
value === '' ||
|
||||
Number(value) === -1 ||
|
||||
String(value) === '-1'
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
return value;
|
||||
},
|
||||
hasApprovedContractFiles() {
|
||||
return (this.contractFileRows || []).some(
|
||||
row =>
|
||||
@@ -1412,7 +1447,7 @@ export default {
|
||||
return this.showBillCycleType && this.settlementRuleForm.billCycleType === '自定义多周期';
|
||||
},
|
||||
showCycleDays() {
|
||||
return this.settlementRuleForm.settlementType === '固定天数周期结算';
|
||||
return this.isFixedCycleSettlementType(this.settlementRuleForm.settlementType);
|
||||
},
|
||||
billCutoffDayOptions() {
|
||||
return Array.from({ length: 31 }, (_, index) => ({
|
||||
@@ -1473,9 +1508,11 @@ export default {
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.loadProjectOptions();
|
||||
this.loadSettlementDictionaries();
|
||||
this.loadOrganizationOptions();
|
||||
if (!this.isPublicViewPage) {
|
||||
this.loadProjectOptions();
|
||||
this.loadSettlementDictionaries();
|
||||
this.loadOrganizationOptions();
|
||||
}
|
||||
if (this.isFormPage) this.initFormPage();
|
||||
if (this.isDetailPage) this.initDetailPage();
|
||||
},
|
||||
@@ -1675,7 +1712,7 @@ export default {
|
||||
copyCount: normalizeOptionalInteger(detail.copyCount, ''),
|
||||
invoiceCycle: normalizeOptionalInteger(detail.invoiceCycle, ''),
|
||||
paymentDays: normalizeOptionalInteger(detail.paymentDays, ''),
|
||||
contractAmount: normalizeOptionalAmount(detail.contractAmount, ''),
|
||||
contractAmount: normalizeOptionalAmount(detail.contractAmount, null),
|
||||
archiveStatus: detail.archiveStatus || '未归档',
|
||||
feeGenerationMode:
|
||||
detail.feeGenerationMode || (Number(detail.billingEnabled) === 0 ? 'manual' : 'system'),
|
||||
@@ -2118,6 +2155,13 @@ export default {
|
||||
integerInput(prop, value) {
|
||||
this.form[prop] = String(value || '').replace(/\D/g, '');
|
||||
},
|
||||
handleContractAmountInput(value) {
|
||||
const text = String(value || '').replace(/[^\d.]/g, '');
|
||||
const parts = text.split('.');
|
||||
const normalized =
|
||||
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
|
||||
this.form.contractAmount = normalized === '' ? null : normalized;
|
||||
},
|
||||
positiveIntegerInput(prop, value) {
|
||||
const normalized = String(value || '').replace(/\D/g, '').replace(/^0+/, '');
|
||||
this.form[prop] = normalized;
|
||||
@@ -2148,6 +2192,16 @@ export default {
|
||||
};
|
||||
this.attachmentDocumentPreviewVisible = true;
|
||||
},
|
||||
handleAttachmentUploadToLocation({ location, rows = [] }) {
|
||||
if (!rows.length) return;
|
||||
if (location === '合同文件') {
|
||||
this.contractFileRows = [...(this.contractFileRows || []), ...rows];
|
||||
return;
|
||||
}
|
||||
if (location === '其它附件') {
|
||||
this.attachmentRows = [...(this.attachmentRows || []), ...rows];
|
||||
}
|
||||
},
|
||||
handleAttachmentPreviewUnsupported() {
|
||||
this.$message.warning('当前文件暂不支持在线预览');
|
||||
},
|
||||
@@ -2206,8 +2260,12 @@ export default {
|
||||
}
|
||||
return true;
|
||||
},
|
||||
isFixedCycleSettlementType(value) {
|
||||
return value === '按固定天数周期' || value === '固定天数周期结算';
|
||||
},
|
||||
normalizeSettlementRule(rule = {}) {
|
||||
const next = { ...defaultSettlementRule(), ...rule };
|
||||
if (next.settlementType === '固定天数周期结算') next.settlementType = '按固定天数周期';
|
||||
if (next.settlementType !== '月结') {
|
||||
next.billCycleType = '';
|
||||
next.billCutoffDay = '';
|
||||
@@ -2222,7 +2280,7 @@ export default {
|
||||
} else {
|
||||
next.customPeriods = [];
|
||||
}
|
||||
if (next.settlementType !== '固定天数周期结算') next.cycleDays = '';
|
||||
if (!this.isFixedCycleSettlementType(next.settlementType)) next.cycleDays = '';
|
||||
return next;
|
||||
},
|
||||
syncSettlementConfig() {
|
||||
@@ -2245,7 +2303,7 @@ export default {
|
||||
this.settlementRuleForm.billCycleType = '';
|
||||
this.settlementRuleForm.billCutoffDay = '';
|
||||
this.settlementRuleForm.customPeriods = [];
|
||||
if (value !== '固定天数周期结算') this.settlementRuleForm.cycleDays = '';
|
||||
if (!this.isFixedCycleSettlementType(value)) this.settlementRuleForm.cycleDays = '';
|
||||
},
|
||||
handleBillCycleTypeChange(value) {
|
||||
if (value !== '固定截单日') this.settlementRuleForm.billCutoffDay = '';
|
||||
@@ -2336,7 +2394,7 @@ export default {
|
||||
validateSettlementRule(rule, label) {
|
||||
if (Number(rule.autoGenerate) !== 1) return true;
|
||||
if (!rule.billStartDate || !rule.settlementType) {
|
||||
this.$message.warning(`${label}:请完整填写账单起始日期和结算类型`);
|
||||
this.$message.warning(`${label}:请完整填写账单起始日和结算类型`);
|
||||
return false;
|
||||
}
|
||||
if (rule.settlementType === '月结' && !rule.billCycleType) {
|
||||
@@ -2354,7 +2412,7 @@ export default {
|
||||
if (rule.settlementType === '月结' && rule.billCycleType === '自定义多周期') {
|
||||
return this.validateCustomPeriods(rule.customPeriods, label);
|
||||
}
|
||||
if (rule.settlementType === '固定天数周期结算' && !rule.cycleDays) {
|
||||
if (this.isFixedCycleSettlementType(rule.settlementType) && !rule.cycleDays) {
|
||||
this.$message.warning(`${label}:请选择周期天数`);
|
||||
return false;
|
||||
}
|
||||
@@ -2406,8 +2464,10 @@ export default {
|
||||
if (!id) return;
|
||||
this.detailLoading = true;
|
||||
this.applyDetailState({});
|
||||
this.api
|
||||
.getDetail(id)
|
||||
const request = this.isPublicViewPage
|
||||
? getMkPublicDetail('contract-manage', id)
|
||||
: this.api.getDetail(id);
|
||||
request
|
||||
.then(res => {
|
||||
this.applyDetailState(res.data?.data || {});
|
||||
})
|
||||
@@ -2576,7 +2636,7 @@ export default {
|
||||
.join(',')}`
|
||||
);
|
||||
}
|
||||
if (config.billStartDate) parts.push(`账单起始日期:${config.billStartDate}`);
|
||||
if (config.billStartDate) parts.push(`账单起始日:${config.billStartDate}`);
|
||||
return parts.join(',');
|
||||
};
|
||||
if (normalized.preSettlementConfig || normalized.formalSettlementConfig) {
|
||||
@@ -2672,6 +2732,15 @@ export default {
|
||||
},
|
||||
detailValue(prop) {
|
||||
const value = this.detailRow[prop];
|
||||
if (prop === 'contractAmount') {
|
||||
return value === undefined ||
|
||||
value === null ||
|
||||
value === '' ||
|
||||
Number(value) === -1 ||
|
||||
String(value) === '-1'
|
||||
? '-'
|
||||
: value;
|
||||
}
|
||||
if (
|
||||
prop === 'contractStage' ||
|
||||
prop === 'approvalStatus' ||
|
||||
@@ -3018,13 +3087,6 @@ export default {
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.settlement-switch-tip {
|
||||
margin: 0 4px;
|
||||
color: #a8abb2;
|
||||
cursor: help;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
&__settlement-grid {
|
||||
@@ -3082,11 +3144,19 @@ export default {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
:deep(.el-input-number) {
|
||||
width: 360px;
|
||||
max-width: 100%;
|
||||
|
||||
.el-input__inner {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-input),
|
||||
:deep(.el-select),
|
||||
:deep(.el-cascader),
|
||||
:deep(.el-tree-select),
|
||||
:deep(.el-input-number),
|
||||
:deep(.el-date-editor) {
|
||||
width: 360px;
|
||||
max-width: 100%;
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<div ref="page" class="project-apply-public-view">
|
||||
<project-apply ref="project" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ProjectApply from './project-apply.vue';
|
||||
import { postMkPublicProcessMessage } from '@/api/mk-process';
|
||||
|
||||
export default {
|
||||
name: 'ProjectApplyPublicView',
|
||||
components: {
|
||||
ProjectApply,
|
||||
},
|
||||
created() {
|
||||
this.handleIframeHeight = () => this.sendIframeHeight();
|
||||
this.handleProcessMessage = event => this.onProcessMessage(event);
|
||||
document.addEventListener('DOMContentLoaded', this.handleIframeHeight, false);
|
||||
window.addEventListener('message', this.handleProcessMessage);
|
||||
},
|
||||
mounted() {
|
||||
document.documentElement.classList.add('mk-iframe-page');
|
||||
document.body.classList.add('mk-iframe-page');
|
||||
const app = document.getElementById('app');
|
||||
if (app) app.classList.add('mk-iframe-page');
|
||||
this.handleIframeHeight();
|
||||
window.addEventListener('load', this.handleIframeHeight);
|
||||
this.mkHeightTimers = [300, 800, 1600].map(delay => setTimeout(this.handleIframeHeight, delay));
|
||||
if (typeof ResizeObserver === 'undefined') return;
|
||||
this.mkHeightObserver = new ResizeObserver(() => this.handleIframeHeight());
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.page) this.mkHeightObserver.observe(this.$refs.page);
|
||||
this.mkHeightObserver.observe(document.body);
|
||||
});
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.removeEventListener('DOMContentLoaded', this.handleIframeHeight, false);
|
||||
window.removeEventListener('load', this.handleIframeHeight);
|
||||
window.removeEventListener('message', this.handleProcessMessage);
|
||||
(this.mkHeightTimers || []).forEach(timer => clearTimeout(timer));
|
||||
if (this.mkHeightObserver) {
|
||||
this.mkHeightObserver.disconnect();
|
||||
this.mkHeightObserver = null;
|
||||
}
|
||||
document.documentElement.classList.remove('mk-iframe-page');
|
||||
document.body.classList.remove('mk-iframe-page');
|
||||
const app = document.getElementById('app');
|
||||
if (app) app.classList.remove('mk-iframe-page');
|
||||
},
|
||||
methods: {
|
||||
sendIframeHeight() {
|
||||
this.$nextTick(() => {
|
||||
window.parent.postMessage({ height: document.body.clientHeight }, '*');
|
||||
});
|
||||
},
|
||||
onProcessMessage(event) {
|
||||
const data = event && event.data;
|
||||
if (!data || typeof data !== 'object') return;
|
||||
if (data.height && !data.status && !data.type) return;
|
||||
if (data.type === 'formValues' || data.type === 'afterSubmit') return;
|
||||
const formValues = data.formValues;
|
||||
if (data.status === 'submit') {
|
||||
this.submitData(formValues);
|
||||
} else if (data.status === 'save') {
|
||||
this.saveData(formValues);
|
||||
}
|
||||
if (data.type === 'getFormValues') {
|
||||
window.parent.postMessage({ type: 'formValues', formData: this.buildFormData() }, '*');
|
||||
}
|
||||
},
|
||||
submitData(lbpmFormValues) {
|
||||
const formData = this.buildFormData();
|
||||
this.postFormData('submit', lbpmFormValues, formData);
|
||||
if (!lbpmFormValues) return;
|
||||
const parameters = Object.assign({}, lbpmFormValues, {
|
||||
loginName: this.getLoginName(lbpmFormValues),
|
||||
formInstanceId: this.getFormId(),
|
||||
subject: formData.subject,
|
||||
});
|
||||
window.parent.postMessage({ type: 'afterSubmit', success: true, parameters }, '*');
|
||||
},
|
||||
saveData(lbpmFormValues) {
|
||||
this.postFormData('save', lbpmFormValues, this.buildFormData());
|
||||
},
|
||||
postFormData(status, formValues, formData) {
|
||||
postMkPublicProcessMessage('project-apply', {
|
||||
status,
|
||||
formValues: formValues || {},
|
||||
formData,
|
||||
}).catch(error => {
|
||||
console.error('项目公开页提交流程数据失败:', error);
|
||||
});
|
||||
},
|
||||
buildFormData() {
|
||||
const form = this.getProjectForm();
|
||||
return {
|
||||
...form,
|
||||
subject: form.projectName || form.projectShortName || '',
|
||||
formInstanceId: this.getFormId(),
|
||||
};
|
||||
},
|
||||
getProjectForm() {
|
||||
const form = this.$refs.project && this.$refs.project.form;
|
||||
if (!form) return {};
|
||||
try {
|
||||
return JSON.parse(JSON.stringify(form));
|
||||
} catch (error) {
|
||||
return { ...form };
|
||||
}
|
||||
},
|
||||
getFormId() {
|
||||
return this.$route.query.id || this.getProjectForm().id || '';
|
||||
},
|
||||
getLoginName(formValues = {}) {
|
||||
return (
|
||||
formValues.loginName ||
|
||||
this.$route.query.loginName ||
|
||||
this.$route.query.submitIdentity ||
|
||||
''
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
html.mk-iframe-page,
|
||||
html.mk-iframe-page body,
|
||||
html.mk-iframe-page #app,
|
||||
html.mk-iframe-page #app.mk-iframe-page {
|
||||
height: auto !important;
|
||||
min-height: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.project-apply-public-view {
|
||||
min-height: 100%;
|
||||
padding: 12px 0 24px;
|
||||
box-sizing: border-box;
|
||||
background: #f0f2f5;
|
||||
|
||||
:deep(.basic-container) {
|
||||
padding: 0 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<basic-container class="project-apply-page">
|
||||
<basic-container class="project-apply-page" :class="{ 'is-public-view': isPublicViewPage }">
|
||||
<avue-crud
|
||||
v-if="!isProjectFormPage"
|
||||
:option="tableOption"
|
||||
@@ -784,6 +784,7 @@
|
||||
</el-form>
|
||||
|
||||
<div
|
||||
v-if="!isPublicViewPage"
|
||||
class="project-apply-dialog__footer"
|
||||
:class="{ 'project-apply-dialog__footer--change': isChangeDialog }"
|
||||
>
|
||||
@@ -909,12 +910,29 @@
|
||||
/>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="publicCustomerVisible"
|
||||
title="查看客商档案"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
width="92%"
|
||||
top="4vh"
|
||||
class="project-apply-public-customer-dialog"
|
||||
>
|
||||
<customer-archive
|
||||
v-if="publicCustomerVisible"
|
||||
:embedded-public-id="publicCustomerId"
|
||||
/>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
import * as api from '@/api/business/project-apply';
|
||||
import { getMkPublicDetail } from '@/api/mk-process';
|
||||
import {
|
||||
getList as getCustomerArchiveList,
|
||||
getDetail as getCustomerArchiveDetail,
|
||||
@@ -1045,7 +1063,9 @@ const majorProjectAttachmentTypeOptions = [
|
||||
].map(item => ({ label: item, value: item })).concat(otherAttachmentType);
|
||||
|
||||
export default {
|
||||
name: 'ProjectApply',
|
||||
components: {
|
||||
CustomerArchive: defineAsyncComponent(() => import('@/views/vehicle/customer-archive.vue')),
|
||||
ElImageViewer,
|
||||
OpenFileViewer,
|
||||
PdfPreview,
|
||||
@@ -1239,6 +1259,8 @@ export default {
|
||||
changeRecordDetail: null,
|
||||
changeRecordDetailRows: [],
|
||||
userBox: false,
|
||||
publicCustomerVisible: false,
|
||||
publicCustomerId: '',
|
||||
userPickType: '',
|
||||
userLoading: false,
|
||||
userData: [],
|
||||
@@ -1315,6 +1337,9 @@ export default {
|
||||
isProjectFormPage() {
|
||||
return this.isFormPageInstance;
|
||||
},
|
||||
isPublicViewPage() {
|
||||
return this.$route.path === '/business/project-apply/public-view';
|
||||
},
|
||||
projectFormContainer() {
|
||||
// 表单页实例始终用页面容器,避免 keep-alive 失活时误切弹窗
|
||||
return this.isFormPageInstance ? 'div' : 'el-dialog';
|
||||
@@ -1361,6 +1386,11 @@ export default {
|
||||
},
|
||||
},
|
||||
created() {
|
||||
if (this.isPublicViewPage) {
|
||||
this.isFormPageInstance = true;
|
||||
this.openPublicProjectForm();
|
||||
return;
|
||||
}
|
||||
this.loadDeptOptions();
|
||||
this.loadCargoTypeOptions();
|
||||
this.loadTransportTypeOptions();
|
||||
@@ -1384,6 +1414,7 @@ export default {
|
||||
this.attachmentDocumentPreviewVisible = false;
|
||||
this.attachmentImagePreviewVisible = false;
|
||||
this.userBox = false;
|
||||
this.publicCustomerVisible = false;
|
||||
},
|
||||
buildTableOption() {
|
||||
return {
|
||||
@@ -1406,6 +1437,75 @@ export default {
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission && this.permission[code], false);
|
||||
},
|
||||
openPublicProjectForm() {
|
||||
const id = this.$route.query.id;
|
||||
this.dialogType = 'view';
|
||||
this.dialogReadonly = true;
|
||||
this.projectBox = true;
|
||||
if (!id) {
|
||||
this.$message.error('缺少项目ID');
|
||||
return;
|
||||
}
|
||||
getMkPublicDetail('project-apply', id)
|
||||
.then(res => {
|
||||
const detail = res.data?.data || {};
|
||||
this.applyPublicDictOptions(detail);
|
||||
const displayDetail = { ...detail };
|
||||
delete displayDetail.cargoTypeOptions;
|
||||
delete displayDetail.transportTypeOptions;
|
||||
delete displayDetail.settlementModeOptions;
|
||||
this.applyProjectDetail(displayDetail);
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error('项目信息加载失败');
|
||||
});
|
||||
},
|
||||
applyPublicDictOptions(detail = {}) {
|
||||
this.cargoTypeOptions = this.resolvePublicDictOptions(
|
||||
detail.cargoTypeOptions,
|
||||
detail.cargoType
|
||||
);
|
||||
this.transportTypeOptions = this.resolvePublicDictOptions(
|
||||
detail.transportTypeOptions,
|
||||
detail.transportType
|
||||
);
|
||||
this.settlementModeOptions = this.resolvePublicDictOptions(
|
||||
detail.settlementModeOptions,
|
||||
detail.settlementMode
|
||||
);
|
||||
if (detail.businessDeptId) {
|
||||
const label = detail.businessDeptName || String(detail.businessDeptId);
|
||||
this.businessDeptTreeOptions = [{ label, value: detail.businessDeptId }];
|
||||
this.deptOptions = [{ label, rawLabel: label, value: detail.businessDeptId }];
|
||||
}
|
||||
if (detail.undertakeDeptId) {
|
||||
this.platformCompanyOptions = [
|
||||
{
|
||||
label: detail.undertakeDeptName || String(detail.undertakeDeptId),
|
||||
value: detail.undertakeDeptId,
|
||||
},
|
||||
];
|
||||
}
|
||||
},
|
||||
resolvePublicDictOptions(options, currentValue) {
|
||||
const list = Array.isArray(options)
|
||||
? options
|
||||
.filter(item => item && item.value !== undefined && item.value !== null && item.value !== '')
|
||||
.map(item => ({
|
||||
label: item.label || String(item.value),
|
||||
value: item.value,
|
||||
}))
|
||||
: [];
|
||||
if (
|
||||
currentValue !== undefined &&
|
||||
currentValue !== null &&
|
||||
currentValue !== '' &&
|
||||
!list.some(item => String(item.value) === String(currentValue))
|
||||
) {
|
||||
list.unshift({ label: String(currentValue), value: currentValue });
|
||||
}
|
||||
return list;
|
||||
},
|
||||
openProjectFormPage() {
|
||||
const type = this.$route.query.mode || 'add';
|
||||
const id = this.$route.query.id;
|
||||
@@ -2012,6 +2112,11 @@ export default {
|
||||
this.$message.warning('客商档案 ID 为空,无法打开');
|
||||
return;
|
||||
}
|
||||
if (this.isPublicViewPage) {
|
||||
this.publicCustomerId = String(id);
|
||||
this.publicCustomerVisible = true;
|
||||
return;
|
||||
}
|
||||
this.$router.push({
|
||||
path: '/vehicle/customer-archive/form',
|
||||
query: { id: String(id), name: '查看客商档案', view: '1' },
|
||||
@@ -2594,6 +2699,12 @@ export default {
|
||||
this.$message.warning('项目ID为空,无法查看变更详情');
|
||||
return;
|
||||
}
|
||||
if (this.isPublicViewPage) {
|
||||
this.changeRecordDetail = { ...row };
|
||||
this.changeRecordDetailRows = this.buildChangeRecordDetailRows(this.changeRecordDetail);
|
||||
this.changeRecordDetailVisible = true;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (!this.transportTypeOptions.length) {
|
||||
await this.loadTransportTypeOptions();
|
||||
@@ -2650,6 +2761,12 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.project-apply-page.is-public-view {
|
||||
:deep(.el-link) {
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.project-apply-form {
|
||||
padding: 0;
|
||||
|
||||
@@ -3019,3 +3136,11 @@ export default {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.project-apply-public-customer-dialog .el-dialog__body {
|
||||
max-height: 78vh;
|
||||
overflow: auto;
|
||||
padding-top: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -99,13 +99,25 @@
|
||||
<el-table :data="attachmentRows" border @selection-change="selectedAttachments = $event">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||
<el-table-column label="文件名" min-width="240" show-overflow-tooltip>
|
||||
<el-table-column label="文件名称" min-width="240" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<el-link type="primary" @click="previewAttachment(row)">
|
||||
{{ attachmentName(row) }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="附件描述" min-width="220">
|
||||
<template #default="{ row }">
|
||||
<span v-if="dialogReadonly">{{ row.description || '-' }}</span>
|
||||
<el-input
|
||||
v-else
|
||||
v-model="row.description"
|
||||
maxlength="200"
|
||||
placeholder="请输入"
|
||||
@input="syncAttachmentsJson"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="文件大小" width="120" align="center">
|
||||
<template #default="{ row }">{{ formatFileSize(row.size) }}</template>
|
||||
</el-table-column>
|
||||
@@ -172,7 +184,7 @@
|
||||
title="查看临时额度申请"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
width="96%"
|
||||
width="1100px"
|
||||
class="temporary-credit-limit-dialog temporary-credit-limit-detail-dialog"
|
||||
>
|
||||
<div v-loading="detailLoading" class="business-crud-page__detail-content">
|
||||
@@ -204,13 +216,16 @@
|
||||
</div>
|
||||
<el-table :data="attachmentRows" empty-text="暂无附件">
|
||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||
<el-table-column label="文件名" min-width="240" show-overflow-tooltip>
|
||||
<el-table-column label="文件名称" min-width="240" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<el-link type="primary" @click="previewAttachment(row)">
|
||||
{{ attachmentName(row) }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="附件描述" min-width="220" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.description || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="文件大小" width="120" align="center">
|
||||
<template #default="{ row }">{{ formatFileSize(row.size) }}</template>
|
||||
</el-table-column>
|
||||
@@ -783,24 +798,37 @@ export default {
|
||||
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
this.attachmentRows = (list || []).map(item => ({
|
||||
...item,
|
||||
description: item.description || '',
|
||||
uploadUserName: item.uploadUserName || uploadUserName,
|
||||
uploadTime: item.uploadTime || uploadTime,
|
||||
}));
|
||||
this.form.attachmentsJson = JSON.stringify(this.attachmentRows);
|
||||
this.syncAttachmentsJson();
|
||||
},
|
||||
removeAttachment(index) {
|
||||
this.attachmentRows.splice(index, 1);
|
||||
this.form.attachmentsJson = JSON.stringify(this.attachmentRows);
|
||||
this.syncAttachmentsJson();
|
||||
},
|
||||
syncAttachmentsJson() {
|
||||
this.form.attachmentsJson = JSON.stringify(this.attachmentRows || []);
|
||||
},
|
||||
parseJsonArray(value) {
|
||||
if (Array.isArray(value)) return value;
|
||||
if (!value) return [];
|
||||
try {
|
||||
const data = JSON.parse(value);
|
||||
return Array.isArray(data) ? data : [];
|
||||
} catch (error) {
|
||||
let list = [];
|
||||
if (Array.isArray(value)) {
|
||||
list = value;
|
||||
} else if (!value) {
|
||||
return [];
|
||||
} else {
|
||||
try {
|
||||
const data = JSON.parse(value);
|
||||
list = Array.isArray(data) ? data : [];
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return list.map(item => ({
|
||||
...item,
|
||||
description: item?.description || '',
|
||||
}));
|
||||
},
|
||||
attachmentName(row = {}) {
|
||||
return row.originalName || row.name || row.fileName || '附件';
|
||||
@@ -907,6 +935,7 @@ export default {
|
||||
.temporary-credit-limit-page {
|
||||
&__field {
|
||||
width: 100%;
|
||||
max-width: 240px;
|
||||
}
|
||||
|
||||
&__attachment-head {
|
||||
@@ -993,7 +1022,7 @@ export default {
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 14px 16px 4px;
|
||||
padding: 14px 0 4px;
|
||||
margin-bottom: 0;
|
||||
color: #303133;
|
||||
font-size: 15px;
|
||||
@@ -1086,9 +1115,36 @@ export default {
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
padding: 0 16px 8px !important;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
// 新增/编辑:控件限宽,避免宽屏下撑满列宽贴到弹窗右边缘
|
||||
&:not(.temporary-credit-limit-detail-dialog) {
|
||||
.el-form-item__content {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.el-form-item__content > .el-input,
|
||||
.el-form-item__content > .el-select,
|
||||
.el-form-item__content > .el-date-editor,
|
||||
.el-form-item__content > .el-cascader,
|
||||
.el-form-item__content > .el-textarea,
|
||||
.temporary-credit-limit-page__field {
|
||||
width: 100%;
|
||||
max-width: 240px;
|
||||
}
|
||||
|
||||
.el-form-item__content > .el-textarea {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.el-date-editor.el-input,
|
||||
.el-date-editor.el-input__wrapper {
|
||||
width: 100%;
|
||||
max-width: 240px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.temporary-credit-limit-dialog .business-crud-page__detail-content .el-descriptions__label {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<mk-public-shell biz-type="waybill-manage" :get-form="getForm">
|
||||
<waybill-manage-page
|
||||
ref="page"
|
||||
:api="api"
|
||||
:config="config"
|
||||
:crud-option="option"
|
||||
:detail-id="detailId"
|
||||
standalone-detail-page
|
||||
/>
|
||||
</mk-public-shell>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MkPublicShell from '@/views/mk/mk-public-shell.vue';
|
||||
import WaybillManagePage from '@/views/business/components/waybill-manage-page.vue';
|
||||
import * as api from '@/api/business/waybill-manage';
|
||||
import { config, option } from '@/option/business/waybill-manage';
|
||||
|
||||
export default {
|
||||
name: 'WaybillManagePublicView',
|
||||
components: { MkPublicShell, WaybillManagePage },
|
||||
data() {
|
||||
return { api, config, option };
|
||||
},
|
||||
computed: {
|
||||
detailId() {
|
||||
return this.$route.query.id || '';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getForm() {
|
||||
const row = this.$refs.page?.detailRow || {};
|
||||
return { ...row, subject: row.waybillNo || '' };
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
Reference in New Issue
Block a user