fix: 系统公告弹窗前端
This commit is contained in:
598
Yi.Ai.Vue3/src/components/SystemAnnouncementDialog/index.vue
Normal file
598
Yi.Ai.Vue3/src/components/SystemAnnouncementDialog/index.vue
Normal file
@@ -0,0 +1,598 @@
|
||||
<script lang="ts" setup>
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
import type { Activity, Announcement } from '@/api'
|
||||
import { useAnnouncementStore } from '@/stores'
|
||||
import type { CloseType } from '@/stores/modules/announcement'
|
||||
|
||||
const router = useRouter()
|
||||
const announcementStore = useAnnouncementStore()
|
||||
|
||||
const activeTab = ref('activity')
|
||||
|
||||
// 窗口宽度响应式状态
|
||||
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 { carousels, activities, announcements, isDialogVisible } = storeToRefs(announcementStore)
|
||||
|
||||
// 分离最新公告和历史公告
|
||||
const latestAnnouncements = computed(() =>
|
||||
announcements.value.filter(a => a.type === 'latest'),
|
||||
)
|
||||
const historyAnnouncements = computed(() =>
|
||||
announcements.value.filter(a => a.type === 'history'),
|
||||
)
|
||||
|
||||
// 处理关闭弹窗
|
||||
function handleClose(type: CloseType) {
|
||||
announcementStore.closeDialog(type)
|
||||
|
||||
const messages = {
|
||||
today: '今日内不再显示',
|
||||
week: '本周内不再显示',
|
||||
permanent: '公告已关闭',
|
||||
}
|
||||
|
||||
ElMessage.success(messages[type])
|
||||
}
|
||||
|
||||
// 查看活动详情
|
||||
function viewActivityDetail(activity: Activity) {
|
||||
router.push({
|
||||
name: 'activityDetail',
|
||||
params: { id: activity.id },
|
||||
})
|
||||
announcementStore.isDialogVisible = false
|
||||
}
|
||||
|
||||
// 查看公告详情
|
||||
function viewAnnouncementDetail(announcement: Announcement) {
|
||||
router.push({
|
||||
name: 'announcementDetail',
|
||||
params: { id: announcement.id },
|
||||
})
|
||||
announcementStore.isDialogVisible = false
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
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')}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="isDialogVisible"
|
||||
title="系统公告"
|
||||
:width="dialogWidth"
|
||||
:close-on-click-modal="false"
|
||||
class="announcement-dialog"
|
||||
>
|
||||
<div 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">
|
||||
<!-- 轮播图 -->
|
||||
<el-carousel
|
||||
v-if="carousels.length > 0"
|
||||
height="250px"
|
||||
class="carousel-container"
|
||||
>
|
||||
<el-carousel-item v-for="item in carousels" :key="item.id">
|
||||
<img
|
||||
:src="item.imageUrl"
|
||||
:alt="item.title"
|
||||
class="carousel-image"
|
||||
>
|
||||
<div v-if="item.title" class="carousel-title">
|
||||
{{ item.title }}
|
||||
</div>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
|
||||
<!-- 活动列表 -->
|
||||
<div class="activity-list">
|
||||
<div
|
||||
v-for="activity in activities"
|
||||
:key="activity.id"
|
||||
class="activity-item"
|
||||
>
|
||||
<div class="activity-header">
|
||||
<h3 class="activity-title">{{ activity.title }}</h3>
|
||||
<el-tag
|
||||
v-if="activity.status === 'active'"
|
||||
type="success"
|
||||
size="small"
|
||||
>
|
||||
进行中
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-else-if="activity.status === 'expired'"
|
||||
type="info"
|
||||
size="small"
|
||||
>
|
||||
已结束
|
||||
</el-tag>
|
||||
</div>
|
||||
<p class="activity-description">{{ activity.description }}</p>
|
||||
<div class="activity-footer">
|
||||
<span class="activity-time">{{ formatTime(activity.createdAt) }}</span>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="viewActivityDetail(activity)"
|
||||
>
|
||||
查看详情
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 公告Tab -->
|
||||
<el-tab-pane label="公告" name="announcement">
|
||||
<div class="tab-content-wrapper">
|
||||
<div class="announcement-content">
|
||||
<!-- 最新公告 -->
|
||||
<div v-if="latestAnnouncements.length > 0" class="latest-section">
|
||||
<h3 class="section-title">最新公告</h3>
|
||||
<div
|
||||
v-for="announcement in latestAnnouncements"
|
||||
:key="announcement.id"
|
||||
class="announcement-item latest"
|
||||
>
|
||||
<div class="announcement-header">
|
||||
<h4 class="announcement-title">
|
||||
<el-icon v-if="announcement.isImportant" color="#f56c6c">
|
||||
<i-ep-warning />
|
||||
</el-icon>
|
||||
{{ announcement.title }}
|
||||
</h4>
|
||||
<span class="announcement-time">{{ formatTime(announcement.publishTime) }}</span>
|
||||
</div>
|
||||
<p class="announcement-summary">{{ announcement.content.substring(0, 100) }}...</p>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="viewAnnouncementDetail(announcement)"
|
||||
>
|
||||
查看详情
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 历史公告(时间线) -->
|
||||
<div v-if="historyAnnouncements.length > 0" class="history-section">
|
||||
<h3 class="section-title">历史公告</h3>
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="announcement in historyAnnouncements"
|
||||
:key="announcement.id"
|
||||
:timestamp="formatTime(announcement.publishTime)"
|
||||
placement="top"
|
||||
>
|
||||
<div class="timeline-item-content">
|
||||
<h4 class="timeline-title">{{ announcement.title }}</h4>
|
||||
<p class="timeline-summary">{{ announcement.content.substring(0, 80) }}...</p>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
size="small"
|
||||
@click="viewAnnouncementDetail(announcement)"
|
||||
>
|
||||
查看详情
|
||||
</el-button>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose('week')">本周关闭</el-button>
|
||||
<el-button @click="handleClose('today')">今日关闭</el-button>
|
||||
<el-button type="primary" @click="handleClose('permanent')">
|
||||
关闭公告
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
// 确保弹窗本身不会溢出
|
||||
:deep(.el-dialog) {
|
||||
margin-top: 5vh !important;
|
||||
margin-bottom: 5vh !important;
|
||||
max-height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__body) {
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
: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: 0 20px 20px;
|
||||
flex-shrink: 0;
|
||||
|
||||
// 确保滚动条样式
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 3px;
|
||||
|
||||
&:hover {
|
||||
background: #a8a8a8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.activity-content {
|
||||
min-height: min-content;
|
||||
|
||||
.carousel-container {
|
||||
margin-bottom: 24px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.carousel-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.carousel-title {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 12px 16px;
|
||||
background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.activity-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.activity-item {
|
||||
padding: 16px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
background: #e8eaed;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.activity-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.activity-title {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.activity-description {
|
||||
margin: 0 0 12px 0;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.activity-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.activity-time {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
.announcement-content {
|
||||
min-height: min-content;
|
||||
|
||||
.section-title {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid #409eff;
|
||||
}
|
||||
|
||||
.latest-section {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.announcement-item {
|
||||
padding: 16px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 12px;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
background: #e8eaed;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
&.latest {
|
||||
border-left: 3px solid #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.announcement-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.announcement-title {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.announcement-time {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.announcement-summary {
|
||||
margin: 0 0 12px 0;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.history-section {
|
||||
:deep(.el-timeline) {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.timeline-item-content {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.timeline-title {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.timeline-summary {
|
||||
margin: 0 0 8px 0;
|
||||
color: #606266;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
// 移动端适配
|
||||
@media screen and (max-width: 768px) {
|
||||
:deep(.el-dialog) {
|
||||
margin: 5vh auto !important;
|
||||
max-height: 90vh;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__header) {
|
||||
padding: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__footer) {
|
||||
padding: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.announcement-tabs {
|
||||
:deep(.el-tabs__header) {
|
||||
margin: 0 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-content-wrapper {
|
||||
height: calc(90vh - 230px);
|
||||
max-height: calc(90vh - 230px);
|
||||
padding: 0 12px 12px;
|
||||
flex-shrink: 0;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
// 移动端滚动条样式
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.activity-content {
|
||||
.carousel-container {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.carousel-title {
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.activity-item {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.activity-title {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.activity-description {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.activity-footer {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.announcement-content {
|
||||
.section-title {
|
||||
font-size: 14px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.announcement-item {
|
||||
padding: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.announcement-title {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.announcement-summary {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.history-section {
|
||||
.timeline-title {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.timeline-summary {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
.el-button {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 平板适配 (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);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user