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