diff --git a/src/api/business/waybill-manage.js b/src/api/business/waybill-manage.js index 5c7f99c..ac31975 100644 --- a/src/api/business/waybill-manage.js +++ b/src/api/business/waybill-manage.js @@ -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({ diff --git a/src/views/business/components/waybill-manage-page.vue b/src/views/business/components/waybill-manage-page.vue index 1731f29..5d6eeeb 100644 --- a/src/views/business/components/waybill-manage-page.vue +++ b/src/views/business/components/waybill-manage-page.vue @@ -2215,6 +2215,14 @@ }} + 编辑 + + + + + + + {{ + waybillPunchEditRecord.nodeName || waybillPunchEditRecord.nodeCode || '-' + }} + + + + + + 已打卡 + 未打卡 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ photo.type || '凭证' }} + + 删除 + + + + 添加 + + + + 在途打卡只保留 1 张照片 + + + + + + + + + 取消 + + 保存 + + + + 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;