fix(dispatch): synchronize loading evidence and review round validation
This commit is contained in:
@@ -3,9 +3,10 @@
|
||||
<view class="heading">
|
||||
<view class="heading-main"><text class="heading-title">装车照片</text></view>
|
||||
<text class="heading-progress">{{ busy ? '同步中' : snapshot ? `${completedCategories}/5 类已保存` : '待同步' }}</text>
|
||||
<button class="heading-refresh" :disabled="busy" @tap="load">刷新</button>
|
||||
<button class="heading-refresh" :disabled="busy" @tap="refresh">刷新</button>
|
||||
</view>
|
||||
<text class="section-hint">每类至少 1 张、最多 5 张,上传后自动保存,可逐张更换。</text>
|
||||
<text class="section-hint">司机驾驶证、车牌照片在理货审核通过后自动带入,可补拍或更换;PDF 驾驶证可点击查看。</text>
|
||||
<text v-if="needsRefresh" class="section-hint">保存状态尚未核实,请先刷新资料后再上传。</text>
|
||||
<view v-if="error" class="error" role="alert"><text>{{ error.message }}</text><text v-if="error.requestId">requestId:{{ error.requestId }}</text><text v-if="error.traceId">traceId:{{ error.traceId }}</text></view>
|
||||
<view class="photo-list">
|
||||
@@ -19,7 +20,8 @@
|
||||
</view>
|
||||
<view v-if="snapshot?.photos.some(item => item.slot === slot)" class="photo-preview-list">
|
||||
<view v-for="photo in snapshot.photos.filter(item => item.slot === slot)" :key="photo.fileId" class="photo-preview">
|
||||
<image v-if="images[photo.fileId]" class="photo-thumbnail" :src="images[photo.fileId]" mode="aspectFill" :aria-label="`预览${slotLabels[slot]}`" @tap="preview(images[photo.fileId])" />
|
||||
<button v-if="photo.contentType === 'application/pdf'" class="photo-read-retry" :disabled="busy" @tap="viewPdf(photo)">查看驾驶证 PDF</button>
|
||||
<image v-else-if="images[photo.fileId]" class="photo-thumbnail" :src="images[photo.fileId]" mode="aspectFill" :aria-label="`预览${slotLabels[slot]}`" @tap="preview(images[photo.fileId])" />
|
||||
<button v-else class="photo-read-retry" :aria-label="`重新读取${slotLabels[slot]}`" :disabled="busy" @tap="load">图片读取失败,重试</button>
|
||||
<text v-if="images[photo.fileId]" class="photo-preview-caption" :aria-label="`查看${slotLabels[slot]}大图`" @tap="preview(images[photo.fileId])">查看大图</text>
|
||||
<button v-if="editable" class="photo-replace" :disabled="busy" :aria-label="`更换${slotLabels[slot]}图片`" @tap="choose(slot, photo.fileId)">更换</button>
|
||||
@@ -48,6 +50,7 @@ export default {
|
||||
key() { return `loading-${Date.now()}-${Math.random().toString(36).slice(2)}-${Math.random().toString(36).slice(2)}` },
|
||||
async previews() {
|
||||
await Promise.allSettled((this.snapshot?.photos || []).map(async photo => {
|
||||
if (photo.contentType === 'application/pdf') return
|
||||
if (this.images[photo.fileId]) return
|
||||
const image = await readLoadingPhoto(this.number, photo); this.ensureScope(); this.images[photo.fileId] = image
|
||||
}))
|
||||
@@ -58,8 +61,10 @@ export default {
|
||||
this.snapshot = value; this.needsRefresh = false; await this.previews()
|
||||
},
|
||||
async load() { await this.run(() => this.refreshFacts()) },
|
||||
async refresh() { await this.run(async () => { await this.refreshFacts(); return true }, true) },
|
||||
async choose(slot, replaceFileId = null) {
|
||||
if (this.busy || !this.editable) return
|
||||
const reviewRound = this.snapshot.reviewRound
|
||||
const capacity = replaceFileId ? 1 : this.limit - this.photosFor(slot).length
|
||||
if (capacity <= 0) return
|
||||
let saved = 0, reconciled = false
|
||||
@@ -70,12 +75,14 @@ export default {
|
||||
if (chosen.tempFiles?.some(file => file.size > 10 * 1024 * 1024)) throw new Error('请选择不超过10MiB的图片')
|
||||
// 逐张以最新版本绑定;部分失败保留前面已保存照片,不重放失败上传。
|
||||
for (const filePath of paths) {
|
||||
const photo = await uploadLoadingPhoto(this.number, slot, filePath, this.key()); this.ensureScope()
|
||||
const uploadKey = this.key()
|
||||
const photo = await uploadLoadingPhoto(this.number, slot, filePath, uploadKey, reviewRound); this.ensureScope()
|
||||
if (photo?.slot !== slot || !photo.fileId) throw new Error('上传结果与装车照片框不一致')
|
||||
const latest = await getLoadingDocuments(this.number); this.ensureScope(); this.snapshot = latest
|
||||
if (latest.reviewRound !== reviewRound) { reconciled = true; throw new Error('审核轮次已变化,请核对新车辆、司机后重新选择资料') }
|
||||
if (!this.editable) throw new Error('当前任务不可修改装车照片,请刷新任务查看')
|
||||
try {
|
||||
const value = await bindLoadingPhoto(this.number, slot, { recordVersion: latest.recordVersion, idempotencyKey: this.key(), fileId: photo.fileId, ...(replaceFileId ? { replaceFileId } : {}) }); this.ensureScope()
|
||||
const value = await bindLoadingPhoto(this.number, slot, { recordVersion: latest.recordVersion, idempotencyKey: this.key(), fileId: photo.fileId, uploadKey, ...(replaceFileId ? { replaceFileId } : {}) }); this.ensureScope()
|
||||
this.snapshot = value
|
||||
} catch (cause) {
|
||||
// 保存可能已成功;只重读最新事实,失败时必须先刷新再继续。
|
||||
@@ -92,6 +99,19 @@ export default {
|
||||
if ((saved || reconciled) && this.active) this.$emit('saved')
|
||||
},
|
||||
preview(url) { uni.previewImage({ current: url, urls: [url] }) },
|
||||
async viewPdf(photo) {
|
||||
await this.run(async () => {
|
||||
const browser = typeof window !== 'undefined' && typeof document !== 'undefined'
|
||||
const popup = browser ? window.open('', '_blank') : null
|
||||
if (browser && !popup) throw new Error('请允许打开驾驶证预览窗口')
|
||||
if (popup) popup.opener = null
|
||||
try {
|
||||
const path = await readLoadingPhoto(this.number, photo); this.ensureScope()
|
||||
if (popup) popup.location.href = path
|
||||
else await new Promise((resolve, reject) => uni.openDocument({ filePath: path, fileType: 'pdf', showMenu: true, success: resolve, fail: reject }))
|
||||
} catch (cause) { if (popup) popup.close(); throw cause }
|
||||
})
|
||||
},
|
||||
async run(work, notify = false) {
|
||||
if (this.busy) return
|
||||
const revision = ++this.revision
|
||||
|
||||
Reference in New Issue
Block a user