From a563ea879ef5fb6897e76d2df661e465dce2ab9b Mon Sep 17 00:00:00 2001
From: huminmin <mac@MacBook-Pro.local>
Date: 星期一, 01 六月 2026 15:02:27 +0800
Subject: [PATCH] Merge branch 'dev_新疆_大罗素马铃薯new' of http://114.132.189.42:9002/r/product-inventory-management into dev_新疆_大罗素马铃薯new

---
 src/layout/components/NotificationCenter/index.vue |  386 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 386 insertions(+), 0 deletions(-)

diff --git a/src/layout/components/NotificationCenter/index.vue b/src/layout/components/NotificationCenter/index.vue
new file mode 100644
index 0000000..66f489a
--- /dev/null
+++ b/src/layout/components/NotificationCenter/index.vue
@@ -0,0 +1,386 @@
+<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 {
+            // 瑙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);
+          }
+        }
+      }
+    } 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 涓鐞嗭級
+  // 杩欓噷鍙礋璐f暟鎹姞杞斤紝涓嶆帶鍒舵樉绀�
+
+  // 鏆撮湶鏂规硶渚涘閮ㄨ皟鐢�
+  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>
+

--
Gitblit v1.9.3