page.tsx 39 KB

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