Merge "add user dropdown" into main
diff --git a/src/app/main.scss b/src/app/main.scss
index f10a723..4c69a7b 100644
--- a/src/app/main.scss
+++ b/src/app/main.scss
@@ -42,7 +42,6 @@
     .p-carousel-next {
       position: absolute;
       top: 50%;
-      transform: translateY(-50%);
       z-index: 10;
       /* 保证在图片之上 */
       width: 2.5rem;
diff --git a/src/app/notification/notification.scss b/src/app/notification/notification.scss
new file mode 100644
index 0000000..d7b6b6d
--- /dev/null
+++ b/src/app/notification/notification.scss
@@ -0,0 +1,30 @@
+.notification-page {
+    max-width: 1200px;
+    max-width: 1200px;
+    margin: 0 auto;
+    padding: 0 2rem;
+
+    h2 {
+        margin-bottom: 1.5rem;
+        color: #333;
+    }
+
+    .title-link {
+        cursor: pointer;
+        font-weight: 500;
+
+        &:hover {
+            text-decoration: underline;
+        }
+    }
+
+    .actions {
+        display: flex;
+        width: 100px;
+        gap: 0.5rem;
+    }
+
+    .p-sidebar-md {
+        width: 30rem;
+    }
+}
\ No newline at end of file
diff --git a/src/app/notification/page.tsx b/src/app/notification/page.tsx
index c48b626..9e83252 100644
--- a/src/app/notification/page.tsx
+++ b/src/app/notification/page.tsx
@@ -1,12 +1,161 @@
 'use client';
-import React from 'react';
 
-const EmptyPage: React.FC = () => {
-  return (
-    <div className="p-d-flex p-jc-center p-ai-center" style={{ height: '100vh' }}>
-      {"一个空页面"}
+import React, { useEffect, useRef, useState } from 'react';
+import axios from 'axios';
+import { DataTable } from 'primereact/datatable';
+import { Column } from 'primereact/column';
+import { Sidebar } from 'primereact/sidebar';
+import { Button } from 'primereact/button';
+import { Tag } from 'primereact/tag';
+import { Toast } from 'primereact/toast';
+import { Paginator, PaginatorPageChangeEvent } from 'primereact/paginator';
+import { Card } from 'primereact/card';
+import { TabView, TabPanel } from 'primereact/tabview'; // ✅ TabView 导入
+
+import './notification.scss';
+
+interface Notification {
+  notificationId: number;
+  title: string;
+  content: string;
+  createAt: string;
+  isRead: boolean;
+  triggeredBy: number;
+  relatedId: number;
+}
+
+export default function NotificationPage() {
+  const toast = useRef<Toast>(null);
+  const [notifications, setNotifications] = useState<Notification[]>([]);
+  const [selectedNotification, setSelectedNotification] = useState<Notification | null>(null);
+  const [visible, setVisible] = useState<boolean>(false);
+  const [activeTab, setActiveTab] = useState<number>(0); // ✅ 当前 Tab 下标
+
+  // 分页相关
+  const [first, setFirst] = useState<number>(0);
+  const [rows, setRows] = useState<number>(5);
+  const [totalRecords, setTotalRecords] = useState<number>(0);
+
+  // 加载数据
+  const fetchNotifications = async () => {
+    try {
+      const pageNumber = first / rows + 1;
+      const res = await axios.get(process.env.PUBLIC_URL + '/notification', {
+        params: { pageNumber, rows, userId: 22301145 },
+      });
+      const { records, total } = res.data;
+      setNotifications(records);
+      setTotalRecords(total);
+    } catch (error) {
+      console.error('无法获取通知数据', error);
+      toast.current?.show({ severity: 'error', summary: '加载失败', detail: '无法获取通知数据' });
+    }
+  };
+
+  useEffect(() => {
+    fetchNotifications();
+  }, [first, rows]); // ✅ 加入分页依赖
+
+  const handleRead = async (id: number) => {
+    try {
+      await axios.post(process.env.PUBLIC_URL + '/notification/read', { notificationId: id });
+      setNotifications(prev =>
+        prev.map(n => (n.notificationId === id ? { ...n, isRead: true } : n))
+      );
+    } catch (error) {
+      console.error('已读失败', error);
+      toast.current?.show({ severity: 'error', summary: '设置已读失败' });
+    }
+  };
+
+  const handleDelete = async (id: number) => {
+    try {
+      await axios.delete(process.env.PUBLIC_URL + '/notification', { data: { notificationId: id } });
+      setNotifications(prev => prev.filter(n => n.notificationId !== id));
+      toast.current?.show({ severity: 'success', summary: '删除成功' });
+    } catch (error) {
+      console.error('删除失败', error);
+      toast.current?.show({ severity: 'error', summary: '删除失败' });
+    }
+  };
+
+  const openSidebar = (notification: Notification) => {
+    if (!notification.isRead) handleRead(notification.notificationId);
+    setSelectedNotification(notification);
+    setVisible(true);
+  };
+
+  const readTemplate = (rowData: Notification) => (
+    <Tag value={rowData.isRead ? '已读' : '未读'} severity={rowData.isRead ? undefined : 'warning'} />
+  );
+
+  const actionTemplate = (rowData: Notification) => (
+    <div className="actions">
+      {!rowData.isRead && (
+        <Button
+          icon="pi pi-check"
+          text
+          severity="success"
+          onClick={() => handleRead(rowData.notificationId)}
+          tooltip="标记为已读"
+        />
+      )}
+      <Button icon="pi pi-times" severity="danger" text onClick={() => handleDelete(rowData.notificationId)} />
     </div>
   );
-};
 
-export default EmptyPage;
+  const onPageChange = (e: PaginatorPageChangeEvent) => {
+    setFirst(e.first);
+    setRows(e.rows);
+  };
+
+  const unreadNotifications = notifications.filter(n => !n.isRead);
+  const readNotifications = notifications.filter(n => n.isRead);
+
+  return (
+    <div className="notification-page">
+      <Toast ref={toast} />
+      <h2>系统通知</h2>
+
+      <TabView activeIndex={activeTab} onTabChange={(e) => setActiveTab(e.index)}>
+        <TabPanel header="未读通知">
+          <DataTable value={unreadNotifications} paginator={false} emptyMessage="暂无未读通知">
+            <Column field="title" header="标题" body={(row) => (
+              <span className="title-link" onClick={() => openSidebar(row)}>{row.title}</span>
+            )} />
+            <Column field="createAt" header="时间" />
+            <Column field="isRead" header="状态" body={readTemplate} />
+            <Column header="操作" style={{ width: '120px', textAlign: 'center' }} body={actionTemplate} />
+          </DataTable>
+        </TabPanel>
+        <TabPanel header="已读通知">
+          <DataTable value={readNotifications} paginator={false} emptyMessage="暂无已读通知">
+            <Column field="title" header="标题" body={(row) => (
+              <span className="title-link" onClick={() => openSidebar(row)}>{row.title}</span>
+            )} />
+            <Column field="createAt" header="时间" />
+            <Column field="isRead" header="状态" body={readTemplate} />
+            <Column header="操作" style={{ width: '120px', textAlign: 'center' }} body={actionTemplate} />
+          </DataTable>
+        </TabPanel>
+      </TabView>
+
+      <Paginator
+        className="Paginator"
+        first={first}
+        rows={rows}
+        totalRecords={totalRecords}
+        onPageChange={onPageChange}
+        rowsPerPageOptions={[5, 10, 20]}
+      />
+
+      <Sidebar visible={visible} position="right" onHide={() => setVisible(false)} className="p-sidebar-md">
+        {selectedNotification && (
+          <Card title={selectedNotification.title} subTitle={selectedNotification.createAt}>
+            <p className="m-0">{selectedNotification.content}</p>
+          </Card>
+        )}
+      </Sidebar>
+    </div>
+  );
+}