page.tsx 20 KB

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