修复详情页账号截图加载

This commit is contained in:
yml2213
2026-06-08 07:35:43 +08:00
parent 353f62edf0
commit c4420dc753
4 changed files with 121 additions and 125 deletions
@@ -10,6 +10,7 @@ import {
type OrderAgreements,
} from '@/features/orders/api/orders'
import { useSessionStore } from '@/stores/session'
import AuthImage from '@/shared/components/business/AuthImage.vue'
import { roundMoney, formatMoney } from '@/shared/utils/money'
import {
assetRegions,
@@ -321,7 +322,13 @@ function listingPrice(item: Listing) {
<div v-if="listing" class="pc-detail-layout">
<section class="pc-detail-main">
<div class="detail-hero-card">
<img v-if="coverURL" :src="coverURL" :alt="getListingTitle(listing)" />
<AuthImage
v-if="coverURL"
:source="coverURL"
:alt="getListingTitle(listing)"
image-class="detail-hero-img"
loading="eager"
/>
<span v-else>HFB ACCOUNT</span>
<div class="detail-hero-overlay">
<div class="detail-tags">
@@ -417,7 +424,7 @@ function listingPrice(item: Listing) {
</div>
<div class="detail-screenshot-grid">
<figure v-for="shot in detailScreenshots" :key="shot.url" class="detail-screenshot">
<img :src="shot.url" :alt="shot.label" loading="lazy" decoding="async" />
<AuthImage :source="shot.url" :alt="shot.label" />
<figcaption>{{ shot.label }}</figcaption>
</figure>
</div>
@@ -10,6 +10,7 @@ import {
type OrderAgreements,
} from '@/features/orders/api/orders'
import { useSessionStore } from '@/stores/session'
import AuthImage from '@/shared/components/business/AuthImage.vue'
import { formatMoney } from '@/shared/utils/money'
import {
assetRegions,
@@ -282,12 +283,12 @@ function readError(error: unknown, fallback: string) {
<!-- 封面图 -->
<div class="cover-area">
<img
<AuthImage
v-if="detailScreenshots[0]?.url"
:src="detailScreenshots[0].url"
:source="detailScreenshots[0].url"
:alt="getListingTitle(listing)"
class="cover-img"
decoding="async"
image-class="cover-img"
loading="eager"
/>
<div v-else class="cover-placeholder">
<van-icon name="photo-o" :size="40" color="#ccc" />
@@ -382,7 +383,7 @@ function readError(error: unknown, fallback: string) {
<h3 class="card-subtitle">账号截图</h3>
<div class="screenshot-grid">
<figure v-for="shot in detailScreenshots" :key="shot.url" class="screenshot-item">
<img :src="shot.url" class="screenshot-thumb" loading="lazy" decoding="async" />
<AuthImage :source="shot.url" :alt="shot.label" image-class="screenshot-thumb" />
<figcaption>{{ shot.label }}</figcaption>
</figure>
</div>
@@ -0,0 +1,106 @@
<script setup lang="ts">
import { computed, onBeforeUnmount, ref, watch } from 'vue'
import { fetchAdminFileBlob, fetchFileBlobByURL } from '@/shared/api/files'
const props = withDefaults(
defineProps<{
source: string
alt?: string
admin?: boolean
imageClass?: string
fallbackClass?: string
loading?: 'eager' | 'lazy'
decoding?: 'async' | 'sync' | 'auto'
}>(),
{
alt: '',
admin: false,
imageClass: '',
fallbackClass: '',
loading: 'lazy',
decoding: 'async',
}
)
const imageURL = ref('')
const failed = ref(false)
let createdObjectURL = ''
const fallbackText = computed(() => (failed.value ? '图片加载失败' : '图片加载中'))
function extractObjectKey(value: string) {
try {
const parsed = new URL(value, window.location.origin)
return parsed.searchParams.get('key') || ''
} catch {
return ''
}
}
function shouldFetchWithAuth(value: string) {
return value.includes('/api/files/object') || value.includes('/api/admin/files/object')
}
function revokeCurrentURL() {
if (!createdObjectURL) return
URL.revokeObjectURL(createdObjectURL)
createdObjectURL = ''
}
async function loadImage() {
revokeCurrentURL()
imageURL.value = ''
failed.value = false
if (!props.source) {
failed.value = true
return
}
if (!shouldFetchWithAuth(props.source)) {
imageURL.value = props.source
return
}
try {
const key = extractObjectKey(props.source)
const blob =
props.admin && key ? await fetchAdminFileBlob(key) : await fetchFileBlobByURL(props.source)
createdObjectURL = URL.createObjectURL(blob)
imageURL.value = createdObjectURL
} catch {
failed.value = true
}
}
watch(() => [props.source, props.admin] as const, loadImage, { immediate: true })
onBeforeUnmount(revokeCurrentURL)
</script>
<template>
<img
v-if="imageURL"
:class="imageClass"
:src="imageURL"
:alt="alt"
:loading="loading"
:decoding="decoding"
/>
<span v-else :class="['auth-image-fallback', fallbackClass]">{{ fallbackText }}</span>
</template>
<style scoped>
.auth-image-fallback {
display: flex;
align-items: center;
justify-content: center;
min-height: 120px;
border: 1px dashed #d8dee8;
border-radius: 12px;
background: #f8fafc;
color: #94a3b8;
font-size: 13px;
font-weight: 700;
}
</style>