page.tsx 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398
  1. "use client";
  2. import {fetchApi, fetchFile} from "@/app/_modules/func";
  3. import {
  4. CaretDownOutlined,
  5. CheckOutlined,
  6. CloseOutlined,
  7. DeleteOutlined,
  8. ExclamationCircleFilled,
  9. FileAddOutlined,
  10. KeyOutlined,
  11. LoadingOutlined,
  12. PlusOutlined,
  13. ReloadOutlined,
  14. SearchOutlined,
  15. } from "@ant-design/icons";
  16. import type {ActionType, ProColumns, ProFormInstance,} from "@ant-design/pro-components";
  17. import {
  18. ModalForm,
  19. PageContainer,
  20. ProCard,
  21. ProForm,
  22. ProFormRadio,
  23. ProFormSelect,
  24. ProFormText,
  25. ProFormTextArea,
  26. ProFormTreeSelect,
  27. ProTable,
  28. } from "@ant-design/pro-components";
  29. import type {GetProp, TreeDataNode, UploadProps} from "antd";
  30. import {
  31. Button,
  32. Checkbox,
  33. Col,
  34. Dropdown,
  35. Flex,
  36. Form,
  37. Input,
  38. message,
  39. Modal,
  40. Row,
  41. Space,
  42. Spin,
  43. Switch,
  44. Tree,
  45. Typography,
  46. Upload,
  47. } from "antd";
  48. import {useRouter} from "next/navigation";
  49. import {
  50. faDownload,
  51. faPenToSquare,
  52. faToggleOff,
  53. faToggleOn,
  54. faUpload,
  55. faUsers,
  56. } from "@fortawesome/free-solid-svg-icons";
  57. import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
  58. import {useEffect, useMemo, useRef, useState} from "react";
  59. type FileType = Parameters<GetProp<UploadProps, "beforeUpload">>[0];
  60. const { Dragger } = Upload;
  61. export type OptionType = {
  62. label: string;
  63. value: string | number;
  64. };
  65. export default function User() {
  66. const { push } = useRouter();
  67. // 添加用于控制删除确认模态框的状态
  68. const [deleteModalVisible, setDeleteModalVisible] = useState(false);
  69. const [deleteUserId, setDeleteUserId] = useState<string | number | null>(null);
  70. // 添加用于控制切换状态确认模态框的状态
  71. const [statusModalVisible, setStatusModalVisible] = useState(false);
  72. const [statusChecked, setStatusChecked] = useState<boolean>(true);
  73. const [statusRecord, setStatusRecord] = useState<any>(null);
  74. //新建用户预置密码值
  75. const [defaultPassword, setDefaultPassword] = useState("");
  76. useEffect(() => {
  77. queryDefaultPassword();
  78. queryPostion();
  79. queryOrgTree();
  80. }, []);
  81. //控制行的状态值的恢复
  82. const [rowStatusMap, setRowStatusMap] = useState<{ [key: number]: boolean }>(
  83. {}
  84. );
  85. //表格列定义
  86. const columns: ProColumns[] = [
  87. {
  88. title: "用户编号",
  89. dataIndex: "userId",
  90. search: false,
  91. },
  92. {
  93. title: "用户名称",
  94. fieldProps: {
  95. placeholder: "请输入用户名称",
  96. },
  97. dataIndex: "userName",
  98. ellipsis: true,
  99. sorter: true,
  100. order: 4,
  101. },
  102. {
  103. title: "用户昵称",
  104. dataIndex: "nickName",
  105. ellipsis: true,
  106. sorter: true,
  107. search: false,
  108. },
  109. {
  110. title: "部门名称",
  111. key: "deptName",
  112. search: false,
  113. render: (text, record) => record.dept?.deptName,
  114. },
  115. {
  116. title: "手机号",
  117. fieldProps: {
  118. placeholder: "请输入手机号",
  119. },
  120. dataIndex: "phonenumber",
  121. order: 3,
  122. },
  123. {
  124. title: "状态",
  125. fieldProps: {
  126. placeholder: "请选择用户状态",
  127. },
  128. dataIndex: "status",
  129. valueType: "select",
  130. order: 2,
  131. valueEnum: {
  132. 0: {
  133. text: "正常",
  134. status: "0",
  135. },
  136. 1: {
  137. text: "停用",
  138. status: "1",
  139. },
  140. },
  141. render: (text, record) => {
  142. return (
  143. <Space>
  144. <Switch
  145. checkedChildren={<CheckOutlined />}
  146. unCheckedChildren={<CloseOutlined />}
  147. defaultChecked={record.status === "0"}
  148. checked={rowStatusMap[record.userId]}
  149. disabled={record.userId == 1}
  150. onChange={(checked, event) => {
  151. showSwitchUserStatusModal(checked, record);
  152. }}
  153. />
  154. </Space>
  155. );
  156. },
  157. },
  158. {
  159. title: "创建时间",
  160. dataIndex: "createTime",
  161. valueType: "dateTime",
  162. search: false,
  163. sorter: true,
  164. },
  165. {
  166. title: "创建时间",
  167. fieldProps: {
  168. placeholder: ["开始日期", "结束日期"],
  169. },
  170. dataIndex: "createTimeRange",
  171. valueType: "dateRange",
  172. hideInTable: true,
  173. order: 1,
  174. search: {
  175. transform: (value) => {
  176. return {
  177. "params[beginTime]": `${value[0]} 00:00:00`,
  178. "params[endTime]": `${value[1]} 23:59:59`,
  179. };
  180. },
  181. },
  182. },
  183. {
  184. title: "操作",
  185. key: "option",
  186. search: false,
  187. render: (_, record) => {
  188. if (record.userId != 1)
  189. return [
  190. <Button
  191. key="modifyBtn"
  192. type="link"
  193. icon={<FontAwesomeIcon icon={faPenToSquare} />}
  194. onClick={() => showRowModifyModal(record)}
  195. >
  196. 修改
  197. </Button>,
  198. <Button
  199. key="deleteBtn"
  200. type="link"
  201. danger
  202. icon={<DeleteOutlined />}
  203. onClick={() => onClickDeleteRow(record)}
  204. >
  205. 删除
  206. </Button>,
  207. <Dropdown
  208. key="moreDrop"
  209. menu={{
  210. items: [
  211. {
  212. key: "1",
  213. label: (
  214. <a
  215. onClick={() => {
  216. modifyUserPwd(record);
  217. }}
  218. >
  219. 重置密码
  220. </a>
  221. ),
  222. icon: <KeyOutlined />,
  223. },
  224. {
  225. key: "2",
  226. label: (
  227. <a
  228. onClick={() =>
  229. push(`/system/user/auth/${record.userId}`)
  230. }
  231. >
  232. 分配角色
  233. </a>
  234. ),
  235. icon: <FontAwesomeIcon icon={faUsers} />,
  236. },
  237. ],
  238. }}
  239. >
  240. <a onClick={(e) => e.preventDefault()}>
  241. <Space>
  242. 更多
  243. <CaretDownOutlined />
  244. </Space>
  245. </a>
  246. </Dropdown>,
  247. ];
  248. },
  249. },
  250. ];
  251. //是否展示修改用户对话框
  252. const [showModifyUserModal, setShowModifyUserModal] = useState(false);
  253. //展示修改用户对话框
  254. const showRowModifyModal = (record?: any) => {
  255. queryUserInfo(record);
  256. setShowModifyUserModal(true);
  257. };
  258. //是否展示修改密码
  259. const [showModifyUserPwdModal, setShowModifyUserPwdModal] = useState(false);
  260. //重置密码表单引用
  261. const [pwdFormRef] = Form.useForm();
  262. const modifyUserPwd = (record: any) => {
  263. attachUserdata["userId"] = record.userId;
  264. attachUserdata["userName"] = record.userName;
  265. setAttachUserdata(attachUserdata);
  266. setShowModifyUserPwdModal(true);
  267. };
  268. //确认重置密码
  269. const confirmModifyUserPwd = () => {
  270. pwdFormRef.submit();
  271. };
  272. //取消重置密码
  273. const cancelModifyUserPwd = () => {
  274. setShowModifyUserPwdModal(false);
  275. };
  276. //执行重置密码
  277. const executeModifyUserPwd = async (values: any) => {
  278. setShowModifyUserPwdModal(false);
  279. values["userId"] = attachUserdata["userId"];
  280. const body = await fetchApi("/api/system/user/resetPwd", push, {
  281. method: "PUT",
  282. headers: {
  283. "Content-Type": "application/json",
  284. },
  285. body: JSON.stringify(values),
  286. });
  287. if (body != undefined) {
  288. if (body.code == 200) {
  289. message.success(`修改${attachUserdata["userName"]}密码成功`);
  290. } else {
  291. message.error(body.msg);
  292. }
  293. }
  294. pwdFormRef.resetFields();
  295. };
  296. //查询用户数据
  297. const getUser = async (params: any, sorter: any, filter: any) => {
  298. const searchParams = {
  299. pageNum: params.current,
  300. ...params,
  301. };
  302. delete searchParams.current;
  303. const queryParams = new URLSearchParams(searchParams);
  304. Object.keys(sorter).forEach((key) => {
  305. queryParams.append("orderByColumn", key);
  306. if (sorter[key] === "ascend") {
  307. queryParams.append("isAsc", "ascending");
  308. } else {
  309. queryParams.append("isAsc", "descending");
  310. }
  311. });
  312. //如果有组织id,添加相应查询参数
  313. if (searchDeptId != 0) {
  314. queryParams.append("deptId", searchDeptId.toString());
  315. }
  316. const body = await fetchApi(`/api/system/user/list?${queryParams}`, push);
  317. if (body !== undefined) {
  318. body.rows.forEach((row: any) => {
  319. setRowStatusMap({ ...rowStatusMap, [row.userId]: row.status === "0" });
  320. });
  321. }
  322. return body;
  323. };
  324. //展示切换用户状态对话框
  325. const showSwitchUserStatusModal = (checked: boolean, record: any) => {
  326. setRowStatusMap({ ...rowStatusMap, [record.userId]: checked });
  327. setStatusChecked(checked);
  328. setStatusRecord(record);
  329. setStatusModalVisible(true);
  330. };
  331. //确认变更用户状态
  332. const executeSwitchStatus = async (
  333. checked: boolean,
  334. userId: string,
  335. erroCallback: () => void
  336. ) => {
  337. const modifyData = {
  338. userId: userId,
  339. status: checked ? "0" : "1",
  340. };
  341. const body = await fetchApi(`/api/system/user/changeStatus`, push, {
  342. method: "PUT",
  343. headers: {
  344. "Content-Type": "application/json",
  345. },
  346. body: JSON.stringify(modifyData),
  347. });
  348. if (body !== undefined) {
  349. if (body.code == 200) {
  350. message.success(body.msg);
  351. } else {
  352. message.error(body.msg);
  353. erroCallback();
  354. }
  355. }
  356. };
  357. //确定切换状态
  358. const confirmSwitchStatus = () => {
  359. if (!statusRecord) return;
  360. executeSwitchStatus(statusChecked, statusRecord.userId, () => {
  361. setRowStatusMap({ ...rowStatusMap, [statusRecord.userId]: !statusChecked });
  362. });
  363. setStatusModalVisible(false);
  364. setStatusRecord(null);
  365. };
  366. //取消切换状态
  367. const cancelSwitchStatus = () => {
  368. if (!statusRecord) return;
  369. setRowStatusMap({ ...rowStatusMap, [statusRecord.userId]: !statusChecked });
  370. setStatusModalVisible(false);
  371. setStatusRecord(null);
  372. };
  373. //删除按钮是否可用,选中行时才可用
  374. const [rowCanDelete, setRowCanDelete] = useState(false);
  375. //选中行操作
  376. const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
  377. const [selectedRow, setSelectedRow] = useState(undefined as any);
  378. //修改按钮是否可用
  379. const [rowCanModify, setRowCanModify] = useState(false);
  380. const rowSelection = {
  381. onChange: (newSelectedRowKeys: React.Key[], selectedRows: any[]) => {
  382. setSelectedRowKeys(newSelectedRowKeys);
  383. setRowCanDelete(newSelectedRowKeys && newSelectedRowKeys.length > 0);
  384. if (newSelectedRowKeys && newSelectedRowKeys.length == 1) {
  385. setSelectedRow(selectedRows[0]);
  386. setRowCanModify(true);
  387. } else {
  388. setRowCanModify(false);
  389. setSelectedRow(undefined);
  390. }
  391. },
  392. getCheckboxProps: (record: any) => ({
  393. disabled: record.userId == 1,
  394. }),
  395. };
  396. //查询用的组织id
  397. const [searchDeptId, setSearchDeptId] = useState(0);
  398. //选择组织树执行过滤
  399. const selectOrgData = (selectedDeptKey: React.Key[]) => {
  400. if (selectedDeptKey && selectedDeptKey.length > 0) {
  401. setSearchDeptId(selectedDeptKey[0] as number);
  402. } else {
  403. setSearchDeptId(0);
  404. }
  405. if (formRef.current) {
  406. formRef.current.submit();
  407. }
  408. };
  409. //用于搜索的组织选择数据
  410. const [orgTreeData, setOrgTreeData] = useState([] as Array<TreeDataNode>);
  411. //用于对话框的组织选择数据
  412. const [orgSelectData, setOrgSelectData] = useState([]);
  413. //查询组织树
  414. const queryOrgTree = async () => {
  415. const body = await fetchApi("/api/system/user/deptTree", push);
  416. if (body !== undefined) {
  417. setOrgTreeData(generateOrgTree(body.data));
  418. setSearchValue("");
  419. setOrgSelectData(body.data);
  420. }
  421. };
  422. //搜索部门的值
  423. const [searchValue, setSearchValue] = useState("");
  424. //搜索组织树数据
  425. const onSearchDept = (e: React.ChangeEvent<HTMLInputElement>) => {
  426. setSearchValue(e.target.value);
  427. };
  428. //搜索过滤后的组织树展示数据
  429. const filterOrgTree = useMemo(() => {
  430. const loop = (data: TreeDataNode[]): TreeDataNode[] =>
  431. data.map((item) => {
  432. const strTitle = item.title as string;
  433. const index = strTitle.indexOf(searchValue);
  434. const beforeStr = strTitle.substring(0, index);
  435. const afterStr = strTitle.slice(index + searchValue.length);
  436. const title =
  437. index > -1 ? (
  438. <span>
  439. {beforeStr}
  440. <span style={{ color: "#f50" }}>{searchValue}</span>
  441. {afterStr}
  442. </span>
  443. ) : (
  444. <span>{strTitle}</span>
  445. );
  446. if (item.children) {
  447. return { title, key: item.key, children: loop(item.children) };
  448. }
  449. return {
  450. title,
  451. key: item.key,
  452. };
  453. });
  454. const data = loop(orgTreeData);
  455. return data;
  456. }, [orgTreeData, searchValue]);
  457. const generateOrgTree = (orgData: []) => {
  458. const children: Array<TreeDataNode> = new Array<TreeDataNode>();
  459. orgData.forEach((parent: any) => {
  460. const hasChild = parent.children && parent.children.length > 0;
  461. const node: TreeDataNode = {
  462. title: parent.label,
  463. key: parent.id,
  464. };
  465. children.push(node);
  466. if (hasChild) {
  467. generateOrgChildTree(parent.children, node);
  468. }
  469. });
  470. return children;
  471. };
  472. const generateOrgChildTree = (orgData: [], parent: TreeDataNode) => {
  473. const children: Array<TreeDataNode> = new Array<TreeDataNode>();
  474. orgData.forEach((item: any) => {
  475. const hasChild = item.children && item.children.length > 0;
  476. const node: TreeDataNode = {
  477. title: item.label,
  478. key: item.id,
  479. isLeaf: !hasChild,
  480. };
  481. children.push(node);
  482. if (hasChild) {
  483. generateOrgChildTree(item.children, node);
  484. }
  485. });
  486. parent.children = children;
  487. return parent;
  488. };
  489. //查询性别分类
  490. const querySexType = async () => {
  491. const body = await fetchApi(
  492. "/api/system/dict/data/type/sys_user_sex",
  493. push
  494. );
  495. if (body !== undefined) {
  496. return body.data;
  497. }
  498. };
  499. //查询新建用户预置密码
  500. const queryDefaultPassword = async () => {
  501. const body = await fetchApi(
  502. "/api/system/config/configKey/sys.user.initPassword",
  503. push
  504. );
  505. if (body !== undefined) {
  506. setDefaultPassword(body.msg);
  507. }
  508. };
  509. //岗位数据
  510. const [positionValue, setPositionValue] = useState<{ [key: number]: string }>(
  511. {}
  512. );
  513. //角色数据
  514. const [roleValue, setRoleValue] = useState<{ [key: number]: string }>({});
  515. //查询岗位
  516. const queryPostion = async () => {
  517. const body = await fetchApi("/api/system/user/", push);
  518. if (body !== undefined) {
  519. body.posts.forEach((post: any) => {
  520. positionValue[post.postId] = post.postName;
  521. setPositionValue(positionValue);
  522. });
  523. body.roles.forEach((role: any) => {
  524. roleValue[role.roleId] = role.roleName;
  525. setRoleValue(roleValue);
  526. });
  527. }
  528. };
  529. //确定新建用户
  530. const executeAddUser = async (values: any) => {
  531. const body = await fetchApi("/api/system/user", push, {
  532. method: "POST",
  533. headers: {
  534. "Content-Type": "application/json",
  535. },
  536. body: JSON.stringify(values),
  537. });
  538. if (body != undefined) {
  539. if (body.code == 200) {
  540. message.success(body.msg);
  541. if (actionRef.current) {
  542. actionRef.current.reload();
  543. }
  544. return true;
  545. }
  546. message.error(body.msg);
  547. return false;
  548. }
  549. return false;
  550. };
  551. //修改用户表单引用
  552. const modifyFormRef = useRef<ProFormInstance>(null);
  553. //待修改用户的岗位可选数据
  554. const [modifyPositionValue, setModifyPositionValue] = useState(
  555. [] as Array<OptionType>
  556. );
  557. //待修改用户的角色可选数据
  558. const [modifyRoleValue, setModifyRoleValue] = useState(
  559. [] as Array<OptionType>
  560. );
  561. //操作用户的附加数据
  562. const [attachUserdata, setAttachUserdata] = useState<{ [key: string]: any }>(
  563. {}
  564. );
  565. //查询用户信息
  566. const queryUserInfo = async (record?: any) => {
  567. const userId = record !== undefined ? record.userId : selectedRow.userId;
  568. const userName =
  569. record !== undefined ? record.userName : selectedRow.userName;
  570. attachUserdata["userId"] = userId;
  571. attachUserdata["userName"] = userName;
  572. setAttachUserdata(attachUserdata);
  573. if (userId !== undefined) {
  574. const body = await fetchApi(`/api/system/user/${userId}`, push);
  575. if (body !== undefined) {
  576. if (body.code == 200) {
  577. const positionArray: Array<OptionType> = new Array<OptionType>();
  578. body.posts.forEach((post: any) => {
  579. const option: OptionType = {
  580. label: post.postName,
  581. value: post.postId,
  582. };
  583. positionArray.push(option);
  584. });
  585. setModifyPositionValue(positionArray);
  586. const roeArray: Array<OptionType> = new Array<OptionType>();
  587. body.roles.forEach((role: any) => {
  588. const option: OptionType = {
  589. label: role.roleName,
  590. value: role.roleId,
  591. };
  592. roeArray.push(option);
  593. });
  594. setModifyRoleValue(roeArray);
  595. modifyFormRef?.current?.setFieldsValue({
  596. nickName: body.data.nickName,
  597. deptId: body.data.deptId,
  598. phonenumber: body.data.phonenumber,
  599. email: body.data.email,
  600. sex: body.data.sex,
  601. status: body.data.status,
  602. postIds: body.postIds,
  603. roleIds: body.roleIds,
  604. remark: body.data.remark,
  605. });
  606. }
  607. }
  608. }
  609. };
  610. //确认修改用户
  611. const executeModifyUser = async (values: any) => {
  612. values["userId"] = attachUserdata["userId"];
  613. values["userName"] = attachUserdata["userName"];
  614. const body = await fetchApi("/api/system/user", push, {
  615. method: "PUT",
  616. headers: {
  617. "Content-Type": "application/json",
  618. },
  619. body: JSON.stringify(values),
  620. });
  621. if (body !== undefined) {
  622. if (body.code == 200) {
  623. message.success(body.msg);
  624. //刷新列表
  625. if (actionRef.current) {
  626. actionRef.current.reload();
  627. }
  628. return true;
  629. }
  630. message.error(body.msg);
  631. return false;
  632. }
  633. };
  634. //点击删除按钮
  635. const onClickDeleteRow = (record?: any) => {
  636. const userId = record !== undefined ? record.userId : selectedRowKeys.join(",");
  637. setDeleteUserId(userId);
  638. setDeleteModalVisible(true);
  639. };
  640. //确定删除选中的用户
  641. const executeDeleteRow = async () => {
  642. if (deleteUserId === null) return;
  643. const body = await fetchApi(`/api/system/user/${deleteUserId}`, push, {
  644. method: "DELETE",
  645. });
  646. if (body !== undefined) {
  647. if (body.code == 200) {
  648. message.success("删除成功");
  649. //修改按钮变回不可点击
  650. setRowCanModify(false);
  651. //删除按钮变回不可点击
  652. setRowCanDelete(false);
  653. //选中行数据重置为空
  654. setSelectedRowKeys([]);
  655. //刷新列表
  656. if (actionRef.current) {
  657. actionRef.current.reload();
  658. }
  659. } else {
  660. message.error(body.msg);
  661. }
  662. }
  663. setDeleteModalVisible(false);
  664. setDeleteUserId(null);
  665. };
  666. //选中上传文件列表
  667. const [fileList, setFileList] = useState<FileType[]>([]);
  668. //上传前检查
  669. const beforeUpload = (file: FileType) => {
  670. setFileList([file]);
  671. const isExcel =
  672. file.type ===
  673. "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
  674. if (!isExcel) {
  675. message.error("请上传 xls、xlsx 格式文件!");
  676. setFileList([]);
  677. }
  678. return false;
  679. };
  680. //移除待上传文件
  681. const removeFile = () => {
  682. setFileList([]);
  683. };
  684. //上传文件是否刷新已有用户数据
  685. const [uploadSupport, setUploadSupport] = useState(false);
  686. //文件上传状态
  687. const [uploading, setUploading] = useState(false);
  688. //上传处理,手动上传下不会执行
  689. const handleChange: UploadProps["onChange"] = (info: any) => {
  690. if (info.file.status === "uploading") {
  691. setUploading(true);
  692. return;
  693. }
  694. if (info.file.status === "done") {
  695. setUploading(false);
  696. console.log(info.file.response);
  697. if (info.file.response.code == 200) {
  698. message.success(info.file.response.msg);
  699. } else {
  700. message.error(info.file.response.msg);
  701. }
  702. }
  703. };
  704. //导入对话框是否展示
  705. const [showImportModal, setShowImportModal] = useState(false);
  706. //点击导入按钮
  707. const onClickImport = () => {
  708. setShowImportModal(true);
  709. };
  710. //确定导入
  711. const executeImport = async () => {
  712. if (fileList.length == 0) {
  713. message.error("请选择上传的文件");
  714. return;
  715. }
  716. setUploading(true);
  717. const file = fileList[0];
  718. const formData = new FormData();
  719. formData.append("file", file);
  720. const body = await fetchApi(
  721. `/api/system/user/importData?updateSupport=${uploadSupport}`,
  722. push,
  723. {
  724. method: "POST",
  725. body: formData,
  726. }
  727. );
  728. setUploading(false);
  729. setUploadSupport(false);
  730. if (body !== undefined) {
  731. setFileList([]);
  732. if (body.code == 200) {
  733. message.success("用户导入成功");
  734. //刷新列表
  735. if (actionRef.current) {
  736. actionRef.current.reload();
  737. }
  738. } else {
  739. message.error(body.msg);
  740. }
  741. }
  742. };
  743. //取消导入对话框
  744. const cancelImportModal = () => {
  745. setShowImportModal(false);
  746. setUploadSupport(false);
  747. setFileList([]);
  748. };
  749. //搜索栏显示状态
  750. const [showSearch, setShowSearch] = useState(true);
  751. //action对象引用
  752. const actionRef = useRef<ActionType>(null);
  753. //表单对象引用
  754. const formRef = useRef<ProFormInstance>(null!);
  755. //当前页数和每页条数
  756. const [page, setPage] = useState(1);
  757. const defaultPageSize = 10;
  758. const [pageSize, setPageSize] = useState(defaultPageSize);
  759. const pageChange = (page: number, pageSize: number) => {
  760. setPage(page);
  761. setPageSize(pageSize);
  762. };
  763. //导出用户
  764. const exportTable = async () => {
  765. if (formRef.current) {
  766. const formData = new FormData();
  767. const data = {
  768. pageNum: page,
  769. pageSize: pageSize,
  770. ...formRef.current.getFieldsValue(),
  771. };
  772. Object.keys(data).forEach((key) => {
  773. if (data[key] !== undefined) {
  774. formData.append(key, data[key]);
  775. }
  776. });
  777. await fetchFile(
  778. "/api/system/user/export",
  779. push,
  780. {
  781. method: "POST",
  782. body: formData,
  783. },
  784. `user_${new Date().getTime()}.xlsx`
  785. );
  786. }
  787. };
  788. return (
  789. <PageContainer title={false}>
  790. <Row gutter={{ xs: 8, sm: 8, md: 8 }}>
  791. <Col xs={24} sm={6} md={6}>
  792. <ProCard>
  793. <Input
  794. style={{ marginBottom: 16 }}
  795. placeholder="输入部门名称搜索"
  796. prefix={<SearchOutlined />}
  797. onChange={onSearchDept}
  798. />
  799. {filterOrgTree.length > 0 ? (
  800. <Flex>
  801. <Tree
  802. switcherIcon={<CaretDownOutlined />}
  803. defaultExpandAll
  804. onSelect={selectOrgData}
  805. treeData={filterOrgTree}
  806. />
  807. </Flex>
  808. ) : (
  809. <Flex justify="center" style={{ marginTop: "16px" }}>
  810. <Spin />
  811. </Flex>
  812. )}
  813. </ProCard>
  814. </Col>
  815. <Col xs={24} sm={18} md={18}>
  816. <ProTable
  817. formRef={formRef}
  818. rowKey="userId"
  819. rowSelection={{
  820. selectedRowKeys,
  821. ...rowSelection,
  822. }}
  823. columns={columns}
  824. request={async (params: any, sorter: any, filter: any) => {
  825. // 表单搜索项会从 params 传入,传递给后端接口。
  826. const data = await getUser(params, sorter, filter);
  827. if (data !== undefined) {
  828. return Promise.resolve({
  829. data: data.rows,
  830. success: true,
  831. total: data.total,
  832. });
  833. }
  834. return Promise.resolve({
  835. data: [],
  836. success: true,
  837. });
  838. }}
  839. pagination={{
  840. defaultPageSize: defaultPageSize,
  841. showQuickJumper: true,
  842. showSizeChanger: true,
  843. onChange: pageChange,
  844. }}
  845. search={
  846. showSearch
  847. ? {
  848. defaultCollapsed: false,
  849. searchText: "搜索",
  850. }
  851. : false
  852. }
  853. dateFormatter="string"
  854. actionRef={actionRef}
  855. toolbar={{
  856. actions: [
  857. <ModalForm
  858. key="addmodal"
  859. title="添加用户"
  860. trigger={
  861. <Button icon={<PlusOutlined />} type="primary">
  862. 新建
  863. </Button>
  864. }
  865. autoFocusFirstInput
  866. modalProps={{
  867. destroyOnHidden: true,
  868. }}
  869. submitTimeout={2000}
  870. onFinish={executeAddUser}
  871. >
  872. <ProForm.Group>
  873. <ProFormText
  874. width="md"
  875. name="nickName"
  876. label="用户昵称"
  877. placeholder="请输入用户昵称"
  878. rules={[{ required: true, message: "请输入用户昵称" }]}
  879. />
  880. <ProFormTreeSelect
  881. width="md"
  882. name="deptId"
  883. label="归属部门"
  884. placeholder="请选择归属部门"
  885. request={async () => {
  886. return orgSelectData;
  887. }}
  888. fieldProps={{
  889. filterTreeNode: true,
  890. showSearch: true,
  891. treeNodeFilterProp: "name",
  892. fieldNames: {
  893. label: "name",
  894. value: "id",
  895. },
  896. }}
  897. />
  898. </ProForm.Group>
  899. <ProForm.Group>
  900. <ProFormText
  901. width="md"
  902. name="phonenumber"
  903. label="手机号码"
  904. placeholder="请输入手机号码"
  905. rules={[
  906. {
  907. pattern: /^1\d{10}$/,
  908. message: "请输入正确的手机号码",
  909. },
  910. ]}
  911. />
  912. <ProFormText
  913. width="md"
  914. name="email"
  915. label="邮箱"
  916. placeholder="请输入邮箱"
  917. rules={[
  918. { type: "email", message: "请输入正确的邮箱地址" },
  919. ]}
  920. />
  921. </ProForm.Group>
  922. <ProForm.Group>
  923. <ProFormText
  924. width="md"
  925. name="userName"
  926. label="用户名称"
  927. placeholder="请输入用户名称"
  928. rules={[{ required: true, message: "请输入用户名称" }]}
  929. />
  930. <ProFormText.Password
  931. width="md"
  932. name="password"
  933. label="用户密码"
  934. initialValue={defaultPassword}
  935. placeholder="请输入用户密码"
  936. />
  937. </ProForm.Group>
  938. <ProForm.Group>
  939. <ProFormSelect
  940. width="md"
  941. name="sex"
  942. label="用户性别"
  943. request={querySexType}
  944. fieldProps={{
  945. fieldNames: {
  946. label: "dictLabel",
  947. value: "dictValue",
  948. },
  949. }}
  950. />
  951. <ProFormRadio.Group
  952. name="status"
  953. width="sm"
  954. label="状态"
  955. initialValue="0"
  956. options={[
  957. {
  958. label: "正常",
  959. value: "0",
  960. },
  961. {
  962. label: "停用",
  963. value: "1",
  964. },
  965. ]}
  966. />
  967. </ProForm.Group>
  968. <ProForm.Group>
  969. <ProFormSelect
  970. width="md"
  971. name="postIds"
  972. label="岗位"
  973. fieldProps={{
  974. mode: "multiple",
  975. }}
  976. valueEnum={positionValue}
  977. />
  978. <ProFormSelect
  979. width="md"
  980. name="roleIds"
  981. label="角色"
  982. fieldProps={{
  983. mode: "multiple",
  984. }}
  985. valueEnum={roleValue}
  986. />
  987. </ProForm.Group>
  988. <ProFormTextArea
  989. name="remark"
  990. width={688}
  991. label="备注"
  992. placeholder="请输入内容"
  993. />
  994. </ModalForm>,
  995. <ModalForm
  996. key="modifymodal"
  997. title="修改用户"
  998. formRef={modifyFormRef}
  999. trigger={
  1000. <Button
  1001. icon={<FontAwesomeIcon icon={faPenToSquare} />}
  1002. disabled={!rowCanModify}
  1003. onClick={() => showRowModifyModal()}
  1004. >
  1005. 修改
  1006. </Button>
  1007. }
  1008. open={showModifyUserModal}
  1009. autoFocusFirstInput
  1010. modalProps={{
  1011. destroyOnHidden: true,
  1012. onCancel: () => {
  1013. setShowModifyUserModal(false);
  1014. },
  1015. }}
  1016. submitTimeout={2000}
  1017. onFinish={executeModifyUser}
  1018. >
  1019. <ProForm.Group>
  1020. <ProFormText
  1021. width="md"
  1022. name="nickName"
  1023. label="用户昵称"
  1024. placeholder="请输入用户昵称"
  1025. rules={[{ required: true, message: "请输入用户昵称" }]}
  1026. />
  1027. <ProFormTreeSelect
  1028. width="md"
  1029. name="deptId"
  1030. label="归属部门"
  1031. placeholder="请选择归属部门"
  1032. request={async () => {
  1033. return orgSelectData;
  1034. }}
  1035. fieldProps={{
  1036. filterTreeNode: true,
  1037. showSearch: true,
  1038. treeNodeFilterProp: "label",
  1039. fieldNames: {
  1040. label: "label",
  1041. value: "key",
  1042. },
  1043. }}
  1044. />
  1045. </ProForm.Group>
  1046. <ProForm.Group>
  1047. <ProFormText
  1048. width="md"
  1049. name="phonenumber"
  1050. label="手机号码"
  1051. placeholder="请输入手机号码"
  1052. rules={[
  1053. {
  1054. pattern: /^1\d{10}$/,
  1055. message: "请输入正确的手机号码",
  1056. },
  1057. ]}
  1058. />
  1059. <ProFormText
  1060. width="md"
  1061. name="email"
  1062. label="邮箱"
  1063. placeholder="请输入邮箱"
  1064. rules={[
  1065. { type: "email", message: "请输入正确的邮箱地址" },
  1066. ]}
  1067. />
  1068. </ProForm.Group>
  1069. <ProForm.Group>
  1070. <ProFormSelect
  1071. width="md"
  1072. name="sex"
  1073. label="用户性别"
  1074. request={querySexType}
  1075. fieldProps={{
  1076. fieldNames: {
  1077. label: "dictLabel",
  1078. value: "dictValue",
  1079. },
  1080. }}
  1081. />
  1082. <ProFormRadio.Group
  1083. name="status"
  1084. width="sm"
  1085. label="状态"
  1086. options={[
  1087. {
  1088. label: "正常",
  1089. value: "0",
  1090. },
  1091. {
  1092. label: "停用",
  1093. value: "1",
  1094. },
  1095. ]}
  1096. />
  1097. </ProForm.Group>
  1098. <ProForm.Group>
  1099. <ProFormSelect
  1100. width="md"
  1101. name="postIds"
  1102. label="岗位"
  1103. fieldProps={{
  1104. mode: "multiple",
  1105. }}
  1106. options={modifyPositionValue}
  1107. />
  1108. <ProFormSelect
  1109. width="md"
  1110. name="roleIds"
  1111. label="角色"
  1112. fieldProps={{
  1113. mode: "multiple",
  1114. }}
  1115. options={modifyRoleValue}
  1116. />
  1117. </ProForm.Group>
  1118. <ProFormTextArea
  1119. name="remark"
  1120. width={688}
  1121. label="备注"
  1122. placeholder="请输入内容"
  1123. />
  1124. </ModalForm>,
  1125. <Button
  1126. key="danger"
  1127. danger
  1128. icon={<DeleteOutlined />}
  1129. disabled={!rowCanDelete}
  1130. onClick={() => onClickDeleteRow()}
  1131. >
  1132. 删除
  1133. </Button>,
  1134. <Button
  1135. key="import"
  1136. type="primary"
  1137. icon={<FontAwesomeIcon icon={faUpload} />}
  1138. onClick={onClickImport}
  1139. >
  1140. 导入
  1141. </Button>,
  1142. <Button
  1143. key="export"
  1144. type="primary"
  1145. icon={<FontAwesomeIcon icon={faDownload} />}
  1146. onClick={exportTable}
  1147. >
  1148. 导出
  1149. </Button>,
  1150. ],
  1151. settings: [
  1152. {
  1153. key: "switch",
  1154. icon: showSearch ? (
  1155. <FontAwesomeIcon icon={faToggleOn} />
  1156. ) : (
  1157. <FontAwesomeIcon icon={faToggleOff} />
  1158. ),
  1159. tooltip: showSearch ? "隐藏搜索栏" : "显示搜索栏",
  1160. onClick: (key: string | undefined) => {
  1161. setShowSearch(!showSearch);
  1162. },
  1163. },
  1164. {
  1165. key: "refresh",
  1166. tooltip: "刷新",
  1167. icon: <ReloadOutlined />,
  1168. onClick: (key: string | undefined) => {
  1169. if (actionRef.current) {
  1170. actionRef.current.reload();
  1171. }
  1172. },
  1173. },
  1174. ],
  1175. }}
  1176. />
  1177. </Col>
  1178. </Row>
  1179. <Modal
  1180. title={`修改${attachUserdata["userName"]}密码`}
  1181. open={showModifyUserPwdModal}
  1182. onOk={confirmModifyUserPwd}
  1183. onCancel={cancelModifyUserPwd}
  1184. >
  1185. <Form form={pwdFormRef} onFinish={executeModifyUserPwd}>
  1186. <Form.Item
  1187. label="新密码"
  1188. name="password"
  1189. rules={[{ required: true, message: "请输入新密码" }]}
  1190. >
  1191. <Input.Password />
  1192. </Form.Item>
  1193. </Form>
  1194. </Modal>
  1195. <Modal
  1196. title="用户导入"
  1197. open={showImportModal}
  1198. onOk={executeImport}
  1199. onCancel={cancelImportModal}
  1200. >
  1201. <Flex justify="center">
  1202. <div>
  1203. <Dragger
  1204. name="file"
  1205. accept=".xls,.xlsx"
  1206. listType="text"
  1207. multiple={false}
  1208. fileList={fileList}
  1209. beforeUpload={beforeUpload}
  1210. onChange={handleChange}
  1211. onRemove={removeFile}
  1212. showUploadList={{
  1213. showDownloadIcon: false,
  1214. showRemoveIcon: true,
  1215. removeIcon: <CloseOutlined />,
  1216. }}
  1217. >
  1218. <p className="ant-upload-drag-icon">
  1219. {uploading ? <LoadingOutlined /> : <FileAddOutlined />}
  1220. </p>
  1221. <p className="ant-upload-text">点击此处或拖曳文件到此处上传</p>
  1222. <p className="ant-upload-hint">仅支持 xls、xlsx 格式文件</p>
  1223. </Dragger>
  1224. </div>
  1225. </Flex>
  1226. <Flex justify="center" style={{ marginTop: 30 }}>
  1227. <Typography.Text>
  1228. <Checkbox
  1229. checked={uploadSupport}
  1230. onChange={(e) => {
  1231. setUploadSupport(e.target.checked);
  1232. }}
  1233. >
  1234. 允许更新已有用户的数据
  1235. </Checkbox>
  1236. </Typography.Text>
  1237. </Flex>
  1238. </Modal>
  1239. {/* 删除确认模态框 */}
  1240. <Modal
  1241. title={
  1242. <div style={{ display: 'flex', alignItems: 'center' }}>
  1243. <ExclamationCircleFilled style={{ color: '#faad14', marginRight: 8 }} />
  1244. <span>系统提示</span>
  1245. </div>
  1246. }
  1247. open={deleteModalVisible}
  1248. onOk={executeDeleteRow}
  1249. onCancel={() => {
  1250. setDeleteModalVisible(false);
  1251. setDeleteUserId(null);
  1252. }}
  1253. okText="确认"
  1254. cancelText="取消"
  1255. >
  1256. <p>{`确定删除用户编号为“${deleteUserId}”的数据项?`}</p>
  1257. </Modal>
  1258. {/* 切换状态确认模态框 */}
  1259. <Modal
  1260. title={
  1261. <div style={{ display: 'flex', alignItems: 'center' }}>
  1262. <ExclamationCircleFilled style={{ color: '#faad14', marginRight: 8 }} />
  1263. <span>系统提示</span>
  1264. </div>
  1265. }
  1266. open={statusModalVisible}
  1267. onOk={confirmSwitchStatus}
  1268. onCancel={cancelSwitchStatus}
  1269. okText="确认"
  1270. cancelText="取消"
  1271. >
  1272. <p>{`确认要${statusChecked ? "启用" : "停用"}"${statusRecord?.userName}"用户吗?`}</p>
  1273. </Modal>
  1274. </PageContainer>
  1275. );
  1276. }