2
0

Users.tsx 8.6 KB

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