page.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  1. "use client";
  2. import { fetchApi, fetchFile } from "@/app/_modules/func";
  3. import {
  4. ClearOutlined,
  5. DeleteOutlined,
  6. ExclamationCircleFilled,
  7. EyeOutlined,
  8. ReloadOutlined,
  9. } from "@ant-design/icons";
  10. import type {
  11. ActionType,
  12. ProColumns,
  13. ProFormInstance,
  14. } from "@ant-design/pro-components";
  15. import {
  16. PageContainer,
  17. ProDescriptions,
  18. ProTable,
  19. } from "@ant-design/pro-components";
  20. import { Button, message, Modal, Space, Tag } from "antd";
  21. import { useRouter } from "next/navigation";
  22. import {
  23. faCheck,
  24. faDownload,
  25. faToggleOff,
  26. faToggleOn,
  27. faXmark,
  28. } from "@fortawesome/free-solid-svg-icons";
  29. import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
  30. import { useRef, useState } from "react";
  31. export default function OperLog() {
  32. const { push } = useRouter();
  33. //表格列定义
  34. const columns: ProColumns[] = [
  35. {
  36. title: "日志编号",
  37. dataIndex: "operId",
  38. search: false,
  39. },
  40. {
  41. title: "系统模块",
  42. fieldProps: {
  43. placeholder: "请输入系统模块",
  44. },
  45. dataIndex: "title",
  46. order: 9,
  47. },
  48. {
  49. title: "操作类型",
  50. fieldProps: {
  51. placeholder: "请选择操作类型",
  52. },
  53. dataIndex: "businessType",
  54. order: 7,
  55. valueEnum: {
  56. 1: {
  57. text: "新增",
  58. status: "1",
  59. },
  60. 2: {
  61. text: "修改",
  62. status: "2",
  63. },
  64. 3: {
  65. text: "删除",
  66. status: "3",
  67. },
  68. 4: {
  69. text: "授权",
  70. status: "4",
  71. },
  72. 5: {
  73. text: "导出",
  74. status: "5",
  75. },
  76. 6: {
  77. text: "导入",
  78. status: "6",
  79. },
  80. 7: {
  81. text: "强退",
  82. status: "7",
  83. },
  84. 8: {
  85. text: "生成代码",
  86. status: "8",
  87. },
  88. 9: {
  89. text: "清空数据",
  90. status: "9",
  91. },
  92. 0: {
  93. text: "其他",
  94. status: "0",
  95. },
  96. },
  97. },
  98. {
  99. title: "操作人员",
  100. fieldProps: {
  101. placeholder: "请输入操作人员",
  102. },
  103. dataIndex: "operName",
  104. sorter: true,
  105. order: 8,
  106. },
  107. {
  108. title: "操作地址",
  109. fieldProps: {
  110. placeholder: "请输入操作地址",
  111. },
  112. dataIndex: "operIp",
  113. order: 10,
  114. },
  115. {
  116. title: "操作地点",
  117. dataIndex: "operLocation",
  118. search: false,
  119. },
  120. {
  121. title: "操作状态",
  122. fieldProps: {
  123. placeholder: "请选择操作状态",
  124. },
  125. dataIndex: "status",
  126. valueType: "select",
  127. render: (_, record) => {
  128. return (
  129. <Space>
  130. <Tag
  131. color={record.status == 0 ? "green" : "red"}
  132. icon={
  133. record.status == 0 ? (
  134. <FontAwesomeIcon icon={faCheck} />
  135. ) : (
  136. <FontAwesomeIcon icon={faXmark} />
  137. )
  138. }
  139. >
  140. {_}
  141. </Tag>
  142. </Space>
  143. );
  144. },
  145. valueEnum: {
  146. 0: {
  147. text: "成功",
  148. status: "0",
  149. },
  150. 1: {
  151. text: "失败",
  152. status: "1",
  153. },
  154. },
  155. order: 6,
  156. },
  157. {
  158. title: "操作时间",
  159. dataIndex: "operTime",
  160. valueType: "dateTime",
  161. search: false,
  162. sorter: true,
  163. },
  164. {
  165. title: "操作时间",
  166. fieldProps: {
  167. placeholder: ["开始日期", "结束日期"],
  168. },
  169. dataIndex: "operTimeRange",
  170. valueType: "dateRange",
  171. hideInTable: true,
  172. order: 5,
  173. search: {
  174. transform: (value) => {
  175. return {
  176. "params[beginTime]": `${value[0]} 00:00:00`,
  177. "params[endTime]": `${value[1]} 23:59:59`,
  178. };
  179. },
  180. },
  181. },
  182. {
  183. title: "消耗时间",
  184. dataIndex: "costTime",
  185. sorter: true,
  186. search: false,
  187. render: (_, record) => {
  188. return <span>{_}毫秒</span>;
  189. },
  190. },
  191. {
  192. title: "操作",
  193. key: "option",
  194. search: false,
  195. render: (_, record) => [
  196. <Button
  197. key={record.operId}
  198. type="link"
  199. icon={<EyeOutlined />}
  200. onClick={() => showRowModal(record)}
  201. >
  202. 详情
  203. </Button>,
  204. ],
  205. },
  206. ];
  207. //查询日志数据
  208. const getLog = async (params: any, sorter: any, filter: any) => {
  209. const searchParams = {
  210. pageNum: params.current,
  211. ...params,
  212. };
  213. delete searchParams.current;
  214. const queryParams = new URLSearchParams(searchParams);
  215. Object.keys(sorter).forEach((key) => {
  216. queryParams.append("orderByColumn", key);
  217. if (sorter[key] === "ascend") {
  218. queryParams.append("isAsc", "ascending");
  219. } else {
  220. queryParams.append("isAsc", "descending");
  221. }
  222. });
  223. const body = await fetchApi(
  224. `/api/monitor/operlog/list?${queryParams}`,
  225. push
  226. );
  227. console.log("data:", body);
  228. return body;
  229. };
  230. //删除按钮是否可用,选中行时才可用
  231. const [rowCanDelete, setRowCanDelete] = useState(false);
  232. //选中行操作
  233. const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
  234. const rowSelection = {
  235. onChange: (newSelectedRowKeys: React.Key[]) => {
  236. setSelectedRowKeys(newSelectedRowKeys);
  237. setRowCanDelete(newSelectedRowKeys && newSelectedRowKeys.length > 0);
  238. },
  239. };
  240. //点击删除按钮
  241. const onClickDeleteRow = () => {
  242. Modal.confirm({
  243. title: "系统提示",
  244. icon: <ExclamationCircleFilled />,
  245. content: `确定删除日志编号为“${selectedRowKeys.join(",")}”的数据项?`,
  246. onOk() {
  247. executeDeleteRow();
  248. },
  249. onCancel() {},
  250. });
  251. };
  252. //点击清空按钮
  253. const onClickClear = () => {
  254. Modal.confirm({
  255. title: "系统提示",
  256. icon: <ExclamationCircleFilled />,
  257. content: "是否确认清空所有操作日志数据项?",
  258. onOk() {
  259. executeClear();
  260. },
  261. onCancel() {},
  262. });
  263. };
  264. //确定删除选中的日志数据
  265. const executeDeleteRow = async () => {
  266. const body = await fetchApi(
  267. `/api/monitor/operlog/${selectedRowKeys.join(",")}`,
  268. push,
  269. {
  270. method: "DELETE",
  271. }
  272. );
  273. if (body !== undefined) {
  274. if (body.code == 200) {
  275. message.success("删除成功");
  276. //删除按钮变回不可点击
  277. setRowCanDelete(false);
  278. //选中的数据恢复为空
  279. setSelectedRowKeys([]);
  280. //刷新列表
  281. if (actionRef.current) {
  282. actionRef.current.reload();
  283. }
  284. } else {
  285. message.error(body.msg);
  286. }
  287. }
  288. };
  289. //确定清空日志数据
  290. const executeClear = async () => {
  291. const body = await fetchApi("/api/monitor/operlog/clean", push, {
  292. method: "DELETE",
  293. });
  294. if (body !== undefined) {
  295. if (body.code == 200) {
  296. message.success("清空成功");
  297. //选中的数据恢复为空
  298. setSelectedRowKeys([]);
  299. //刷新列表
  300. if (actionRef.current) {
  301. actionRef.current.reload();
  302. }
  303. } else {
  304. message.error(body.msg);
  305. }
  306. }
  307. };
  308. //控制是否展示行详情模态框
  309. const [isModalOpen, setIsModalOpen] = useState(false);
  310. //关闭行详情展示
  311. function closeRowModal() {
  312. setIsModalOpen(false);
  313. }
  314. const [selectedRow, setSelectedRow] = useState(undefined as any);
  315. //展示行详情
  316. function showRowModal(record: any) {
  317. setIsModalOpen(true);
  318. setSelectedRow(record);
  319. }
  320. //搜索栏显示状态
  321. const [showSearch, setShowSearch] = useState(true);
  322. //action对象引用
  323. const actionRef = useRef<ActionType>();
  324. //表单对象引用
  325. const formRef = useRef<ProFormInstance>();
  326. //当前页数和每页条数
  327. const [page, setPage] = useState(1);
  328. const defaultPageSize = 10;
  329. const [pageSize, setPageSize] = useState(defaultPageSize);
  330. const pageChange = (page: number, pageSize: number) => {
  331. setPage(page);
  332. setPageSize(pageSize);
  333. };
  334. //导出日志文件
  335. const exportTable = async () => {
  336. if (formRef.current) {
  337. const formData = new FormData();
  338. const data = {
  339. pageNum: page,
  340. pageSize: pageSize,
  341. ...formRef.current.getFieldsValue(),
  342. };
  343. Object.keys(data).forEach((key) => {
  344. if (data[key] !== undefined) {
  345. formData.append(key, data[key]);
  346. }
  347. });
  348. await fetchFile(
  349. "/api/monitor/operlog/export",
  350. push,
  351. {
  352. method: "POST",
  353. body: formData,
  354. },
  355. `operlog_${new Date().getTime()}.xlsx`
  356. );
  357. }
  358. };
  359. return (
  360. <PageContainer title={false}>
  361. <ProTable
  362. formRef={formRef}
  363. rowKey="operId"
  364. rowSelection={{
  365. selectedRowKeys,
  366. ...rowSelection,
  367. }}
  368. columns={columns}
  369. request={async (params: any, sorter: any, filter: any) => {
  370. // 表单搜索项会从 params 传入,传递给后端接口。
  371. console.log(params, sorter, filter);
  372. const data = await getLog(params, sorter, filter);
  373. if (data !== undefined) {
  374. return Promise.resolve({
  375. data: data.rows,
  376. success: true,
  377. total: data.total,
  378. });
  379. }
  380. return Promise.resolve({
  381. data: [],
  382. success: true,
  383. });
  384. }}
  385. pagination={{
  386. defaultPageSize: defaultPageSize,
  387. showQuickJumper: true,
  388. showSizeChanger: true,
  389. onChange: pageChange,
  390. }}
  391. search={
  392. showSearch
  393. ? {
  394. defaultCollapsed: false,
  395. searchText: "搜索",
  396. }
  397. : false
  398. }
  399. dateFormatter="string"
  400. actionRef={actionRef}
  401. toolbar={{
  402. actions: [
  403. <Button
  404. key="danger"
  405. danger
  406. icon={<DeleteOutlined />}
  407. disabled={!rowCanDelete}
  408. onClick={onClickDeleteRow}
  409. >
  410. 删除
  411. </Button>,
  412. <Button
  413. key="clear"
  414. danger
  415. icon={<ClearOutlined />}
  416. onClick={onClickClear}
  417. >
  418. 清空
  419. </Button>,
  420. <Button
  421. key="export"
  422. type="primary"
  423. icon={<FontAwesomeIcon icon={faDownload} />}
  424. onClick={exportTable}
  425. >
  426. 导出
  427. </Button>,
  428. ],
  429. settings: [
  430. {
  431. key: "switch",
  432. icon: showSearch ? (
  433. <FontAwesomeIcon icon={faToggleOn} />
  434. ) : (
  435. <FontAwesomeIcon icon={faToggleOff} />
  436. ),
  437. tooltip: showSearch ? "隐藏搜索栏" : "显示搜索栏",
  438. onClick: (key: string | undefined) => {
  439. setShowSearch(!showSearch);
  440. },
  441. },
  442. {
  443. key: "refresh",
  444. tooltip: "刷新",
  445. icon: <ReloadOutlined />,
  446. onClick: (key: string | undefined) => {
  447. if (actionRef.current) {
  448. actionRef.current.reload();
  449. }
  450. },
  451. },
  452. ],
  453. }}
  454. />
  455. {selectedRow !== undefined && (
  456. <Modal
  457. title="操作日志详情"
  458. footer={<Button onClick={closeRowModal}>关闭</Button>}
  459. open={isModalOpen}
  460. onCancel={closeRowModal}
  461. >
  462. <ProDescriptions column={10}>
  463. <ProDescriptions.Item span={3} label="操作模块">
  464. {selectedRow.title} /
  465. </ProDescriptions.Item>
  466. <ProDescriptions.Item
  467. span={2}
  468. valueEnum={{
  469. 1: {
  470. text: "新增",
  471. status: "1",
  472. },
  473. 2: {
  474. text: "修改",
  475. status: "2",
  476. },
  477. 3: {
  478. text: "删除",
  479. status: "3",
  480. },
  481. 4: {
  482. text: "授权",
  483. status: "4",
  484. },
  485. 5: {
  486. text: "导出",
  487. status: "5",
  488. },
  489. 6: {
  490. text: "导入",
  491. status: "6",
  492. },
  493. 7: {
  494. text: "强退",
  495. status: "7",
  496. },
  497. 8: {
  498. text: "生成代码",
  499. status: "8",
  500. },
  501. 9: {
  502. text: "清空数据",
  503. status: "9",
  504. },
  505. 0: {
  506. text: "其他",
  507. status: "0",
  508. },
  509. }}
  510. >
  511. {selectedRow.businessType}
  512. </ProDescriptions.Item>
  513. <ProDescriptions.Item span={5} label="请求地址">
  514. {selectedRow.operUrl}
  515. </ProDescriptions.Item>
  516. </ProDescriptions>
  517. <ProDescriptions column={6}>
  518. <ProDescriptions.Item span={3} label="登录信息">
  519. {selectedRow.operName}/{selectedRow.operIp}/
  520. {selectedRow.operLocation}
  521. </ProDescriptions.Item>
  522. <ProDescriptions.Item span={3} label="请求方式">
  523. {selectedRow.requestMethod}
  524. </ProDescriptions.Item>
  525. <ProDescriptions.Item span={6} label="操作方法">
  526. {selectedRow.method}
  527. </ProDescriptions.Item>
  528. <ProDescriptions.Item span={6} label="请求参数">
  529. {selectedRow.operParam}
  530. </ProDescriptions.Item>
  531. <ProDescriptions.Item span={6} label="返回参数">
  532. {selectedRow.jsonResult}
  533. </ProDescriptions.Item>
  534. </ProDescriptions>
  535. <ProDescriptions column={8}>
  536. <ProDescriptions.Item
  537. span={2}
  538. label="操作状态"
  539. valueEnum={{
  540. 0: {
  541. text: "成功",
  542. status: "0",
  543. },
  544. 1: {
  545. text: "失败",
  546. status: "1",
  547. },
  548. }}
  549. >
  550. {selectedRow.status}
  551. </ProDescriptions.Item>
  552. <ProDescriptions.Item span={2} label="消耗时间">
  553. {selectedRow.costTime}
  554. </ProDescriptions.Item>
  555. <ProDescriptions.Item span={4} label="操作时间">
  556. {selectedRow.operTime}
  557. </ProDescriptions.Item>
  558. </ProDescriptions>
  559. {selectedRow.errorMsg && (
  560. <ProDescriptions>
  561. <ProDescriptions.Item label="异常信息">
  562. {selectedRow.errorMsg}
  563. </ProDescriptions.Item>
  564. </ProDescriptions>
  565. )}
  566. </Modal>
  567. )}
  568. </PageContainer>
  569. );
  570. }