From d5c31777d2c1de4f0038651b8316a0992959cee5 Mon Sep 17 00:00:00 2001 From: zyh Date: Thu, 10 Apr 2025 09:07:31 +0000 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=B6=88=E6=81=AF=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E9=A1=B5=E9=9D=A2=EF=BC=8C=E6=95=B4=E5=90=88=E6=B6=88?= =?UTF-8?q?=E6=81=AFAPI=EF=BC=8C=E6=94=AF=E6=8C=81=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E5=B1=95=E7=A4=BA=E3=80=81=E6=9C=AA=E8=AF=BB?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E7=BB=9F=E8=AE=A1=E3=80=81=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E6=A0=87=E8=AE=B0=E4=B8=BA=E5=B7=B2=E8=AF=BB=E5=8F=8A=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=8F=90=E5=8D=87=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=B6=88=E6=81=AF=E7=AE=A1=E7=90=86=E4=BD=93=E9=AA=8C?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/admin/api.ts | 88 +++++++++- client/admin/pages_messages.tsx | 283 ++++++++++++++++++++++++++++++++ client/admin/web_app.tsx | 13 +- server/routes_users.ts | 2 +- 4 files changed, 383 insertions(+), 3 deletions(-) create mode 100644 client/admin/pages_messages.tsx diff --git a/client/admin/api.ts b/client/admin/api.ts index e5de79a..8c21d68 100644 --- a/client/admin/api.ts +++ b/client/admin/api.ts @@ -3,7 +3,9 @@ import type { MinioUploadPolicy, OSSUploadPolicy } from '@d8d-appcontainer/types import 'dayjs/locale/zh-cn'; import type { User, FileLibrary, FileCategory, ThemeSettings, - SystemSetting, SystemSettingGroupData, LoginLocation, LoginLocationDetail + SystemSetting, SystemSettingGroupData, + LoginLocation, LoginLocationDetail, + Message, UserMessage } from '../share/types.ts'; @@ -502,6 +504,90 @@ export const ChartAPI = { } }; +// 消息API接口类型 +interface MessagesResponse { + data: UserMessage[]; + pagination: { + total: number; + current: number; + pageSize: number; + totalPages: number; + }; +} + +interface MessageResponse { + data: Message; + message?: string; +} + +interface MessageCountResponse { + count: number; +} + +// 消息API +export const MessageAPI = { + // 获取消息列表 + getMessages: async (params?: { + page?: number, + pageSize?: number, + type?: string, + status?: string, + search?: string + }): Promise => { + try { + const response = await axios.get(`${API_BASE_URL}/messages`, { params }); + return response.data; + } catch (error) { + throw error; + } + }, + + // 发送消息 + sendMessage: async (data: { + title: string, + content: string, + type: string, + receiver_ids: number[] + }): Promise => { + try { + const response = await axios.post(`${API_BASE_URL}/messages`, data); + return response.data; + } catch (error) { + throw error; + } + }, + + // 获取未读消息数 + getUnreadCount: async (): Promise => { + try { + const response = await axios.get(`${API_BASE_URL}/messages/count/unread`); + return response.data; + } catch (error) { + throw error; + } + }, + + // 标记消息为已读 + markAsRead: async (id: number): Promise => { + try { + const response = await axios.post(`${API_BASE_URL}/messages/${id}/read`); + return response.data; + } catch (error) { + throw error; + } + }, + + // 删除消息 + deleteMessage: async (id: number): Promise => { + try { + const response = await axios.delete(`${API_BASE_URL}/messages/${id}`); + return response.data; + } catch (error) { + throw error; + } + } +}; + // 地图相关API的接口类型定义 export interface LoginLocationResponse { message: string; diff --git a/client/admin/pages_messages.tsx b/client/admin/pages_messages.tsx new file mode 100644 index 0000000..89c5da9 --- /dev/null +++ b/client/admin/pages_messages.tsx @@ -0,0 +1,283 @@ +import React, { useState } from 'react'; +import { useQuery, useMutation, useQueryClient, UseMutationResult } from '@tanstack/react-query'; +import { Button, Table, Space, Modal, Form, Input, Select, message } from 'antd'; +import type { TableProps } from 'antd'; +import dayjs from 'dayjs'; +import 'dayjs/locale/zh-cn'; + +import { MessageAPI } from './api.ts'; +import { UserAPI } from './api.ts'; +import type { UserMessage } from '../share/types.ts'; +import { MessageStatusNameMap , MessageStatus} from '../share/types.ts'; + +export const MessagesPage = () => { + const queryClient = useQueryClient(); + const [form] = Form.useForm(); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + const [isModalVisible, setIsModalVisible] = useState(false); + const [searchParams, setSearchParams] = useState({ + page: 1, + pageSize: 10, + type: undefined, + status: undefined, + search: undefined + }); + + // 获取消息列表 + const { data: messages, isLoading } = useQuery({ + queryKey: ['messages', searchParams], + queryFn: () => MessageAPI.getMessages(searchParams), + }); + + // 获取用户列表 + const { data: users } = useQuery({ + queryKey: ['users'], + queryFn: () => UserAPI.getUsers({ page: 1, limit: 1000 }), + }); + + // 获取未读消息数 + const { data: unreadCount } = useQuery({ + queryKey: ['unreadCount'], + queryFn: () => MessageAPI.getUnreadCount(), + }); + + // 标记消息为已读 + const markAsReadMutation = useMutation({ + mutationFn: (id: number) => MessageAPI.markAsRead(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['messages'] }); + queryClient.invalidateQueries({ queryKey: ['unreadCount'] }); + message.success('标记已读成功'); + }, + }); + + // 删除消息 + const deleteMutation = useMutation({ + mutationFn: (id: number) => MessageAPI.deleteMessage(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['messages'] }); + message.success('删除成功'); + }, + }); + + // 发送消息 + const sendMessageMutation = useMutation({ + mutationFn: (data: any) => MessageAPI.sendMessage(data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['messages'] }); + queryClient.invalidateQueries({ queryKey: ['unreadCount'] }); + message.success('发送成功'); + setIsModalVisible(false); + form.resetFields(); + }, + }); + + const columns: TableProps['columns'] = [ + { + title: '标题', + dataIndex: 'title', + key: 'title', + }, + { + title: '类型', + dataIndex: 'type', + key: 'type', + }, + { + title: '发送人', + dataIndex: 'sender_name', + key: 'sender_name', + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + render: (status: MessageStatus) => ( + + {MessageStatusNameMap[status]} + + ), + }, + { + title: '发送时间', + dataIndex: 'created_at', + key: 'created_at', + render: (date: string) => dayjs(date).format('YYYY-MM-DD HH:mm'), + }, + { + title: '操作', + key: 'action', + render: (_: any, record) => ( + + + + + ), + }, + ]; + + const handleSearch = (values: any) => { + setSearchParams({ + ...searchParams, + ...values, + page: 1 + }); + }; + + const handleTableChange = (pagination: any) => { + setSearchParams({ + ...searchParams, + page: pagination.current, + pageSize: pagination.pageSize + }); + }; + + const handleSendMessage = (values: any) => { + sendMessageMutation.mutate(values); + }; + + return ( +
+
+

