pages_messages.tsx 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. import React, { useState } from 'react';
  2. import { useQuery, useMutation, useQueryClient, UseMutationResult } from '@tanstack/react-query';
  3. import { Button, Table, Space, Modal, Form, Input, Select, message } from 'antd';
  4. import type { TableProps } from 'antd';
  5. import dayjs from 'dayjs';
  6. import 'dayjs/locale/zh-cn';
  7. import { MessageAPI } from './api.ts';
  8. import { UserAPI } from './api.ts';
  9. import type { UserMessage } from '../share/types.ts';
  10. import { MessageStatusNameMap , MessageStatus} from '../share/types.ts';
  11. export const MessagesPage = () => {
  12. const queryClient = useQueryClient();
  13. const [form] = Form.useForm();
  14. const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
  15. const [isModalVisible, setIsModalVisible] = useState(false);
  16. const [searchParams, setSearchParams] = useState({
  17. page: 1,
  18. pageSize: 10,
  19. type: undefined,
  20. status: undefined,
  21. search: undefined
  22. });
  23. // 获取消息列表
  24. const { data: messages, isLoading } = useQuery({
  25. queryKey: ['messages', searchParams],
  26. queryFn: () => MessageAPI.getMessages(searchParams),
  27. });
  28. // 获取用户列表
  29. const { data: users } = useQuery({
  30. queryKey: ['users'],
  31. queryFn: () => UserAPI.getUsers({ page: 1, limit: 1000 }),
  32. });
  33. // 获取未读消息数
  34. const { data: unreadCount } = useQuery({
  35. queryKey: ['unreadCount'],
  36. queryFn: () => MessageAPI.getUnreadCount(),
  37. });
  38. // 标记消息为已读
  39. const markAsReadMutation = useMutation({
  40. mutationFn: (id: number) => MessageAPI.markAsRead(id),
  41. onSuccess: () => {
  42. queryClient.invalidateQueries({ queryKey: ['messages'] });
  43. queryClient.invalidateQueries({ queryKey: ['unreadCount'] });
  44. message.success('标记已读成功');
  45. },
  46. });
  47. // 删除消息
  48. const deleteMutation = useMutation({
  49. mutationFn: (id: number) => MessageAPI.deleteMessage(id),
  50. onSuccess: () => {
  51. queryClient.invalidateQueries({ queryKey: ['messages'] });
  52. message.success('删除成功');
  53. },
  54. });
  55. // 发送消息
  56. const sendMessageMutation = useMutation({
  57. mutationFn: (data: any) => MessageAPI.sendMessage(data),
  58. onSuccess: () => {
  59. queryClient.invalidateQueries({ queryKey: ['messages'] });
  60. queryClient.invalidateQueries({ queryKey: ['unreadCount'] });
  61. message.success('发送成功');
  62. setIsModalVisible(false);
  63. form.resetFields();
  64. },
  65. });
  66. const columns: TableProps<UserMessage>['columns'] = [
  67. {
  68. title: '标题',
  69. dataIndex: 'title',
  70. key: 'title',
  71. },
  72. {
  73. title: '类型',
  74. dataIndex: 'type',
  75. key: 'type',
  76. },
  77. {
  78. title: '发送人',
  79. dataIndex: 'sender_name',
  80. key: 'sender_name',
  81. },
  82. {
  83. title: '状态',
  84. dataIndex: 'user_status',
  85. key: 'user_status',
  86. render: (user_status: MessageStatus) => (
  87. <span style={{ color: user_status === MessageStatus.UNREAD ? 'red' : 'green' }}>
  88. {MessageStatusNameMap[user_status]}
  89. </span>
  90. ),
  91. },
  92. {
  93. title: '发送时间',
  94. dataIndex: 'created_at',
  95. key: 'created_at',
  96. render: (date: string) => dayjs(date).format('YYYY-MM-DD HH:mm'),
  97. },
  98. {
  99. title: '操作',
  100. key: 'action',
  101. render: (_: any, record) => (
  102. <Space size="middle">
  103. <Button
  104. type="link"
  105. onClick={() => markAsReadMutation.mutate(record.id)}
  106. disabled={record.user_status === MessageStatus.READ}
  107. >
  108. 标记已读
  109. </Button>
  110. <Button
  111. type="link"
  112. danger
  113. onClick={() => deleteMutation.mutate(record.id)}
  114. >
  115. 删除
  116. </Button>
  117. </Space>
  118. ),
  119. },
  120. ];
  121. const handleSearch = (values: any) => {
  122. setSearchParams({
  123. ...searchParams,
  124. ...values,
  125. page: 1
  126. });
  127. };
  128. const handleTableChange = (pagination: any) => {
  129. setSearchParams({
  130. ...searchParams,
  131. page: pagination.current,
  132. pageSize: pagination.pageSize
  133. });
  134. };
  135. const handleSendMessage = (values: any) => {
  136. sendMessageMutation.mutate(values);
  137. };
  138. return (
  139. <div className="p-4">
  140. <div className="flex justify-between items-center mb-4">
  141. <h1 className="text-2xl font-bold">消息管理</h1>
  142. <div className="flex items-center space-x-4">
  143. {unreadCount && unreadCount.count > 0 && (
  144. <span className="text-red-500">{unreadCount.count}条未读</span>
  145. )}
  146. <Button type="primary" onClick={() => setIsModalVisible(true)}>
  147. 发送消息
  148. </Button>
  149. </div>
  150. </div>
  151. <div className="bg-white p-4 rounded shadow">
  152. <Form layout="inline" onFinish={handleSearch} className="mb-4">
  153. <Form.Item name="type" label="类型">
  154. <Select
  155. style={{ width: 120 }}
  156. allowClear
  157. options={[
  158. { value: 'SYSTEM', label: '系统消息' },
  159. { value: 'NOTICE', label: '公告' },
  160. { value: 'PERSONAL', label: '个人消息' },
  161. ]}
  162. />
  163. </Form.Item>
  164. <Form.Item name="status" label="状态">
  165. <Select
  166. style={{ width: 120 }}
  167. allowClear
  168. options={[
  169. { value: 'UNREAD', label: '未读' },
  170. { value: 'READ', label: '已读' },
  171. ]}
  172. />
  173. </Form.Item>
  174. <Form.Item name="search" label="搜索">
  175. <Input placeholder="输入标题或内容" />
  176. </Form.Item>
  177. <Form.Item>
  178. <Button type="primary" htmlType="submit">
  179. 搜索
  180. </Button>
  181. </Form.Item>
  182. </Form>
  183. <Table
  184. columns={columns}
  185. dataSource={messages?.data}
  186. loading={isLoading}
  187. rowKey="id"
  188. pagination={{
  189. current: searchParams.page,
  190. pageSize: searchParams.pageSize,
  191. total: messages?.pagination?.total,
  192. showSizeChanger: true,
  193. }}
  194. onChange={handleTableChange}
  195. />
  196. </div>
  197. <Modal
  198. title="发送消息"
  199. visible={isModalVisible}
  200. onCancel={() => setIsModalVisible(false)}
  201. footer={null}
  202. width={800}
  203. >
  204. <Form
  205. form={form}
  206. layout="vertical"
  207. onFinish={handleSendMessage}
  208. >
  209. <Form.Item
  210. name="title"
  211. label="标题"
  212. rules={[{ required: true, message: '请输入标题' }]}
  213. >
  214. <Input placeholder="请输入消息标题" />
  215. </Form.Item>
  216. <Form.Item
  217. name="type"
  218. label="消息类型"
  219. rules={[{ required: true, message: '请选择消息类型' }]}
  220. >
  221. <Select
  222. options={[
  223. { value: 'SYSTEM', label: '系统消息' },
  224. { value: 'NOTICE', label: '公告' },
  225. { value: 'PERSONAL', label: '个人消息' },
  226. ]}
  227. />
  228. </Form.Item>
  229. <Form.Item
  230. name="receiver_ids"
  231. label="接收人"
  232. rules={[{ required: true, message: '请选择接收人' }]}
  233. >
  234. <Select
  235. mode="multiple"
  236. placeholder="请选择接收人"
  237. options={users?.data?.map((user: any) => ({
  238. value: user.id,
  239. label: user.username,
  240. }))}
  241. />
  242. </Form.Item>
  243. <Form.Item
  244. name="content"
  245. label="内容"
  246. rules={[{ required: true, message: '请输入消息内容' }]}
  247. >
  248. <Input.TextArea rows={6} placeholder="请输入消息内容" />
  249. </Form.Item>
  250. <Form.Item>
  251. <Button
  252. type="primary"
  253. htmlType="submit"
  254. loading={sendMessageMutation.status === 'pending'}
  255. >
  256. 发送
  257. </Button>
  258. </Form.Item>
  259. </Form>
  260. </Modal>
  261. </div>
  262. );
  263. };