page.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. "use client";
  2. import { fetchApi, fetchFile } from "@/app/_modules/func";
  3. import {
  4. ClearOutlined,
  5. DeleteOutlined,
  6. EyeOutlined,
  7. ImportOutlined,
  8. ReloadOutlined,
  9. ExclamationCircleFilled,
  10. UnlockOutlined,
  11. } from "@ant-design/icons";
  12. import type {
  13. ProColumns,
  14. ProFormInstance,
  15. ActionType,
  16. } from "@ant-design/pro-components";
  17. import {
  18. PageContainer,
  19. ProDescriptions,
  20. ProTable,
  21. } from "@ant-design/pro-components";
  22. import { Button, Modal, Space, Tag, message } from "antd";
  23. import { useRouter } from "next/navigation";
  24. import {
  25. faCheck,
  26. faToggleOff,
  27. faToggleOn,
  28. faXmark,
  29. faDownload,
  30. } from "@fortawesome/free-solid-svg-icons";
  31. import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
  32. import { useRef, useState } from "react";
  33. export default function OperLog() {
  34. const { push } = useRouter();
  35. //表格列定义
  36. const columns: ProColumns[] = [
  37. {
  38. title: "访问编号",
  39. dataIndex: "infoId",
  40. search: false,
  41. },
  42. {
  43. title: "用户名称",
  44. fieldProps: {
  45. placeholder: "请输入用户名称",
  46. },
  47. dataIndex: "userName",
  48. order: 3,
  49. sorter: true,
  50. },
  51. {
  52. title: "登录地址",
  53. fieldProps: {
  54. placeholder: "请输入登录地址",
  55. },
  56. dataIndex: "ipaddr",
  57. order: 4,
  58. },
  59. {
  60. title: "登录地点",
  61. dataIndex: "loginLocation",
  62. search: false,
  63. },
  64. {
  65. title: "浏览器",
  66. dataIndex: "browser",
  67. search: false,
  68. },
  69. {
  70. title: "操作系统",
  71. dataIndex: "os",
  72. search: false,
  73. },
  74. {
  75. title: "登录状态",
  76. fieldProps: {
  77. placeholder: "请选择登录状态",
  78. },
  79. dataIndex: "status",
  80. valueType: "select",
  81. order: 2,
  82. render: (_, record) => {
  83. return (
  84. <Space>
  85. <Tag
  86. color={record.status == 0 ? "green" : "red"}
  87. icon={
  88. record.status == 0 ? (
  89. <FontAwesomeIcon icon={faCheck} />
  90. ) : (
  91. <FontAwesomeIcon icon={faXmark} />
  92. )
  93. }
  94. >
  95. {_}
  96. </Tag>
  97. </Space>
  98. );
  99. },
  100. valueEnum: {
  101. 0: {
  102. text: "成功",
  103. status: "0",
  104. },
  105. 1: {
  106. text: "失败",
  107. status: "1",
  108. },
  109. },
  110. },
  111. {
  112. title: "操作信息",
  113. dataIndex: "msg",
  114. search: false,
  115. },
  116. {
  117. title: "登录时间",
  118. dataIndex: "loginTime",
  119. valueType: "dateTime",
  120. search: false,
  121. sorter: true,
  122. },
  123. {
  124. title: "操作时间",
  125. fieldProps: {
  126. placeholder: ["开始日期", "结束日期"],
  127. },
  128. dataIndex: "loginTimeRange",
  129. valueType: "dateRange",
  130. hideInTable: true,
  131. order: 1,
  132. search: {
  133. transform: (value) => {
  134. return {
  135. "params[beginTime]": `${value[0]} 00:00:00`,
  136. "params[endTime]": `${value[1]} 23:59:59`,
  137. };
  138. },
  139. },
  140. },
  141. ];
  142. //查询日志数据
  143. const getLog = async (params: any, sorter: any, filter: any) => {
  144. const searchParams = {
  145. pageNum: params.current,
  146. ...params,
  147. };
  148. delete searchParams.current;
  149. const queryParams = new URLSearchParams(searchParams);
  150. Object.keys(sorter).forEach((key) => {
  151. queryParams.append("orderByColumn", key);
  152. if (sorter[key] === "ascend") {
  153. queryParams.append("isAsc", "ascending");
  154. } else {
  155. queryParams.append("isAsc", "descending");
  156. }
  157. });
  158. const body = await fetchApi(
  159. `/api/monitor/logininfor/list?${queryParams}`,
  160. push
  161. );
  162. console.log("data:", body);
  163. return body;
  164. };
  165. //删除按钮是否可用,选中行时才可用
  166. const [rowCanDelete, setRowCanDelete] = useState(false);
  167. //选中行操作
  168. const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
  169. const [selectedRow, setSelectedRow] = useState(undefined as any);
  170. //解锁按钮是否可用
  171. const [rowCanUnlock, setRowCanUnlock] = useState(false);
  172. const rowSelection = {
  173. onChange: (newSelectedRowKeys: React.Key[], selectedRows: any[]) => {
  174. setSelectedRowKeys(newSelectedRowKeys);
  175. setRowCanDelete(newSelectedRowKeys && newSelectedRowKeys.length > 0);
  176. if (newSelectedRowKeys && newSelectedRowKeys.length == 1) {
  177. console.log("row:", selectedRows[0]);
  178. setSelectedRow(selectedRows[0]);
  179. setRowCanUnlock(true);
  180. } else {
  181. setRowCanUnlock(false);
  182. setSelectedRow(undefined);
  183. }
  184. },
  185. };
  186. //点击删除按钮
  187. const onClickDeleteRow = () => {
  188. Modal.confirm({
  189. title: "系统提示",
  190. icon: <ExclamationCircleFilled />,
  191. content: `确定删除访问编号为“${selectedRowKeys.join(",")}”的数据项?`,
  192. onOk() {
  193. executeDeleteRow();
  194. },
  195. onCancel() {},
  196. });
  197. };
  198. //点击清空按钮
  199. const onClickClear = () => {
  200. Modal.confirm({
  201. title: "系统提示",
  202. icon: <ExclamationCircleFilled />,
  203. content: "是否确认清空所有操作日志数据项?",
  204. onOk() {
  205. executeClear();
  206. },
  207. onCancel() {},
  208. });
  209. };
  210. //点击解锁按钮
  211. const onClickUnlock = () => {
  212. console.log("row:", selectedRow);
  213. Modal.confirm({
  214. title: "系统提示",
  215. icon: <ExclamationCircleFilled />,
  216. content: `确定解锁用户"${selectedRow.userName}"数据项?`,
  217. onOk() {
  218. executeUnlock(selectedRow.userName);
  219. },
  220. onCancel() {},
  221. });
  222. };
  223. //确定删除选中的日志数据
  224. const executeDeleteRow = async () => {
  225. const body = await fetchApi(
  226. `/api/monitor/logininfor/${selectedRowKeys.join(",")}`,
  227. push,
  228. {
  229. method: "DELETE",
  230. }
  231. );
  232. if (body !== undefined) {
  233. if (body.code == 200) {
  234. message.success("删除成功");
  235. //删除按钮变回不可点击
  236. setRowCanDelete(false);
  237. //选中行数据重置为空
  238. setSelectedRowKeys([]);
  239. //刷新列表
  240. if (actionRef.current) {
  241. actionRef.current.reload();
  242. }
  243. } else {
  244. message.error(body.msg);
  245. }
  246. }
  247. };
  248. //确定清空日志数据
  249. const executeClear = async () => {
  250. const body = await fetchApi("/api/monitor/logininfor/clean", push, {
  251. method: "DELETE",
  252. });
  253. if (body !== undefined) {
  254. if (body.code == 200) {
  255. message.success("清空成功");
  256. //选中行数据重置为空
  257. setSelectedRowKeys([]);
  258. //刷新列表
  259. if (actionRef.current) {
  260. actionRef.current.reload();
  261. }
  262. } else {
  263. message.error(body.msg);
  264. }
  265. }
  266. };
  267. //确定解锁用户
  268. const executeUnlock = async (userName: string) => {
  269. const body = await fetchApi(
  270. `/api/monitor/logininfor/unlock/${userName}`,
  271. push
  272. );
  273. if (body !== undefined) {
  274. if (body.code == 200) {
  275. message.success("解锁成功");
  276. //刷新列表
  277. if (actionRef.current) {
  278. actionRef.current.reload();
  279. }
  280. } else {
  281. message.error(body.msg);
  282. }
  283. }
  284. };
  285. //搜索栏显示状态
  286. const [showSearch, setShowSearch] = useState(true);
  287. //action对象引用
  288. const actionRef = useRef<ActionType>();
  289. //表单对象引用
  290. const formRef = useRef<ProFormInstance>();
  291. //当前页数和每页条数
  292. const [page, setPage] = useState(1);
  293. const defaultPageSize = 10;
  294. const [pageSize, setPageSize] = useState(defaultPageSize);
  295. const pageChange = (page: number, pageSize: number) => {
  296. setPage(page);
  297. setPageSize(pageSize);
  298. };
  299. //导出日志文件
  300. const exportTable = async () => {
  301. if (formRef.current) {
  302. const formData = new FormData();
  303. const data = {
  304. pageNum: page,
  305. pageSize: pageSize,
  306. ...formRef.current.getFieldsValue(),
  307. };
  308. Object.keys(data).forEach((key) => {
  309. if (data[key] !== undefined) {
  310. formData.append(key, data[key]);
  311. }
  312. });
  313. await fetchFile(
  314. "/api/monitor/logininfor/export",
  315. push,
  316. {
  317. method: "POST",
  318. body: formData,
  319. },
  320. `logininfor_${new Date().getTime()}.xlsx`
  321. );
  322. }
  323. };
  324. return (
  325. <PageContainer title={false}>
  326. <ProTable
  327. formRef={formRef}
  328. rowKey="infoId"
  329. rowSelection={{
  330. selectedRowKeys,
  331. ...rowSelection,
  332. }}
  333. columns={columns}
  334. request={async (params: any, sorter: any, filter: any) => {
  335. // 表单搜索项会从 params 传入,传递给后端接口。
  336. const data = await getLog(params, sorter, filter);
  337. if (data !== undefined) {
  338. return Promise.resolve({
  339. data: data.rows,
  340. success: true,
  341. total: data.total,
  342. });
  343. }
  344. return Promise.resolve({
  345. data: [],
  346. success: true,
  347. });
  348. }}
  349. pagination={{
  350. defaultPageSize: defaultPageSize,
  351. showQuickJumper: true,
  352. showSizeChanger: true,
  353. onChange: pageChange,
  354. }}
  355. search={
  356. showSearch
  357. ? {
  358. defaultCollapsed: false,
  359. searchText: "搜索",
  360. }
  361. : false
  362. }
  363. dateFormatter="string"
  364. actionRef={actionRef}
  365. toolbar={{
  366. actions: [
  367. <Button
  368. key="danger"
  369. danger
  370. icon={<DeleteOutlined />}
  371. disabled={!rowCanDelete}
  372. onClick={onClickDeleteRow}
  373. >
  374. 删除
  375. </Button>,
  376. <Button
  377. key="clear"
  378. danger
  379. icon={<ClearOutlined />}
  380. onClick={onClickClear}
  381. >
  382. 清空
  383. </Button>,
  384. <Button
  385. key="unlock"
  386. icon={<UnlockOutlined />}
  387. disabled={!rowCanUnlock}
  388. onClick={onClickUnlock}
  389. >
  390. 解锁
  391. </Button>,
  392. <Button
  393. key="export"
  394. type="primary"
  395. icon={<FontAwesomeIcon icon={faDownload} />}
  396. onClick={exportTable}
  397. >
  398. 导出
  399. </Button>,
  400. ],
  401. settings: [
  402. {
  403. key: "switch",
  404. icon: showSearch ? (
  405. <FontAwesomeIcon icon={faToggleOn} />
  406. ) : (
  407. <FontAwesomeIcon icon={faToggleOff} />
  408. ),
  409. tooltip: showSearch ? "隐藏搜索栏" : "显示搜索栏",
  410. onClick: (key?: string | undefined) => {
  411. setShowSearch(!showSearch);
  412. },
  413. },
  414. {
  415. key: "refresh",
  416. tooltip: "刷新",
  417. icon: <ReloadOutlined />,
  418. onClick: (key?: string | undefined) => {
  419. if (actionRef.current) {
  420. actionRef.current.reload();
  421. }
  422. },
  423. },
  424. ],
  425. }}
  426. />
  427. </PageContainer>
  428. );
  429. }