style: 优化资产列表图片自适应展示

This commit is contained in:
GouJ
2026-09-22 11:52:46 +08:00
parent a3599b03d0
commit 5f78efaad1
2 changed files with 11 additions and 73 deletions
@@ -1,13 +1,12 @@
<script setup lang="ts">
import { computed, nextTick, reactive, ref } from 'vue'
import { NAlert, NButton, NFormItem, NImage, NInput, NPopconfirm, NSelect, NTag, NTooltip } from 'naive-ui'
import { Eye, Info, RefreshCw, Search, Trash2, Upload } from '@lucide/vue'
import { computed, reactive, ref } from 'vue'
import { NAlert, NButton, NFormItem, NInput, NPopconfirm, NSelect, NTag, NTooltip } from 'naive-ui'
import { RefreshCw, Search, Trash2, Upload } from '@lucide/vue'
import { AppDialog, AssetImage, EmptyState } from '../../components/ui'
import AppForm from '../../components/ui/AppForm.vue'
import WorkspacePage from '../../components/ui/WorkspacePage.vue'
import { useQuery } from '../../composables/useQuery'
import { fieldRule } from '../../lib/form-rules'
import { referenceImageUrl } from '../../lib/assets'
import { errorMessage } from '../../lib/http'
import { projectsApi } from '../projects/api'
import { projectAssetsApi } from './api'
@@ -40,8 +39,6 @@ const uploadOpen = ref(false)
const editOpen = ref(false)
const selectedFile = ref<File | null>(null)
const selectedAsset = ref<GlobalProjectAsset | null>(null)
const previewAsset = ref<GlobalProjectAsset | null>(null)
const previewImage = ref<{ showPreview: () => void } | null>(null)
const overflowTitles = reactive(new Set<string>())
const uploadForm = reactive({ projectId: '', name: '', category: 'reference' })
const editForm = reactive({ name: '', category: '' })
@@ -178,13 +175,6 @@ function checkTitleOverflow(assetId: string, event: MouseEvent) {
else overflowTitles.delete(assetId)
}
/** 预览按钮独立打开 Naive 图片查看器,不触发卡片详情。 */
async function openPreview(asset: GlobalProjectAsset) {
previewAsset.value = asset
await nextTick()
previewImage.value?.showPreview()
}
async function updateAsset() {
const asset = selectedAsset.value
if (!asset || busy.value) return
@@ -311,28 +301,6 @@ async function removeAsset() {
>
<AssetImage :src="asset.publicUrl" :alt="asset.name" class="asset-library-image" />
<div class="asset-library-overlay">
<div class="asset-library-actions">
<NButton
quaternary
circle
class="asset-overlay-button"
aria-label="预览图片"
title="预览图片"
@click.stop="openPreview(asset)"
>
<template #icon><Eye :size="16" /></template>
</NButton>
<NButton
quaternary
circle
class="asset-overlay-button"
aria-label="查看素材详情"
title="查看素材详情"
@click.stop="openEdit(asset)"
>
<template #icon><Info :size="16" /></template>
</NButton>
</div>
<NTooltip placement="top-start" trigger="hover" :disabled="!overflowTitles.has(asset.id)">
<template #trigger>
<h2 class="asset-library-title" @mouseenter="checkTitleOverflow(asset.id, $event)">
@@ -365,16 +333,6 @@ async function removeAsset() {
</EmptyState>
<p v-else class="py-10 text-center text-sm text-muted" role="status">正在读取全部资产图片</p>
<NImage
v-if="previewAsset"
ref="previewImage"
class="sr-only"
:src="referenceImageUrl(previewAsset.publicUrl) ?? undefined"
:preview-src="referenceImageUrl(previewAsset.publicUrl) ?? undefined"
:alt="previewAsset.name"
:previewed-img-props="{ alt: previewAsset.name, referrerpolicy: 'no-referrer' }"
/>
<AppDialog
v-model:open="uploadOpen"
title="上传资产图片"
@@ -485,36 +443,19 @@ async function removeAsset() {
box-shadow: inset 0 0 0 2px var(--color-accent);
}
.asset-library-image {
@apply block aspect-auto;
@apply block aspect-auto bg-transparent;
}
.asset-library-image :deep(.reveal-image),
.asset-library-image :deep(.n-image),
.asset-library-image :deep(.n-image) {
@apply block h-auto min-h-0 bg-transparent;
}
.asset-library-image :deep(.n-image img) {
@apply h-auto min-h-[160px];
@apply block w-full h-auto min-h-0 object-contain;
}
.asset-library-overlay {
@apply absolute inset-0 flex flex-col justify-end p-[10px] text-white;
background: linear-gradient(to top, rgb(0 0 0 / 0.84) 0, rgb(0 0 0 / 0.35) 42%, transparent 70%);
}
.asset-library-actions {
@apply absolute top-[10px] left-[10px] flex items-center gap-1 opacity-0 pointer-events-none;
transform: translateY(-4px);
transition:
opacity 0.18s ease,
transform 0.18s ease;
}
.asset-library-card:hover .asset-library-actions,
.asset-library-card:focus-visible .asset-library-actions,
.asset-library-card:focus-within .asset-library-actions {
@apply opacity-100 pointer-events-auto;
transform: translateY(0);
}
.asset-overlay-button {
@apply text-white bg-black/45 backdrop-blur-sm;
}
.asset-overlay-button:hover {
@apply text-white bg-black/60;
}
.asset-library-title {
@apply block w-full truncate text-sm font-semibold;
}
@@ -87,12 +87,9 @@ describe('全局资产库', () => {
expect(wrapper.get('.asset-library-card').text()).not.toContain('rain-street-original.png')
expect(wrapper.get('.asset-library-card').text()).not.toContain('4.0 KB')
await wrapper.get('[aria-label="预览图片"]').trigger('click')
await flushPromises()
expect(document.querySelector('[role="dialog"][aria-label="素材详情与管理"]')).toBeNull()
expect(document.querySelector('.n-image-preview-container')).not.toBeNull()
await wrapper.get('[aria-label="查看素材详情"]').trigger('click')
expect(wrapper.find('[aria-label="预览图片"]').exists()).toBe(false)
expect(wrapper.find('[aria-label="查看素材详情"]').exists()).toBe(false)
await wrapper.get('.asset-library-card').trigger('click')
await flushPromises()
const dialog = document.querySelector('[role="dialog"][aria-label="素材详情与管理"]')!
expect(dialog).not.toBeNull()