Users.tsx 9.9 KB

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