Users.tsx 8.4 KB

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