Merge remote-tracking branch 'websoft/dev'
# Conflicts: # src/views/transportCapacity/vehicle.vue
This commit is contained in:
@@ -12,6 +12,8 @@ import { h } from 'vue';
|
||||
* 1. 混入本 mixin:mixins: [organizationSearch]
|
||||
* 2. option 中「所属组织」列加上 searchslot: true(prop 为 organizationName 或 deptName 均可)
|
||||
* 3. 在 created / 初始化时调用 this.loadOrganizationOptions()
|
||||
* 4. 需要「未分配组织」筛选项的页面在 data 里置 enableUnassignedOrgFilter: true;
|
||||
* 该项只追加进搜索树 organizationSearchTreeOptions,表单级联 organizationTreeOptions 不含哨兵
|
||||
*
|
||||
* 说明:
|
||||
* - 自动识别 organizationName / deptName 两种 prop,并挂载 column.renderSearch
|
||||
@@ -26,6 +28,12 @@ import { h } from 'vue';
|
||||
*/
|
||||
const ORG_PROPS = ['organizationName', 'deptName'];
|
||||
|
||||
// 「未分配组织」筛选项的哨兵值:不是真实组织名,选中后提交给后端的就是这串字面量。
|
||||
// 后端 TransportBusiConstant.UNASSIGNED_ORG_FILTER、DriverMapper.xml / TransportVehicleMapper.xml
|
||||
// 的 organizationName 分支以及司机、车辆导出查询都按它改判为 organization_name = '';
|
||||
// 后端换成空串会让整段组织条件不生效,筛选静默失效。前端与后端这几处必须同步修改。
|
||||
const UNASSIGNED_ORG_FILTER = '__UNASSIGNED_ORG__';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
@@ -34,9 +42,18 @@ export default {
|
||||
organizationTreeOptions: [],
|
||||
organizationTreeFlatOptions: [],
|
||||
organizationLoading: false,
|
||||
// 组织筛选是否追加「未分配组织」一项;页面在 data 里覆盖为 true 才生效
|
||||
enableUnassignedOrgFilter: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 搜索栏级联的选项:开关打开时在真实组织最前面追加「未分配组织」。
|
||||
// 不能直接改 organizationTreeOptions——司机/车辆页的编辑表单也用这棵树选组织。
|
||||
organizationSearchTreeOptions() {
|
||||
return this.enableUnassignedOrgFilter
|
||||
? [this.unassignedOrgOption(), ...this.organizationTreeOptions]
|
||||
: this.organizationTreeOptions;
|
||||
},
|
||||
// 所属组织级联配置:checkStrictly 允许选中任意层级,不必选到末级
|
||||
organizationCascaderProps() {
|
||||
return {
|
||||
@@ -61,7 +78,7 @@ export default {
|
||||
? this.excludeExternalOrganization(source)
|
||||
: source;
|
||||
this.organizationTreeOptions = this.normalizeOrgTree(tree);
|
||||
this.organizationTreeFlatOptions = this.flattenOrgTree(this.organizationTreeOptions);
|
||||
this.organizationTreeFlatOptions = this.flattenOrgTree(this.organizationSearchTreeOptions);
|
||||
const option = this.tableOption || this.option;
|
||||
if (!option) return;
|
||||
ORG_PROPS.forEach(prop => {
|
||||
@@ -100,12 +117,20 @@ export default {
|
||||
};
|
||||
});
|
||||
},
|
||||
// 哨兵节点的 id 与 rawLabel 都取哨兵值,resolveOrgName 取到的仍是哨兵而不是某个真实组织名
|
||||
unassignedOrgOption() {
|
||||
return { id: UNASSIGNED_ORG_FILTER, label: '未分配组织', rawLabel: UNASSIGNED_ORG_FILTER };
|
||||
},
|
||||
flattenOrgTree(tree = []) {
|
||||
return (tree || []).flatMap(item => [
|
||||
{ id: String(item.id), label: item.label, rawLabel: item.rawLabel },
|
||||
...this.flattenOrgTree(item.children || []),
|
||||
]);
|
||||
},
|
||||
// 组织列渲染用:空串或纯空白即「未分配组织」(组织列 NOT NULL,无组织存空串)
|
||||
isBlankOrganizationName(value) {
|
||||
return String(value ?? '').trim() === '';
|
||||
},
|
||||
// 兼容两种入参:级联路径数组(取末级)或单个部门 id
|
||||
findOrgById(id) {
|
||||
const value = Array.isArray(id) ? id.at(-1) : id;
|
||||
@@ -155,7 +180,7 @@ export default {
|
||||
// 级联单选:modelValue 必须是 id 路径数组,否则控件内部状态会错乱导致无法再次选中
|
||||
if (scope.row) scope.row[prop] = Array.isArray(value) ? value : [];
|
||||
},
|
||||
options: this.organizationTreeOptions,
|
||||
options: this.organizationSearchTreeOptions,
|
||||
props: {
|
||||
label: 'label',
|
||||
value: 'id',
|
||||
|
||||
@@ -133,7 +133,7 @@ export default {
|
||||
hide: true,
|
||||
span: 24,
|
||||
order: 55,
|
||||
rules: [{ required: true, message: '请选择所属组织', trigger: 'change' }],
|
||||
// 所属组织可为空:为空表示未分配组织,账号的数据权限为空集
|
||||
},
|
||||
{
|
||||
label: '所属组织',
|
||||
|
||||
@@ -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 })),
|
||||
});
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -214,6 +214,7 @@
|
||||
- 到场、装货、发货、在途、到货、卸货、签收节点的操作要求包含是否打卡、打卡定位;装货、发货、到货、卸货、签收还包含上传货量和上传货量类型;附件要求包含上传凭证和凭证类型。
|
||||
- 在途节点额外包含打卡频次和打卡时段,打卡频次使用普通输入框,打卡时段使用时间选择器。
|
||||
- 回单节点的操作要求包含是否确认、确认完成,是否确认提供“是 / 无需确认完成”单选,确认完成使用“内部人员”复选框。
|
||||
- 回单节点的“内部人员”复选框只读,不接受手工勾选:是否确认为“是”时自动勾选(即回单需内部人员确认),为“无需确认完成”时自动取消勾选。
|
||||
- 各节点中是否打卡、上传货量、上传凭证均使用单选或复选输入,控件类型与截图保持一致。
|
||||
|
||||
- 异常与边界:
|
||||
@@ -233,6 +234,7 @@
|
||||
- 不允许非在途节点配置打卡频次和打卡时段。
|
||||
- 不允许是否打卡为否时仍强制填写打卡定位或打卡时段。
|
||||
- 不允许上传凭证关闭后仍提交凭证类型。
|
||||
- 不允许手工勾选或取消回单节点的“内部人员”复选框。
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user