✨ 打卡照片按凭证类型分组,编辑弹窗去掉打卡状态开关
- 打卡记录照片按类型分组展示与上传,每个类型各自最多 9 张,组内第一张标「主图」 - 打卡照片是每个打卡节点必有的类型,不由过程配置决定,与各凭证类型并列且互不替代 - 附件区抽出 punch-record-photos 组件,运单详情与配载单详情共用;编辑态沿用 96px 固定方图 - 弹窗上传体积上限由 5MB 提到 20MB,与小程序一致 - 弹窗不再提供「打卡状态」开关:未打卡的记录点「补卡」进来,保存即补出一条已打卡流水
This commit is contained in:
@@ -0,0 +1,305 @@
|
||||
<template>
|
||||
<div v-if="groups.length" class="punch-record-photos">
|
||||
<section v-for="group in groups" :key="group.type" class="punch-record-photos__group">
|
||||
<div class="punch-record-photos__head">
|
||||
<span class="punch-record-photos__title">{{ group.type }}</span>
|
||||
<span v-if="editable" class="punch-record-photos__count">{{ countText(group) }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="punch-record-photos__grid"
|
||||
:class="{ 'punch-record-photos__grid--edit': editable }"
|
||||
>
|
||||
<div
|
||||
v-for="(item, groupIndex) in group.items"
|
||||
:key="`${item.photo.url}-${item.index}`"
|
||||
class="punch-record-photos__item"
|
||||
@click="handlePreview(item.index)"
|
||||
>
|
||||
<el-image
|
||||
:src="item.photo.url"
|
||||
:preview-src-list="nativePreview ? previewUrls : []"
|
||||
:initial-index="nativePreview ? item.index : 0"
|
||||
:preview-teleported="nativePreview"
|
||||
: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"
|
||||
type="danger"
|
||||
:underline="false"
|
||||
@click.stop="$emit('remove', item.index)"
|
||||
>删除</el-link
|
||||
>
|
||||
</div>
|
||||
<el-upload
|
||||
v-if="editable && !group.full"
|
||||
class="punch-record-photos__upload"
|
||||
:action="uploadAction"
|
||||
name="file"
|
||||
:headers="uploadHeaders"
|
||||
:show-file-list="false"
|
||||
:before-upload="file => handleBeforeUpload(file, group.type)"
|
||||
:on-success="res => $emit('upload-success', res, group.type)"
|
||||
:on-error="error => $emit('upload-error', error, group.type)"
|
||||
>
|
||||
<div class="punch-record-photos__add">+ 添加</div>
|
||||
</el-upload>
|
||||
<div v-else-if="editable" class="punch-record-photos__add punch-record-photos__add--full">
|
||||
已满 {{ maxPerType }} 张
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
// 打卡照片是每个打卡节点必有的一个照片类型,不由过程配置的凭证类型决定;
|
||||
// 它与各凭证类型各自最多 9 张,同一类型下第一张即界面上的「主图」。
|
||||
const PUNCH_PHOTO_TYPE = '打卡照片';
|
||||
// 照片没带类型时的兜底名称,与后端落库时的兜底口径一致
|
||||
const PHOTO_TYPE_FALLBACK = '凭证';
|
||||
const MAX_PHOTOS_PER_TYPE = 9;
|
||||
|
||||
const props = defineProps({
|
||||
// 扁平照片数组:管理端回显用 voucherType,司机端回显与旧数据用 type
|
||||
photos: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
// 恒定展示的照片类型:「打卡照片」+ 该节点过程配置勾选的凭证类型。
|
||||
// 留空则只按照片自身带的类型分组(只读回显用)。
|
||||
groupTypes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
// 编辑态:每个类型自带「添加」格与删除链接,并显示已选/上限
|
||||
editable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 每个类型的张数上限;0 表示不限制(在途打卡只有一个照片字段,张数由「只保留第一张」约束)
|
||||
maxPerType: {
|
||||
type: Number,
|
||||
default: MAX_PHOTOS_PER_TYPE,
|
||||
},
|
||||
// 编辑态用 el-image 自带预览;只读态不发预览事件,把点击交给调用方开自己的看图器
|
||||
nativePreview: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
lazy: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
uploadAction: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
uploadHeaders: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
// 返回 false 即中止本次上传;类型、体积、张数校验都在调用方
|
||||
beforeUpload: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['preview', 'remove', 'upload-success', 'upload-error']);
|
||||
|
||||
const photoType = photo =>
|
||||
String(photo?.voucherType || photo?.type || '').trim() || PHOTO_TYPE_FALLBACK;
|
||||
|
||||
const groups = computed(() => {
|
||||
const photos = Array.isArray(props.photos) ? props.photos : [];
|
||||
const names = [];
|
||||
const buckets = new Map();
|
||||
const ensure = name => {
|
||||
if (!buckets.has(name)) {
|
||||
buckets.set(name, []);
|
||||
names.push(name);
|
||||
}
|
||||
return buckets.get(name);
|
||||
};
|
||||
// 恒定展示的类型先占位:一张照片都没有的类型也要出现,否则编辑态下没有添加入口
|
||||
props.groupTypes.forEach(type => {
|
||||
const name = String(type || '').trim();
|
||||
if (name) ensure(name);
|
||||
});
|
||||
// 按上传顺序入组,组间顺序即类型首次出现的顺序,不重排照片
|
||||
photos.forEach((photo, index) => {
|
||||
if (!photo?.url) return;
|
||||
ensure(photoType(photo)).push({ photo, index });
|
||||
});
|
||||
const list = names.map(type => {
|
||||
const items = buckets.get(type);
|
||||
return {
|
||||
type,
|
||||
items,
|
||||
full: props.editable && props.maxPerType > 0 && items.length >= props.maxPerType,
|
||||
};
|
||||
});
|
||||
// 编辑态至少要有一个分组,否则记录里还没有照片时就没有添加入口
|
||||
if (props.editable && !list.length) {
|
||||
return [{ type: props.groupTypes[0] || PUNCH_PHOTO_TYPE, items: [], full: false }];
|
||||
}
|
||||
return list;
|
||||
});
|
||||
|
||||
// 预览列表按原始下标对齐,删除/分组都不改变点击某张时打开的是哪一张
|
||||
const previewUrls = computed(() =>
|
||||
(Array.isArray(props.photos) ? props.photos : []).map(photo => photo?.url || '')
|
||||
);
|
||||
|
||||
const handlePreview = index => {
|
||||
if (props.nativePreview) return;
|
||||
emit('preview', index);
|
||||
};
|
||||
|
||||
const handleBeforeUpload = (file, type) => {
|
||||
if (typeof props.beforeUpload !== 'function') return true;
|
||||
return props.beforeUpload(file, type);
|
||||
};
|
||||
|
||||
const countText = group =>
|
||||
props.maxPerType > 0 ? `${group.items.length}/${props.maxPerType}` : String(group.items.length);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.punch-record-photos {
|
||||
// 弹窗里本组件挂在 el-form-item__content 下,那是个 flex 容器:
|
||||
// 不给宽度就会收缩成内容宽,里面的网格跟着塌成一小块
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-top: 4px;
|
||||
|
||||
&__group {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
&__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
&__count {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
// 只读回显:撑满容器宽度的五列网格
|
||||
&__grid {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
// 编辑态沿用弹窗原本的固定 96px 方图 + 自动换行:尺寸不随容器宽窄变化
|
||||
&__grid--edit {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
&__item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
.el-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
border: 1px solid #eff1f7;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background: #f5f7fa;
|
||||
}
|
||||
}
|
||||
|
||||
&__grid--edit &__item {
|
||||
width: 96px;
|
||||
|
||||
.el-image {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
aspect-ratio: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&__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;
|
||||
}
|
||||
|
||||
&__upload {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__grid--edit &__upload {
|
||||
width: 96px;
|
||||
}
|
||||
|
||||
&__add {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
border: 1px dashed #dcdfe6;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&__grid--edit &__add {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
aspect-ratio: auto;
|
||||
}
|
||||
|
||||
&__add--full {
|
||||
color: #c0c4cc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -2221,7 +2221,7 @@
|
||||
type="primary"
|
||||
:underline="false"
|
||||
@click="openWaybillPunchRecordEdit(record)"
|
||||
>编辑</el-link
|
||||
>{{ record.punched ? '编辑打卡' : '补卡' }}</el-link
|
||||
>
|
||||
</div>
|
||||
<template v-if="record.punched">
|
||||
@@ -2245,22 +2245,12 @@
|
||||
>
|
||||
备注:{{ record.remark }}
|
||||
</div>
|
||||
<div
|
||||
<punch-record-photos
|
||||
v-if="record.photos && record.photos.length"
|
||||
class="waybill-manage-page__punch-record-photos"
|
||||
>
|
||||
<div
|
||||
v-for="(photo, pi) in record.photos"
|
||||
:key="`${record.nodeCode || index}-${pi}`"
|
||||
class="waybill-manage-page__driver-upload-item"
|
||||
@click="previewDriverUploadPhoto(record.photos, pi)"
|
||||
>
|
||||
<el-image :src="photo.url" fit="cover" lazy />
|
||||
<div class="waybill-manage-page__driver-upload-label">
|
||||
{{ photo.label || `${record.nodeName}-凭证` }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
:photos="record.photos"
|
||||
lazy
|
||||
@preview="previewDriverUploadPhoto(record.photos, $event)"
|
||||
/>
|
||||
</template>
|
||||
<div v-else class="waybill-manage-page__punch-record-line">暂未打卡</div>
|
||||
</div>
|
||||
@@ -2787,138 +2777,99 @@
|
||||
>
|
||||
<section-card title="打卡信息">
|
||||
<el-row :gutter="18">
|
||||
<el-col :span="12">
|
||||
<el-col :span="24">
|
||||
<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="该节点过程配置为不打卡,标记已打卡仅作数据补录"
|
||||
title="该节点过程配置为不打卡,保存仅作数据补录"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col v-if="!waybillPunchEditForm.punched" :span="24">
|
||||
<el-alert
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
title="保存后该节点的打卡流水将被清除,时间轴上显示为「未打卡」"
|
||||
/>
|
||||
<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"
|
||||
readonly
|
||||
maxlength="500"
|
||||
placeholder="点击选择地图地址"
|
||||
:suffix-icon="Location"
|
||||
@click="waybillPunchAddressMapVisible = true"
|
||||
/>
|
||||
</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 :span="24">
|
||||
<el-form-item label="打卡照片">
|
||||
<punch-record-photos
|
||||
:photos="waybillPunchEditForm.photos"
|
||||
:group-types="waybillPunchEditPhotoTypes"
|
||||
:max-per-type="waybillPunchEditPhotoLimit"
|
||||
editable
|
||||
native-preview
|
||||
upload-action="/api/blade-resource/oss/endpoint/put-file"
|
||||
:upload-headers="waybillExceptionUploadHeaders"
|
||||
:before-upload="beforeWaybillPunchPhotoUpload"
|
||||
@remove="removeWaybillPunchEditPhoto"
|
||||
@upload-success="handleWaybillPunchPhotoSuccess"
|
||||
@upload-error="handleWaybillPunchPhotoError"
|
||||
/>
|
||||
<div
|
||||
v-if="waybillPunchEditRecord.type === 'enroute'"
|
||||
class="waybill-manage-page__punch-edit-photo-tip"
|
||||
>
|
||||
在途打卡只保留 1 张照片
|
||||
</div>
|
||||
</el-form-item>
|
||||
</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"
|
||||
readonly
|
||||
maxlength="500"
|
||||
placeholder="点击选择地图地址"
|
||||
:suffix-icon="Location"
|
||||
@click="waybillPunchAddressMapVisible = true"
|
||||
/>
|
||||
</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 :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>
|
||||
@@ -3679,6 +3630,7 @@ import {
|
||||
trackVehicle,
|
||||
} from '@/api/business/waybill-manage';
|
||||
import VoucherExecutionDetail from './voucher-execution-detail.vue';
|
||||
import PunchRecordPhotos from './punch-record-photos.vue';
|
||||
import AddressMapPicker from '@/components/address-map-picker/main.vue';
|
||||
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
||||
import {
|
||||
@@ -3886,6 +3838,7 @@ export default {
|
||||
ElImageViewer,
|
||||
OpenFileViewer,
|
||||
VoucherExecutionDetail,
|
||||
PunchRecordPhotos,
|
||||
AddressMapPicker,
|
||||
},
|
||||
name: 'WaybillManagePage',
|
||||
@@ -3983,7 +3936,6 @@ export default {
|
||||
},
|
||||
waybillPunchEditRecord: {},
|
||||
waybillPunchEditForm: {
|
||||
punched: true,
|
||||
punchTime: '',
|
||||
address: '',
|
||||
longitude: '',
|
||||
@@ -4386,11 +4338,21 @@ export default {
|
||||
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);
|
||||
// 照片类型只有两个来源:固定的「打卡照片」,和该节点过程配置勾选的凭证类型;
|
||||
// 不接受配置外的类型。在途打卡表只有一个照片字段、也没有类型列,不按凭证类型分组
|
||||
waybillPunchEditPhotoTypes() {
|
||||
if (this.waybillPunchEditRecord.type === 'enroute') return [];
|
||||
const voucherTypes = Array.isArray(this.waybillPunchEditNode?.voucherTypes)
|
||||
? this.waybillPunchEditNode.voucherTypes
|
||||
: [];
|
||||
return ['打卡照片', ...voucherTypes];
|
||||
},
|
||||
// 打卡照片与各凭证类型各自最多 9 张,不另设节点总张数上限;
|
||||
// 在途打卡用 0 表示不限制——它保存时只保留第一张,张数由那一步约束
|
||||
waybillPunchEditPhotoLimit() {
|
||||
return this.waybillPunchEditRecord.type === 'enroute' ? 0 : 9;
|
||||
},
|
||||
// 打卡时间轴按打卡时间先后展示:已打卡记录按打卡时间升序在前,
|
||||
// 未打卡记录保持流程节点原顺序排在其后。
|
||||
@@ -5876,8 +5838,8 @@ export default {
|
||||
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 || '',
|
||||
@@ -5914,26 +5876,40 @@ export default {
|
||||
this.waybillPunchEditForm.latitude = Number(selection.latitude).toFixed(6);
|
||||
}
|
||||
},
|
||||
beforeWaybillPunchPhotoUpload(file) {
|
||||
beforeWaybillPunchPhotoUpload(file, type = '') {
|
||||
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');
|
||||
// 单张 20MB 与司机端一致:凭证要原图,5MB 会把手机拍的正常凭证挡在门外
|
||||
if (file.size / 1024 / 1024 >= 20) {
|
||||
this.$message.error('图片大小不能超过 20MB');
|
||||
return false;
|
||||
}
|
||||
// 每个照片类型各自最多 9 张。界面已经不给满员的类型「添加」入口,这里再兜一层。
|
||||
// 张数按界面上的分组口径统计:没带类型的照片算进兜底类型「凭证」
|
||||
const limit = this.waybillPunchEditPhotoLimit;
|
||||
if (limit > 0 && type) {
|
||||
const used = (this.waybillPunchEditForm.photos || []).filter(
|
||||
photo => (String(photo?.type || '').trim() || '凭证') === type
|
||||
).length;
|
||||
if (used >= limit) {
|
||||
this.$message.error(`${type}最多上传 ${limit} 张`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
handleWaybillPunchPhotoSuccess(res) {
|
||||
handleWaybillPunchPhotoSuccess(res, type = '') {
|
||||
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: '' }];
|
||||
// 照片落进点开的那一组并带上该类型的名字:类型写空,保存时会被按下标回推成别的类型
|
||||
const photos = [...this.waybillPunchEditForm.photos, { url, type }];
|
||||
// 在途打卡表只有一个照片字段(单张),多传的保存时会被丢掉,这里先只留一张
|
||||
this.waybillPunchEditForm.photos =
|
||||
this.waybillPunchEditRecord.type === 'enroute' ? photos.slice(0, 1) : photos;
|
||||
@@ -5950,7 +5926,6 @@ export default {
|
||||
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;
|
||||
@@ -5960,17 +5935,17 @@ export default {
|
||||
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 })) : [],
|
||||
// 弹窗没有打卡状态开关:无论「补卡」还是「编辑打卡」,保存都落一条已打卡流水
|
||||
punched: true,
|
||||
punchTime: form.punchTime,
|
||||
address: form.address,
|
||||
longitude: form.longitude,
|
||||
latitude: form.latitude,
|
||||
weight: form.weight,
|
||||
volume: form.volume,
|
||||
quantity: form.quantity,
|
||||
remark: form.remark,
|
||||
photos: form.photos.map(photo => ({ url: photo.url, type: photo.type })),
|
||||
});
|
||||
this.$message.success('保存成功');
|
||||
this.waybillPunchRecordEditDialog.visible = false;
|
||||
@@ -11606,50 +11581,6 @@ export default {
|
||||
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;
|
||||
@@ -11665,7 +11596,6 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
&__punch-record-photos,
|
||||
&__driver-upload-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
|
||||
@@ -694,22 +694,12 @@
|
||||
>
|
||||
备注:{{ record.remark }}
|
||||
</div>
|
||||
<div
|
||||
<punch-record-photos
|
||||
v-if="record.photos && record.photos.length"
|
||||
class="loading-detail__punch-record-photos"
|
||||
>
|
||||
<div
|
||||
v-for="(photo, pi) in record.photos"
|
||||
:key="`${record.nodeCode || index}-${pi}`"
|
||||
class="loading-detail__driver-upload-item"
|
||||
@click="previewDriverUploadPhoto(record.photos, pi)"
|
||||
>
|
||||
<el-image :src="photo.url" fit="cover" lazy />
|
||||
<div class="loading-detail__driver-upload-label">
|
||||
{{ photo.label || `${record.nodeName}-凭证` }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
:photos="record.photos"
|
||||
lazy
|
||||
@preview="previewDriverUploadPhoto(record.photos, $event)"
|
||||
/>
|
||||
</template>
|
||||
<div v-else class="loading-detail__punch-record-line">暂未打卡</div>
|
||||
</div>
|
||||
@@ -1715,6 +1705,7 @@ import {
|
||||
getVoucherImages as getProcessConfigVoucherImages,
|
||||
} from '@/api/business/process-config';
|
||||
import VoucherExecutionDetail from './components/voucher-execution-detail.vue';
|
||||
import PunchRecordPhotos from './components/punch-record-photos.vue';
|
||||
import {
|
||||
carrierTypeOptions,
|
||||
loadingManageConfig,
|
||||
@@ -1925,6 +1916,7 @@ export default {
|
||||
Rank,
|
||||
Search,
|
||||
VoucherExecutionDetail,
|
||||
PunchRecordPhotos,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -4921,7 +4913,6 @@ export default {
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
.loading-detail__punch-record-photos,
|
||||
.loading-detail__driver-upload-grid,
|
||||
.loading-detail__voucher-image-grid {
|
||||
display: grid;
|
||||
|
||||
Reference in New Issue
Block a user