| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="notification-popover-content"> |
| | | <div class="popover-header"> |
| | | <span class="popover-title">æ¶æ¯éç¥</span> |
| | | <el-button type="primary" |
| | | size="small" |
| | | @click="handleMarkAllAsRead" |
| | | :disabled="unreadCount === 0"> |
| | | ä¸é®å·²è¯» |
| | | </el-button> |
| | | </div> |
| | | <div class="notification-content"> |
| | | <el-tabs v-model="activeTab" |
| | | @tab-change="handleTabChange"> |
| | | <el-tab-pane :label="`æªè¯»(${unreadCount})`" |
| | | name="unread"> |
| | | <div v-if="unreadList.length === 0" |
| | | class="empty-state"> |
| | | <el-empty description="ææ æªè¯»æ¶æ¯" /> |
| | | </div> |
| | | <div v-else |
| | | class="notification-list"> |
| | | <div v-for="item in unreadList" |
| | | :key="item.id" |
| | | class="notification-item"> |
| | | <div class="notification-icon"> |
| | | <el-icon :size="24" |
| | | color="#67C23A"> |
| | | <Bell /> |
| | | </el-icon> |
| | | </div> |
| | | <div class="notification-content-wrapper"> |
| | | <div class="notification-title">{{ item.noticeTitle }}</div> |
| | | <div class="notification-detail">{{ item.noticeContent }}</div> |
| | | <div class="notification-time">{{ item.createTime }}</div> |
| | | </div> |
| | | <div class="notification-action"> |
| | | <el-button type="primary" |
| | | size="small" |
| | | @click="handleConfirm(item)"> |
| | | 确认 |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-tab-pane> |
| | | <el-tab-pane label="已读" |
| | | name="read"> |
| | | <div v-if="readList.length === 0" |
| | | class="empty-state"> |
| | | <el-empty description="ææ å·²è¯»æ¶æ¯" /> |
| | | </div> |
| | | <div v-else |
| | | class="notification-list"> |
| | | <div v-for="item in readList" |
| | | :key="item.id" |
| | | class="notification-item read"> |
| | | <div class="notification-icon"> |
| | | <el-icon :size="24" |
| | | color="#909399"> |
| | | <Bell /> |
| | | </el-icon> |
| | | </div> |
| | | <div class="notification-content-wrapper"> |
| | | <div class="notification-title">{{ item.noticeTitle }}</div> |
| | | <div class="notification-detail">{{ item.noticeContent }}</div> |
| | | <div class="notification-time">{{ item.createTime }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | <!-- å页 --> |
| | | <div class="pagination-wrapper" |
| | | v-if="total > 0"> |
| | | <el-pagination v-model:current-page="pageNum" |
| | | v-model:page-size="pageSize" |
| | | :page-sizes="[10, 20, 50, 100]" |
| | | :total="total" |
| | | layout="prev, pager, next, sizes" |
| | | @size-change="handleSizeChange" |
| | | @current-change="handlePageChange" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Bell } from "@element-plus/icons-vue"; |
| | | import { |
| | | listMessage, |
| | | markAsRead, |
| | | markAllAsRead, |
| | | confirmMessage, |
| | | getUnreadCount, |
| | | } from "@/api/system/message"; |
| | | import { ElMessage } from "element-plus"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { useRouter } from "vue-router"; |
| | | |
| | | const userStore = useUserStore(); |
| | | const router = useRouter(); |
| | | const emit = defineEmits(["unreadCountChange"]); |
| | | |
| | | const activeTab = ref("unread"); |
| | | const unreadList = ref([]); |
| | | const readList = ref([]); |
| | | const unreadCount = ref(0); |
| | | const total = ref(0); |
| | | const pageNum = ref(1); |
| | | const pageSize = ref(10); |
| | | |
| | | // å è½½æ¶æ¯å表 |
| | | const loadMessages = async () => { |
| | | try { |
| | | const consigneeId = userStore.id; |
| | | if (!consigneeId) { |
| | | console.warn("æªè·åå°å½åç»å½ç¨æ·ID"); |
| | | return; |
| | | } |
| | | const params = { |
| | | consigneeId: consigneeId, |
| | | current: pageNum.value, |
| | | size: pageSize.value, |
| | | status: activeTab.value === "read" ? 1 : 0, |
| | | }; |
| | | const res = await listMessage(params); |
| | | if (res.code === 200) { |
| | | if (activeTab.value === "unread") { |
| | | unreadList.value = res.data.records || []; |
| | | } else { |
| | | readList.value = res.data.records || []; |
| | | } |
| | | total.value = res.data.total || 0; |
| | | } |
| | | } catch (error) { |
| | | console.error("å è½½æ¶æ¯å表失败:", error); |
| | | } |
| | | }; |
| | | |
| | | // å è½½æªè¯»æ°é |
| | | const loadUnreadCount = async () => { |
| | | try { |
| | | const consigneeId = userStore.id; |
| | | if (!consigneeId) { |
| | | console.warn("æªè·åå°å½åç»å½ç¨æ·ID"); |
| | | return; |
| | | } |
| | | const res = await getUnreadCount(consigneeId); |
| | | if (res.code === 200) { |
| | | unreadCount.value = res.data || 0; |
| | | emit("unreadCountChange", unreadCount.value); |
| | | } |
| | | } catch (error) { |
| | | console.error("å è½½æªè¯»æ°é失败:", error); |
| | | } |
| | | }; |
| | | |
| | | // æ ç¾é¡µåæ¢ |
| | | const handleTabChange = tab => { |
| | | pageNum.value = 1; |
| | | loadMessages(); |
| | | }; |
| | | |
| | | // ç¡®è®¤æ¶æ¯ |
| | | const handleConfirm = async item => { |
| | | try { |
| | | console.log("item", item); |
| | | const res = await confirmMessage(item.noticeId, 1); |
| | | if (res.code === 200) { |
| | | ElMessage.success("确认æå"); |
| | | // éæ°å è½½æ°æ® |
| | | loadMessages(); |
| | | loadUnreadCount(); |
| | | |
| | | // æ ¹æ® jumpPath è¿è¡é¡µé¢è·³è½¬ |
| | | if (item.jumpPath) { |
| | | try { |
| | | // è§£æ jumpPathï¼å离路å¾åæ¥è¯¢åæ° |
| | | const [path, queryString] = item.jumpPath.split("?"); |
| | | let query = {}; |
| | | |
| | | if (queryString) { |
| | | // è§£ææ¥è¯¢åæ° |
| | | queryString.split("&").forEach(param => { |
| | | const [key, value] = param.split("="); |
| | | if (key && value) { |
| | | query[key] = decodeURIComponent(value); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | // è·³è½¬å°æå®é¡µé¢ |
| | | router.push({ |
| | | path: path, |
| | | query: query, |
| | | }); |
| | | } catch (error) { |
| | | console.error("页é¢è·³è½¬å¤±è´¥:", error); |
| | | } |
| | | } |
| | | } |
| | | } catch (error) { |
| | | console.error("ç¡®è®¤æ¶æ¯å¤±è´¥:", error); |
| | | ElMessage.error("确认失败"); |
| | | } |
| | | }; |
| | | |
| | | // ä¸é®å·²è¯» |
| | | const handleMarkAllAsRead = async () => { |
| | | try { |
| | | const res = await markAllAsRead(); |
| | | if (res.code === 200) { |
| | | ElMessage.success("å·²å
¨é¨æ 记为已读"); |
| | | loadMessages(); |
| | | loadUnreadCount(); |
| | | } |
| | | } catch (error) { |
| | | console.error("ä¸é®å·²è¯»å¤±è´¥:", error); |
| | | ElMessage.error("æä½å¤±è´¥"); |
| | | } |
| | | }; |
| | | |
| | | // å页大尿¹å |
| | | const handleSizeChange = size => { |
| | | pageSize.value = size; |
| | | pageNum.value = 1; |
| | | loadMessages(); |
| | | }; |
| | | |
| | | // é¡µç æ¹å |
| | | const handlePageChange = page => { |
| | | pageNum.value = page; |
| | | loadMessages(); |
| | | }; |
| | | |
| | | // ç»ä»¶æè½½æ¶å è½½æªè¯»æ°é |
| | | onMounted(() => { |
| | | loadUnreadCount(); |
| | | }); |
| | | |
| | | // çå¬ç¶ç»ä»¶ä¼ éç visible ç¶æï¼éè¿ watch å¨ Navbar ä¸å¤çï¼ |
| | | // è¿éåªè´è´£æ°æ®å è½½ï¼ä¸æ§å¶æ¾ç¤º |
| | | |
| | | // æ´é²æ¹æ³ä¾å¤é¨è°ç¨ |
| | | defineExpose({ |
| | | loadUnreadCount, |
| | | loadMessages, |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .notification-popover-content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | width: 500px; |
| | | padding: 16px; |
| | | background: rgba(255, 255, 255, 0.92); |
| | | } |
| | | |
| | | .popover-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | width: 100%; |
| | | margin-bottom: 16px; |
| | | padding-bottom: 12px; |
| | | border-bottom: 1px solid var(--surface-border); |
| | | |
| | | .popover-title { |
| | | font-size: 18px; |
| | | font-weight: 500; |
| | | color: var(--text-primary); |
| | | } |
| | | } |
| | | |
| | | .notification-content { |
| | | max-height: 60vh; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | :deep(.el-tabs) { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | min-height: 0; |
| | | |
| | | .el-tabs__header { |
| | | margin-bottom: 0; |
| | | flex-shrink: 0; |
| | | padding: 0; |
| | | } |
| | | |
| | | .el-tabs__content { |
| | | flex: 1; |
| | | overflow-y: auto; |
| | | min-height: 0; |
| | | padding-top: 16px; |
| | | } |
| | | |
| | | .el-tab-pane { |
| | | height: 100%; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .empty-state { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | min-height: 300px; |
| | | padding: 40px 0; |
| | | } |
| | | |
| | | .notification-list { |
| | | .notification-item { |
| | | display: flex; |
| | | padding: 12px 0; |
| | | border-bottom: 1px solid rgba(148, 163, 184, 0.18); |
| | | transition: background-color 0.3s; |
| | | |
| | | &:hover { |
| | | background-color: #f8fbff; |
| | | } |
| | | |
| | | &.read { |
| | | opacity: 0.7; |
| | | } |
| | | |
| | | .notification-icon { |
| | | flex-shrink: 0; |
| | | width: 40px; |
| | | height: 40px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background-color: rgba(59, 130, 246, 0.12); |
| | | border-radius: 50%; |
| | | margin-right: 12px; |
| | | } |
| | | |
| | | .notification-content-wrapper { |
| | | flex: 1; |
| | | min-width: 0; |
| | | |
| | | .notification-title { |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | color: var(--text-primary); |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .notification-detail { |
| | | font-size: 13px; |
| | | color: var(--text-secondary); |
| | | line-height: 1.5; |
| | | margin-bottom: 8px; |
| | | word-break: break-all; |
| | | } |
| | | |
| | | .notification-time { |
| | | font-size: 12px; |
| | | color: var(--text-tertiary); |
| | | } |
| | | } |
| | | |
| | | .notification-action { |
| | | flex-shrink: 0; |
| | | margin-left: 12px; |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .pagination-wrapper { |
| | | margin-top: 16px; |
| | | padding-top: 16px; |
| | | border-top: 1px solid var(--surface-border); |
| | | display: flex; |
| | | justify-content: center; |
| | | padding-left: 0; |
| | | padding-right: 0; |
| | | } |
| | | </style> |
| | | |