fix(dispatch): present loading evidence as a single-column list
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import dayjs from 'dayjs'
|
||||
import { useAuthStore } from '@/core/auth/auth'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
@@ -41,6 +42,9 @@ async function load() {
|
||||
}
|
||||
async function refresh() { if (await load()) emit('saved') }
|
||||
function photosFor(slot: LoadingPhotoSlot) { return snapshot.value?.photos.filter(photo => photo.slot === slot) || [] }
|
||||
function savedTime(value?: string | null) {
|
||||
return value && dayjs(value).isValid() ? dayjs(value).format('YYYY-MM-DD HH:mm:ss') : '—'
|
||||
}
|
||||
async function upload(slot: LoadingPhotoSlot, event: Event, replaceFileId?: string) {
|
||||
const input = event.target as HTMLInputElement, files = Array.from(input.files || []); input.value = ''
|
||||
if (!files.length || busy.value || !editable.value) return
|
||||
@@ -88,36 +92,59 @@ onBeforeUnmount(() => { active = false; Object.values(images.value).forEach(url
|
||||
defineExpose({ busy })
|
||||
</script>
|
||||
<template>
|
||||
<div v-loading="busy" class="density-dialog-sections">
|
||||
<div v-loading="busy" class="loading-documents">
|
||||
<p>可边装车边上传五类装车资料,每类至少 1 张、最多 5 张;装车确认完成且五类齐全后自动进入派送中。</p>
|
||||
<p>司机驾驶证、车牌照片在理货审核通过后从本趟资料自动带入,可补充或更换;驾驶证支持 PDF。</p>
|
||||
<div><el-button :loading="busy" :disabled="busy" aria-label="刷新装车资料" @click="refresh">刷新</el-button></div>
|
||||
<p v-if="needsRefresh">保存状态尚未核实,请先重新读取资料后再上传。</p>
|
||||
<LoadErrorState v-if="error" compact title="装车资料操作失败" :message="error.message" :show-retry="true" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
|
||||
<table class="density-table loading-documents-table" aria-label="装车照片"><thead><tr><th class="photo-actions">操作</th><th class="photo-category">照片类别</th><th>文件名称</th><th class="photo-preview">图片预览</th><th class="photo-time">保存时间</th></tr></thead><tbody>
|
||||
<tr v-for="slot in loadingPhotoSlots" :key="slot">
|
||||
<td><label v-if="editable" class="photo-upload" :class="{ 'is-disabled': busy || photosFor(slot).length >= 5 }">{{ photosFor(slot).length >= 5 ? '已满 5 张' : '上传资料' }}<input type="file" multiple :accept="slot === 'LOADING_SCENE' ? 'image/jpeg,image/png,application/pdf' : 'image/jpeg,image/png'" :aria-label="`上传${loadingPhotoLabels[slot]}`" :disabled="busy || photosFor(slot).length >= 5" @change="upload(slot, $event)" /></label><span v-else>—</span></td>
|
||||
<td>{{ loadingPhotoLabels[slot] }}<div>{{ photosFor(slot).length }}/5 张</div></td>
|
||||
<td><div v-for="photo in snapshot?.photos.filter(item => item.slot === slot) || []" :key="photo.fileId">{{ photo.fileName }}</div><span v-if="!snapshot?.photos.some(item => item.slot === slot)">未上传</span></td>
|
||||
<td><template v-for="photo in snapshot?.photos.filter(item => item.slot === slot) || []" :key="photo.fileId">
|
||||
<a v-if="images[photo.fileId] && photo.contentType === 'application/pdf'" :href="images[photo.fileId]" target="_blank" rel="noopener noreferrer">查看驾驶证 PDF</a>
|
||||
<el-image v-else-if="images[photo.fileId]" :src="images[photo.fileId]" :preview-src-list="[images[photo.fileId]!]" fit="contain" class="loading-photo-image" preview-teleported />
|
||||
<el-button v-else link :disabled="busy" @click="load">图片读取失败,点击重试</el-button>
|
||||
<label v-if="editable" class="photo-upload">更换<input type="file" :accept="slot === 'LOADING_SCENE' ? 'image/jpeg,image/png,application/pdf' : 'image/jpeg,image/png'" :aria-label="`更换${photo.fileName}`" :disabled="busy" @change="upload(slot, $event, photo.fileId)" /></label>
|
||||
</template><span v-if="!snapshot?.photos.some(item => item.slot === slot)">—</span></td>
|
||||
<td><div v-for="photo in snapshot?.photos.filter(item => item.slot === slot) || []" :key="photo.fileId">{{ photo.savedAt?.replace('T', ' ') || '—' }}</div><span v-if="!snapshot?.photos.some(item => item.slot === slot)">—</span></td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<div class="loading-document-list" aria-label="装车照片">
|
||||
<section v-for="slot in loadingPhotoSlots" :key="slot" class="loading-document-category" :aria-label="loadingPhotoLabels[slot]">
|
||||
<header class="loading-document-heading">
|
||||
<h3>{{ loadingPhotoLabels[slot] }}</h3>
|
||||
<span class="photo-count">{{ photosFor(slot).length }}/5 张</span>
|
||||
<label v-if="editable" class="photo-upload category-upload" :class="{ 'is-disabled': busy || photosFor(slot).length >= 5 }">
|
||||
{{ photosFor(slot).length >= 5 ? '已满 5 张' : '上传资料' }}
|
||||
<input type="file" multiple :accept="slot === 'LOADING_SCENE' ? 'image/jpeg,image/png,application/pdf' : 'image/jpeg,image/png'" :aria-label="`上传${loadingPhotoLabels[slot]}`" :disabled="busy || photosFor(slot).length >= 5" @change="upload(slot, $event)" />
|
||||
</label>
|
||||
</header>
|
||||
<ol v-if="photosFor(slot).length" class="loading-photo-list">
|
||||
<li v-for="(photo, index) in photosFor(slot)" :key="photo.fileId" class="loading-photo-row" :aria-label="`${loadingPhotoLabels[slot]}第${index + 1}张`">
|
||||
<span class="photo-index">{{ index + 1 }}</span>
|
||||
<div class="photo-preview">
|
||||
<a v-if="images[photo.fileId] && photo.contentType === 'application/pdf'" :href="images[photo.fileId]" target="_blank" rel="noopener noreferrer">查看驾驶证 PDF</a>
|
||||
<el-image v-else-if="images[photo.fileId]" :src="images[photo.fileId]" :alt="`${loadingPhotoLabels[slot]}第${index + 1}张`" :preview-src-list="[images[photo.fileId]!]" fit="contain" class="loading-photo-image" preview-teleported />
|
||||
<el-button v-else link :disabled="busy" @click="load">图片读取失败,点击重试</el-button>
|
||||
</div>
|
||||
<span class="photo-time">保存时间:{{ savedTime(photo.savedAt) }}</span>
|
||||
<label v-if="editable" class="photo-upload" :class="{ 'is-disabled': busy }">
|
||||
更换
|
||||
<input type="file" :accept="slot === 'LOADING_SCENE' ? 'image/jpeg,image/png,application/pdf' : 'image/jpeg,image/png'" :aria-label="`更换${loadingPhotoLabels[slot]}第${index + 1}张`" :disabled="busy" @change="upload(slot, $event, photo.fileId)" />
|
||||
</label>
|
||||
</li>
|
||||
</ol>
|
||||
<p v-else class="photo-empty">未上传</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
.loading-documents-table { table-layout: fixed; }
|
||||
.loading-documents-table td { overflow-wrap: anywhere; }
|
||||
.photo-actions { width: 110px; }
|
||||
.photo-category { width: 120px; }
|
||||
.photo-preview { width: 140px; }
|
||||
.photo-time { width: 190px; }
|
||||
.loading-photo-image { display: block; width: 100px; height: 64px; }
|
||||
.loading-documents { display: flex; flex-direction: column; gap: 12px; }
|
||||
.loading-documents > p { margin: 0; }
|
||||
.loading-document-list { border: 1px solid var(--cc-border, var(--el-border-color)); }
|
||||
.loading-document-category + .loading-document-category { border-top: 1px solid var(--cc-border, var(--el-border-color)); }
|
||||
.loading-document-heading { display: flex; align-items: center; gap: 12px; padding: 8px 12px; background: var(--el-fill-color-light); }
|
||||
.loading-document-heading h3 { margin: 0; font-size: 14px; }
|
||||
.photo-count, .photo-time, .photo-index, .photo-empty { color: var(--el-text-color-secondary); }
|
||||
.category-upload { margin-left: auto; }
|
||||
.loading-photo-list { display: flex; flex-direction: column; margin: 0; padding: 0 12px; list-style: none; }
|
||||
.loading-photo-row { display: flex; align-items: center; flex-wrap: wrap; gap: 16px; padding: 8px 0; }
|
||||
.loading-photo-row + .loading-photo-row { border-top: 1px solid var(--el-border-color-lighter); }
|
||||
.photo-index { width: 20px; text-align: center; }
|
||||
.photo-preview { display: flex; align-items: center; flex: 0 0 160px; min-height: 96px; }
|
||||
.loading-photo-image { display: block; width: 160px; height: 96px; }
|
||||
.photo-time { font-variant-numeric: tabular-nums; }
|
||||
.photo-empty { margin: 0; padding: 12px; }
|
||||
.photo-upload { position: relative; display: inline-flex; color: var(--el-color-primary); cursor: pointer; }
|
||||
.photo-upload input { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; }
|
||||
.photo-upload:focus-within { outline: 2px solid var(--el-color-primary); outline-offset: 2px; }
|
||||
|
||||
Reference in New Issue
Block a user