From c435f1c060982615c4ad0c3049b14ca84babe0df Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期三, 04 三月 2026 18:00:00 +0800
Subject: [PATCH] 客户档案新增回访提醒、跟进、详情功能
---
src/layout/components/NotificationCenter/index.vue | 591 ++++++++++++++++++++++++++++++----------------------------
1 files changed, 302 insertions(+), 289 deletions(-)
diff --git a/src/layout/components/NotificationCenter/index.vue b/src/layout/components/NotificationCenter/index.vue
index 6098153..2517c22 100644
--- a/src/layout/components/NotificationCenter/index.vue
+++ b/src/layout/components/NotificationCenter/index.vue
@@ -2,25 +2,30 @@
<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"
- :key="item.id"
- class="notification-item"
- >
+ <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">
+ <el-icon :size="24"
+ color="#67C23A">
<Bell />
</el-icon>
</div>
@@ -30,25 +35,29 @@
<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"
- :key="item.id"
- class="notification-item read"
- >
+ <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">
+ <el-icon :size="24"
+ color="#909399">
<Bell />
</el-icon>
</div>
@@ -61,312 +70,316 @@
</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 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'
+ 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
- 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 || []
+ // 鍔犺浇娑堟伅鍒楄〃
+ const loadMessages = async () => {
+ try {
+ const consigneeId = userStore.id;
+ if (!consigneeId) {
+ console.warn("鏈幏鍙栧埌褰撳墠鐧诲綍鐢ㄦ埛ID");
+ return;
}
- total.value = res.data.total || 0
+ 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);
}
- } catch (error) {
- console.error('鍔犺浇娑堟伅鍒楄〃澶辫触:', error)
- }
-}
+ };
-// 鍔犺浇鏈鏁伴噺
-const loadUnreadCount = async () => {
- try {
- const consigneeId = userStore.id
- if (!consigneeId) {
- console.warn('鏈幏鍙栧埌褰撳墠鐧诲綍鐢ㄦ埛ID')
- return
+ // 鍔犺浇鏈鏁伴噺
+ 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 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 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 {
- // 瑙f瀽 jumpPath锛屽垎绂昏矾寰勫拰鏌ヨ鍙傛暟
- const [path, queryString] = item.jumpPath.split('?')
- let query = {}
-
- if (queryString) {
- // 瑙f瀽鏌ヨ鍙傛暟
- queryString.split('&').forEach(param => {
- const [key, value] = param.split('=')
- if (key && value) {
- query[key] = decodeURIComponent(value)
- }
- })
+ // 纭娑堟伅
+ 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 {
+ // 瑙f瀽 jumpPath锛屽垎绂昏矾寰勫拰鏌ヨ鍙傛暟
+ const [path, queryString] = item.jumpPath.split("?");
+ let query = {};
+
+ if (queryString) {
+ // 瑙f瀽鏌ヨ鍙傛暟
+ 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);
}
-
- // 璺宠浆鍒版寚瀹氶〉闈�
- router.push({
- path: path,
- query: query
- })
- } catch (error) {
- console.error('椤甸潰璺宠浆澶辫触:', error)
}
}
+ } catch (error) {
+ console.error("纭娑堟伅澶辫触:", error);
+ ElMessage.error("纭澶辫触");
}
- } catch (error) {
- console.error('纭娑堟伅澶辫触:', error)
- ElMessage.error('纭澶辫触')
- }
-}
+ };
-// 涓�閿凡璇�
-const handleMarkAllAsRead = async () => {
- try {
- const res = await markAllAsRead()
- if (res.code === 200) {
- ElMessage.success('宸插叏閮ㄦ爣璁颁负宸茶')
- loadMessages()
- loadUnreadCount()
+ // 涓�閿凡璇�
+ const handleMarkAllAsRead = async () => {
+ try {
+ const res = await markAllAsRead();
+ if (res.code === 200) {
+ ElMessage.success("宸插叏閮ㄦ爣璁颁负宸茶");
+ loadMessages();
+ loadUnreadCount();
+ }
+ } catch (error) {
+ console.error("涓�閿凡璇诲け璐�:", error);
+ ElMessage.error("鎿嶄綔澶辫触");
}
- } catch (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()
-})
+ // 缁勪欢鎸傝浇鏃跺姞杞芥湭璇绘暟閲�
+ onMounted(() => {
+ loadUnreadCount();
+ });
-// 鐩戝惉鐖剁粍浠朵紶閫掔殑 visible 鐘舵�侊紙閫氳繃 watch 鍦� Navbar 涓鐞嗭級
-// 杩欓噷鍙礋璐f暟鎹姞杞斤紝涓嶆帶鍒舵樉绀�
+ // 鐩戝惉鐖剁粍浠朵紶閫掔殑 visible 鐘舵�侊紙閫氳繃 watch 鍦� Navbar 涓鐞嗭級
+ // 杩欓噷鍙礋璐f暟鎹姞杞斤紝涓嶆帶鍒舵樉绀�
-// 鏆撮湶鏂规硶渚涘閮ㄨ皟鐢�
-defineExpose({
- loadUnreadCount,
- loadMessages
-})
+ // 鏆撮湶鏂规硶渚涘閮ㄨ皟鐢�
+ defineExpose({
+ loadUnreadCount,
+ loadMessages,
+ });
</script>
<style lang="scss" scoped>
-.notification-popover-content {
- display: flex;
- flex-direction: column;
- width: 500px;
- padding: 16px;
-}
-
-.popover-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- width: 100%;
- margin-bottom: 16px;
- padding-bottom: 12px;
- border-bottom: 1px solid #f0f0f0;
-
- .popover-title {
- font-size: 18px;
- font-weight: 500;
- color: #303133;
- }
-}
-
-.notification-content {
- max-height: 60vh;
- display: flex;
- flex-direction: column;
-
- :deep(.el-tabs) {
- flex: 1;
+ .notification-popover-content {
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%;
- }
+ width: 500px;
+ padding: 16px;
}
-}
-.empty-state {
- display: flex;
- justify-content: center;
- align-items: center;
- min-height: 300px;
- padding: 40px 0;
-}
-
-.notification-list {
- .notification-item {
+ .popover-header {
display: flex;
- padding: 12px 0;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+ margin-bottom: 16px;
+ padding-bottom: 12px;
border-bottom: 1px solid #f0f0f0;
- transition: background-color 0.3s;
- &:hover {
- background-color: #f5f7fa;
- }
-
- &.read {
- opacity: 0.7;
- }
-
- .notification-icon {
- flex-shrink: 0;
- width: 40px;
- height: 40px;
- display: flex;
- align-items: center;
- justify-content: center;
- background-color: #f0f9ff;
- border-radius: 50%;
- margin-right: 12px;
- }
-
- .notification-content-wrapper {
- flex: 1;
- min-width: 0;
-
- .notification-title {
- font-size: 14px;
- font-weight: 500;
- color: #303133;
- margin-bottom: 8px;
- }
-
- .notification-detail {
- font-size: 13px;
- color: #606266;
- line-height: 1.5;
- margin-bottom: 8px;
- word-break: break-all;
- }
-
- .notification-time {
- font-size: 12px;
- color: #909399;
- }
- }
-
- .notification-action {
- flex-shrink: 0;
- margin-left: 12px;
- display: flex;
- align-items: center;
+ .popover-title {
+ font-size: 18px;
+ font-weight: 500;
+ color: #303133;
}
}
-}
-.pagination-wrapper {
- margin-top: 16px;
- padding-top: 16px;
- border-top: 1px solid #f0f0f0;
- display: flex;
- justify-content: center;
- padding-left: 0;
- padding-right: 0;
-}
+ .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 #f0f0f0;
+ transition: background-color 0.3s;
+
+ &:hover {
+ background-color: #f5f7fa;
+ }
+
+ &.read {
+ opacity: 0.7;
+ }
+
+ .notification-icon {
+ flex-shrink: 0;
+ width: 40px;
+ height: 40px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: #f0f9ff;
+ border-radius: 50%;
+ margin-right: 12px;
+ }
+
+ .notification-content-wrapper {
+ flex: 1;
+ min-width: 0;
+
+ .notification-title {
+ font-size: 14px;
+ font-weight: 500;
+ color: #303133;
+ margin-bottom: 8px;
+ }
+
+ .notification-detail {
+ font-size: 13px;
+ color: #606266;
+ line-height: 1.5;
+ margin-bottom: 8px;
+ word-break: break-all;
+ }
+
+ .notification-time {
+ font-size: 12px;
+ color: #909399;
+ }
+ }
+
+ .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 #f0f0f0;
+ display: flex;
+ justify-content: center;
+ padding-left: 0;
+ padding-right: 0;
+ }
</style>
--
Gitblit v1.9.3