消息管理

+
+ {unreadCount && unreadCount.count > 0 && ( + {unreadCount.count}条未读 + )} + +
+
+ +
+
+ + + + + + + + + +
+ + + + + setIsModalVisible(false)} + footer={null} + width={800} + > +
+ + + + + + ({ + value: user.id, + label: user.username, + }))} + /> + + + + + + + + + + +
+ + ); +}; \ No newline at end of file diff --git a/client/admin/web_app.tsx b/client/admin/web_app.tsx index 1dac7ca..edc6671 100644 --- a/client/admin/web_app.tsx +++ b/client/admin/web_app.tsx @@ -74,7 +74,8 @@ import { KnowInfoPage, FileLibraryPage } from './pages_sys.tsx'; -import { +import { MessagesPage } from './pages_messages.tsx'; +import { SettingsPage, ThemeSettingsPage, } from './pages_settings.tsx'; @@ -184,6 +185,11 @@ const MainLayout = () => { icon: , label: '用户管理', }, + { + key: '/messages', + icon: , + label: '消息管理', + }, { key: '/settings', icon: , @@ -555,6 +561,11 @@ const App = () => { element: , errorElement: }, + { + path: 'messages', + element: , + errorElement: + }, ], }, ]); diff --git a/server/routes_users.ts b/server/routes_users.ts index 4f680f0..f1cce12 100644 --- a/server/routes_users.ts +++ b/server/routes_users.ts @@ -28,7 +28,7 @@ export function createUserRoutes(withAuth: WithAuth) { } const total = await query.clone().count() - const users = await query.select('id', 'username', 'nickname', 'email', 'phone', 'role', 'created_at') + const users = await query.select('id', 'username', 'nickname', 'email', 'phone', 'created_at') .limit(pageSize).offset(offset) return c.json({