page.tsx 19 KB

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