feat: improve image browsing experience

This commit is contained in:
2026-05-21 19:56:42 +08:00
parent 7504cce7f1
commit 6d8acce295
6 changed files with 661 additions and 32 deletions
+117
View File
@@ -0,0 +1,117 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
const props = withDefaults(defineProps<{
open: boolean
imageUrl: string
imageAlt: string
title: string
subtitle?: string
badgeLabel?: string
badgeClass?: string
panelWidth?: string
}>(), {
subtitle: '',
badgeLabel: '',
badgeClass: '',
panelWidth: '20rem',
})
const emit = defineEmits<{
close: []
}>()
const collapsed = ref(false)
const rootStyle = computed(() => ({
'--detail-panel-width': props.panelWidth,
}) as Record<string, string>)
const imageClass = computed(() => {
return collapsed.value
? 'block h-full w-auto max-w-full object-contain lg:max-w-[92vw]'
: 'block h-full w-auto max-w-full object-contain lg:max-w-[calc(92vw-var(--detail-panel-width))]'
})
watch(() => props.open, open => {
if (open) {
collapsed.value = false
}
})
function close() {
emit('close')
}
function togglePanel() {
collapsed.value = !collapsed.value
}
</script>
<template>
<Teleport to="body">
<div
v-if="open"
class="fixed inset-0 z-[110] flex items-center justify-center bg-black/85 px-4 py-6"
@click="close"
>
<div
class="relative h-[80vh] w-fit max-w-[92vw] overflow-hidden rounded-[30px] shadow-2xl lg:flex"
:style="rootStyle"
@click.stop
>
<div class="flex min-h-0 flex-1 items-center justify-center overflow-hidden lg:flex-none">
<img :src="imageUrl" :alt="imageAlt" :class="imageClass" />
</div>
<div
class="relative h-[38vh] transition-[width] duration-300 lg:h-full lg:shrink-0"
:class="collapsed ? 'lg:w-0' : 'lg:w-[var(--detail-panel-width)]'"
>
<button
type="button"
@click="togglePanel"
class="absolute left-0 top-1/2 z-10 hidden h-14 w-8 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full border border-slate-200 bg-white text-slate-500 shadow-lg transition-colors hover:text-slate-800 lg:flex"
:aria-label="collapsed ? '展开详细信息' : '收起详细信息'"
:title="collapsed ? '展开详细信息' : '收起详细信息'"
>
<span class="text-lg leading-none">{{ collapsed ? '‹' : '›' }}</span>
</button>
<div
class="flex h-full flex-col overflow-hidden border-t border-slate-200 bg-white transition-[border-color] duration-300 lg:border-t-0"
:class="collapsed ? 'lg:border-l-0' : 'lg:border-l'"
>
<div class="flex items-start justify-between border-b border-slate-200 px-6 py-5">
<div>
<div class="flex items-center gap-3">
<h2 class="text-2xl font-bold text-slate-900">{{ title }}</h2>
<span
v-if="badgeLabel"
class="rounded-full border px-3 py-1 text-xs font-medium"
:class="badgeClass"
>
{{ badgeLabel }}
</span>
</div>
<p v-if="subtitle" class="mt-2 text-sm text-slate-500">{{ subtitle }}</p>
</div>
<button
type="button"
@click="close"
class="rounded-full p-2 text-slate-400 transition-colors hover:bg-slate-100 hover:text-slate-700"
>
✕
</button>
</div>
<div class="flex-1 overflow-y-auto px-6 py-5">
<slot />
</div>
</div>
</div>
</div>
</div>
</Teleport>
</template>