page.tsx 16 KB

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