page.tsx 37 KB

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