Files
Yi.Admin/Yi.Ai.Vue3/src/components/SystemAnnouncementDialog/index.vue

1375 lines
30 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts" setup>
import type { AnnouncementLogDto } from '@/api';
import type { CloseType } from '@/stores/modules/announcement';
import { getSystemAnnouncements } from '@/api';
import { ElMessage } from 'element-plus';
import { storeToRefs } from 'pinia';
import { useAnnouncementStore } from '@/stores';
const announcementStore = useAnnouncementStore();
const isLoadingData = ref(false);
const activeTab = ref('activity');
// 图片预览相关状态
const isImageViewerVisible = ref(false);
const currentPreviewUrl = ref('');
// 打开图片预览
function openImagePreview(url: string) {
currentPreviewUrl.value = url;
isImageViewerVisible.value = true;
}
// 关闭图片预览
function closeImagePreview() {
isImageViewerVisible.value = false;
}
// 窗口宽度响应式状态
const windowWidth = ref(typeof window !== 'undefined' ? window.innerWidth : 1920);
// 监听窗口大小变化
onMounted(() => {
const handleResize = () => {
windowWidth.value = window.innerWidth;
};
window.addEventListener('resize', handleResize);
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
});
});
// 响应式弹窗宽度
const dialogWidth = computed(() => {
if (windowWidth.value < 768)
return '95%';
if (windowWidth.value < 1024)
return '90%';
return '700px';
});
// 从store获取数据
const { announcements, isDialogVisible } = storeToRefs(announcementStore);
// 分离活动和系统公告
const activities = computed(() => {
if (!Array.isArray(announcements.value))
return [];
return announcements.value.filter(a => a.type === 'Activity');
});
const systemAnnouncements = computed(() => {
if (!Array.isArray(announcements.value))
return [];
return announcements.value.filter(a => a.type === 'System');
});
// 处理关闭弹窗
function handleClose(type: CloseType) {
announcementStore.closeDialog(type);
const messages = {
today: '一周内不再显示',
permanent: '公告已关闭',
};
ElMessage.success(messages[type]);
}
// 格式化时间
function formatTime(time: string) {
const date = new Date(time);
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
}
// 判断活动状态
function getActivityStatus(activity: AnnouncementLogDto): 'active' | 'expired' {
if (!activity.endTime)
return 'active';
const now = new Date();
const endTime = new Date(activity.endTime);
return now > endTime ? 'expired' : 'active';
}
// 监听弹窗显示状态,每次打开时从后端获取最新数据
watch(isDialogVisible, async (newValue) => {
if (newValue) {
// 弹窗打开时,从后端获取最新数据
isLoadingData.value = true;
try {
const res = await getSystemAnnouncements();
if (res && res.data && Array.isArray(res.data)) {
announcementStore.setAnnouncementData(res.data);
}
else {
announcementStore.setAnnouncementData([]);
}
}
catch (error) {
console.error('获取系统公告失败:', error);
ElMessage.error('获取公告数据失败,请稍后重试');
announcementStore.setAnnouncementData([]);
}
finally {
isLoadingData.value = false;
}
}
});
</script>
<template>
<el-dialog
v-model="isDialogVisible"
title="系统公告"
:width="dialogWidth"
:close-on-click-modal="true"
class="announcement-dialog"
>
<div v-loading="isLoadingData" class="announcement-dialog-content">
<el-tabs v-model="activeTab" class="announcement-tabs">
<!-- 活动Tab -->
<el-tab-pane label="活动" name="activity">
<div class="tab-content-wrapper">
<div class="activity-content">
<!-- 活动列表 -->
<div v-if="activities.length > 0" class="activity-list">
<div
v-for="(activity, index) in activities"
:key="index"
class="activity-item"
>
<!-- 活动图片 - 浮动在右上方 -->
<div v-if="activity.imageUrl" class="activity-image-wrapper" @click="openImagePreview(activity.imageUrl)">
<img :src="activity.imageUrl" :alt="activity.title" class="activity-image">
<!-- 放大图标提示 -->
<div class="zoom-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
<line x1="11" y1="8" x2="11" y2="14"></line>
<line x1="8" y1="11" x2="14" y2="11"></line>
</svg>
</div>
<!-- 图片上的状态标签 -->
<div class="activity-status-badge">
<el-tag
v-if="getActivityStatus(activity) === 'active'"
type="success"
size="small"
effect="dark"
>
进行中
</el-tag>
<el-tag
v-else
type="info"
size="small"
effect="dark"
>
已结束
</el-tag>
</div>
</div>
<div class="activity-body">
<div class="activity-header">
<h3 class="activity-title">
{{ activity.title }}
</h3>
</div>
<!-- 活动内容 -->
<div class="activity-content-list">
<p v-for="(line, idx) in activity.content" :key="idx" class="content-line">
{{ line }}
</p>
</div>
<div class="activity-footer">
<div class="activity-time-range">
<span class="activity-time">{{ formatTime(activity.startTime) }}</span>
<span v-if="activity.endTime" class="activity-time"> {{ formatTime(activity.endTime) }}</span>
</div>
<!-- 查看详情按钮 -->
<a
v-if="activity.url"
:href="activity.url"
target="_blank"
class="detail-link"
>
查看详情
<span class="detail-icon"></span>
</a>
</div>
</div>
</div>
</div>
<el-empty v-else description="暂无活动" />
</div>
</div>
</el-tab-pane>
<!-- 公告Tab -->
<el-tab-pane label="公告" name="announcement">
<div class="tab-content-wrapper">
<div class="announcement-content">
<!-- 系统公告列表 -->
<div v-if="systemAnnouncements.length > 0">
<el-timeline>
<el-timeline-item
v-for="(announcement, index) in systemAnnouncements"
:key="index"
:timestamp="formatTime(announcement.startTime)"
placement="top"
>
<div class="announcement-item">
<div class="announcement-header">
<h4 class="announcement-title">
{{ announcement.title }}
</h4>
</div>
<!-- 公告内容 -->
<div class="announcement-content-list">
<p v-for="(line, idx) in announcement.content" :key="idx" class="content-line">
{{ line }}
</p>
</div>
</div>
</el-timeline-item>
</el-timeline>
</div>
<el-empty v-else description="暂无公告" />
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
<!-- 底部按钮 -->
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose('today')">
关闭一周
</el-button>
<el-button type="primary" @click="handleClose('permanent')">
关闭公告
</el-button>
</div>
</template>
<!-- 图片预览组件 -->
<teleport to="body">
<el-image-viewer
v-if="isImageViewerVisible"
:url-list="[currentPreviewUrl]"
:initial-index="0"
@close="closeImagePreview"
/>
</teleport>
</el-dialog>
</template>
<style scoped lang="scss">
// 弹窗遮罩动画
:deep(.el-overlay) {
backdrop-filter: blur(4px);
animation: fadeIn 0.3s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
// 确保弹窗本身不会溢出
:deep(.el-dialog) {
margin-top: 5vh !important;
margin-bottom: 5vh !important;
max-height: 90vh;
display: flex;
flex-direction: column;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 20px 60px rgba(102, 126, 234, 0.3);
animation: slideInDown 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes slideInDown {
from {
opacity: 0;
transform: translateY(-30px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
:deep(.el-dialog__header) {
padding: 24px 28px;
border-bottom: none;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: -50%;
right: -5%;
width: 200px;
height: 200px;
background: radial-gradient(circle, rgba(255, 255, 255, 0.15) 0%, transparent 70%);
border-radius: 50%;
}
&::after {
content: '';
position: absolute;
bottom: -20%;
left: -3%;
width: 150px;
height: 150px;
background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%);
border-radius: 50%;
}
.el-dialog__title {
color: #fff;
font-size: 20px;
font-weight: 700;
letter-spacing: 0.5px;
position: relative;
z-index: 1;
display: flex;
align-items: center;
gap: 10px;
&::before {
content: '📣';
font-size: 24px;
animation: bounce 2s infinite;
}
}
.el-dialog__headerbtn {
top: 24px;
right: 24px;
z-index: 1;
.el-dialog__close {
color: #fff;
font-size: 22px;
width: 32px;
height: 32px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(10px);
transition: all 0.3s;
&:hover {
color: #fff;
background: rgba(255, 255, 255, 0.25);
transform: rotate(90deg);
}
}
}
}
@keyframes bounce {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-5px);
}
}
:deep(.el-dialog__body) {
overflow: hidden;
flex: 1;
display: flex;
flex-direction: column;
padding: 0 !important;
background: linear-gradient(to bottom, #f8f9fa 0%, #ffffff 100%);
position: relative;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, #667eea 0%, #764ba2 50%, #667eea 100%);
background-size: 200% 100%;
animation: shimmer 3s linear infinite;
}
}
@keyframes shimmer {
0% {
background-position: -200% 0;
}
100% {
background-position: 200% 0;
}
}
:deep(.el-dialog__footer) {
padding: 20px 28px;
border-top: 1px solid #e4e7ed;
background: linear-gradient(to top, #fafbfc 0%, #ffffff 100%);
box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.03);
}
.announcement-dialog-content {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.announcement-tabs {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
:deep(.el-tabs__header) {
flex-shrink: 0;
margin: 0 20px;
padding-top: 16px;
background-color: #f8f9fa;
}
:deep(.el-tabs__nav-wrap::after) {
display: none;
}
:deep(.el-tabs__active-bar) {
background-color: #667eea;
height: 3px;
}
:deep(.el-tabs__item) {
font-size: 15px;
font-weight: 500;
color: #606266;
&:hover {
color: #667eea;
}
&.is-active {
color: #667eea;
}
}
:deep(.el-tabs__content) {
flex: 1;
overflow: hidden;
}
:deep(.el-tab-pane) {
height: auto;
display: flex;
flex-direction: column;
}
}
.tab-content-wrapper {
height: 60vh;
max-height: 60vh;
overflow-y: auto;
overflow-x: hidden;
padding: 20px 24px 24px;
flex-shrink: 0;
// 自定义滚动条样式
&::-webkit-scrollbar {
width: 8px;
}
&::-webkit-scrollbar-track {
background: #f1f3f5;
border-radius: 4px;
}
&::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #667eea 0%, #764ba2 100%);
border-radius: 4px;
transition: background 0.3s;
&:hover {
background: linear-gradient(180deg, #5568d3 0%, #65408b 100%);
}
}
}
.activity-content {
min-height: min-content;
.activity-list {
display: flex;
flex-direction: column;
gap: 20px;
}
.activity-item {
position: relative;
padding: 16px;
background: #fff;
border-radius: 16px;
overflow: visible;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
border: 2px solid transparent;
background-clip: padding-box;
// 清除浮动,确保父容器高度正确
&::after {
content: '';
display: table;
clear: both;
}
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 16px;
padding: 2px;
background: linear-gradient(135deg, transparent 0%, transparent 100%);
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
opacity: 0;
transition: opacity 0.4s;
}
&:hover {
transform: translateY(-8px) scale(1.01);
box-shadow: 0 16px 40px rgba(102, 126, 234, 0.25);
border-color: #667eea;
&::before {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
opacity: 1;
}
.activity-image-wrapper {
box-shadow: 0 8px 24px rgba(102, 126, 234, 0.2);
}
.detail-link {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
padding-right: 16px;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
.detail-icon {
transform: translateX(6px) scale(1.2);
}
}
}
}
.activity-image-wrapper {
position: relative;
float: right;
width: 160px;
height: 160px;
margin-left: 16px;
margin-bottom: 8px;
border-radius: 12px;
overflow: hidden;
background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%);
shape-outside: inset(0);
transition: box-shadow 0.3s;
cursor: pointer;
z-index: 1; // 确保在内容之上
&:hover {
box-shadow: 0 4px 16px rgba(102, 126, 234, 0.3);
}
&:hover .activity-image {
transform: scale(1.1);
}
&:hover .zoom-icon {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
&::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
z-index: 1;
animation: shine 3s infinite;
pointer-events: none; // 确保不拦截鼠标事件
}
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 100px;
background: linear-gradient(to top, rgba(0, 0, 0, 0.5), transparent);
pointer-events: none;
z-index: 1;
}
}
@keyframes shine {
0% {
left: -100%;
}
50%, 100% {
left: 100%;
}
}
.activity-image {
width: 100%;
height: 100%;
object-fit: contain; // 等比例缩放,完整显示图片,不裁剪
transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
}
.activity-status-badge {
position: absolute;
top: 12px;
right: 12px;
z-index: 2;
animation: fadeInScale 0.5s ease-out 0.3s both;
:deep(.el-tag) {
border-radius: 16px;
padding: 5px 14px;
font-weight: 700;
font-size: 11px;
border: none;
backdrop-filter: blur(12px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
transition: all 0.3s;
text-transform: uppercase;
letter-spacing: 0.5px;
&.el-tag--success.el-tag--dark {
background: linear-gradient(135deg, rgba(16, 185, 129, 0.95), rgba(5, 150, 105, 0.95));
color: #fff;
animation: pulse 2s ease-in-out infinite;
}
&.el-tag--info.el-tag--dark {
background: rgba(148, 163, 184, 0.95);
color: #fff;
}
&:hover {
transform: scale(1.05);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.25);
}
}
}
@keyframes fadeInScale {
from {
opacity: 0;
transform: scale(0.5);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes pulse {
0%, 100% {
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);
}
50% {
box-shadow: 0 4px 20px rgba(16, 185, 129, 0.6);
}
}
.zoom-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0.8);
width: 48px;
height: 48px;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(8px);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
pointer-events: none; // 不拦截鼠标事件让父容器接收hover
z-index: 10; // 提高层级,确保在状态标签之上
svg {
width: 24px;
height: 24px;
color: #fff;
stroke-width: 2;
}
}
.activity-body {
// 内容会自动环绕浮动的图片
position: relative;
z-index: 0; // 确保在浮动图片之下
}
.activity-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 8px;
}
.activity-title {
margin: 0;
font-size: 16px;
font-weight: 700;
color: #1a1a1a;
flex: 1;
line-height: 1.3;
background: linear-gradient(135deg, #1a1a1a 0%, #4a5568 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
transition: all 0.3s;
.activity-item:hover & {
letter-spacing: 0.3px;
}
}
.activity-content-list {
padding: 0;
margin: 0 0 12px 0;
color: #4a5568;
font-size: 13px;
line-height: 1.6;
.content-line {
margin: 6px 0;
padding-left: 16px;
position: relative;
&::before {
content: '';
position: absolute;
left: 0;
top: 9px;
width: 5px;
height: 5px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 50%;
}
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
}
}
.activity-footer {
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
padding-top: 12px;
border-top: 1px dashed #e8e9eb;
clear: both; // 清除浮动始终在新行显示占满100%宽度
}
.activity-time-range {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.activity-time {
font-size: 12px;
color: #718096;
display: flex;
align-items: center;
gap: 6px;
&:first-child::before {
content: '🕐';
font-size: 13px;
}
}
.detail-link {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 16px;
background: linear-gradient(135deg, #f0f1f3 0%, #e8eaed 100%);
color: #667eea;
border-radius: 20px;
font-size: 12px;
font-weight: 700;
text-decoration: none;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
white-space: nowrap;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
background: rgba(255, 255, 255, 0.3);
transform: translate(-50%, -50%);
transition: width 0.6s, height 0.6s;
}
&:hover::before {
width: 300px;
height: 300px;
}
&:hover {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
transform: translateX(4px) scale(1.05);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
}
.detail-icon {
font-size: 18px;
font-weight: bold;
transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
position: relative;
z-index: 1;
}
span:not(.detail-icon) {
position: relative;
z-index: 1;
}
}
}
.announcement-content {
min-height: min-content;
:deep(.el-timeline) {
padding-left: 20px;
}
:deep(.el-timeline-item__node) {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: 3px solid #fff;
box-shadow: 0 0 0 2px #e8e9eb;
width: 16px;
height: 16px;
}
:deep(.el-timeline-item__tail) {
border-left: 2px dashed #d1d5db;
}
:deep(.el-timeline-item__timestamp) {
color: #718096;
font-size: 13px;
font-weight: 500;
margin-bottom: 10px;
display: inline-flex;
align-items: center;
gap: 6px;
white-space: nowrap;
&::before {
content: '📅';
font-size: 14px;
}
}
.announcement-item {
position: relative;
padding: 24px;
background: #fff;
border-radius: 12px;
overflow: hidden;
margin-bottom: 16px;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
border: 1px solid #e8e9eb;
border-left: 4px solid #e8e9eb;
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 4px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
transform: scaleY(0);
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
&::after {
content: '';
position: absolute;
top: 50%;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(102, 126, 234, 0.05), transparent);
transform: translateY(-50%);
transition: left 0.6s;
}
&:hover {
transform: translateX(8px) scale(1.02);
box-shadow: 0 8px 24px rgba(102, 126, 234, 0.15);
border-color: transparent;
background: linear-gradient(to right, rgba(102, 126, 234, 0.03), #fff);
&::before {
transform: scaleY(1);
}
&::after {
left: 100%;
}
.announcement-title {
color: #667eea;
transform: translateX(4px);
}
}
}
.announcement-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.announcement-title {
margin: 0;
font-size: 16px;
font-weight: 700;
color: #1a1a1a;
line-height: 1.4;
display: flex;
align-items: center;
gap: 10px;
transition: all 0.3s;
&::before {
content: '📢';
font-size: 20px;
animation: swing 2s ease-in-out infinite;
transform-origin: top center;
}
}
@keyframes swing {
0%, 100% {
transform: rotate(0deg);
}
10%, 30% {
transform: rotate(-10deg);
}
20%, 40% {
transform: rotate(10deg);
}
50% {
transform: rotate(0deg);
}
}
.announcement-content-list {
padding: 0;
margin: 0;
color: #4a5568;
font-size: 14px;
line-height: 1.8;
.content-line {
margin: 8px 0;
padding-left: 18px;
position: relative;
&::before {
content: '';
position: absolute;
left: 0;
top: 11px;
width: 6px;
height: 6px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 50%;
}
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
}
}
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
:deep(.el-button) {
border-radius: 24px;
padding: 12px 28px;
font-weight: 600;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
background: rgba(255, 255, 255, 0.3);
transform: translate(-50%, -50%);
transition: width 0.6s, height 0.6s;
}
&:active::before {
width: 300px;
height: 300px;
}
&:not(.el-button--primary) {
border: 2px solid #d1d5db;
color: #4a5568;
background: #fff;
&:hover {
border-color: #667eea;
color: #667eea;
background: linear-gradient(135deg, rgba(102, 126, 234, 0.05), rgba(118, 75, 162, 0.05));
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15);
}
}
&.el-button--primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
&:hover {
background: linear-gradient(135deg, #5568d3 0%, #65408b 100%);
transform: translateY(-3px) scale(1.02);
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.5);
}
&:active {
transform: translateY(-1px) scale(0.98);
}
}
span {
position: relative;
z-index: 1;
}
}
}
// 空状态样式优化
:deep(.el-empty) {
padding: 60px 0;
.el-empty__image {
width: 120px;
}
.el-empty__description {
color: #9ca3af;
font-size: 14px;
margin-top: 16px;
}
}
// 移动端适配
@media screen and (max-width: 768px) {
:deep(.el-dialog) {
margin: 5vh auto !important;
max-height: 90vh;
border-radius: 10px;
}
:deep(.el-dialog__header) {
padding: 16px 20px;
.el-dialog__title {
font-size: 16px;
}
}
:deep(.el-dialog__footer) {
padding: 12px 16px;
}
.announcement-tabs {
:deep(.el-tabs__header) {
margin: 0 12px;
padding-top: 12px;
}
:deep(.el-tabs__item) {
font-size: 14px;
padding: 0 16px;
}
}
.tab-content-wrapper {
height: calc(90vh - 230px);
max-height: calc(90vh - 230px);
padding: 16px 16px 20px;
-webkit-overflow-scrolling: touch;
&::-webkit-scrollbar {
width: 4px;
}
}
.activity-content {
.activity-list {
gap: 16px;
}
.activity-item {
border-radius: 10px;
padding: 0;
}
.activity-body {
padding: 16px;
}
.activity-image-wrapper {
float: none;
width: 100%;
height: 180px;
min-height: 180px;
margin: 0 0 16px 0;
border-radius: 10px 10px 0 0;
}
.activity-status-badge {
top: 12px;
right: 12px;
}
.activity-header {
margin-bottom: 10px;
}
.activity-title {
font-size: 16px;
}
.activity-content-list {
margin-bottom: 12px;
font-size: 13px;
line-height: 1.7;
}
.activity-footer {
padding-top: 12px;
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.detail-link {
width: 100%;
justify-content: center;
padding: 10px 16px;
}
}
.announcement-content {
:deep(.el-timeline) {
padding-left: 16px;
}
.announcement-item {
padding: 16px;
border-radius: 10px;
margin-bottom: 12px;
}
.announcement-title {
font-size: 15px;
&::before {
font-size: 16px;
}
}
.announcement-content-list {
font-size: 13px;
line-height: 1.7;
}
}
.dialog-footer {
flex-direction: column;
gap: 8px;
:deep(.el-button) {
width: 100%;
margin: 0;
padding: 11px 24px;
}
}
}
// 平板适配 (768px - 1024px)
@media screen and (min-width: 768px) and (max-width: 1024px) {
:deep(.el-dialog) {
margin: 5vh auto !important;
max-height: 90vh;
}
.announcement-tabs {
max-height: calc(90vh - 200px);
}
.activity-content {
.activity-image-wrapper {
float: none;
width: 100%;
height: 200px;
margin: 0 0 16px 0;
}
.activity-item {
padding: 0;
}
.activity-body {
padding: 18px;
}
}
.announcement-content {
.announcement-item {
padding: 18px;
}
}
}
// 添加入场动画
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(30px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.activity-item {
animation: slideInUp 0.5s ease-out both;
@for $i from 1 through 10 {
&:nth-child(#{$i}) {
animation-delay: #{$i * 0.08}s;
}
}
}
.announcement-item {
animation: slideInUp 0.5s ease-out both;
}
// 滚动条美化
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #f1f3f5;
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #667eea 0%, #764ba2 100%);
border-radius: 4px;
transition: background 0.3s;
&:hover {
background: linear-gradient(180deg, #5568d3 0%, #65408b 100%);
}
}
// 添加标签页切换动画
:deep(.el-tabs__content) {
.el-tab-pane {
animation: fadeInContent 0.4s ease-out;
}
}
@keyframes fadeInContent {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>