pages_users.tsx 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. import React, { useState } from 'react';
  2. import {
  3. Button, Table, Space, Form, Input, Select,
  4. message, Modal, Card, Typography, Tag, Popconfirm
  5. } from 'antd';
  6. import { useQuery } from '@tanstack/react-query';
  7. import dayjs from 'dayjs';
  8. import { UserAPI } from './api/users.ts';
  9. const { Title } = Typography;
  10. // 用户管理页面
  11. export const UsersPage = () => {
  12. const [searchParams, setSearchParams] = useState({
  13. page: 1,
  14. limit: 10,
  15. search: ''
  16. });
  17. const [modalVisible, setModalVisible] = useState(false);
  18. const [modalTitle, setModalTitle] = useState('');
  19. const [editingUser, setEditingUser] = useState<any>(null);
  20. const [form] = Form.useForm();
  21. const [convertingId, setConvertingId] = useState<number | null>(null);
  22. const { data: usersData, isLoading, refetch } = useQuery({
  23. queryKey: ['users', searchParams],
  24. queryFn: async () => {
  25. return await UserAPI.getUsers(searchParams);
  26. }
  27. });
  28. const users = usersData?.data || [];
  29. const pagination = {
  30. current: searchParams.page,
  31. pageSize: searchParams.limit,
  32. total: usersData?.pagination?.total || 0
  33. };
  34. // 处理搜索
  35. const handleSearch = (values: any) => {
  36. setSearchParams(prev => ({
  37. ...prev,
  38. search: values.search || '',
  39. page: 1
  40. }));
  41. };
  42. // 处理分页变化
  43. const handleTableChange = (newPagination: any) => {
  44. setSearchParams(prev => ({
  45. ...prev,
  46. page: newPagination.current,
  47. limit: newPagination.pageSize
  48. }));
  49. };
  50. // 打开创建用户模态框
  51. const showCreateModal = () => {
  52. setModalTitle('创建用户');
  53. setEditingUser(null);
  54. form.resetFields();
  55. setModalVisible(true);
  56. };
  57. // 打开编辑用户模态框
  58. const showEditModal = (user: any) => {
  59. setModalTitle('编辑用户');
  60. setEditingUser(user);
  61. form.setFieldsValue(user);
  62. setModalVisible(true);
  63. };
  64. // 处理模态框确认
  65. const handleModalOk = async () => {
  66. try {
  67. const values = await form.validateFields();
  68. if (editingUser) {
  69. // 编辑用户
  70. await UserAPI.updateUser(editingUser.id, values);
  71. message.success('用户更新成功');
  72. } else {
  73. // 创建用户
  74. await UserAPI.createUser(values);
  75. message.success('用户创建成功');
  76. }
  77. setModalVisible(false);
  78. form.resetFields();
  79. refetch(); // 刷新用户列表
  80. } catch (error) {
  81. console.error('表单提交失败:', error);
  82. message.error('操作失败,请重试');
  83. }
  84. };
  85. // 处理删除用户
  86. const handleDelete = async (id: number) => {
  87. try {
  88. await UserAPI.deleteUser(id);
  89. message.success('用户删除成功');
  90. refetch(); // 刷新用户列表
  91. } catch (error) {
  92. console.error('删除用户失败:', error);
  93. message.error('删除失败,请重试');
  94. }
  95. };
  96. // 处理转为学员
  97. const handleConvertToStudent = async (id: number) => {
  98. setConvertingId(id);
  99. try {
  100. await UserAPI.convertToStudent(id);
  101. message.success('用户已转为学员');
  102. refetch(); // 刷新用户列表
  103. } catch (error) {
  104. console.error('转为学员失败:', error);
  105. message.error('操作失败,请重试');
  106. } finally {
  107. setConvertingId(null);
  108. }
  109. };
  110. const columns = [
  111. {
  112. title: '用户名',
  113. dataIndex: 'username',
  114. key: 'username',
  115. },
  116. {
  117. title: '昵称',
  118. dataIndex: 'nickname',
  119. key: 'nickname',
  120. },
  121. {
  122. title: '邮箱',
  123. dataIndex: 'email',
  124. key: 'email',
  125. },
  126. {
  127. title: '角色',
  128. dataIndex: 'role',
  129. key: 'role',
  130. render: (role: string) => (
  131. <Tag color={role === 'admin' ? 'red' : 'blue'}>
  132. {role === 'admin' ? '管理员' : '普通用户'}
  133. </Tag>
  134. ),
  135. },
  136. {
  137. title: '创建时间',
  138. dataIndex: 'created_at',
  139. key: 'created_at',
  140. render: (date: string) => dayjs(date).format('YYYY-MM-DD HH:mm:ss'),
  141. },
  142. {
  143. title: '有效期至',
  144. dataIndex: 'expires_at',
  145. key: 'expires_at',
  146. render: (date: string) => date ? dayjs(date).format('YYYY-MM-DD') : '永久',
  147. },
  148. {
  149. title: '操作',
  150. key: 'action',
  151. render: (_: any, record: any) => (
  152. <Space size="middle">
  153. <Button type="link" onClick={() => showEditModal(record)}>
  154. 编辑
  155. </Button>
  156. {record.role === 'admin' && (
  157. <Popconfirm
  158. title="确定要删除此用户吗?"
  159. onConfirm={() => handleDelete(record.id)}
  160. okText="确定"
  161. cancelText="取消"
  162. >
  163. <Button type="link" danger>
  164. 删除
  165. </Button>
  166. </Popconfirm>
  167. )}
  168. {record.role !== 'student' && (
  169. <Button
  170. type="link"
  171. onClick={() => handleConvertToStudent(record.id)}
  172. loading={convertingId === record.id}
  173. >
  174. 转为学员
  175. </Button>
  176. )}
  177. </Space>
  178. ),
  179. },
  180. ];
  181. return (
  182. <div>
  183. <Title level={2}>用户管理</Title>
  184. <Card>
  185. <Form layout="inline" onFinish={handleSearch} style={{ marginBottom: 16 }}>
  186. <Form.Item name="search" label="搜索">
  187. <Input placeholder="用户名/昵称/邮箱" allowClear />
  188. </Form.Item>
  189. <Form.Item>
  190. <Space>
  191. <Button type="primary" htmlType="submit">
  192. 搜索
  193. </Button>
  194. <Button type="primary" onClick={showCreateModal}>
  195. 创建用户
  196. </Button>
  197. </Space>
  198. </Form.Item>
  199. </Form>
  200. <Table
  201. columns={columns}
  202. dataSource={users}
  203. loading={isLoading}
  204. rowKey="id"
  205. pagination={{
  206. ...pagination,
  207. showSizeChanger: true,
  208. showQuickJumper: true,
  209. showTotal: (total) => `共 ${total} 条记录`
  210. }}
  211. onChange={handleTableChange}
  212. />
  213. </Card>
  214. {/* 创建/编辑用户模态框 */}
  215. <Modal
  216. title={modalTitle}
  217. open={modalVisible}
  218. onOk={handleModalOk}
  219. onCancel={() => {
  220. setModalVisible(false);
  221. form.resetFields();
  222. }}
  223. width={600}
  224. >
  225. <Form
  226. form={form}
  227. layout="vertical"
  228. >
  229. <Form.Item
  230. name="username"
  231. label="用户名"
  232. rules={[
  233. { required: true, message: '请输入用户名' },
  234. { min: 3, message: '用户名至少3个字符' }
  235. ]}
  236. >
  237. <Input placeholder="请输入用户名" />
  238. </Form.Item>
  239. <Form.Item
  240. name="nickname"
  241. label="昵称"
  242. rules={[{ required: true, message: '请输入昵称' }]}
  243. >
  244. <Input placeholder="请输入昵称" />
  245. </Form.Item>
  246. <Form.Item
  247. name="email"
  248. label="邮箱"
  249. rules={[
  250. { required: true, message: '请输入邮箱' },
  251. { type: 'email', message: '请输入有效的邮箱地址' }
  252. ]}
  253. >
  254. <Input placeholder="请输入邮箱" />
  255. </Form.Item>
  256. {!editingUser && (
  257. <Form.Item
  258. name="password"
  259. label="密码"
  260. rules={[
  261. { required: true, message: '请输入密码' },
  262. { min: 6, message: '密码至少6个字符' }
  263. ]}
  264. >
  265. <Input.Password placeholder="请输入密码" />
  266. </Form.Item>
  267. )}
  268. <Form.Item
  269. name="role"
  270. label="角色"
  271. rules={[{ required: true, message: '请选择角色' }]}
  272. >
  273. <Select placeholder="请选择角色">
  274. <Select.Option value="user">普通用户</Select.Option>
  275. <Select.Option value="admin">管理员</Select.Option>
  276. </Select>
  277. </Form.Item>
  278. </Form>
  279. </Modal>
  280. </div>
  281. );
  282. };