| | |
| | | <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 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-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" |
| | | <div v-else |
| | | class="notification-list"> |
| | | <div v-for="item in unreadList" |
| | | :key="item.id" |
| | | class="notification-item" |
| | | > |
| | | class="notification-item"> |
| | | <div class="notification-icon"> |
| | | <el-icon :size="24" color="#67C23A"> |
| | | <el-icon :size="24" |
| | | color="#67C23A"> |
| | | <Bell /> |
| | | </el-icon> |
| | | </div> |
| | |
| | | <div class="notification-time">{{ item.createTime }}</div> |
| | | </div> |
| | | <div class="notification-action"> |
| | | <el-button type="primary" size="small" @click="handleConfirm(item)"> |
| | | <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-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" |
| | | <div v-else |
| | | class="notification-list"> |
| | | <div v-for="item in readList" |
| | | :key="item.id" |
| | | class="notification-item read" |
| | | > |
| | | class="notification-item read"> |
| | | <div class="notification-icon"> |
| | | <el-icon :size="24" color="#909399"> |
| | | <el-icon :size="24" |
| | | color="#909399"> |
| | | <Bell /> |
| | | </el-icon> |
| | | </div> |
| | |
| | | </div> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | |
| | | <!-- 分页 --> |
| | | <div class="pagination-wrapper" v-if="total > 0"> |
| | | <el-pagination |
| | | v-model:current-page="pageNum" |
| | | <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" |
| | | /> |
| | | @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' |
| | | 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 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 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 |
| | | const consigneeId = userStore.id; |
| | | if (!consigneeId) { |
| | | console.warn('未获取到当前登录用户ID') |
| | | return |
| | | 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) |
| | | status: activeTab.value === "read" ? 1 : 0, |
| | | }; |
| | | const res = await listMessage(params); |
| | | if (res.code === 200) { |
| | | if (activeTab.value === 'unread') { |
| | | unreadList.value = res.data.records || [] |
| | | if (activeTab.value === "unread") { |
| | | unreadList.value = res.data.records || []; |
| | | } else { |
| | | readList.value = res.data.records || [] |
| | | readList.value = res.data.records || []; |
| | | } |
| | | total.value = res.data.total || 0 |
| | | total.value = res.data.total || 0; |
| | | } |
| | | } catch (error) { |
| | | console.error('加载消息列表失败:', error) |
| | | console.error("加载消息列表失败:", error); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // 加载未读数量 |
| | | const loadUnreadCount = async () => { |
| | | try { |
| | | const consigneeId = userStore.id |
| | | const consigneeId = userStore.id; |
| | | if (!consigneeId) { |
| | | console.warn('未获取到当前登录用户ID') |
| | | return |
| | | console.warn("未获取到当前登录用户ID"); |
| | | return; |
| | | } |
| | | const res = await getUnreadCount(consigneeId) |
| | | const res = await getUnreadCount(consigneeId); |
| | | if (res.code === 200) { |
| | | unreadCount.value = res.data || 0 |
| | | emit('unreadCountChange', unreadCount.value) |
| | | unreadCount.value = res.data || 0; |
| | | emit("unreadCountChange", unreadCount.value); |
| | | } |
| | | } catch (error) { |
| | | console.error('加载未读数量失败:', error) |
| | | console.error("加载未读数量失败:", error); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // 标签页切换 |
| | | const handleTabChange = (tab) => { |
| | | pageNum.value = 1 |
| | | loadMessages() |
| | | } |
| | | const handleTabChange = tab => { |
| | | pageNum.value = 1; |
| | | loadMessages(); |
| | | }; |
| | | |
| | | // 确认消息 |
| | | const handleConfirm = async (item) => { |
| | | const handleConfirm = async item => { |
| | | try { |
| | | console.log('item', item) |
| | | const res = await confirmMessage(item.noticeId, 1) |
| | | console.log("item", item); |
| | | const res = await confirmMessage(item.noticeId, 1); |
| | | if (res.code === 200) { |
| | | ElMessage.success('确认成功') |
| | | ElMessage.success("确认成功"); |
| | | // 重新加载数据 |
| | | loadMessages() |
| | | loadUnreadCount() |
| | | loadMessages(); |
| | | loadUnreadCount(); |
| | | |
| | | // 根据 jumpPath 进行页面跳转 |
| | | if (item.jumpPath) { |
| | | try { |
| | | // 解析 jumpPath,分离路径和查询参数 |
| | | const [path, queryString] = item.jumpPath.split('?') |
| | | let query = {} |
| | | const [path, queryString] = item.jumpPath.split("?"); |
| | | let query = {}; |
| | | |
| | | if (queryString) { |
| | | // 解析查询参数 |
| | | queryString.split('&').forEach(param => { |
| | | const [key, value] = param.split('=') |
| | | queryString.split("&").forEach(param => { |
| | | const [key, value] = param.split("="); |
| | | if (key && value) { |
| | | query[key] = decodeURIComponent(value) |
| | | query[key] = decodeURIComponent(value); |
| | | } |
| | | }) |
| | | }); |
| | | } |
| | | |
| | | // 跳转到指定页面 |
| | | router.push({ |
| | | path: path, |
| | | query: query |
| | | }) |
| | | query: query, |
| | | }); |
| | | } catch (error) { |
| | | console.error('页面跳转失败:', error) |
| | | console.error("页面跳转失败:", error); |
| | | } |
| | | } |
| | | } |
| | | } catch (error) { |
| | | console.error('确认消息失败:', error) |
| | | ElMessage.error('确认失败') |
| | | console.error("确认消息失败:", error); |
| | | ElMessage.error("确认失败"); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // 一键已读 |
| | | const handleMarkAllAsRead = async () => { |
| | | try { |
| | | const res = await markAllAsRead() |
| | | const res = await markAllAsRead(); |
| | | if (res.code === 200) { |
| | | ElMessage.success('已全部标记为已读') |
| | | loadMessages() |
| | | loadUnreadCount() |
| | | ElMessage.success("已全部标记为已读"); |
| | | loadMessages(); |
| | | loadUnreadCount(); |
| | | } |
| | | } catch (error) { |
| | | console.error('一键已读失败:', error) |
| | | ElMessage.error('操作失败') |
| | | console.error("一键已读失败:", error); |
| | | ElMessage.error("操作失败"); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // 分页大小改变 |
| | | const handleSizeChange = (size) => { |
| | | pageSize.value = size |
| | | pageNum.value = 1 |
| | | loadMessages() |
| | | } |
| | | const handleSizeChange = size => { |
| | | pageSize.value = size; |
| | | pageNum.value = 1; |
| | | loadMessages(); |
| | | }; |
| | | |
| | | // 页码改变 |
| | | const handlePageChange = (page) => { |
| | | pageNum.value = page |
| | | loadMessages() |
| | | } |
| | | const handlePageChange = page => { |
| | | pageNum.value = page; |
| | | loadMessages(); |
| | | }; |
| | | |
| | | // 组件挂载时加载未读数量 |
| | | onMounted(() => { |
| | | loadUnreadCount() |
| | | }) |
| | | loadUnreadCount(); |
| | | }); |
| | | |
| | | // 监听父组件传递的 visible 状态(通过 watch 在 Navbar 中处理) |
| | | // 这里只负责数据加载,不控制显示 |
| | |
| | | // 暴露方法供外部调用 |
| | | defineExpose({ |
| | | loadUnreadCount, |
| | | loadMessages |
| | | }) |
| | | loadMessages, |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |