blob: 55f0ec531388b7f2f7d65fb206ca8e8b67478f18 [file] [log] [blame]
LaoeGaoci85307e62025-05-30 23:28:42 +08001'use client';
LaoeGaoci85307e62025-05-30 23:28:42 +08002
LaoeGaoci502ecae2025-06-04 17:54:24 +08003import React, { useEffect, useRef, useState } from 'react';
4import axios from 'axios';
5import { DataTable } from 'primereact/datatable';
6import { Column } from 'primereact/column';
7import { Sidebar } from 'primereact/sidebar';
8import { Button } from 'primereact/button';
9import { Tag } from 'primereact/tag';
10import { Toast } from 'primereact/toast';
11import { Paginator, PaginatorPageChangeEvent } from 'primereact/paginator';
12import { Card } from 'primereact/card';
13import { TabView, TabPanel } from 'primereact/tabview'; // ✅ TabView 导入
LaoeGaocid0773912025-06-09 00:38:40 +080014import { useLocalStorage } from '../hook/useLocalStorage';
LaoeGaoci502ecae2025-06-04 17:54:24 +080015import './notification.scss';
LaoeGaocid0773912025-06-09 00:38:40 +080016interface User {
17 Id: number;
18}
LaoeGaoci502ecae2025-06-04 17:54:24 +080019interface Notification {
20 notificationId: number;
21 title: string;
22 content: string;
23 createAt: string;
24 isRead: boolean;
25 triggeredBy: number;
26 relatedId: number;
27}
28
29export default function NotificationPage() {
LaoeGaocid0773912025-06-09 00:38:40 +080030 const user = useLocalStorage<User>('user');
31 const userId: number = user?.Id ?? -1;
LaoeGaoci502ecae2025-06-04 17:54:24 +080032 const toast = useRef<Toast>(null);
33 const [notifications, setNotifications] = useState<Notification[]>([]);
34 const [selectedNotification, setSelectedNotification] = useState<Notification | null>(null);
35 const [visible, setVisible] = useState<boolean>(false);
36 const [activeTab, setActiveTab] = useState<number>(0); // ✅ 当前 Tab 下标
37
38 // 分页相关
39 const [first, setFirst] = useState<number>(0);
40 const [rows, setRows] = useState<number>(5);
41 const [totalRecords, setTotalRecords] = useState<number>(0);
42
43 // 加载数据
44 const fetchNotifications = async () => {
45 try {
46 const pageNumber = first / rows + 1;
47 const res = await axios.get(process.env.PUBLIC_URL + '/notification', {
LaoeGaocid0773912025-06-09 00:38:40 +080048 params: { pageNumber, rows, userId },
LaoeGaoci502ecae2025-06-04 17:54:24 +080049 });
50 const { records, total } = res.data;
51 setNotifications(records);
52 setTotalRecords(total);
53 } catch (error) {
54 console.error('无法获取通知数据', error);
55 toast.current?.show({ severity: 'error', summary: '加载失败', detail: '无法获取通知数据' });
56 }
57 };
58
59 useEffect(() => {
60 fetchNotifications();
61 }, [first, rows]); // ✅ 加入分页依赖
62
63 const handleRead = async (id: number) => {
64 try {
65 await axios.post(process.env.PUBLIC_URL + '/notification/read', { notificationId: id });
66 setNotifications(prev =>
67 prev.map(n => (n.notificationId === id ? { ...n, isRead: true } : n))
68 );
69 } catch (error) {
70 console.error('已读失败', error);
71 toast.current?.show({ severity: 'error', summary: '设置已读失败' });
72 }
73 };
74
75 const handleDelete = async (id: number) => {
76 try {
77 await axios.delete(process.env.PUBLIC_URL + '/notification', { data: { notificationId: id } });
78 setNotifications(prev => prev.filter(n => n.notificationId !== id));
79 toast.current?.show({ severity: 'success', summary: '删除成功' });
80 } catch (error) {
81 console.error('删除失败', error);
82 toast.current?.show({ severity: 'error', summary: '删除失败' });
83 }
84 };
85
86 const openSidebar = (notification: Notification) => {
87 if (!notification.isRead) handleRead(notification.notificationId);
88 setSelectedNotification(notification);
89 setVisible(true);
90 };
91
92 const readTemplate = (rowData: Notification) => (
93 <Tag value={rowData.isRead ? '已读' : '未读'} severity={rowData.isRead ? undefined : 'warning'} />
94 );
95
96 const actionTemplate = (rowData: Notification) => (
97 <div className="actions">
98 {!rowData.isRead && (
99 <Button
100 icon="pi pi-check"
101 text
102 severity="success"
103 onClick={() => handleRead(rowData.notificationId)}
104 tooltip="标记为已读"
105 />
106 )}
107 <Button icon="pi pi-times" severity="danger" text onClick={() => handleDelete(rowData.notificationId)} />
LaoeGaoci85307e62025-05-30 23:28:42 +0800108 </div>
109 );
LaoeGaoci85307e62025-05-30 23:28:42 +0800110
LaoeGaoci502ecae2025-06-04 17:54:24 +0800111 const onPageChange = (e: PaginatorPageChangeEvent) => {
112 setFirst(e.first);
113 setRows(e.rows);
114 };
115
116 const unreadNotifications = notifications.filter(n => !n.isRead);
117 const readNotifications = notifications.filter(n => n.isRead);
118
119 return (
120 <div className="notification-page">
121 <Toast ref={toast} />
122 <h2>系统通知</h2>
123
124 <TabView activeIndex={activeTab} onTabChange={(e) => setActiveTab(e.index)}>
125 <TabPanel header="未读通知">
126 <DataTable value={unreadNotifications} paginator={false} emptyMessage="暂无未读通知">
127 <Column field="title" header="标题" body={(row) => (
128 <span className="title-link" onClick={() => openSidebar(row)}>{row.title}</span>
129 )} />
130 <Column field="createAt" header="时间" />
131 <Column field="isRead" header="状态" body={readTemplate} />
132 <Column header="操作" style={{ width: '120px', textAlign: 'center' }} body={actionTemplate} />
133 </DataTable>
134 </TabPanel>
135 <TabPanel header="已读通知">
136 <DataTable value={readNotifications} paginator={false} emptyMessage="暂无已读通知">
137 <Column field="title" header="标题" body={(row) => (
138 <span className="title-link" onClick={() => openSidebar(row)}>{row.title}</span>
139 )} />
140 <Column field="createAt" header="时间" />
141 <Column field="isRead" header="状态" body={readTemplate} />
142 <Column header="操作" style={{ width: '120px', textAlign: 'center' }} body={actionTemplate} />
143 </DataTable>
144 </TabPanel>
145 </TabView>
146
147 <Paginator
148 className="Paginator"
149 first={first}
150 rows={rows}
151 totalRecords={totalRecords}
152 onPageChange={onPageChange}
153 rowsPerPageOptions={[5, 10, 20]}
154 />
155
156 <Sidebar visible={visible} position="right" onHide={() => setVisible(false)} className="p-sidebar-md">
157 {selectedNotification && (
158 <Card title={selectedNotification.title} subTitle={selectedNotification.createAt}>
159 <p className="m-0">{selectedNotification.content}</p>
160 </Card>
161 )}
162 </Sidebar>
163 </div>
164 );
165}