feat(record): 留痕详情照片预览
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2ae7aec861
commit
b21d7fb163
@ -122,6 +122,11 @@ export function getRecord(id: string): Promise<ServiceRecordDetail> {
|
|||||||
return http.get<ServiceRecordDetail>(`/record/${id}`)
|
return http.get<ServiceRecordDetail>(`/record/${id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 照片 GET 预签名 URL(临时预览/下载,默认 1 小时有效)。 */
|
||||||
|
export function getPhotoUrl(photoId: string): Promise<{ url: string }> {
|
||||||
|
return http.get<{ url: string }>(`/record/photo/${photoId}/url`)
|
||||||
|
}
|
||||||
|
|
||||||
/** 垫付台账:某老人 is_prepaid=1 的留痕列表。 */
|
/** 垫付台账:某老人 is_prepaid=1 的留痕列表。 */
|
||||||
export function listPrepaidRecords(elderId: string): Promise<ServiceRecord[]> {
|
export function listPrepaidRecords(elderId: string): Promise<ServiceRecord[]> {
|
||||||
return http.get<ServiceRecord[]>('/record/prepaid-list', { params: { elderId } })
|
return http.get<ServiceRecord[]>('/record/prepaid-list', { params: { elderId } })
|
||||||
|
|||||||
@ -70,7 +70,21 @@
|
|||||||
|
|
||||||
<el-divider content-position="left">照片</el-divider>
|
<el-divider content-position="left">照片</el-divider>
|
||||||
<el-table v-if="detail.photos.length > 0" :data="detail.photos" border size="small">
|
<el-table v-if="detail.photos.length > 0" :data="detail.photos" border size="small">
|
||||||
<el-table-column prop="filePath" label="文件 Key(MinIO)" min-width="280" />
|
<el-table-column label="预览" min-width="140" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tooltip v-if="photoUrlOf(row.id)" :content="row.filePath" placement="top">
|
||||||
|
<el-image
|
||||||
|
:src="photoUrlOf(row.id)"
|
||||||
|
:preview-src-list="[photoUrlOf(row.id)]"
|
||||||
|
preview-teleported
|
||||||
|
fit="cover"
|
||||||
|
class="record-page__photo"
|
||||||
|
/>
|
||||||
|
</el-tooltip>
|
||||||
|
<span v-else-if="photoFailed(row.id)" class="text-xs text-text-placeholder">获取失败</span>
|
||||||
|
<span v-else class="text-xs text-text-placeholder">加载中…</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="fileSize" label="大小(字节)" width="120" align="right" />
|
<el-table-column prop="fileSize" label="大小(字节)" width="120" align="right" />
|
||||||
<el-table-column label="上传时间" width="160">
|
<el-table-column label="上传时间" width="160">
|
||||||
<template #default="{ row }">{{ formatDateTime(row.createdAt) }}</template>
|
<template #default="{ row }">{{ formatDateTime(row.createdAt) }}</template>
|
||||||
@ -115,6 +129,7 @@ import {
|
|||||||
EXEC_RESULT_TAG_MAP,
|
EXEC_RESULT_TAG_MAP,
|
||||||
LOCATE_STATUS_TAG_MAP,
|
LOCATE_STATUS_TAG_MAP,
|
||||||
RECORD_STATUS_TAG_MAP,
|
RECORD_STATUS_TAG_MAP,
|
||||||
|
getPhotoUrl,
|
||||||
getRecord,
|
getRecord,
|
||||||
listRecords,
|
listRecords,
|
||||||
type ServiceRecord,
|
type ServiceRecord,
|
||||||
@ -177,6 +192,8 @@ const list = ref<ServiceRecord[]>([])
|
|||||||
const detailVisible = ref(false)
|
const detailVisible = ref(false)
|
||||||
const detailLoading = ref(false)
|
const detailLoading = ref(false)
|
||||||
const detail = ref<ServiceRecordDetail | null>(null)
|
const detail = ref<ServiceRecordDetail | null>(null)
|
||||||
|
/** photoId → presigned url;null 表示获取失败,缺失表示加载中。 */
|
||||||
|
const photoUrls = ref<Record<string, string | null>>({})
|
||||||
|
|
||||||
const str = (v: unknown): string | undefined => (typeof v === 'string' && v !== '' ? v : undefined)
|
const str = (v: unknown): string | undefined => (typeof v === 'string' && v !== '' ? v : undefined)
|
||||||
|
|
||||||
@ -246,9 +263,11 @@ async function loadMaps(): Promise<void> {
|
|||||||
async function openDetail(row: Record<string, any>): Promise<void> {
|
async function openDetail(row: Record<string, any>): Promise<void> {
|
||||||
detailVisible.value = true
|
detailVisible.value = true
|
||||||
detail.value = null
|
detail.value = null
|
||||||
|
photoUrls.value = {}
|
||||||
detailLoading.value = true
|
detailLoading.value = true
|
||||||
try {
|
try {
|
||||||
detail.value = await getRecord(String(row.id))
|
detail.value = await getRecord(String(row.id))
|
||||||
|
if (detail.value) void loadPhotoUrls(detail.value.photos)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof ApiError) ElMessage.error(e.message)
|
if (e instanceof ApiError) ElMessage.error(e.message)
|
||||||
} finally {
|
} finally {
|
||||||
@ -256,6 +275,30 @@ async function openDetail(row: Record<string, any>): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 为每张照片并发取 presigned url;失败置 null → 模板显示「获取失败」。 */
|
||||||
|
async function loadPhotoUrls(photos: ServiceRecordDetail['photos']): Promise<void> {
|
||||||
|
const next: Record<string, string | null> = {}
|
||||||
|
await Promise.all(
|
||||||
|
photos.map(async (p) => {
|
||||||
|
try {
|
||||||
|
const { url } = await getPhotoUrl(p.id)
|
||||||
|
next[p.id] = url
|
||||||
|
} catch {
|
||||||
|
next[p.id] = null
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
photoUrls.value = next
|
||||||
|
}
|
||||||
|
|
||||||
|
function photoUrlOf(id: string): string {
|
||||||
|
return photoUrls.value[id] ?? ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function photoFailed(id: string): boolean {
|
||||||
|
return photoUrls.value[id] === null
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
void loadMaps()
|
void loadMaps()
|
||||||
fetchList()
|
fetchList()
|
||||||
@ -278,4 +321,8 @@ onMounted(() => {
|
|||||||
.record-page__detail {
|
.record-page__detail {
|
||||||
@apply max-h-[60vh] overflow-auto;
|
@apply max-h-[60vh] overflow-auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.record-page__photo {
|
||||||
|
@apply block h-16 w-16 cursor-zoom-in rounded object-cover;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user