<script setup lang="ts">
|
|
import { ref, watch, onMounted, onBeforeUnmount } from 'vue';
|
|
import { Icon } from '@iconify/vue';
|
|
|
|
// 定义弹窗的属性
|
|
const props = defineProps({
|
|
// 是否显示弹窗
|
|
visible: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
// 弹窗标题
|
|
title: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
// 弹窗内容
|
|
content: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
// 弹窗图片
|
|
image: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
// 来源信息(适用于媒体报道)
|
|
source: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
// 日期信息
|
|
date: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
// 弹窗类型(post 或 media)
|
|
type: {
|
|
type: String,
|
|
default: 'post'
|
|
}
|
|
});
|
|
|
|
// 弹窗动画状态
|
|
const modalOpen = ref(false);
|
|
const modalReady = ref(false);
|
|
|
|
// 监听visible属性变化
|
|
watch(() => props.visible, (newVal) => {
|
|
if (newVal) {
|
|
document.body.classList.add('overflow-hidden');
|
|
modalReady.value = true;
|
|
setTimeout(() => {
|
|
modalOpen.value = true;
|
|
}, 50);
|
|
} else {
|
|
modalOpen.value = false;
|
|
setTimeout(() => {
|
|
modalReady.value = false;
|
|
document.body.classList.remove('overflow-hidden');
|
|
}, 300);
|
|
}
|
|
}, { immediate: true });
|
|
|
|
// 定义关闭弹窗的事件
|
|
const emit = defineEmits(['close']);
|
|
|
|
const closeModal = () => {
|
|
emit('close');
|
|
};
|
|
|
|
// 点击弹窗外部关闭弹窗
|
|
const handleOutsideClick = (e: MouseEvent) => {
|
|
const modal = document.querySelector('.modal-content');
|
|
if (modal && !modal.contains(e.target as Node)) {
|
|
closeModal();
|
|
}
|
|
};
|
|
|
|
// 按ESC键关闭弹窗
|
|
const handleEscKey = (e: KeyboardEvent) => {
|
|
if (e.key === 'Escape') {
|
|
closeModal();
|
|
}
|
|
};
|
|
|
|
// 添加事件监听器
|
|
onMounted(() => {
|
|
document.addEventListener('mousedown', handleOutsideClick);
|
|
document.addEventListener('keydown', handleEscKey);
|
|
});
|
|
|
|
// 移除事件监听器
|
|
onBeforeUnmount(() => {
|
|
document.removeEventListener('mousedown', handleOutsideClick);
|
|
document.removeEventListener('keydown', handleEscKey);
|
|
});
|
|
|
|
// 添加默认导出
|
|
defineExpose({
|
|
closeModal
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
v-if="modalReady"
|
|
class="fixed inset-0 z-50 flex items-center justify-center p-4 md:p-6"
|
|
:class="{ 'modal-visible': modalOpen }"
|
|
>
|
|
<!-- 背景遮罩 -->
|
|
<div
|
|
class="modal-backdrop absolute inset-0 bg-black transition-opacity duration-300"
|
|
:class="{ 'opacity-60': modalOpen, 'opacity-0': !modalOpen }"
|
|
></div>
|
|
|
|
<!-- 弹窗内容 -->
|
|
<div
|
|
class="modal-content relative bg-background w-full max-w-3xl max-h-[90vh] overflow-y-auto rounded-xl shadow-2xl transform transition-all duration-300"
|
|
:class="{ 'opacity-100 scale-100': modalOpen, 'opacity-0 scale-95': !modalOpen }"
|
|
>
|
|
<!-- 关闭按钮 -->
|
|
<button
|
|
@click="closeModal"
|
|
class="absolute top-4 right-4 z-10 w-8 h-8 flex items-center justify-center rounded-full bg-background-dark/50 hover:bg-background-dark/80 text-text-secondary hover:text-text transition-colors duration-200"
|
|
>
|
|
<Icon icon="carbon:close" width="20" height="20" />
|
|
</button>
|
|
|
|
<!-- 弹窗头部 - 图片 -->
|
|
<div class="relative w-full h-64 md:h-80 overflow-hidden">
|
|
<img
|
|
:src="image"
|
|
:alt="title"
|
|
class="w-full h-full object-cover"
|
|
/>
|
|
<div class="absolute inset-0 bg-gradient-to-t from-background to-transparent"></div>
|
|
|
|
<!-- 来源和日期信息 -->
|
|
<div class="absolute bottom-4 left-4 flex flex-wrap gap-2">
|
|
<div v-if="source" class="bg-black/50 backdrop-blur-sm px-3 py-1 rounded-lg text-white text-sm flex items-center">
|
|
<Icon icon="carbon:document" class="mr-1.5" width="16" height="16" />
|
|
{{ source }}
|
|
</div>
|
|
<div v-if="date" class="bg-black/50 backdrop-blur-sm px-3 py-1 rounded-lg text-white text-sm flex items-center">
|
|
<Icon icon="carbon:calendar" class="mr-1.5" width="16" height="16" />
|
|
{{ date }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 弹窗内容 -->
|
|
<div class="p-6 md:p-8">
|
|
<h2 class="text-2xl md:text-3xl font-bold text-text mb-6">{{ title }}</h2>
|
|
<div class="prose prose-sm md:prose-base max-w-none text-text-secondary" v-html="content"></div>
|
|
|
|
<!-- 底部操作区 -->
|
|
<div class="mt-8 flex justify-end">
|
|
<button
|
|
@click="closeModal"
|
|
class="px-6 py-2 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors duration-200"
|
|
>
|
|
关闭
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.modal-visible {
|
|
animation: fadeIn 0.3s ease;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; }
|
|
to { opacity: 1; }
|
|
}
|
|
|
|
/* 自定义滚动条 */
|
|
.modal-content {
|
|
scrollbar-width: thin;
|
|
scrollbar-color: var(--color-primary) transparent;
|
|
}
|
|
|
|
.modal-content::-webkit-scrollbar {
|
|
width: 6px;
|
|
}
|
|
|
|
.modal-content::-webkit-scrollbar-track {
|
|
background: transparent;
|
|
}
|
|
|
|
.modal-content::-webkit-scrollbar-thumb {
|
|
background-color: var(--color-primary-light);
|
|
border-radius: 20px;
|
|
}
|
|
|
|
/* 富文本内容样式 */
|
|
:deep(.prose) {
|
|
color: inherit;
|
|
}
|
|
|
|
:deep(.prose a) {
|
|
color: var(--color-primary);
|
|
text-decoration: none;
|
|
}
|
|
|
|
:deep(.prose a:hover) {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
:deep(.prose img) {
|
|
border-radius: 0.5rem;
|
|
}
|
|
|
|
:deep(.prose h2) {
|
|
color: var(--color-text);
|
|
font-weight: 600;
|
|
margin-top: 2em;
|
|
margin-bottom: 1em;
|
|
}
|
|
|
|
:deep(.prose h3) {
|
|
color: var(--color-text);
|
|
font-weight: 600;
|
|
margin-top: 1.5em;
|
|
margin-bottom: 0.75em;
|
|
}
|
|
</style>
|