✨ 运单详情打卡详情支持编辑打卡记录
打卡时间轴每条记录后面加「编辑」文本按钮,弹窗回显当前填写的信息,可改可存; 打卡状态也在可编辑之列。 - 字段按过程配置取舍:打卡定位决定地址/经纬度,上传货量类型决定重量/体积/数量, 上传凭证决定照片;记录里已有值的项即使配置后来改小也保留,否则历史数据再也改不掉 - 接单/回单是确认节点,过程配置里没有打卡定位开关,不采信其模板默认的 location - 未打卡保存即清除该节点打卡流水,弹窗有对应提示;在途打卡表只有单张照片字段, 上传时只留一张 - 入口与运单编辑同权限(waybill_manage_edit),管理员天然可见
This commit is contained in:
@@ -34,6 +34,14 @@ export const getPunchRecords = waybillId =>
|
||||
params: { waybillId },
|
||||
});
|
||||
|
||||
/** 编辑打卡记录(含打卡状态;punched=false 表示取消打卡) */
|
||||
export const savePunchRecord = data =>
|
||||
request({
|
||||
url: `${baseUrl}/punch-record/save`,
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
|
||||
/** 运单车辆实时定位 */
|
||||
export const locateVehicle = id =>
|
||||
request({
|
||||
|
||||
@@ -2215,6 +2215,14 @@
|
||||
}}</el-link
|
||||
>
|
||||
</template>
|
||||
<el-link
|
||||
v-if="canEditWaybillPunchRecord"
|
||||
class="waybill-manage-page__punch-record-edit-link"
|
||||
type="primary"
|
||||
:underline="false"
|
||||
@click="openWaybillPunchRecordEdit(record)"
|
||||
>编辑</el-link
|
||||
>
|
||||
</div>
|
||||
<template v-if="record.punched">
|
||||
<div
|
||||
@@ -2762,6 +2770,168 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="waybillPunchRecordEditDialog.visible"
|
||||
title="编辑打卡记录"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
width="720px"
|
||||
top="8vh"
|
||||
class="waybill-manage-page__punch-edit-dialog"
|
||||
>
|
||||
<el-form
|
||||
ref="waybillPunchRecordEditFormRef"
|
||||
:model="waybillPunchEditForm"
|
||||
:rules="waybillPunchEditRules"
|
||||
label-position="right"
|
||||
label-width="96px"
|
||||
>
|
||||
<section-card title="打卡信息">
|
||||
<el-row :gutter="18">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="过程节点">
|
||||
<span class="waybill-manage-page__punch-edit-readonly">{{
|
||||
waybillPunchEditRecord.nodeName || waybillPunchEditRecord.nodeCode || '-'
|
||||
}}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="打卡状态" prop="punched">
|
||||
<el-radio-group v-model="waybillPunchEditForm.punched">
|
||||
<el-radio :label="true">已打卡</el-radio>
|
||||
<el-radio :label="false">未打卡</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col v-if="waybillPunchEditPunchDisabled" :span="24">
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
title="该节点过程配置为不打卡,标记已打卡仅作数据补录"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col v-if="!waybillPunchEditForm.punched" :span="24">
|
||||
<el-alert
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
title="保存后该节点的打卡流水将被清除,时间轴上显示为「未打卡」"
|
||||
/>
|
||||
</el-col>
|
||||
<template v-else>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="打卡时间" prop="punchTime">
|
||||
<el-date-picker
|
||||
v-model="waybillPunchEditForm.punchTime"
|
||||
type="datetime"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="请选择打卡时间"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col v-if="waybillPunchEditFields.location" :span="24">
|
||||
<el-form-item label="打卡地址">
|
||||
<el-input
|
||||
v-model="waybillPunchEditForm.address"
|
||||
maxlength="500"
|
||||
placeholder="请输入"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col v-if="waybillPunchEditFields.location" :span="12">
|
||||
<el-form-item label="经度">
|
||||
<el-input v-model="waybillPunchEditForm.longitude" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col v-if="waybillPunchEditFields.location" :span="12">
|
||||
<el-form-item label="纬度">
|
||||
<el-input v-model="waybillPunchEditForm.latitude" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col v-for="item in waybillPunchEditFields.cargoFields" :key="item.prop" :span="8">
|
||||
<el-form-item :label="item.label">
|
||||
<el-input
|
||||
v-model="waybillPunchEditForm[item.prop]"
|
||||
maxlength="32"
|
||||
placeholder="请输入"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="waybillPunchEditForm.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
placeholder="请输入"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col v-if="waybillPunchEditFields.voucher" :span="24">
|
||||
<el-form-item label="打卡照片">
|
||||
<div class="waybill-manage-page__punch-edit-photos">
|
||||
<div
|
||||
v-for="(photo, index) in waybillPunchEditForm.photos"
|
||||
:key="`${photo.url}-${index}`"
|
||||
class="waybill-manage-page__punch-edit-photo"
|
||||
>
|
||||
<el-image
|
||||
:src="photo.url"
|
||||
:preview-src-list="waybillPunchEditPhotoUrls"
|
||||
:initial-index="index"
|
||||
fit="cover"
|
||||
/>
|
||||
<div class="waybill-manage-page__punch-edit-photo-label">
|
||||
{{ photo.type || '凭证' }}
|
||||
</div>
|
||||
<el-link
|
||||
type="danger"
|
||||
:underline="false"
|
||||
@click="removeWaybillPunchEditPhoto(index)"
|
||||
>删除</el-link
|
||||
>
|
||||
</div>
|
||||
<el-upload
|
||||
action="/api/blade-resource/oss/endpoint/put-file"
|
||||
name="file"
|
||||
:headers="waybillExceptionUploadHeaders"
|
||||
:show-file-list="false"
|
||||
:before-upload="beforeWaybillPunchPhotoUpload"
|
||||
:on-success="handleWaybillPunchPhotoSuccess"
|
||||
:on-error="handleWaybillPunchPhotoError"
|
||||
>
|
||||
<div class="waybill-manage-page__punch-edit-photo-add">+ 添加</div>
|
||||
</el-upload>
|
||||
</div>
|
||||
<div
|
||||
v-if="waybillPunchEditRecord.type === 'enroute'"
|
||||
class="waybill-manage-page__punch-edit-photo-tip"
|
||||
>
|
||||
在途打卡只保留 1 张照片
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
</el-row>
|
||||
</section-card>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="waybillPunchRecordEditDialog.visible = false">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="waybillPunchRecordEditDialog.submitting"
|
||||
@click="submitWaybillPunchRecordEdit"
|
||||
>
|
||||
保存
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="attachmentDocumentPreviewVisible"
|
||||
:title="attachmentPreviewFile.name || '附件预览'"
|
||||
@@ -3492,7 +3662,12 @@ import {
|
||||
getList as getProcessConfigList,
|
||||
getVoucherImages as getProcessConfigVoucherImages,
|
||||
} from '@/api/business/process-config';
|
||||
import { getPunchRecords as getWaybillPunchRecords, locateVehicle, trackVehicle } from '@/api/business/waybill-manage';
|
||||
import {
|
||||
getPunchRecords as getWaybillPunchRecords,
|
||||
locateVehicle,
|
||||
savePunchRecord,
|
||||
trackVehicle,
|
||||
} from '@/api/business/waybill-manage';
|
||||
import VoucherExecutionDetail from './voucher-execution-detail.vue';
|
||||
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
||||
import {
|
||||
@@ -3790,6 +3965,23 @@ export default {
|
||||
{ max: 500, message: '上报说明不能超过500个字符', trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
waybillPunchRecordEditDialog: {
|
||||
visible: false,
|
||||
submitting: false,
|
||||
},
|
||||
waybillPunchEditRecord: {},
|
||||
waybillPunchEditForm: {
|
||||
punched: true,
|
||||
punchTime: '',
|
||||
address: '',
|
||||
longitude: '',
|
||||
latitude: '',
|
||||
weight: '',
|
||||
volume: '',
|
||||
quantity: '',
|
||||
remark: '',
|
||||
photos: [],
|
||||
},
|
||||
waybillRouteChangeBox: false,
|
||||
waybillRouteChangeTab: 'change',
|
||||
waybillRouteChangeRows: [],
|
||||
@@ -4143,6 +4335,49 @@ export default {
|
||||
addBtn: this.canCreate,
|
||||
};
|
||||
},
|
||||
// 打卡记录编辑入口与运单编辑同权限:能改运单的人才能订正它的打卡流水
|
||||
canEditWaybillPunchRecord() {
|
||||
return this.hasPermission(`${this.config.permission}_edit`) && !this.readonlyScope;
|
||||
},
|
||||
waybillPunchEditNode() {
|
||||
return this.findWaybillPunchEditNodeConfig(this.waybillPunchEditRecord);
|
||||
},
|
||||
// 编辑弹窗的字段按过程配置取舍:配置里没采的项不展示。
|
||||
// 记录里已有值的项即使配置后来改小也保留——否则历史数据里的货量/照片再也改不掉。
|
||||
waybillPunchEditFields() {
|
||||
const record = this.waybillPunchEditRecord || {};
|
||||
const node = this.waybillPunchEditNode;
|
||||
const hasValue = value => value !== undefined && value !== null && String(value).trim() !== '';
|
||||
// 接单/回单是确认节点,过程配置里没有「打卡定位」开关,其 location 是模板默认值,不采信
|
||||
const isConfirmNode = ['accept', 'return'].includes(String(node?.key || ''));
|
||||
const cargoTypes = Array.isArray(node?.cargoTypes) ? node.cargoTypes : [];
|
||||
const cargoFields = [
|
||||
{ prop: 'weight', label: '重量(吨)', cargoType: '重量' },
|
||||
{ prop: 'volume', label: '体积(方)', cargoType: '体积' },
|
||||
{ prop: 'quantity', label: '数量(件)', cargoType: '数量' },
|
||||
].filter(item => cargoTypes.includes(item.cargoType) || hasValue(record[item.prop]));
|
||||
return {
|
||||
location:
|
||||
!isConfirmNode &&
|
||||
(Boolean(node?.location) ||
|
||||
hasValue(record.address) ||
|
||||
hasValue(record.longitude) ||
|
||||
hasValue(record.latitude)),
|
||||
cargoFields,
|
||||
voucher: Boolean(node?.uploadVoucher) || (record.photos || []).length > 0,
|
||||
};
|
||||
},
|
||||
// 过程配置为「不打卡」的节点,补录只是数据订正,司机端不会因此出现该节点的打卡
|
||||
waybillPunchEditPunchDisabled() {
|
||||
return this.waybillPunchEditNode?.punch === false;
|
||||
},
|
||||
waybillPunchEditRules() {
|
||||
if (!this.waybillPunchEditForm.punched) return {};
|
||||
return { punchTime: [{ required: true, message: '请选择打卡时间', trigger: 'change' }] };
|
||||
},
|
||||
waybillPunchEditPhotoUrls() {
|
||||
return (this.waybillPunchEditForm.photos || []).map(item => item.url).filter(Boolean);
|
||||
},
|
||||
// 打卡时间轴按打卡时间先后展示:已打卡记录按打卡时间升序在前,
|
||||
// 未打卡记录保持流程节点原顺序排在其后。
|
||||
orderedWaybillPunchRecords() {
|
||||
@@ -5596,6 +5831,105 @@ export default {
|
||||
this.waybillPunchRecordsLoading = false;
|
||||
});
|
||||
},
|
||||
// 打卡记录 → 过程配置节点:nodeCode 命中优先,回退 nodeName(与后端 listPunchRecords 同口径)
|
||||
findWaybillPunchEditNodeConfig(record = {}) {
|
||||
const nodes = Array.isArray(this.waybillDetailProcessNodes)
|
||||
? this.waybillDetailProcessNodes
|
||||
: [];
|
||||
const nodeCode = String(record?.nodeCode || '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
const nodeName = String(record?.nodeName || '').trim();
|
||||
return (
|
||||
nodes.find(node => nodeCode && String(node.key || '').trim().toLowerCase() === nodeCode) ||
|
||||
nodes.find(node => nodeName && String(node.name || '').trim() === nodeName) ||
|
||||
null
|
||||
);
|
||||
},
|
||||
openWaybillPunchRecordEdit(record = {}) {
|
||||
if (!this.detailRow?.id || !record) return;
|
||||
this.waybillPunchEditRecord = { ...record };
|
||||
this.waybillPunchEditForm = {
|
||||
punched: record.punched !== false,
|
||||
punchTime: record.punchTime || this.$dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||
address: record.address || '',
|
||||
longitude: record.longitude || '',
|
||||
latitude: record.latitude || '',
|
||||
weight: record.weight || '',
|
||||
volume: record.volume || '',
|
||||
quantity: record.quantity || '',
|
||||
remark: record.remark || '',
|
||||
photos: (record.photos || [])
|
||||
.filter(photo => photo?.url)
|
||||
.map(photo => ({ url: photo.url, type: photo.voucherType || photo.type || '' })),
|
||||
};
|
||||
this.waybillPunchRecordEditDialog.visible = true;
|
||||
},
|
||||
beforeWaybillPunchPhotoUpload(file) {
|
||||
const validType = ['image/jpeg', 'image/png', 'image/jpg', 'image/bmp'].includes(file.type);
|
||||
if (!validType) {
|
||||
this.$message.error('仅支持 JPG、PNG、BMP 图片');
|
||||
return false;
|
||||
}
|
||||
if (file.size / 1024 / 1024 >= 5) {
|
||||
this.$message.error('图片大小不能超过 5MB');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
handleWaybillPunchPhotoSuccess(res) {
|
||||
if (res.code !== 200 || !res.data) {
|
||||
this.$message.error(res.msg || '上传失败');
|
||||
return;
|
||||
}
|
||||
const url = res.data.link || res.data.url || res.data.domain || '';
|
||||
if (!url) return;
|
||||
const photos = [...this.waybillPunchEditForm.photos, { url, type: '' }];
|
||||
// 在途打卡表只有一个照片字段(单张),多传的保存时会被丢掉,这里先只留一张
|
||||
this.waybillPunchEditForm.photos =
|
||||
this.waybillPunchEditRecord.type === 'enroute' ? photos.slice(0, 1) : photos;
|
||||
},
|
||||
handleWaybillPunchPhotoError() {
|
||||
this.$message.error('上传失败');
|
||||
},
|
||||
removeWaybillPunchEditPhoto(index) {
|
||||
const photos = [...this.waybillPunchEditForm.photos];
|
||||
photos.splice(index, 1);
|
||||
this.waybillPunchEditForm.photos = photos;
|
||||
},
|
||||
async submitWaybillPunchRecordEdit() {
|
||||
await this.$refs.waybillPunchRecordEditFormRef.validate();
|
||||
const record = this.waybillPunchEditRecord;
|
||||
const form = this.waybillPunchEditForm;
|
||||
const keepDetail = form.punched;
|
||||
const submit =
|
||||
typeof this.api?.savePunchRecord === 'function' ? this.api.savePunchRecord : savePunchRecord;
|
||||
this.waybillPunchRecordEditDialog.submitting = true;
|
||||
try {
|
||||
await submit({
|
||||
waybillId: this.detailRow.id,
|
||||
type: record.type === 'enroute' ? 'enroute' : 'node',
|
||||
nodeCode: record.nodeCode || '',
|
||||
nodeName: record.nodeName || '',
|
||||
punched: form.punched,
|
||||
// 未打卡时后端会清空该节点流水,明细项不再上传
|
||||
punchTime: keepDetail ? form.punchTime : '',
|
||||
address: keepDetail ? form.address : '',
|
||||
longitude: keepDetail ? form.longitude : '',
|
||||
latitude: keepDetail ? form.latitude : '',
|
||||
weight: keepDetail ? form.weight : '',
|
||||
volume: keepDetail ? form.volume : '',
|
||||
quantity: keepDetail ? form.quantity : '',
|
||||
remark: keepDetail ? form.remark : '',
|
||||
photos: keepDetail ? form.photos.map(photo => ({ url: photo.url, type: photo.type })) : [],
|
||||
});
|
||||
this.$message.success('保存成功');
|
||||
this.waybillPunchRecordEditDialog.visible = false;
|
||||
this.loadWaybillPunchRecords();
|
||||
} finally {
|
||||
this.waybillPunchRecordEditDialog.submitting = false;
|
||||
}
|
||||
},
|
||||
previewDriverUploadPhoto(photos, index) {
|
||||
const list = (photos || []).map(item => item?.url).filter(Boolean);
|
||||
if (!list.length) return;
|
||||
@@ -11212,6 +11546,67 @@ export default {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
// 编辑入口推到行尾:它是整条记录的操作,与节点名旁的异常链接区分开
|
||||
&__punch-record-edit-link {
|
||||
margin-left: auto;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&__punch-edit-readonly {
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
&__punch-edit-photos {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
&__punch-edit-photo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
width: 96px;
|
||||
|
||||
.el-image {
|
||||
display: block;
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
border: 1px solid #eff1f7;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&__punch-edit-photo-label {
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
color: #606266;
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
&__punch-edit-photo-add {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
border: 1px dashed #dcdfe6;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&__punch-edit-photo-tip {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
&__punch-record-line {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
|
||||
Reference in New Issue
Block a user