Users.tsx 8.6 KB

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