page.tsx 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  1. "use client";
  2. import {fetchApi} from "@/app/_modules/func";
  3. import {DeleteOutlined, ExclamationCircleFilled, PlusOutlined, ReloadOutlined,} from "@ant-design/icons";
  4. import type {ActionType, ProColumns, ProFormInstance,} from "@ant-design/pro-components";
  5. import {
  6. ModalForm,
  7. PageContainer,
  8. ProForm,
  9. ProFormDigit,
  10. ProFormRadio,
  11. ProFormText,
  12. ProFormTreeSelect,
  13. ProTable,
  14. } from "@ant-design/pro-components";
  15. import {Button, message, Modal, Space, Tag} from "antd";
  16. import {useRouter} from "next/navigation";
  17. import {
  18. faArrowsUpDown,
  19. faCheck,
  20. faPenToSquare,
  21. faToggleOff,
  22. faToggleOn,
  23. faXmark,
  24. } from "@fortawesome/free-solid-svg-icons";
  25. import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
  26. import {useRef, useState} from "react";
  27. //查询表格数据API
  28. const queryAPI = "/api/system/dept/list";
  29. //新建数据API
  30. const newAPI = "/api/system/dept";
  31. //修改数据API
  32. const modifyAPI = "/api/system/dept";
  33. //查询详情数据API
  34. const queryDetailAPI = "/api/system/dept";
  35. //删除API
  36. const deleteAPI = "/api/system/dept";
  37. export default function Dept() {
  38. const { push } = useRouter();
  39. //表格列定义
  40. const columns: ProColumns[] = [
  41. {
  42. title: "部门名称",
  43. fieldProps: {
  44. placeholder: "请输入部门名称",
  45. },
  46. dataIndex: "deptName",
  47. order: 2,
  48. },
  49. {
  50. title: "排序",
  51. dataIndex: "orderNum",
  52. search: false,
  53. },
  54. {
  55. title: "状态",
  56. fieldProps: {
  57. placeholder: "请选择部门状态",
  58. },
  59. dataIndex: "status",
  60. valueType: "select",
  61. render: (_, record) => {
  62. return (
  63. <Space>
  64. <Tag
  65. color={record.status === "0" ? "green" : "red"}
  66. icon={
  67. record.status == 0 ? (
  68. <FontAwesomeIcon icon={faCheck} />
  69. ) : (
  70. <FontAwesomeIcon icon={faXmark} />
  71. )
  72. }
  73. >
  74. {_}
  75. </Tag>
  76. </Space>
  77. );
  78. },
  79. valueEnum: {
  80. 0: {
  81. text: "正常",
  82. status: "0",
  83. },
  84. 1: {
  85. text: "停用",
  86. status: "1",
  87. },
  88. },
  89. order: 1,
  90. },
  91. {
  92. title: "创建时间",
  93. dataIndex: "createTime",
  94. valueType: "dateTime",
  95. search: false,
  96. },
  97. {
  98. title: "操作",
  99. key: "option",
  100. search: false,
  101. render: (_, record) => {
  102. if (record.deptId == 100) {
  103. return [
  104. <Button
  105. key="modifyBtn"
  106. type="link"
  107. icon={<FontAwesomeIcon icon={faPenToSquare} />}
  108. onClick={() => onClickShowRowModifyModal(record)}
  109. >
  110. 修改
  111. </Button>,
  112. <Button
  113. key="newBtn"
  114. type="link"
  115. icon={<PlusOutlined />}
  116. onClick={() => onClickAdd(record)}
  117. >
  118. 新建
  119. </Button>,
  120. ];
  121. } else {
  122. return [
  123. <Button
  124. key="modifyBtn"
  125. type="link"
  126. icon={<FontAwesomeIcon icon={faPenToSquare} />}
  127. onClick={() => onClickShowRowModifyModal(record)}
  128. >
  129. 修改
  130. </Button>,
  131. <Button
  132. key="newBtn"
  133. type="link"
  134. icon={<PlusOutlined />}
  135. onClick={() => onClickAdd(record)}
  136. >
  137. 新建
  138. </Button>,
  139. <Button
  140. key="deleteBtn"
  141. type="link"
  142. danger
  143. icon={<DeleteOutlined />}
  144. onClick={() => onClickDeleteRow(record)}
  145. >
  146. 删除
  147. </Button>,
  148. ];
  149. }
  150. },
  151. },
  152. ];
  153. //0.查询表格数据
  154. //原始的可展开的所有行的 id
  155. const [defaultExpandKeys, setDefaultExpandKeys] = useState<any[]>([]);
  156. //控制行展开的数据
  157. const [expandKeys, setExpandKeys] = useState<any[]>([]);
  158. const queryTableData = async (params: any, sorter: any, filter: any) => {
  159. const searchParams = {
  160. ...params,
  161. };
  162. const queryParams = new URLSearchParams(searchParams);
  163. Object.keys(sorter).forEach((key) => {
  164. queryParams.append("orderByColumn", key);
  165. if (sorter[key] === "ascend") {
  166. queryParams.append("isAsc", "ascending");
  167. } else {
  168. queryParams.append("isAsc", "descending");
  169. }
  170. });
  171. const body = await fetchApi(`${queryAPI}?${queryParams}`, push);
  172. const root = getRoot(body.data);
  173. if (!root) {
  174. return body.data;
  175. }
  176. getChildren(body.data, root);
  177. const dataArray = [root];
  178. const newExpandedKeys: any[] = [];
  179. const render = (treeDatas: any[]) => {
  180. // 获取到所有可展开的父节点
  181. treeDatas.map((item) => {
  182. if (item.children) {
  183. newExpandedKeys.push(item.deptId);
  184. render(item.children);
  185. }
  186. });
  187. return newExpandedKeys;
  188. };
  189. const keys = render(dataArray);
  190. setDefaultExpandKeys(keys);
  191. setExpandKeys(keys);
  192. return dataArray;
  193. };
  194. const getRoot = (data: any[]) => {
  195. for (let index = 0; index < data.length; index++) {
  196. const item = data[index];
  197. if (item.parentId === 0) {
  198. return item;
  199. }
  200. }
  201. };
  202. const getChildren = (data: any[], parentNode: any) => {
  203. for (let index = 0; index < data.length; index++) {
  204. const item = data[index];
  205. if (item.parentId === parentNode.deptId) {
  206. parentNode.children.push(item);
  207. getChildren(data, item);
  208. }
  209. }
  210. if (parentNode.children.length == 0) {
  211. delete parentNode.children;
  212. }
  213. };
  214. //1.新建
  215. const [showAddModal, setShowAddModal] = useState(false);
  216. //新建表单是否带有父节点id
  217. const [rowParentId, setRowParentId] = useState(100);
  218. //点击新建,如果从行点击新建,给定父组织
  219. const onClickAdd = (record?: any) => {
  220. setRowParentId(record.deptId);
  221. setShowAddModal(true);
  222. };
  223. const cancelAddModal = () => {
  224. setShowAddModal(false);
  225. setRowParentId(100);
  226. };
  227. //确定新建数据
  228. const executeAddData = async (values: any) => {
  229. const body = await fetchApi(newAPI, push, {
  230. method: "POST",
  231. headers: {
  232. "Content-Type": "application/json",
  233. },
  234. body: JSON.stringify(values),
  235. });
  236. if (body != undefined) {
  237. if (body.code == 200) {
  238. message.success(body.msg);
  239. if (actionTableRef.current) {
  240. actionTableRef.current.reload();
  241. }
  242. setShowAddModal(false);
  243. return true;
  244. }
  245. message.error(body.msg);
  246. return false;
  247. }
  248. return false;
  249. };
  250. //2.修改
  251. //是否展示修改对话框
  252. const [isShowModifyDataModal, setIsShowModifyDataModal] = useState(false);
  253. //展示修改对话框
  254. const onClickShowRowModifyModal = (record: any) => {
  255. queryRowData(record);
  256. setIsShowModifyDataModal(true);
  257. };
  258. //修改数据表单引用
  259. const modifyFormRef = useRef<ProFormInstance>(null);
  260. //操作当前数据的附加数据
  261. const [operatRowData, setOperateRowData] = useState<{
  262. [key: string]: any;
  263. }>({});
  264. //查询并加载待修改数据的详细信息
  265. const queryRowData = async (record: any) => {
  266. const deptId = record.deptId;
  267. operatRowData["deptId"] = deptId;
  268. operatRowData["ancestors"] = record.ancestors;
  269. setOperateRowData(operatRowData);
  270. if (deptId !== undefined) {
  271. const body = await fetchApi(`${queryDetailAPI}/${deptId}`, push);
  272. if (body !== undefined) {
  273. if (body.code == 200) {
  274. console.log("modi:", modifyFormRef);
  275. modifyFormRef?.current?.setFieldsValue({
  276. //需要加载到修改表单中的数据
  277. parentId: body.data.parentId,
  278. deptName: body.data.deptName,
  279. orderNum: body.data.orderNum,
  280. leader: body.data.leader,
  281. phone: body.data.phone,
  282. email: body.data.email,
  283. status: body.data.status,
  284. });
  285. }
  286. }
  287. }
  288. };
  289. //确认修改数据
  290. const executeModifyData = async (values: any) => {
  291. values["deptId"] = operatRowData["deptId"];
  292. values["ancestors"] = operatRowData["ancestors"];
  293. const body = await fetchApi(modifyAPI, push, {
  294. method: "PUT",
  295. headers: {
  296. "Content-Type": "application/json",
  297. },
  298. body: JSON.stringify(values),
  299. });
  300. if (body !== undefined) {
  301. if (body.code == 200) {
  302. message.success(body.msg);
  303. //刷新列表
  304. if (actionTableRef.current) {
  305. actionTableRef.current.reload();
  306. }
  307. setIsShowModifyDataModal(false);
  308. return true;
  309. }
  310. message.error(body.msg);
  311. return false;
  312. }
  313. };
  314. //3.展开/折叠
  315. //点击展开/折叠按钮
  316. const onClickExpandRow = () => {
  317. if (expandKeys.length > 0) {
  318. setExpandKeys([]);
  319. } else {
  320. setExpandKeys(defaultExpandKeys);
  321. }
  322. };
  323. //处理行的展开/折叠逻辑
  324. const handleExpand = (expanded: boolean, record: any) => {
  325. let keys = [...expandKeys];
  326. if (expanded) {
  327. keys.push(record.deptId);
  328. } else {
  329. keys = keys.filter((key: number) => key !== record.deptId);
  330. }
  331. setExpandKeys(keys);
  332. };
  333. //4.导出
  334. //5.选择行
  335. //搜索栏显示状态
  336. const [showSearch, setShowSearch] = useState(true);
  337. //action对象引用
  338. const actionTableRef = useRef<ActionType>(null);
  339. //搜索表单对象引用
  340. const searchTableFormRef = useRef<ProFormInstance>(null!);
  341. const getDeptList = async () => {
  342. const body = await fetchApi(queryAPI, push);
  343. if (body !== undefined) {
  344. const root = getRoot(body.data);
  345. if (!root) {
  346. return body.data;
  347. }
  348. getChildren(body.data, root);
  349. return [root];
  350. }
  351. };
  352. //点击删除按钮
  353. const onClickDeleteRow = (record: any) => {
  354. Modal.confirm({
  355. title: "系统提示",
  356. icon: <ExclamationCircleFilled />,
  357. content: `确定删除部门名称为“${record.deptName}”的数据项?`,
  358. onOk() {
  359. executeDeleteRow(record.deptId);
  360. },
  361. onCancel() {},
  362. });
  363. };
  364. //确定删除选中的部门
  365. const executeDeleteRow = async (roleId: any) => {
  366. const body = await fetchApi(`${deleteAPI}/${roleId}`, push, {
  367. method: "DELETE",
  368. });
  369. if (body !== undefined) {
  370. if (body.code == 200) {
  371. message.success("删除成功");
  372. //刷新列表
  373. if (actionTableRef.current) {
  374. actionTableRef.current.reload();
  375. }
  376. } else {
  377. message.error(body.msg);
  378. }
  379. }
  380. };
  381. return (
  382. <PageContainer title={false}>
  383. <ProTable
  384. formRef={searchTableFormRef}
  385. rowKey="deptId"
  386. columns={columns}
  387. expandable={{
  388. expandedRowKeys: expandKeys,
  389. onExpand: handleExpand,
  390. }}
  391. request={async (params: any, sorter: any, filter: any) => {
  392. // 表单搜索项会从 params 传入,传递给后端接口。
  393. const data = await queryTableData(params, sorter, filter);
  394. if (data !== undefined) {
  395. return Promise.resolve({
  396. data: data,
  397. success: true,
  398. total: data.length,
  399. });
  400. }
  401. return Promise.resolve({
  402. data: [],
  403. success: true,
  404. });
  405. }}
  406. pagination={false}
  407. search={
  408. showSearch
  409. ? {
  410. defaultCollapsed: false,
  411. searchText: "搜索",
  412. }
  413. : false
  414. }
  415. dateFormatter="string"
  416. actionRef={actionTableRef}
  417. toolbar={{
  418. actions: [
  419. <ModalForm
  420. key="addmodal"
  421. title="添加部门"
  422. open={showAddModal}
  423. trigger={
  424. <Button icon={<PlusOutlined />} type="primary">
  425. 新建
  426. </Button>
  427. }
  428. autoFocusFirstInput
  429. modalProps={{
  430. destroyOnHidden: true,
  431. onCancel: () => {
  432. cancelAddModal();
  433. },
  434. }}
  435. submitTimeout={2000}
  436. onFinish={executeAddData}
  437. >
  438. <ProForm.Group>
  439. <ProFormTreeSelect
  440. width="md"
  441. name="parentId"
  442. initialValue={rowParentId}
  443. label="上级部门"
  444. placeholder="请选择上级部门"
  445. rules={[{ required: true, message: "请选择上级部门" }]}
  446. request={getDeptList}
  447. fieldProps={{
  448. filterTreeNode: true,
  449. showSearch: true,
  450. treeNodeFilterProp: "label",
  451. fieldNames: {
  452. label: "deptName",
  453. value: "deptId",
  454. },
  455. }}
  456. />
  457. <ProFormText
  458. width="md"
  459. name="deptName"
  460. label="部门名称"
  461. placeholder="请输入部门名称"
  462. rules={[{ required: true, message: "请输入部门名称" }]}
  463. />
  464. </ProForm.Group>
  465. <ProForm.Group>
  466. <ProFormDigit
  467. fieldProps={{ precision: 0 }}
  468. width="md"
  469. name="orderNum"
  470. initialValue="0"
  471. label="排序"
  472. placeholder="请输入排序"
  473. rules={[{ required: true, message: "请输入排序" }]}
  474. />
  475. <ProFormRadio.Group
  476. name="status"
  477. width="sm"
  478. label="状态"
  479. initialValue="0"
  480. options={[
  481. {
  482. label: "正常",
  483. value: "0",
  484. },
  485. {
  486. label: "停用",
  487. value: "1",
  488. },
  489. ]}
  490. />
  491. </ProForm.Group>
  492. <ProForm.Group>
  493. <ProFormText
  494. width="md"
  495. name="leader"
  496. label="负责人"
  497. placeholder="请输入负责人"
  498. />
  499. <ProFormText
  500. width="md"
  501. name="phone"
  502. label="联系电话"
  503. placeholder="请输入联系电话"
  504. rules={[
  505. {
  506. pattern: /^1\d{10}$/,
  507. message: "请输入正确的手机号码",
  508. },
  509. ]}
  510. />
  511. </ProForm.Group>
  512. <ProForm.Group>
  513. <ProFormText
  514. width="md"
  515. name="email"
  516. label="联系邮箱"
  517. placeholder="请输入联系邮箱"
  518. rules={[{ type: "email", message: "请输入正确的邮箱地址" }]}
  519. />
  520. </ProForm.Group>
  521. </ModalForm>,
  522. <Button
  523. key="expand"
  524. icon={<FontAwesomeIcon icon={faArrowsUpDown} />}
  525. onClick={() => onClickExpandRow()}
  526. >
  527. 折叠/展开
  528. </Button>,
  529. ],
  530. settings: [
  531. {
  532. key: "switch",
  533. icon: showSearch ? (
  534. <FontAwesomeIcon icon={faToggleOn} />
  535. ) : (
  536. <FontAwesomeIcon icon={faToggleOff} />
  537. ),
  538. tooltip: showSearch ? "隐藏搜索栏" : "显示搜索栏",
  539. onClick: (key: string | undefined) => {
  540. setShowSearch(!showSearch);
  541. },
  542. },
  543. {
  544. key: "refresh",
  545. tooltip: "刷新",
  546. icon: <ReloadOutlined />,
  547. onClick: (key: string | undefined) => {
  548. if (actionTableRef.current) {
  549. actionTableRef.current.reload();
  550. }
  551. },
  552. },
  553. ],
  554. }}
  555. />
  556. <ModalForm
  557. key="modifymodal"
  558. title="修改部门"
  559. formRef={modifyFormRef}
  560. open={isShowModifyDataModal}
  561. autoFocusFirstInput
  562. modalProps={{
  563. destroyOnHidden: true,
  564. onCancel: () => {
  565. setIsShowModifyDataModal(false);
  566. },
  567. }}
  568. submitTimeout={2000}
  569. onFinish={executeModifyData}
  570. >
  571. <ProForm.Group>
  572. <ProFormTreeSelect
  573. width="md"
  574. name="parentId"
  575. label="上级部门"
  576. placeholder="请选择上级部门"
  577. rules={[{ required: true, message: "请选择上级部门" }]}
  578. request={getDeptList}
  579. fieldProps={{
  580. filterTreeNode: true,
  581. showSearch: true,
  582. treeNodeFilterProp: "label",
  583. fieldNames: {
  584. label: "deptName",
  585. value: "deptId",
  586. },
  587. }}
  588. />
  589. <ProFormText
  590. width="md"
  591. name="deptName"
  592. label="部门名称"
  593. placeholder="请输入部门名称"
  594. rules={[{ required: true, message: "请输入部门名称" }]}
  595. />
  596. </ProForm.Group>
  597. <ProForm.Group>
  598. <ProFormDigit
  599. fieldProps={{ precision: 0 }}
  600. width="md"
  601. name="orderNum"
  602. initialValue="0"
  603. label="排序"
  604. placeholder="请输入排序"
  605. rules={[{ required: true, message: "请输入排序" }]}
  606. />
  607. <ProFormRadio.Group
  608. name="status"
  609. width="sm"
  610. label="状态"
  611. initialValue="0"
  612. options={[
  613. {
  614. label: "正常",
  615. value: "0",
  616. },
  617. {
  618. label: "停用",
  619. value: "1",
  620. },
  621. ]}
  622. />
  623. </ProForm.Group>
  624. <ProForm.Group>
  625. <ProFormText
  626. width="md"
  627. name="leader"
  628. label="负责人"
  629. placeholder="请输入负责人"
  630. />
  631. <ProFormText
  632. width="md"
  633. name="phone"
  634. label="联系电话"
  635. placeholder="请输入联系电话"
  636. rules={[
  637. {
  638. pattern: /^1\d{10}$/,
  639. message: "请输入正确的手机号码",
  640. },
  641. ]}
  642. />
  643. </ProForm.Group>
  644. <ProForm.Group>
  645. <ProFormText
  646. width="md"
  647. name="email"
  648. label="联系邮箱"
  649. placeholder="请输入联系邮箱"
  650. rules={[{ type: "email", message: "请输入正确的邮箱地址" }]}
  651. />
  652. </ProForm.Group>
  653. </ModalForm>
  654. ,
  655. </PageContainer>
  656. );
  657. }