2
0

Users.tsx 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  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. <div className="mb-6 flex justify-between items-center">
  191. <Title level={2}>用户管理</Title>
  192. </div>
  193. <Card className="shadow-md transition-all duration-300 hover:shadow-lg">
  194. <Form layout="inline" onFinish={handleSearch} style={{ marginBottom: 16, padding: '16px 0' }}>
  195. <Form.Item name="search" label="搜索">
  196. <Input placeholder="用户名/昵称/邮箱" allowClear />
  197. </Form.Item>
  198. <Form.Item>
  199. <Space>
  200. <Button type="primary" htmlType="submit">
  201. 搜索
  202. </Button>
  203. <Button type="primary" onClick={showCreateModal}>
  204. 创建用户
  205. </Button>
  206. </Space>
  207. </Form.Item>
  208. </Form>
  209. <Table
  210. columns={columns}
  211. dataSource={users}
  212. loading={isLoading}
  213. rowKey="id"
  214. pagination={{
  215. ...pagination,
  216. showSizeChanger: true,
  217. showQuickJumper: true,
  218. showTotal: (total) => `共 ${total} 条记录`
  219. }}
  220. onChange={handleTableChange}
  221. bordered
  222. scroll={{ x: 'max-content' }}
  223. rowClassName={(record, index) => index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}
  224. />
  225. </Card>
  226. {/* 创建/编辑用户模态框 */}
  227. <Modal
  228. title={modalTitle}
  229. open={modalVisible}
  230. onOk={handleModalOk}
  231. onCancel={() => {
  232. setModalVisible(false);
  233. form.resetFields();
  234. }}
  235. width={600}
  236. centered
  237. destroyOnClose
  238. maskClosable={false}
  239. >
  240. <Form
  241. form={form}
  242. layout="vertical"
  243. labelCol={{ span: 5 }}
  244. wrapperCol={{ span: 19 }}
  245. >
  246. <Form.Item
  247. name="username"
  248. label="用户名"
  249. required
  250. rules={[
  251. { required: true, message: '请输入用户名' },
  252. { min: 3, message: '用户名至少3个字符' }
  253. ]}
  254. >
  255. <Input placeholder="请输入用户名" />
  256. </Form.Item>
  257. <Form.Item
  258. name="nickname"
  259. label="昵称"
  260. rules={[{ required: false, message: '请输入昵称' }]}
  261. >
  262. <Input placeholder="请输入昵称" />
  263. </Form.Item>
  264. <Form.Item
  265. name="email"
  266. label="邮箱"
  267. rules={[
  268. { required: false, message: '请输入邮箱' },
  269. { type: 'email', message: '请输入有效的邮箱地址' }
  270. ]}
  271. >
  272. <Input placeholder="请输入邮箱" />
  273. </Form.Item>
  274. <Form.Item
  275. name="phone"
  276. label="手机号"
  277. rules={[
  278. { required: false, message: '请输入手机号' },
  279. { pattern: /^1[3-9]\d{9}$/, message: '请输入有效的手机号' }
  280. ]}
  281. >
  282. <Input placeholder="请输入手机号" />
  283. </Form.Item>
  284. <Form.Item
  285. name="name"
  286. label="真实姓名"
  287. rules={[{ required: false, message: '请输入真实姓名' }]}
  288. >
  289. <Input placeholder="请输入真实姓名" />
  290. </Form.Item>
  291. {!editingUser && (
  292. <Form.Item
  293. name="password"
  294. label="密码"
  295. required
  296. rules={[
  297. { required: true, message: '请输入密码' },
  298. { min: 6, message: '密码至少6个字符' }
  299. ]}
  300. >
  301. <Input.Password placeholder="请输入密码" />
  302. </Form.Item>
  303. )}
  304. <Form.Item
  305. name="isDisabled"
  306. label="状态"
  307. required
  308. rules={[{ required: true, message: '请选择状态' }]}
  309. >
  310. <Select placeholder="请选择状态">
  311. <Select.Option value={0}>启用</Select.Option>
  312. <Select.Option value={1}>禁用</Select.Option>
  313. </Select>
  314. </Form.Item>
  315. </Form>
  316. </Modal>
  317. </div>
  318. );
  319. };