feat(admin): synchronize fleet photos and multi-image loading documents
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, ref, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import AppDialog from '@/shared/components/AppDialog.vue'
|
||||
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||||
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
||||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
import { getResourcePhoto, uploadResourcePhoto, downloadResourcePhoto, type ResourcePhotoKind, type ResourcePhotoDetail } from '../api/resourcePhotos'
|
||||
import { overseasMasterDataPermissions as permission } from '../permissions'
|
||||
|
||||
const props = defineProps<{ kind: ResourcePhotoKind; resourceId: string }>()
|
||||
const access = useAccessPreviewStore(), detail = ref<ResourcePhotoDetail | null>(null), loading = ref(false), busy = ref(false)
|
||||
const error = ref<ApiErrorDetails | null>(null), input = ref<HTMLInputElement | null>(null), previewOpen = ref(false), url = ref('')
|
||||
const label = computed(() => props.kind === 'VEHICLE' ? '车辆图片' : '司机照片')
|
||||
const editable = computed(() => access.can(props.kind === 'VEHICLE' ? permission.vehicleEdit : permission.driverUpload))
|
||||
let generation = 0
|
||||
function closePreview() { previewOpen.value = false; if (url.value) URL.revokeObjectURL(url.value); url.value = '' }
|
||||
async function load() {
|
||||
const current = ++generation; closePreview(); detail.value = null; loading.value = true; error.value = null; busy.value = false
|
||||
try { const result = await getResourcePhoto(props.kind, props.resourceId); if (current === generation) detail.value = result }
|
||||
catch (cause) { if (current === generation) error.value = getApiErrorDetails(cause, '档案照片加载失败') }
|
||||
finally { if (current === generation) loading.value = false }
|
||||
}
|
||||
async function upload(event: Event) {
|
||||
const element = event.target as HTMLInputElement, file = element.files?.[0]; element.value = ''
|
||||
if (!file || !detail.value || busy.value || !editable.value) return
|
||||
if (!['image/jpeg', 'image/png'].includes(file.type) || !file.size || file.size > 10 * 1024 * 1024) { ElMessage.warning('请选择非空 JPG 或 PNG,最大 10MiB'); return }
|
||||
const current = generation; busy.value = true; error.value = null
|
||||
try {
|
||||
const saved = await uploadResourcePhoto(props.kind, props.resourceId, detail.value.recordVersion, file)
|
||||
if (current !== generation) return
|
||||
detail.value = saved; closePreview(); ElMessage.success(`${label.value}已保存,新排车会自动带入`)
|
||||
} catch (cause) { if (current === generation) error.value = getApiErrorDetails(cause, '照片保存未完成,请重新读取后重试') }
|
||||
finally { if (current === generation) busy.value = false }
|
||||
}
|
||||
async function preview() {
|
||||
if (!detail.value?.photo || busy.value) return
|
||||
const current = generation; busy.value = true; error.value = null
|
||||
try {
|
||||
const blob = await downloadResourcePhoto(props.kind, props.resourceId, detail.value.photo.fileId)
|
||||
if (current !== generation) return
|
||||
closePreview(); url.value = URL.createObjectURL(blob); previewOpen.value = true
|
||||
} catch (cause) { if (current === generation) error.value = getApiErrorDetails(cause, '照片预览失败') }
|
||||
finally { if (current === generation) busy.value = false }
|
||||
}
|
||||
watch(() => [props.kind, props.resourceId], load, { immediate: true })
|
||||
onBeforeUnmount(() => { generation++; closePreview() })
|
||||
</script>
|
||||
<template>
|
||||
<section v-loading="loading" :aria-label="label">
|
||||
<LoadErrorState v-if="error" title="档案照片操作未完成" :message="error.message" compact @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
|
||||
<div class="density-actions">
|
||||
<el-button v-if="detail?.photo" :disabled="busy" @click="preview">查看{{ label }}</el-button><span v-else-if="detail">尚未上传{{ label }}</span>
|
||||
<el-button v-if="editable" :disabled="!detail || busy" :loading="busy" @click="input?.click()">上传 / 替换{{ label }}</el-button>
|
||||
<input ref="input" hidden type="file" accept="image/jpeg,image/png" :aria-label="'上传' + label" @change="upload">
|
||||
</div>
|
||||
<p class="density-dialog-note">排车选择此档案后自动带入照片;替换不影响已保存的排车资料。JPG、PNG,最大 10MiB。</p>
|
||||
<AppDialog :model-value="previewOpen" class="admin-density-dialog" :title="label" width="620px" :show-confirm="false" cancel-text="关闭" @update:model-value="closePreview"><img v-if="url" class="resource-photo-preview" :src="url" :alt="label"></AppDialog>
|
||||
</section>
|
||||
</template>
|
||||
<style scoped>.resource-photo-preview { display: block; width: 100%; max-height: 60vh; object-fit: contain; }</style>
|
||||
Reference in New Issue
Block a user