page.tsx 37 KB

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