Users.tsx 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  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: 'name',
  148. key: 'name',
  149. },
  150. {
  151. title: '角色',
  152. dataIndex: 'role',
  153. key: 'role',
  154. render: (role: string) => (
  155. <Tag color={role === 'admin' ? 'red' : 'blue'}>
  156. {role === 'admin' ? '管理员' : '普通用户'}
  157. </Tag>
  158. ),
  159. },
  160. {
  161. title: '创建时间',
  162. dataIndex: 'created_at',
  163. key: 'created_at',
  164. render: (date: string) => dayjs(date).format('YYYY-MM-DD HH:mm:ss'),
  165. },
  166. {
  167. title: '操作',
  168. key: 'action',
  169. render: (_: any, record: any) => (
  170. <Space size="middle">
  171. <Button type="link" onClick={() => showEditModal(record)}>
  172. 编辑
  173. </Button>
  174. <Popconfirm
  175. title="确定要删除此用户吗?"
  176. onConfirm={() => handleDelete(record.id)}
  177. okText="确定"
  178. cancelText="取消"
  179. >
  180. <Button type="link" danger>
  181. 删除
  182. </Button>
  183. </Popconfirm>
  184. </Space>
  185. ),
  186. },
  187. ];
  188. return (
  189. <div>
  190. <Title level={2}>用户管理</Title>
  191. <Card>
  192. <Form layout="inline" onFinish={handleSearch} style={{ marginBottom: 16 }}>
  193. <Form.Item name="search" label="搜索">
  194. <Input placeholder="用户名/昵称/邮箱" allowClear />
  195. </Form.Item>
  196. <Form.Item>
  197. <Space>
  198. <Button type="primary" htmlType="submit">
  199. 搜索
  200. </Button>
  201. <Button type="primary" onClick={showCreateModal}>
  202. 创建用户
  203. </Button>
  204. </Space>
  205. </Form.Item>
  206. </Form>
  207. <Table
  208. columns={columns}
  209. dataSource={users}
  210. loading={isLoading}
  211. rowKey="id"
  212. pagination={{
  213. ...pagination,
  214. showSizeChanger: true,
  215. showQuickJumper: true,
  216. showTotal: (total) => `共 ${total} 条记录`
  217. }}
  218. onChange={handleTableChange}
  219. />
  220. </Card>
  221. {/* 创建/编辑用户模态框 */}
  222. <Modal
  223. title={modalTitle}
  224. open={modalVisible}
  225. onOk={handleModalOk}
  226. onCancel={() => {
  227. setModalVisible(false);
  228. form.resetFields();
  229. }}
  230. width={600}
  231. >
  232. <Form
  233. form={form}
  234. layout="vertical"
  235. >
  236. <Form.Item
  237. name="username"
  238. label="用户名"
  239. required
  240. rules={[
  241. { required: true, message: '请输入用户名' },
  242. { min: 3, message: '用户名至少3个字符' }
  243. ]}
  244. >
  245. <Input placeholder="请输入用户名" />
  246. </Form.Item>
  247. <Form.Item
  248. name="nickname"
  249. label="昵称"
  250. rules={[{ required: false, message: '请输入昵称' }]}
  251. >
  252. <Input placeholder="请输入昵称" />
  253. </Form.Item>
  254. <Form.Item
  255. name="email"
  256. label="邮箱"
  257. rules={[
  258. { required: false, message: '请输入邮箱' },
  259. { type: 'email', message: '请输入有效的邮箱地址' }
  260. ]}
  261. >
  262. <Input placeholder="请输入邮箱" />
  263. </Form.Item>
  264. <Form.Item
  265. name="phone"
  266. label="手机号"
  267. rules={[
  268. { required: false, message: '请输入手机号' },
  269. { pattern: /^1[3-9]\d{9}$/, message: '请输入有效的手机号' }
  270. ]}
  271. >
  272. <Input placeholder="请输入手机号" />
  273. </Form.Item>
  274. <Form.Item
  275. name="name"
  276. label="真实姓名"
  277. rules={[{ required: false, message: '请输入真实姓名' }]}
  278. >
  279. <Input placeholder="请输入真实姓名" />
  280. </Form.Item>
  281. {!editingUser && (
  282. <Form.Item
  283. name="password"
  284. label="密码"
  285. required
  286. rules={[
  287. { required: true, message: '请输入密码' },
  288. { min: 6, message: '密码至少6个字符' }
  289. ]}
  290. >
  291. <Input.Password placeholder="请输入密码" />
  292. </Form.Item>
  293. )}
  294. <Form.Item
  295. name="isDisabled"
  296. label="状态"
  297. required
  298. rules={[{ required: true, message: '请选择状态' }]}
  299. >
  300. <Select placeholder="请选择状态">
  301. <Select.Option value={0}>启用</Select.Option>
  302. <Select.Option value={1}>禁用</Select.Option>
  303. </Select>
  304. </Form.Item>
  305. </Form>
  306. </Modal>
  307. </div>
  308. );
  309. };