diff --git a/mock/asyncRoutes.ts b/mock/asyncRoutes.ts index 080d4e521..a8c86184b 100644 --- a/mock/asyncRoutes.ts +++ b/mock/asyncRoutes.ts @@ -85,7 +85,7 @@ const systemMonitorRouter = { }, { path: "/monitor/operation-logs", - component: "monitor/logs/operation", + component: "monitor/logs/operation/index", name: "OperationLog", meta: { icon: "ri:history-fill", diff --git a/mock/system.ts b/mock/system.ts index 393235a45..6d391b7d0 100644 --- a/mock/system.ts +++ b/mock/system.ts @@ -734,7 +734,7 @@ export default defineFakeRoute([ title: "menus.hsOperationLog", name: "OperationLog", path: "/monitor/operation-logs", - component: "monitor/logs/operation", + component: "monitor/logs/operation/index", rank: null, redirect: "", icon: "ri:history-fill", @@ -1092,5 +1092,51 @@ export default defineFakeRoute([ } }; } + }, + // 操作日志 + { + url: "/operation-logs", + method: "post", + response: ({ body }) => { + let list = [ + { + id: 1, + username: "admin", + ip: faker.internet.ipv4(), + address: "中国河南省信阳市", + system: "macOS", + browser: "Chrome", + status: 1, // 操作状态 1 成功 0 失败 + summary: "菜单管理-添加菜单", // 操作概要 + module: "系统管理", // 所属模块 + operatingTime: new Date() // 操作时间 + }, + { + id: 2, + username: "common", + ip: faker.internet.ipv4(), + address: "中国广东省深圳市", + system: "Windows", + browser: "Firefox", + status: 0, + summary: "列表分页查询", + module: "在线用户", + operatingTime: new Date() + } + ]; + list = list.filter(item => item.module.includes(body?.module)); + list = list.filter(item => + String(item.status).includes(String(body?.status)) + ); + return { + success: true, + data: { + list, + total: list.length, // 总条目数 + pageSize: 10, // 每页显示条目个数 + currentPage: 1 // 当前页数 + } + }; + } } ]); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 64b860d0b..16f4700bd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1369,7 +1369,7 @@ packages: pathe: 1.1.2 picocolors: 1.0.0 source-map-js: 1.0.2 - unplugin: 1.8.0 + unplugin: 1.8.1 vue-i18n: 9.10.1(vue@3.4.21) transitivePeerDependencies: - rollup @@ -3006,7 +3006,7 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.23.0 - caniuse-lite: 1.0.30001593 + caniuse-lite: 1.0.30001594 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.0.0 @@ -3162,7 +3162,7 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001593 + caniuse-lite: 1.0.30001594 electron-to-chromium: 1.4.692 node-releases: 2.0.14 update-browserslist-db: 1.0.13(browserslist@4.23.0) @@ -3254,13 +3254,13 @@ packages: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} dependencies: browserslist: 4.23.0 - caniuse-lite: 1.0.30001593 + caniuse-lite: 1.0.30001594 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 dev: true - /caniuse-lite@1.0.30001593: - resolution: {integrity: sha512-UWM1zlo3cZfkpBysd7AS+z+v007q9G1+fLTUU42rQnY6t2axoogPW/xol6T7juU5EUoOhML4WgBIdG+9yYqAjQ==} + /caniuse-lite@1.0.30001594: + resolution: {integrity: sha512-VblSX6nYqyJVs8DKFMldE2IVCJjZ225LW00ydtUWwh5hk9IfkTOffO6r8gJNsH0qqqeAF8KrbMYA2VEwTlGW5g==} /canvas@2.11.2: resolution: {integrity: sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==} @@ -4197,7 +4197,6 @@ packages: /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - requiresBuild: true /emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -4752,7 +4751,6 @@ packages: /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - requiresBuild: true /fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} @@ -5177,7 +5175,6 @@ packages: /inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - requiresBuild: true dependencies: once: 1.4.0 wrappy: 1.0.2 @@ -5188,7 +5185,6 @@ packages: /inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - requiresBuild: true /ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} @@ -6638,7 +6634,6 @@ packages: /once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - requiresBuild: true dependencies: wrappy: 1.0.2 @@ -6736,7 +6731,6 @@ packages: /path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} - requiresBuild: true /path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} @@ -7909,7 +7903,6 @@ packages: /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - requiresBuild: true /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -8230,7 +8223,6 @@ packages: /string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - requiresBuild: true dependencies: safe-buffer: 5.2.1 @@ -8342,7 +8334,7 @@ packages: postcss-scss: 4.0.9(postcss@8.4.35) stylelint: 16.2.1(typescript@5.3.3) stylelint-config-recommended: 14.0.0(stylelint@16.2.1) - stylelint-scss: 6.2.0(stylelint@16.2.1) + stylelint-scss: 6.2.1(stylelint@16.2.1) dev: true /stylelint-config-recommended-vue@1.5.0(postcss-html@1.6.0)(stylelint@16.2.1): @@ -8416,8 +8408,8 @@ packages: stylelint: 16.2.1(typescript@5.3.3) dev: true - /stylelint-scss@6.2.0(stylelint@16.2.1): - resolution: {integrity: sha512-ktYsWKNN+zh4VlpdNMajYCOREwaPI9xZLVue/H5vX4f4v7Kg+ej9Bj0b7fG41J2UboNujZNU9qi0yM/KK3KhOQ==} + /stylelint-scss@6.2.1(stylelint@16.2.1): + resolution: {integrity: sha512-ZoGLbVb1keZYRVGQlhB8G6sZOoNqw61whzzzGFWp05N12ErqLFfBv3JPrXiMLZaW98sBS7K/vUQhRnvUj4vwdw==} engines: {node: '>=18.12.0'} peerDependencies: stylelint: ^16.0.2 @@ -8854,7 +8846,7 @@ packages: acorn: 8.11.3 estree-walker: 3.0.3 magic-string: 0.30.8 - unplugin: 1.8.0 + unplugin: 1.8.1 dev: false optional: true @@ -8884,7 +8876,7 @@ packages: pkg-types: 1.0.3 scule: 1.3.0 strip-literal: 1.3.0 - unplugin: 1.8.0 + unplugin: 1.8.1 transitivePeerDependencies: - rollup dev: false @@ -8900,8 +8892,8 @@ packages: engines: {node: '>= 10.0.0'} dev: true - /unplugin@1.8.0: - resolution: {integrity: sha512-yGEQsodWICmgt7asHF7QzqDZYeEP9h14vyd9Lul98UnYf29pLZZLwI09z2QdTjwU/FCkum1SRvsK7cx232X8NA==} + /unplugin@1.8.1: + resolution: {integrity: sha512-NDAvOEnZmeSRRmjfD0FoLzfve2/9lqceO5bR4J/2V72zphnFdq7UYo3fg6F1y1HfZEaSHa+7bZgbEN+z5x8ZDQ==} dependencies: acorn: 8.11.3 chokidar: 3.6.0 @@ -9431,7 +9423,6 @@ packages: /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - requiresBuild: true /write-file-atomic@3.0.3: resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} diff --git a/src/api/system.ts b/src/api/system.ts index c820c3735..9d8062f12 100644 --- a/src/api/system.ts +++ b/src/api/system.ts @@ -58,3 +58,8 @@ export const getOnlineLogsList = (data?: object) => { export const getLoginLogsList = (data?: object) => { return http.request("post", "/login-logs", { data }); }; + +/** 获取系统监控-操作日志列表 */ +export const getOperationLogsList = (data?: object) => { + return http.request("post", "/operation-logs", { data }); +}; diff --git a/src/views/monitor/logs/operation.vue b/src/views/monitor/logs/operation.vue deleted file mode 100644 index 58030ff47..000000000 --- a/src/views/monitor/logs/operation.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/views/monitor/logs/operation/hook.tsx b/src/views/monitor/logs/operation/hook.tsx new file mode 100644 index 000000000..067777996 --- /dev/null +++ b/src/views/monitor/logs/operation/hook.tsx @@ -0,0 +1,174 @@ +import dayjs from "dayjs"; +import { message } from "@/utils/message"; +import { getKeyList } from "@pureadmin/utils"; +import { getOperationLogsList } from "@/api/system"; +import { usePublicHooks } from "@/views/system/hooks"; +import type { PaginationProps } from "@pureadmin/table"; +import { type Ref, reactive, ref, onMounted, toRaw } from "vue"; + +export function useRole(tableRef: Ref) { + const form = reactive({ + module: "", + status: "", + operatingTime: "" + }); + const dataList = ref([]); + const loading = ref(true); + const selectedNum = ref(0); + const { tagStyle } = usePublicHooks(); + + const pagination = reactive({ + total: 0, + pageSize: 10, + currentPage: 1, + background: true + }); + const columns: TableColumnList = [ + { + label: "勾选列", // 如果需要表格多选,此处label必须设置 + type: "selection", + fixed: "left", + reserveSelection: true // 数据刷新后保留选项 + }, + { + label: "序号", + prop: "id", + minWidth: 90 + }, + { + label: "操作人员", + prop: "username", + minWidth: 100 + }, + { + label: "所属模块", + prop: "module", + minWidth: 140 + }, + { + label: "操作概要", + prop: "summary", + minWidth: 140 + }, + { + label: "操作 IP", + prop: "ip", + minWidth: 100 + }, + { + label: "操作地点", + prop: "address", + minWidth: 140 + }, + { + label: "操作系统", + prop: "system", + minWidth: 100 + }, + { + label: "浏览器类型", + prop: "browser", + minWidth: 100 + }, + { + label: "操作状态", + prop: "status", + minWidth: 100, + cellRenderer: ({ row, props }) => ( + + {row.status === 1 ? "成功" : "失败"} + + ) + }, + { + label: "操作时间", + prop: "loginTime", + minWidth: 180, + formatter: ({ loginTime }) => + dayjs(loginTime).format("YYYY-MM-DD HH:mm:ss") + } + ]; + + function handleSizeChange(val: number) { + console.log(`${val} items per page`); + } + + function handleCurrentChange(val: number) { + console.log(`current page: ${val}`); + } + + /** 当CheckBox选择项发生变化时会触发该事件 */ + function handleSelectionChange(val) { + selectedNum.value = val.length; + // 重置表格高度 + tableRef.value.setAdaptive(); + } + + /** 取消选择 */ + function onSelectionCancel() { + selectedNum.value = 0; + // 用于多选表格,清空用户的选择 + tableRef.value.getTableRef().clearSelection(); + } + + /** 批量删除 */ + function onbatchDel() { + // 返回当前选中的行 + const curSelected = tableRef.value.getTableRef().getSelectionRows(); + // 接下来根据实际业务,通过选中行的某项数据,比如下面的id,调用接口进行批量删除 + message(`已删除序号为 ${getKeyList(curSelected, "id")} 的数据`, { + type: "success" + }); + tableRef.value.getTableRef().clearSelection(); + onSearch(); + } + + /** 清空日志 */ + function clearAll() { + // 根据实际业务,调用接口删除所有日志数据 + message("已删除所有日志数据", { + type: "success" + }); + onSearch(); + } + + async function onSearch() { + loading.value = true; + const { data } = await getOperationLogsList(toRaw(form)); + dataList.value = data.list; + pagination.total = data.total; + pagination.pageSize = data.pageSize; + pagination.currentPage = data.currentPage; + + setTimeout(() => { + loading.value = false; + }, 500); + } + + const resetForm = formEl => { + if (!formEl) return; + formEl.resetFields(); + onSearch(); + }; + + onMounted(() => { + onSearch(); + }); + + return { + form, + loading, + columns, + dataList, + pagination, + selectedNum, + onSearch, + clearAll, + resetForm, + onbatchDel, + handleSizeChange, + onSelectionCancel, + handleCurrentChange, + handleSelectionChange + }; +} diff --git a/src/views/monitor/logs/operation/index.vue b/src/views/monitor/logs/operation/index.vue new file mode 100644 index 000000000..1a95dba68 --- /dev/null +++ b/src/views/monitor/logs/operation/index.vue @@ -0,0 +1,165 @@ + + + + +