✨ MK公开页、预结算体验优化、地图选址与认证密码规则完善

This commit is contained in:
2026-09-22 12:06:48 +08:00
parent 57368f0179
commit ac87208ba9
41 changed files with 1882 additions and 391 deletions
@@ -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());