style: 优化资产列表图片自适应展示
This commit is contained in:
@@ -1,13 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, nextTick, reactive, ref } from 'vue'
|
import { computed, reactive, ref } from 'vue'
|
||||||
import { NAlert, NButton, NFormItem, NImage, NInput, NPopconfirm, NSelect, NTag, NTooltip } from 'naive-ui'
|
import { NAlert, NButton, NFormItem, NInput, NPopconfirm, NSelect, NTag, NTooltip } from 'naive-ui'
|
||||||
import { Eye, Info, RefreshCw, Search, Trash2, Upload } from '@lucide/vue'
|
import { RefreshCw, Search, Trash2, Upload } from '@lucide/vue'
|
||||||
import { AppDialog, AssetImage, EmptyState } from '../../components/ui'
|
import { AppDialog, AssetImage, EmptyState } from '../../components/ui'
|
||||||
import AppForm from '../../components/ui/AppForm.vue'
|
import AppForm from '../../components/ui/AppForm.vue'
|
||||||
import WorkspacePage from '../../components/ui/WorkspacePage.vue'
|
import WorkspacePage from '../../components/ui/WorkspacePage.vue'
|
||||||
import { useQuery } from '../../composables/useQuery'
|
import { useQuery } from '../../composables/useQuery'
|
||||||
import { fieldRule } from '../../lib/form-rules'
|
import { fieldRule } from '../../lib/form-rules'
|
||||||
import { referenceImageUrl } from '../../lib/assets'
|
|
||||||
import { errorMessage } from '../../lib/http'
|
import { errorMessage } from '../../lib/http'
|
||||||
import { projectsApi } from '../projects/api'
|
import { projectsApi } from '../projects/api'
|
||||||
import { projectAssetsApi } from './api'
|
import { projectAssetsApi } from './api'
|
||||||
@@ -40,8 +39,6 @@ const uploadOpen = ref(false)
|
|||||||
const editOpen = ref(false)
|
const editOpen = ref(false)
|
||||||
const selectedFile = ref<File | null>(null)
|
const selectedFile = ref<File | null>(null)
|
||||||
const selectedAsset = ref<GlobalProjectAsset | 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 overflowTitles = reactive(new Set<string>())
|
||||||
const uploadForm = reactive({ projectId: '', name: '', category: 'reference' })
|
const uploadForm = reactive({ projectId: '', name: '', category: 'reference' })
|
||||||
const editForm = reactive({ name: '', category: '' })
|
const editForm = reactive({ name: '', category: '' })
|
||||||
@@ -178,13 +175,6 @@ function checkTitleOverflow(assetId: string, event: MouseEvent) {
|
|||||||
else overflowTitles.delete(assetId)
|
else overflowTitles.delete(assetId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 预览按钮独立打开 Naive 图片查看器,不触发卡片详情。 */
|
|
||||||
async function openPreview(asset: GlobalProjectAsset) {
|
|
||||||
previewAsset.value = asset
|
|
||||||
await nextTick()
|
|
||||||
previewImage.value?.showPreview()
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateAsset() {
|
async function updateAsset() {
|
||||||
const asset = selectedAsset.value
|
const asset = selectedAsset.value
|
||||||
if (!asset || busy.value) return
|
if (!asset || busy.value) return
|
||||||
@@ -311,28 +301,6 @@ async function removeAsset() {
|
|||||||
>
|
>
|
||||||
<AssetImage :src="asset.publicUrl" :alt="asset.name" class="asset-library-image" />
|
<AssetImage :src="asset.publicUrl" :alt="asset.name" class="asset-library-image" />
|
||||||
<div class="asset-library-overlay">
|
<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)">
|
<NTooltip placement="top-start" trigger="hover" :disabled="!overflowTitles.has(asset.id)">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<h2 class="asset-library-title" @mouseenter="checkTitleOverflow(asset.id, $event)">
|
<h2 class="asset-library-title" @mouseenter="checkTitleOverflow(asset.id, $event)">
|
||||||
@@ -365,16 +333,6 @@ async function removeAsset() {
|
|||||||
</EmptyState>
|
</EmptyState>
|
||||||
<p v-else class="py-10 text-center text-sm text-muted" role="status">正在读取全部资产图片…</p>
|
<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
|
<AppDialog
|
||||||
v-model:open="uploadOpen"
|
v-model:open="uploadOpen"
|
||||||
title="上传资产图片"
|
title="上传资产图片"
|
||||||
@@ -485,36 +443,19 @@ async function removeAsset() {
|
|||||||
box-shadow: inset 0 0 0 2px var(--color-accent);
|
box-shadow: inset 0 0 0 2px var(--color-accent);
|
||||||
}
|
}
|
||||||
.asset-library-image {
|
.asset-library-image {
|
||||||
@apply block aspect-auto;
|
@apply block aspect-auto bg-transparent;
|
||||||
}
|
}
|
||||||
.asset-library-image :deep(.reveal-image),
|
.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) {
|
.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 {
|
.asset-library-overlay {
|
||||||
@apply absolute inset-0 flex flex-col justify-end p-[10px] text-white;
|
@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%);
|
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 {
|
.asset-library-title {
|
||||||
@apply block w-full truncate text-sm font-semibold;
|
@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('rain-street-original.png')
|
||||||
expect(wrapper.get('.asset-library-card').text()).not.toContain('4.0 KB')
|
expect(wrapper.get('.asset-library-card').text()).not.toContain('4.0 KB')
|
||||||
|
|
||||||
await wrapper.get('[aria-label="预览图片"]').trigger('click')
|
expect(wrapper.find('[aria-label="预览图片"]').exists()).toBe(false)
|
||||||
await flushPromises()
|
expect(wrapper.find('[aria-label="查看素材详情"]').exists()).toBe(false)
|
||||||
expect(document.querySelector('[role="dialog"][aria-label="素材详情与管理"]')).toBeNull()
|
await wrapper.get('.asset-library-card').trigger('click')
|
||||||
expect(document.querySelector('.n-image-preview-container')).not.toBeNull()
|
|
||||||
|
|
||||||
await wrapper.get('[aria-label="查看素材详情"]').trigger('click')
|
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
const dialog = document.querySelector('[role="dialog"][aria-label="素材详情与管理"]')!
|
const dialog = document.querySelector('[role="dialog"][aria-label="素材详情与管理"]')!
|
||||||
expect(dialog).not.toBeNull()
|
expect(dialog).not.toBeNull()
|
||||||
|
|||||||
Reference in New Issue
Block a user