VipSelector.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. import * as React from 'react';
  2. import { Modal, Table, Input, Select, Space, message, Tree, Divider, Button } from 'antd';
  3. import type { TableColumnsType } from 'antd';
  4. import type { DataNode } from 'antd/es/tree';
  5. import { Tree as TreeIcon, Group, Search } from 'iconoir-react';
  6. import './VipSelector.scss';
  7. interface VipUser {
  8. userId: string;
  9. userName: string;
  10. nickName: string;
  11. deptName: string;
  12. deptId: string;
  13. userTypeName: string;
  14. }
  15. interface Department {
  16. deptId: string;
  17. deptName: string;
  18. children?: Department[];
  19. }
  20. interface VipSelectorProps {
  21. open: boolean;
  22. onClose: () => void;
  23. onConfirm: (users: VipUser[]) => void;
  24. existingUsers?: VipUser[];
  25. }
  26. // Mock 数据 - 部门树
  27. const mockDepartments: Department[] = [
  28. {
  29. deptId: 'dept_001',
  30. deptName: '政务服务中心',
  31. children: [
  32. { deptId: 'dept_001_01', deptName: '窗口服务科' },
  33. { deptId: 'dept_001_02', deptName: '在线咨询科' },
  34. ],
  35. },
  36. {
  37. deptId: 'dept_002',
  38. deptName: '市场监管局',
  39. children: [
  40. { deptId: 'dept_002_01', deptName: '企业注册科' },
  41. { deptId: 'dept_002_02', deptName: '质量监管科' },
  42. ],
  43. },
  44. {
  45. deptId: 'dept_003',
  46. deptName: '大数据局',
  47. children: [
  48. { deptId: 'dept_003_01', deptName: '数据共享科' },
  49. { deptId: 'dept_003_02', deptName: '智慧城市科' },
  50. ],
  51. },
  52. {
  53. deptId: 'dept_004',
  54. deptName: '财政局',
  55. children: [
  56. { deptId: 'dept_004_01', deptName: '预算科' },
  57. { deptId: 'dept_004_02', deptName: '国库科' },
  58. ],
  59. },
  60. {
  61. deptId: 'dept_005',
  62. deptName: '人社局',
  63. children: [
  64. { deptId: 'dept_005_01', deptName: '社保科' },
  65. { deptId: 'dept_005_02', deptName: '就业科' },
  66. ],
  67. },
  68. ];
  69. // Mock 数据 - 用户列表(带部门关联)
  70. const mockUserList: VipUser[] = [
  71. { userId: 'user_001', userName: 'zhangsan', nickName: '张三', deptName: '政务服务中心', deptId: 'dept_001', userTypeName: '普通用户' },
  72. { userId: 'user_002', userName: 'lisi', nickName: '李四', deptName: '市场监管局', deptId: 'dept_002', userTypeName: 'VIP 用户' },
  73. { userId: 'user_003', userName: 'wangwu', nickName: '王五', deptName: '大数据局', deptId: 'dept_003', userTypeName: '管理员' },
  74. { userId: 'user_004', userName: 'zhaoliu', nickName: '赵六', deptName: '财政局', deptId: 'dept_004', userTypeName: '普通用户' },
  75. { userId: 'user_005', userName: 'qianqi', nickName: '钱七', deptName: '人社局', deptId: 'dept_005', userTypeName: 'VIP 用户' },
  76. { userId: 'user_006', userName: 'sunba', nickName: '孙八', deptName: '政务服务中心', deptId: 'dept_001', userTypeName: '普通用户' },
  77. { userId: 'user_007', userName: 'zhoujiu', nickName: '周九', deptName: '市场监管局', deptId: 'dept_002', userTypeName: '管理员' },
  78. { userId: 'user_008', userName: 'zhengshi', nickName: '郑十', deptName: '大数据局', deptId: 'dept_003', userTypeName: 'VIP 用户' },
  79. { userId: 'user_009', userName: 'wushi', nickName: '吴十', deptName: '财政局', deptId: 'dept_004', userTypeName: '普通用户' },
  80. { userId: 'user_010', userName: 'zhengyi', nickName: '郑十一', deptName: '人社局', deptId: 'dept_005', userTypeName: '管理员' },
  81. ];
  82. // 将部门树转换为 Tree 组件需要的格式
  83. const convertToTreeData = (departments: Department[]): DataNode[] => {
  84. return departments.map(dept => ({
  85. title: dept.deptName,
  86. key: dept.deptId,
  87. children: dept.children?.map(child => ({
  88. title: child.deptName,
  89. key: child.deptId,
  90. isLeaf: true,
  91. })),
  92. }));
  93. };
  94. const VipSelector: React.FC<VipSelectorProps> = ({ open, onClose, onConfirm, existingUsers = [] }) => {
  95. const [selectedRowKeys, setSelectedRowKeys] = React.useState<React.Key[]>([]);
  96. const [selectedDeptId, setSelectedDeptId] = React.useState<string>('');
  97. const [searchUserName, setSearchUserName] = React.useState('');
  98. const [searchNickName, setSearchNickName] = React.useState('');
  99. const [searchUserType, setSearchUserType] = React.useState<string | undefined>();
  100. // 初始化选中项为已选的 VIP 用户
  101. React.useEffect(() => {
  102. if (open) {
  103. setSelectedRowKeys(existingUsers.map(item => item.userId));
  104. }
  105. }, [open, existingUsers]);
  106. // 部门树选择处理
  107. const onDeptSelect: TreeProps['onSelect'] = (selectedKeys) => {
  108. if (selectedKeys.length > 0) {
  109. setSelectedDeptId(selectedKeys[0] as string);
  110. } else {
  111. setSelectedDeptId('');
  112. }
  113. };
  114. // 根据部门和筛选条件过滤用户
  115. const filteredUsers = mockUserList.filter(user => {
  116. const matchDept = !selectedDeptId || user.deptId === selectedDeptId ||
  117. mockDepartments.some(d => d.deptId === selectedDeptId && user.deptId === d.deptId);
  118. const matchUserName = !searchUserName || user.userName.toLowerCase().includes(searchUserName.toLowerCase());
  119. const matchNickName = !searchNickName || user.nickName.includes(searchNickName);
  120. const matchUserType = !searchUserType || user.userTypeName === searchUserType;
  121. return matchDept && matchUserName && matchNickName && matchUserType;
  122. });
  123. const columns: TableColumnsType<VipUser> = [
  124. {
  125. title: '昵称',
  126. dataIndex: 'nickName',
  127. key: 'nickName',
  128. width: 100,
  129. },
  130. {
  131. title: '用户名称',
  132. dataIndex: 'userName',
  133. key: 'userName',
  134. width: 120,
  135. },
  136. {
  137. title: '部门',
  138. dataIndex: 'deptName',
  139. key: 'deptName',
  140. width: 140,
  141. },
  142. {
  143. title: '用户类型',
  144. dataIndex: 'userTypeName',
  145. key: 'userTypeName',
  146. width: 100,
  147. },
  148. ];
  149. const rowSelection = {
  150. selectedRowKeys,
  151. onChange: (newSelectedRowKeys: React.Key[]) => {
  152. setSelectedRowKeys(newSelectedRowKeys);
  153. },
  154. };
  155. const handleConfirm = () => {
  156. const selectedUsers = filteredUsers.filter(user =>
  157. selectedRowKeys.includes(user.userId)
  158. );
  159. onConfirm(selectedUsers);
  160. message.success(`已选择 ${selectedUsers.length} 个用户`);
  161. };
  162. const handleSelectAll = () => {
  163. setSelectedRowKeys(filteredUsers.map(user => user.userId));
  164. };
  165. const handleClearAll = () => {
  166. setSelectedRowKeys([]);
  167. };
  168. return (
  169. <Modal
  170. title="请选择指定用户"
  171. open={open}
  172. onOk={handleConfirm}
  173. onCancel={onClose}
  174. width={900}
  175. className='vip-selector-modal'
  176. okText="确定"
  177. cancelText="取消"
  178. >
  179. <div className='vip-selector-container'>
  180. {/* 左侧:部门树 */}
  181. <div className='vip-selector-left'>
  182. <div className='section-title'>
  183. <TreeIcon width="18" height="18" />
  184. 选择部门
  185. </div>
  186. <Tree
  187. treeData={convertToTreeData(mockDepartments)}
  188. onSelect={onDeptSelect}
  189. selectedKeys={[selectedDeptId]}
  190. showLine
  191. blockNode
  192. />
  193. <Divider className='dept-divider' />
  194. <div className='selected-count'>
  195. 已选:{selectedRowKeys.length} 人
  196. </div>
  197. </div>
  198. {/* 右侧:用户列表 */}
  199. <div className='vip-selector-right'>
  200. <div className='section-title'>
  201. <Group width="18" height="18" />
  202. 用户列表
  203. {selectedDeptId && (
  204. <span className='dept-filter-tag'>
  205. {mockDepartments.find(d => d.deptId === selectedDeptId)?.deptName || '已筛选'}
  206. </span>
  207. )}
  208. </div>
  209. {/* 筛选条件 */}
  210. <Space className='filter-bar' wrap>
  211. <Input
  212. placeholder="搜索用户名称"
  213. value={searchUserName}
  214. onChange={(e) => setSearchUserName(e.target.value)}
  215. style={{ width: 140 }}
  216. allowClear
  217. prefix={<Search width="14" height="14" />}
  218. />
  219. <Input
  220. placeholder="搜索昵称"
  221. value={searchNickName}
  222. onChange={(e) => setSearchNickName(e.target.value)}
  223. style={{ width: 120 }}
  224. allowClear
  225. />
  226. <Select
  227. placeholder="用户类型"
  228. value={searchUserType}
  229. onChange={setSearchUserType}
  230. style={{ width: 120 }}
  231. allowClear
  232. >
  233. <Select.Option value="普通用户">普通用户</Select.Option>
  234. <Select.Option value="VIP 用户">VIP 用户</Select.Option>
  235. <Select.Option value="管理员">管理员</Select.Option>
  236. </Select>
  237. <Button size="small" onClick={handleSelectAll}>全选</Button>
  238. <Button size="small" onClick={handleClearAll}>清空</Button>
  239. </Space>
  240. {/* 用户表格 */}
  241. <Table
  242. rowKey="userId"
  243. rowSelection={rowSelection}
  244. columns={columns}
  245. dataSource={filteredUsers}
  246. pagination={{
  247. showTotal: (total) => `共 ${total} 条`,
  248. showSizeChanger: false,
  249. showQuickJumper: true,
  250. current: 1,
  251. pageSize: 10,
  252. total: filteredUsers.length,
  253. }}
  254. scroll={{ y: 360 }}
  255. size="small"
  256. />
  257. </div>
  258. </div>
  259. </Modal>
  260. );
  261. };
  262. export default VipSelector;