|
|
@@ -11,13 +11,95 @@
|
|
|
<screenfull id="screenfull" class="right-menu-item hover-effect" />
|
|
|
|
|
|
<!-- 消息中心 -->
|
|
|
- <a-tooltip title="消息中心" placement="bottom">
|
|
|
- <div class="right-menu-item hover-effect message-center" @click="goMessage">
|
|
|
+ <a-popover
|
|
|
+ v-model:open="messagePopoverVisible"
|
|
|
+ trigger="click"
|
|
|
+ placement="bottomRight"
|
|
|
+ overlay-class-name="message-popover"
|
|
|
+ :arrow="false"
|
|
|
+ >
|
|
|
+ <template #content>
|
|
|
+ <div class="message-panel">
|
|
|
+ <a-tabs v-model:activeKey="messageTab" centered>
|
|
|
+ <a-tab-pane key="notification" tab="通知">
|
|
|
+ <div class="message-list">
|
|
|
+ <template v-if="notificationList.length > 0">
|
|
|
+ <div
|
|
|
+ v-for="item in notificationList"
|
|
|
+ :key="item.id"
|
|
|
+ class="message-item"
|
|
|
+ @click="handleNotificationClick(item)"
|
|
|
+ >
|
|
|
+ <div class="message-icon" :class="item.type">
|
|
|
+ <BellOutlined v-if="item.type === 'system'" />
|
|
|
+ <MessageOutlined v-else-if="item.type === 'message'" />
|
|
|
+ <CheckCircleOutlined v-else-if="item.type === 'success'" />
|
|
|
+ <InfoCircleOutlined v-else />
|
|
|
+ </div>
|
|
|
+ <div class="message-content">
|
|
|
+ <div class="message-title">{{ item.title }}</div>
|
|
|
+ <div class="message-time">{{ item.time }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <a-empty v-else description="暂无通知" :image="Empty.PRESENTED_IMAGE_SIMPLE" />
|
|
|
+ </div>
|
|
|
+ </a-tab-pane>
|
|
|
+ <a-tab-pane key="message" tab="消息">
|
|
|
+ <div class="message-list">
|
|
|
+ <template v-if="messageList.length > 0">
|
|
|
+ <div
|
|
|
+ v-for="item in messageList"
|
|
|
+ :key="item.id"
|
|
|
+ class="message-item"
|
|
|
+ @click="handleMessageClick(item)"
|
|
|
+ >
|
|
|
+ <a-avatar :src="item.avatar" :size="36">{{ item.sender?.charAt(0) }}</a-avatar>
|
|
|
+ <div class="message-content">
|
|
|
+ <div class="message-title">{{ item.sender }}</div>
|
|
|
+ <div class="message-desc">{{ item.content }}</div>
|
|
|
+ <div class="message-time">{{ item.time }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <a-empty v-else description="暂无消息" :image="Empty.PRESENTED_IMAGE_SIMPLE" />
|
|
|
+ </div>
|
|
|
+ </a-tab-pane>
|
|
|
+ <a-tab-pane key="todo" tab="待办">
|
|
|
+ <div class="message-list">
|
|
|
+ <template v-if="todoList.length > 0">
|
|
|
+ <div
|
|
|
+ v-for="item in todoList"
|
|
|
+ :key="item.id"
|
|
|
+ class="message-item"
|
|
|
+ @click="handleTodoClick(item)"
|
|
|
+ >
|
|
|
+ <div class="message-icon todo">
|
|
|
+ <ClockCircleOutlined />
|
|
|
+ </div>
|
|
|
+ <div class="message-content">
|
|
|
+ <div class="message-title">{{ item.title }}</div>
|
|
|
+ <div class="message-desc">{{ item.description }}</div>
|
|
|
+ <div class="message-time">{{ item.time }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <a-empty v-else description="暂无待办" :image="Empty.PRESENTED_IMAGE_SIMPLE" />
|
|
|
+ </div>
|
|
|
+ </a-tab-pane>
|
|
|
+ </a-tabs>
|
|
|
+ <div class="message-footer">
|
|
|
+ <a-button type="link" @click="clearAll">清空</a-button>
|
|
|
+ <a-button type="link" @click="goMessageCenter">查看更多</a-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div class="right-menu-item hover-effect message-center">
|
|
|
<a-badge :count="unreadCount" :number-style="{ display: unreadCount === 0 ? 'none' : 'block' }" :overflow-count="99">
|
|
|
- <MessageOutlined :style="{ fontSize: '18px' }" />
|
|
|
+ <BellOutlined :style="{ fontSize: '18px' }" />
|
|
|
</a-badge>
|
|
|
</div>
|
|
|
- </a-tooltip>
|
|
|
+ </a-popover>
|
|
|
</template>
|
|
|
|
|
|
<a-dropdown class="avatar-container right-menu-item hover-effect" trigger="hover">
|
|
|
@@ -47,8 +129,8 @@
|
|
|
<script setup>
|
|
|
import { ref, onMounted, onUnmounted } from 'vue'
|
|
|
import { useRouter } from 'vue-router'
|
|
|
-import { Modal } from 'ant-design-vue'
|
|
|
-import { MessageOutlined } from '@ant-design/icons-vue'
|
|
|
+import { Modal, Empty } from 'ant-design-vue'
|
|
|
+import { BellOutlined, MessageOutlined, CheckCircleOutlined, InfoCircleOutlined, ClockCircleOutlined } from '@ant-design/icons-vue'
|
|
|
import Breadcrumb from '@/components/Breadcrumb'
|
|
|
import TopNav from '@/components/TopNav'
|
|
|
import Hamburger from '@/components/Hamburger'
|
|
|
@@ -64,11 +146,65 @@ const appStore = useAppStore()
|
|
|
const userStore = useUserStore()
|
|
|
const settingsStore = useSettingsStore()
|
|
|
|
|
|
+// 消息弹窗
|
|
|
+const messagePopoverVisible = ref(false)
|
|
|
+const messageTab = ref('notification')
|
|
|
+
|
|
|
// 未读消息数
|
|
|
const unreadCount = ref(0)
|
|
|
|
|
|
+// 通知列表
|
|
|
+const notificationList = ref([
|
|
|
+ { id: 1, type: 'system', title: '系统升级通知', time: '10分钟前' },
|
|
|
+ { id: 2, type: 'success', title: '您的申请已通过审核', time: '1小时前' },
|
|
|
+ { id: 3, type: 'info', title: '新功能上线提醒', time: '2小时前' },
|
|
|
+])
|
|
|
+
|
|
|
+// 消息列表
|
|
|
+const messageList = ref([
|
|
|
+ { id: 1, sender: '张三', avatar: '', content: '请问项目进度如何?', time: '5分钟前' },
|
|
|
+ { id: 2, sender: '李四', avatar: '', content: '文档已经发送给您了', time: '30分钟前' },
|
|
|
+])
|
|
|
+
|
|
|
+// 待办列表
|
|
|
+const todoList = ref([
|
|
|
+ { id: 1, title: '审批流程', description: '有3个待审批事项', time: '待处理' },
|
|
|
+ { id: 2, title: '周报提交', description: '本周周报尚未提交', time: '截止今天' },
|
|
|
+])
|
|
|
+
|
|
|
+// 处理通知点击
|
|
|
+function handleNotificationClick(item) {
|
|
|
+ console.log('点击通知:', item)
|
|
|
+ messagePopoverVisible.value = false
|
|
|
+}
|
|
|
+
|
|
|
+// 处理消息点击
|
|
|
+function handleMessageClick(item) {
|
|
|
+ console.log('点击消息:', item)
|
|
|
+ messagePopoverVisible.value = false
|
|
|
+ router.push('/message')
|
|
|
+}
|
|
|
+
|
|
|
+// 处理待办点击
|
|
|
+function handleTodoClick(item) {
|
|
|
+ console.log('点击待办:', item)
|
|
|
+ messagePopoverVisible.value = false
|
|
|
+}
|
|
|
+
|
|
|
+// 清空消息
|
|
|
+function clearAll() {
|
|
|
+ if (messageTab.value === 'notification') {
|
|
|
+ notificationList.value = []
|
|
|
+ } else if (messageTab.value === 'message') {
|
|
|
+ messageList.value = []
|
|
|
+ } else {
|
|
|
+ todoList.value = []
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
// 跳转消息中心
|
|
|
-function goMessage() {
|
|
|
+function goMessageCenter() {
|
|
|
+ messagePopoverVisible.value = false
|
|
|
router.push('/message')
|
|
|
}
|
|
|
|
|
|
@@ -326,3 +462,135 @@ html.dark .navbar {
|
|
|
}
|
|
|
}
|
|
|
</style>
|
|
|
+
|
|
|
+<style lang="scss">
|
|
|
+.message-popover {
|
|
|
+ .ant-popover-inner {
|
|
|
+ padding: 0;
|
|
|
+ border-radius: 8px;
|
|
|
+ box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.message-panel {
|
|
|
+ width: 336px;
|
|
|
+
|
|
|
+ .ant-tabs-nav {
|
|
|
+ margin-bottom: 0;
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .ant-tabs-tab {
|
|
|
+ padding: 12px 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .message-list {
|
|
|
+ max-height: 320px;
|
|
|
+ overflow-y: auto;
|
|
|
+
|
|
|
+ .message-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ padding: 12px 16px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: background 0.2s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: #f5f5f5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .message-icon {
|
|
|
+ width: 36px;
|
|
|
+ height: 36px;
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin-right: 12px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ font-size: 18px;
|
|
|
+
|
|
|
+ &.system {
|
|
|
+ background: #e6f7ff;
|
|
|
+ color: #1890ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.success {
|
|
|
+ background: #f6ffed;
|
|
|
+ color: #52c41a;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.info {
|
|
|
+ background: #fff7e6;
|
|
|
+ color: #fa8c16;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.message {
|
|
|
+ background: #f9f0ff;
|
|
|
+ color: #722ed1;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.todo {
|
|
|
+ background: #fff1f0;
|
|
|
+ color: #f5222d;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .ant-avatar {
|
|
|
+ margin-right: 12px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .message-content {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+
|
|
|
+ .message-title {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .message-desc {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #999;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .message-time {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #bbb;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .ant-empty {
|
|
|
+ padding: 40px 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .message-footer {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ border-top: 1px solid #f0f0f0;
|
|
|
+ padding: 8px 0;
|
|
|
+
|
|
|
+ .ant-btn-link {
|
|
|
+ color: #666;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ color: #1890ff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|