feat(ui): 统一图片预览为 Naive UI 并修复工具栏交互
This commit is contained in:
parent
32aa5d0f69
commit
3bab2b008f
|
|
@ -757,7 +757,7 @@ display: flex;
|
|||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 10px;
|
||||
|
||||
.dark & {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { NModal, NImage, NTooltip } from 'naive-ui'
|
||||
import { NImage, NTooltip } from 'naive-ui'
|
||||
import PlusIcon from '../icons/custom/PlusIcon.vue'
|
||||
export interface CardItem {
|
||||
id: string | number
|
||||
|
|
@ -42,16 +42,7 @@ const isExpanded = ref(false)
|
|||
const isWrapperHovered = ref(false)
|
||||
const hoveredCardId = ref<string | number | null>(null)
|
||||
const containerRef = ref<HTMLElement | null>(null)
|
||||
const previewCard = ref<CardItem | null>(null)
|
||||
const canUpload = computed(() => props.supportsFiles || props.supportsVision)
|
||||
const isPreviewVisible = computed({
|
||||
get: () => !!previewCard.value,
|
||||
set: (value: boolean) => {
|
||||
if (!value) {
|
||||
closePreview()
|
||||
}
|
||||
},
|
||||
})
|
||||
const CARD_SCALE = 0.5
|
||||
|
||||
// 默认颜色调色板 - 霓虹色系
|
||||
|
|
@ -138,21 +129,6 @@ function getCardImageUrl(card: CardItem) {
|
|||
return card.thumbnail || ''
|
||||
}
|
||||
|
||||
function openPreview(card: CardItem) {
|
||||
if (card.type !== 'image') return
|
||||
const imageUrl = getCardImageUrl(card)
|
||||
if (!imageUrl) return
|
||||
previewCard.value = card
|
||||
}
|
||||
|
||||
function closePreview() {
|
||||
previewCard.value = null
|
||||
}
|
||||
|
||||
function removePreviewCard() {
|
||||
closePreview()
|
||||
}
|
||||
|
||||
function removeCard(card: CardItem) {
|
||||
emit('remove', card.id)
|
||||
}
|
||||
|
|
@ -170,10 +146,6 @@ function handleCardClick(card: CardItem) {
|
|||
expandCards()
|
||||
return
|
||||
}
|
||||
|
||||
if (card.type === 'image') {
|
||||
openPreview(card)
|
||||
}
|
||||
}
|
||||
|
||||
const cardStyle = computed(() => (index: number, total: number) => {
|
||||
|
|
@ -214,9 +186,21 @@ const cardStyle = computed(() => (index: number, total: number) => {
|
|||
|
||||
const handleDocumentClick = (event: MouseEvent) => {
|
||||
if (!containerRef.value) return
|
||||
const target = event.target as HTMLElement | null
|
||||
|
||||
// Naive UI 图片预览层挂载在 body 下,点击其工具栏不应触发卡片收起
|
||||
if (
|
||||
target?.closest(
|
||||
'.n-image-preview-container, .n-image-preview-toolbar, .n-image-preview-overlay, .n-image-preview-close',
|
||||
)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!target) return
|
||||
|
||||
// 点击卡片组外部时收起
|
||||
if (!containerRef.value.contains(event.target as Node)) {
|
||||
if (!containerRef.value.contains(target)) {
|
||||
if (isExpanded.value) {
|
||||
isExpanded.value = false
|
||||
hoveredCardId.value = null
|
||||
|
|
@ -273,7 +257,13 @@ watch(
|
|||
@mouseenter="hoveredCardId = card.id" @mouseleave="hoveredCardId = null" @click="handleCardClick(card)">
|
||||
<div class="card-glow" :style="{ background: getCardColor(card, index) }" />
|
||||
<div v-if="card.type === 'image' && getCardImageUrl(card)" class="card-media">
|
||||
<img :src="getCardImageUrl(card)" :alt="getCardTitle(card, index)" />
|
||||
<NImage
|
||||
:src="getCardImageUrl(card)"
|
||||
:alt="getCardTitle(card, index)"
|
||||
object-fit="cover"
|
||||
:preview-disabled="!isExpanded || card.uploading || card.deleting"
|
||||
:img-props="{ loading: 'lazy' }"
|
||||
/>
|
||||
<div v-if="card.uploading" class="card-media-uploading">
|
||||
<div class="card-media-uploading-spinner" aria-hidden="true" />
|
||||
<span class="card-media-uploading-text">上传中</span>
|
||||
|
|
@ -285,7 +275,13 @@ watch(
|
|||
</div>
|
||||
<div v-else-if="card.type === 'image'" class="card-preview">
|
||||
<div v-if="getCardImageUrl(card)" class="card-preview-image">
|
||||
<img :src="getCardImageUrl(card)" :alt="getCardTitle(card, index)" />
|
||||
<NImage
|
||||
:src="getCardImageUrl(card)"
|
||||
:alt="getCardTitle(card, index)"
|
||||
object-fit="cover"
|
||||
:preview-disabled="!isExpanded || card.uploading || card.deleting"
|
||||
:img-props="{ loading: 'lazy' }"
|
||||
/>
|
||||
</div>
|
||||
<div class="card-preview-fallback">
|
||||
<span class="card-icon">
|
||||
|
|
@ -341,23 +337,6 @@ watch(
|
|||
<PlusIcon :size="12" />
|
||||
</button>
|
||||
|
||||
<NModal v-model:show="isPreviewVisible" preset="card" :mask-closable="true" :close-on-esc="true"
|
||||
:on-close="removePreviewCard" class="image-preview-modal"
|
||||
:content-style="{ width: '100%', height: '100%', padding: '0', overflow: 'hidden' }"
|
||||
:style="{ width: '100vw', height: '100vh', maxWidth: '100vw', margin: '0', padding: '0', borderRadius: '0', background: 'rgba(5, 7, 12, 0.96)' }">
|
||||
<div class="image-preview-shell">
|
||||
<NImage v-if="previewCard" :src="getCardImageUrl(previewCard)" :alt="getCardTitle(previewCard, 0)"
|
||||
object-fit="contain" preview-disabled :img-props="{
|
||||
style: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%',
|
||||
},
|
||||
}" class="image-preview-content" />
|
||||
</div>
|
||||
</NModal>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -420,11 +399,11 @@ watch(
|
|||
position: absolute;
|
||||
width: 74%;
|
||||
height: 100%;
|
||||
border-radius: 5px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--card-border-color, var(--ffffff, #FFF));
|
||||
background: url(<path-to-image>) lightgray 50% / cover no-repeat;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
/* overflow: hidden; */
|
||||
transition: transform 0.35s ease-out, opacity 0.35s ease-out;
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
|
|
@ -439,6 +418,8 @@ watch(
|
|||
|
||||
.card-media {
|
||||
cursor: default;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-media-uploading {
|
||||
|
|
@ -529,8 +510,8 @@ watch(
|
|||
|
||||
.card-delete-btn {
|
||||
position: absolute;
|
||||
top: 0.3rem;
|
||||
right: 0.3rem;
|
||||
top: -0.3rem;
|
||||
right: -0.3rem;
|
||||
z-index: 4;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
|
@ -556,8 +537,14 @@ watch(
|
|||
display: block;
|
||||
}
|
||||
|
||||
.card-media img,
|
||||
.card-preview-image img {
|
||||
.card-media :deep(.n-image),
|
||||
.card-preview-image :deep(.n-image) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.card-media :deep(.n-image img),
|
||||
.card-preview-image :deep(.n-image img) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
|
|
@ -816,61 +803,6 @@ watch(
|
|||
transform: translateX(-50%) translateY(6px);
|
||||
}
|
||||
|
||||
.image-preview-modal :deep(.n-card) {
|
||||
background: transparent;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.image-preview-modal :deep(.n-card__content) {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.image-preview-shell {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.image-preview-content {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: calc(100vw - 48px);
|
||||
max-height: calc(100vh - 48px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.image-preview-content :deep(img) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 640px) {
|
||||
.stacked-cards-container {
|
||||
|
|
|
|||
Loading…
Reference in New Issue