first commit

This commit is contained in:
2025-11-24 16:06:44 +08:00
commit e57d497751
165 changed files with 59349 additions and 0 deletions

View File

@@ -0,0 +1,308 @@
<template>
<el-drawer
:model-value="visible"
:title="title"
direction="rtl"
size="400px"
:with-header="true"
@close="handleClose"
@update:model-value="(val) => emit('update:visible', val)"
class="notification-drawer"
>
<div class="notification-container">
<div class="notification-content">
<el-empty
v-if="appStore.notifications.length === 0"
description="暂无通知"
class="empty-notification"
/>
<div v-else class="notification-list">
<div
v-for="notification in appStore.notifications"
:key="notification.id"
class="notification-item"
>
<div class="notification-icon">
<div class="icon-container" :class="themeClass">
<el-icon class="notification-icon-svg">
<Info />
</el-icon>
</div>
</div>
<div class="notification-body">
<h4 class="notification-title">{{ notification.title }}</h4>
<p class="notification-message">{{ notification.message }}</p>
<p class="notification-time">
{{ formatDate(notification.timestamp, 'MM-DD HH:mm') }}
</p>
</div>
<div class="notification-actions">
<el-button
@click="appStore.removeNotification(notification.id)"
:icon="Close"
circle
text
size="small"
class="close-button"
/>
</div>
</div>
</div>
</div>
</div>
</el-drawer>
</template>
<script setup>
import { useAppStore } from '@/stores/app'
import { formatDate } from '@/utils'
import { XMarkIcon as Close, InformationCircleIcon as Info } from '@heroicons/vue/24/outline'
const props = defineProps({
visible: {
type: Boolean,
required: true
},
title: {
type: String,
default: '通知'
},
theme: {
type: String,
default: 'user', // 'user' | 'admin'
validator: (value) => ['user', 'admin'].includes(value)
}
})
const emit = defineEmits(['update:visible'])
const appStore = useAppStore()
// 主题样式类
const themeClass = computed(() => {
return props.theme === 'admin' ? 'admin-theme' : 'user-theme'
})
const handleClose = () => {
emit('update:visible', false)
}
</script>
<style scoped>
.notification-drawer {
--el-drawer-bg-color: #fff;
}
.notification-container {
height: 100%;
display: flex;
flex-direction: column;
}
.notification-content {
flex: 1;
overflow-y: auto;
padding: 0;
}
.empty-notification {
padding: 40px 20px;
}
.notification-list {
padding: 16px;
}
.notification-item {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 16px;
margin-bottom: 12px;
background: #f8f9fa;
border-radius: 8px;
border: 1px solid #e9ecef;
transition: all 0.2s ease;
}
.notification-item:hover {
background: #f1f3f4;
border-color: #dee2e6;
}
.notification-icon {
flex-shrink: 0;
}
.icon-container {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.icon-container.user-theme {
background-color: #e3f2fd;
}
.icon-container.admin-theme {
background-color: #ffebee;
}
.notification-icon-svg {
font-size: 20px;
}
.user-theme .notification-icon-svg {
color: #1976d2;
}
.admin-theme .notification-icon-svg {
color: #d32f2f;
}
.notification-body {
flex: 1;
min-width: 0;
}
.notification-title {
font-size: 14px;
font-weight: 600;
color: #212529;
margin: 0 0 4px 0;
line-height: 1.4;
}
.notification-message {
font-size: 13px;
color: #6c757d;
margin: 0 0 8px 0;
line-height: 1.4;
}
.notification-time {
font-size: 11px;
color: #adb5bd;
margin: 0;
line-height: 1;
}
.notification-actions {
flex-shrink: 0;
}
.close-button {
color: #adb5bd;
transition: color 0.2s;
}
.close-button:hover {
color: #6c757d;
}
/* 响应式设计 */
@media (max-width: 768px) {
.notification-list {
padding: 12px;
}
.notification-item {
padding: 12px;
margin-bottom: 8px;
}
.icon-container {
width: 32px;
height: 32px;
}
.notification-icon-svg {
font-size: 16px;
}
.notification-title {
font-size: 13px;
}
.notification-message {
font-size: 12px;
}
.notification-time {
font-size: 10px;
}
}
@media (max-width: 480px) {
.notification-list {
padding: 8px;
}
.notification-item {
padding: 10px;
margin-bottom: 6px;
gap: 8px;
}
.icon-container {
width: 28px;
height: 28px;
}
.notification-icon-svg {
font-size: 14px;
}
.notification-title {
font-size: 12px;
}
.notification-message {
font-size: 11px;
}
.notification-time {
font-size: 9px;
}
}
/* Element Plus Drawer 样式覆盖 */
:deep(.el-drawer__header) {
padding: 16px 20px;
border-bottom: 1px solid #e4e7ed;
margin-bottom: 0;
}
:deep(.el-drawer__title) {
font-size: 16px;
font-weight: 600;
color: #303133;
}
:deep(.el-drawer__body) {
padding: 0;
height: calc(100% - 60px);
}
/* 滚动条样式 */
.notification-content::-webkit-scrollbar {
width: 6px;
}
.notification-content::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.notification-content::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.notification-content::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
</style>