Merge remote-tracking branch 'websoft/dev'

# Conflicts:
#	src/views/transportCapacity/vehicle.vue
This commit is contained in:
2026-10-26 10:51:35 +08:00
8 changed files with 110 additions and 42 deletions
@@ -10,7 +10,7 @@
:class="{ 'punch-record-photos__grid--edit': editable }"
>
<div
v-for="(item, groupIndex) in group.items"
v-for="item in group.items"
:key="`${item.photo.url}-${item.index}`"
class="punch-record-photos__item"
@click="handlePreview(item.index)"
@@ -23,8 +23,6 @@
:lazy="lazy"
fit="cover"
/>
<!-- 主图 = 该类型的第一张,按上传顺序取;没有独立字段,也不可切换 -->
<div v-if="groupIndex === 0" class="punch-record-photos__badge">主图</div>
<el-link
v-if="editable"
class="punch-record-photos__remove"
@@ -59,7 +57,7 @@
import { computed } from 'vue';
// 打卡照片是每个打卡节点必有的一个照片类型,不由过程配置的凭证类型决定;
// 它与各凭证类型各自最多 9 张,同一类型下第一张即界面上的「主图」。
// 它与各凭证类型各自最多 9 张。
const PUNCH_PHOTO_TYPE = '打卡照片';
// 照片没带类型时的兜底名称,与后端落库时的兜底口径一致
const PHOTO_TYPE_FALLBACK = '凭证';
@@ -252,18 +250,6 @@ const countText = group =>
}
}
&__badge {
position: absolute;
top: 0;
left: 0;
padding: 0 6px;
border-radius: 4px 0 4px 0;
background: rgba(64, 158, 255, 0.9);
font-size: 12px;
line-height: 18px;
color: #fff;
}
&__remove {
align-self: center;
font-size: 12px;
@@ -2271,6 +2271,13 @@
<span v-if="record.volume">体积 {{ record.volume }}方</span>
<span v-if="record.quantity">数量 {{ record.quantity }}</span>
</div>
<!-- 里程只挂在末位节点上(needMileage 由后端下发),显示的是运单里程 -->
<div
v-if="record.needMileage && Number(record.mileage) > 0"
class="waybill-manage-page__punch-record-line"
>
里程 {{ record.mileage }} 公里
</div>
<div
v-if="record.remark"
class="waybill-manage-page__punch-record-line"
@@ -2867,6 +2874,20 @@
/>
</el-form-item>
</el-col>
<!-- 里程只在末位打卡节点出现:后端按「末位 + 计费规则含里程」下发 needMileage,前端不自己推 -->
<el-col v-if="waybillPunchEditMileageVisible" :span="8">
<el-form-item label="里程">
<el-input
v-model="waybillPunchEditForm.mileage"
maxlength="10"
:disabled="waybillPunchEditMileageLocked"
placeholder="请输入"
@input="handleWaybillPunchMileageInput"
>
<template #append>公里</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注">
<el-input
@@ -4029,6 +4050,7 @@ export default {
weight: '',
volume: '',
quantity: '',
mileage: '',
remark: '',
photos: [],
},
@@ -4431,6 +4453,15 @@ export default {
waybillPunchEditPunchDisabled() {
return this.waybillPunchEditNode?.punch === false;
},
// 里程:后端下发 needMileage 才出现(末位打卡节点 + 计费规则含里程);在途打卡不采集里程
waybillPunchEditMileageVisible() {
const record = this.waybillPunchEditRecord || {};
return record.needMileage === true && record.type !== 'enroute';
},
// 已生成结算单的运单里程冻结:输入框置灰,改动由后端拒绝
waybillPunchEditMileageLocked() {
return this.waybillPunchEditRecord?.mileageLocked === true;
},
waybillPunchEditRules() {
return { punchTime: [{ required: true, message: '请选择打卡时间', trigger: 'change' }] };
},
@@ -5962,6 +5993,8 @@ export default {
weight: record.weight || '',
volume: record.volume || '',
quantity: record.quantity || '',
// 里程只有正整数口径,后端若仍下发 "200.00" 会过不了提交时的正整数校验
mileage: Number(record.mileage) > 0 ? String(Number(record.mileage)) : '',
remark: record.remark || '',
photos: (record.photos || [])
.filter(photo => photo?.url)
@@ -6037,10 +6070,22 @@ export default {
photos.splice(index, 1);
this.waybillPunchEditForm.photos = photos;
},
// 里程只收整数:非数字直接丢掉,长度与「维护里程」一致(≤10 位)
handleWaybillPunchMileageInput(value) {
this.waybillPunchEditForm.mileage = String(value || '')
.replace(/\D/g, '')
.slice(0, 10);
},
async submitWaybillPunchRecordEdit() {
await this.$refs.waybillPunchRecordEditFormRef.validate();
const record = this.waybillPunchEditRecord;
const form = this.waybillPunchEditForm;
// 管理端里程选填:留空表示不改动运单里程,所以只校验填了值的情况
const mileageText = String(form.mileage || '').trim();
if (this.waybillPunchEditMileageVisible && mileageText && !/^[1-9]\d{0,9}$/.test(mileageText)) {
this.$message.warning('里程必须为不超过10位的正整数');
return;
}
const submit =
typeof this.api?.savePunchRecord === 'function' ? this.api.savePunchRecord : savePunchRecord;
this.waybillPunchRecordEditDialog.submitting = true;
@@ -6059,6 +6104,8 @@ export default {
weight: form.weight,
volume: form.volume,
quantity: form.quantity,
// 不传代表不改;后端按 null 处理
mileage: mileageText ? Number(mileageText) : undefined,
remark: form.remark,
photos: form.photos.map(photo => ({ url: photo.url, type: photo.type })),
});
+13 -10
View File
@@ -245,7 +245,7 @@
<el-radio-group
v-model="row.confirmMode"
:disabled="dialogReadonly || !row.enabled"
@change="handleReturnConfirmChange(row)"
@change="syncNodeFields"
>
<el-radio label="yes">是</el-radio>
<el-radio label="no_confirm_complete">无需确认完成</el-radio>
@@ -253,10 +253,7 @@
</div>
<div class="process-config-form__require-line">
<span class="process-config-form__require-label">确认完成</span>
<el-checkbox
v-model="row.confirmInternal"
:disabled="dialogReadonly || !row.enabled || row.confirmMode !== 'yes'"
@change="syncNodeFields"
<el-checkbox v-model="row.confirmInternal" :disabled="true"
>内部人员</el-checkbox
>
</div>
@@ -549,15 +546,18 @@ export default {
row.confirmDriver = row.confirmMode === 'yes';
this.syncNodeFields();
},
handleReturnConfirmChange(row) {
row.confirmInternal = row.confirmMode === 'yes';
this.syncNodeFields();
},
enforceAcceptDriver(node) {
if (!node || (node.key !== 'accept' && node.name !== '接单')) return node;
node.confirmDriver = node.confirmMode === 'yes';
return node;
},
// 回单「确认完成」只有内部人员一种角色,是否确认为「是」时即由内部人员确认,
// 界面上只读展示,值始终由 confirmMode 推导
enforceReturnInternal(node) {
if (!node || (node.key !== 'return' && node.name !== '回单')) return node;
node.confirmInternal = node.confirmMode === 'yes';
return node;
},
initDeptOptions() {
getDeptTree(this.userInfo.tenantId).then(res => {
const deptColumn = this.findColumn(this.option.column, 'searchDeptId');
@@ -808,7 +808,10 @@ export default {
this.syncNodeFields();
},
syncNodeFields() {
this.nodeRows.forEach(item => this.enforceAcceptDriver(item));
this.nodeRows.forEach(item => {
this.enforceAcceptDriver(item);
this.enforceReturnInternal(item);
});
const enabledNodes = this.nodeRows.filter(item => item.enabled);
this.form.includedNodes = enabledNodes.map(item => item.name).join(',');
this.form.nodeConfigJson = JSON.stringify(
-10
View File
@@ -315,11 +315,6 @@ export default {
},
rowSave(row, done, loading) {
const payload = this.normalizeSubmitRow(row, false);
if (!payload.deptId) {
this.$message.warning('请选择所属组织');
loading();
return;
}
if (payload.dataScopeRange === 3 && (!payload.dataScopeDeptIds || !payload.dataScopeDeptIds.length)) {
this.$message.warning('请选择自定义数据权限组织');
loading();
@@ -336,11 +331,6 @@ export default {
},
rowUpdate(row, index, done, loading) {
const payload = this.normalizeSubmitRow(row, true);
if (!payload.deptId) {
this.$message.warning('请选择所属组织');
loading();
return;
}
if (payload.dataScopeRange === 3 && (!payload.dataScopeDeptIds || !payload.dataScopeDeptIds.length)) {
this.$message.warning('请选择自定义数据权限组织');
loading();
+18 -3
View File
@@ -60,7 +60,11 @@
<span>{{ row.idCardNo || '-' }}</span>
</template>
<template #organizationName="{ row }">
{{ String(row.organizationName || '').replace(/^[\s\u3000]+/, '') }}
<span
v-if="isBlankOrganizationName(row.organizationName)"
class="organization-name--empty"
>未分配组织</span>
<span v-else>{{ String(row.organizationName || '').replace(/^[\s\u3000]+/, '') }}</span>
</template>
<template #status="{ row }">
<el-tag :type="row.status === 1 ? 'primary' : 'info'" class="status-text">
@@ -596,7 +600,11 @@
<span>{{ row.plateNo || '-' }}</span>
</template>
<template #organizationName="{ row }">
{{ String(row.organizationName || '').replace(/^[\s\u3000]+/, '') }}
<span
v-if="isBlankOrganizationName(row.organizationName)"
class="organization-name--empty"
>未分配组织</span>
<span v-else>{{ String(row.organizationName || '').replace(/^[\s\u3000]+/, '') }}</span>
</template>
<template #boundDriver="{ row }">
<span>{{ row.boundDriver || '-' }}</span>
@@ -747,6 +755,8 @@ export default {
expired: 0,
},
driverBox: false,
// 搜索栏组织级联追加「未分配组织」一项(哨兵值见 organization-search mixin)
enableUnassignedOrgFilter: true,
vehicleSelectorVisible: false,
vehicleSelectorLoading: false,
vehicleSelectorOption,
@@ -861,7 +871,7 @@ export default {
emergencyContactMobile: [
{ required: true, message: '请输入紧急联系人手机号', trigger: 'blur' },
],
organizationName: [{ required: true, message: '请选择所属组织', trigger: 'change' }],
// 后端已允许所属组织为空,这里不做必填校验
idCardFront: [{ required: true, message: '请上传身份证正面照', trigger: 'change' }],
idCardBack: [{ required: true, message: '请上传身份证反面照', trigger: 'change' }],
drivingLicenseFront: [{ required: true, message: '请上传驾驶证主页', trigger: 'change' }],
@@ -1958,6 +1968,11 @@ export default {
}
}
// 空组织记录在列表里的占位文案
.organization-name--empty {
color: #909399;
}
.driver-page__date-expiring {
color: #d40000;
font-weight: 600;