mirror of
https://github.com/pure-admin/pure-admin-thin.git
synced 2025-04-25 07:57:18 +08:00
feat: 新增操作日志列表
This commit is contained in:
parent
b262de72fb
commit
61a980a37d
@ -7,6 +7,7 @@
|
|||||||
const include = [
|
const include = [
|
||||||
"qs",
|
"qs",
|
||||||
"mitt",
|
"mitt",
|
||||||
|
"xlsx",
|
||||||
"dayjs",
|
"dayjs",
|
||||||
"axios",
|
"axios",
|
||||||
"pinia",
|
"pinia",
|
||||||
|
@ -55,7 +55,8 @@
|
|||||||
"typeit": "^8.7.1",
|
"typeit": "^8.7.1",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-router": "^4.2.2",
|
"vue-router": "^4.2.2",
|
||||||
"vue-types": "^5.1.0"
|
"vue-types": "^5.1.0",
|
||||||
|
"xlsx": "^0.18.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^17.6.6",
|
"@commitlint/cli": "^17.6.6",
|
||||||
|
89
pnpm-lock.yaml
generated
89
pnpm-lock.yaml
generated
@ -88,6 +88,7 @@ specifiers:
|
|||||||
vue-router: ^4.2.2
|
vue-router: ^4.2.2
|
||||||
vue-tsc: ^1.8.1
|
vue-tsc: ^1.8.1
|
||||||
vue-types: ^5.1.0
|
vue-types: ^5.1.0
|
||||||
|
xlsx: ^0.18.5
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
"@pureadmin/descriptions": 1.1.1_element-plus@2.3.6
|
"@pureadmin/descriptions": 1.1.1_element-plus@2.3.6
|
||||||
@ -117,6 +118,7 @@ dependencies:
|
|||||||
vue: 3.3.4
|
vue: 3.3.4
|
||||||
vue-router: 4.2.2_vue@3.3.4
|
vue-router: 4.2.2_vue@3.3.4
|
||||||
vue-types: 5.1.0_vue@3.3.4
|
vue-types: 5.1.0_vue@3.3.4
|
||||||
|
xlsx: 0.18.5
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
"@commitlint/cli": 17.6.6
|
"@commitlint/cli": 17.6.6
|
||||||
@ -2310,6 +2312,14 @@ packages:
|
|||||||
engines: { node: ">=0.4.0" }
|
engines: { node: ">=0.4.0" }
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
/adler-32/1.3.1:
|
||||||
|
resolution:
|
||||||
|
{
|
||||||
|
integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==
|
||||||
|
}
|
||||||
|
engines: { node: ">=0.8" }
|
||||||
|
dev: false
|
||||||
|
|
||||||
/agent-base/6.0.2:
|
/agent-base/6.0.2:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@ -2732,6 +2742,17 @@ packages:
|
|||||||
integrity: sha512-sdQZOJdmt3GJs1UMNpCCCyeuS2IEGLXnHyAo9yIO5JJDjbjoVRij4M1qep6P6gFpptD1PqIYgzM+gwJbOi92mw==
|
integrity: sha512-sdQZOJdmt3GJs1UMNpCCCyeuS2IEGLXnHyAo9yIO5JJDjbjoVRij4M1qep6P6gFpptD1PqIYgzM+gwJbOi92mw==
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/cfb/1.2.2:
|
||||||
|
resolution:
|
||||||
|
{
|
||||||
|
integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==
|
||||||
|
}
|
||||||
|
engines: { node: ">=0.8" }
|
||||||
|
dependencies:
|
||||||
|
adler-32: 1.3.1
|
||||||
|
crc-32: 1.2.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/chalk/2.4.2:
|
/chalk/2.4.2:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@ -2870,6 +2891,14 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/codepage/1.15.0:
|
||||||
|
resolution:
|
||||||
|
{
|
||||||
|
integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==
|
||||||
|
}
|
||||||
|
engines: { node: ">=0.8" }
|
||||||
|
dev: false
|
||||||
|
|
||||||
/color-convert/1.9.3:
|
/color-convert/1.9.3:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@ -3105,6 +3134,15 @@ packages:
|
|||||||
path-type: 4.0.0
|
path-type: 4.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/crc-32/1.2.2:
|
||||||
|
resolution:
|
||||||
|
{
|
||||||
|
integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==
|
||||||
|
}
|
||||||
|
engines: { node: ">=0.8" }
|
||||||
|
hasBin: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
/create-require/1.1.1:
|
/create-require/1.1.1:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@ -4343,6 +4381,14 @@ packages:
|
|||||||
mime-types: 2.1.35
|
mime-types: 2.1.35
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/frac/1.1.2:
|
||||||
|
resolution:
|
||||||
|
{
|
||||||
|
integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==
|
||||||
|
}
|
||||||
|
engines: { node: ">=0.8" }
|
||||||
|
dev: false
|
||||||
|
|
||||||
/fraction.js/4.2.0:
|
/fraction.js/4.2.0:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@ -7965,6 +8011,16 @@ packages:
|
|||||||
readable-stream: 3.6.2
|
readable-stream: 3.6.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/ssf/0.11.2:
|
||||||
|
resolution:
|
||||||
|
{
|
||||||
|
integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==
|
||||||
|
}
|
||||||
|
engines: { node: ">=0.8" }
|
||||||
|
dependencies:
|
||||||
|
frac: 1.1.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/stable/0.1.8:
|
/stable/0.1.8:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@ -9210,6 +9266,14 @@ packages:
|
|||||||
isexe: 2.0.0
|
isexe: 2.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/wmf/1.0.2:
|
||||||
|
resolution:
|
||||||
|
{
|
||||||
|
integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==
|
||||||
|
}
|
||||||
|
engines: { node: ">=0.8" }
|
||||||
|
dev: false
|
||||||
|
|
||||||
/word-wrap/1.2.3:
|
/word-wrap/1.2.3:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@ -9218,6 +9282,14 @@ packages:
|
|||||||
engines: { node: ">=0.10.0" }
|
engines: { node: ">=0.10.0" }
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/word/0.3.0:
|
||||||
|
resolution:
|
||||||
|
{
|
||||||
|
integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==
|
||||||
|
}
|
||||||
|
engines: { node: ">=0.8" }
|
||||||
|
dev: false
|
||||||
|
|
||||||
/wrap-ansi/6.2.0:
|
/wrap-ansi/6.2.0:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@ -9271,6 +9343,23 @@ packages:
|
|||||||
signal-exit: 4.0.2
|
signal-exit: 4.0.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/xlsx/0.18.5:
|
||||||
|
resolution:
|
||||||
|
{
|
||||||
|
integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==
|
||||||
|
}
|
||||||
|
engines: { node: ">=0.8" }
|
||||||
|
hasBin: true
|
||||||
|
dependencies:
|
||||||
|
adler-32: 1.3.1
|
||||||
|
cfb: 1.2.2
|
||||||
|
codepage: 1.15.0
|
||||||
|
crc-32: 1.2.2
|
||||||
|
ssf: 0.11.2
|
||||||
|
wmf: 1.0.2
|
||||||
|
word: 0.3.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/xml-name-validator/4.0.0:
|
/xml-name-validator/4.0.0:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
|
52
src/api/system/log.ts
Normal file
52
src/api/system/log.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { http } from "@/utils/http";
|
||||||
|
|
||||||
|
export interface OperationLogsQuery extends BasePageQuery {
|
||||||
|
businessType?: string;
|
||||||
|
requestModule?: string;
|
||||||
|
status?: string;
|
||||||
|
username?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OperationLogDTO {
|
||||||
|
businessType?: number;
|
||||||
|
businessTypeStr?: string;
|
||||||
|
calledMethod?: string;
|
||||||
|
deptId?: number;
|
||||||
|
deptName?: string;
|
||||||
|
errorStack?: string;
|
||||||
|
operationId?: number;
|
||||||
|
operationParam?: string;
|
||||||
|
operationResult?: string;
|
||||||
|
operationTime?: Date;
|
||||||
|
operatorIp?: string;
|
||||||
|
operatorLocation?: string;
|
||||||
|
operatorType?: number;
|
||||||
|
operatorTypeStr?: string;
|
||||||
|
requestMethod?: string;
|
||||||
|
requestModule?: string;
|
||||||
|
requestUrl?: string;
|
||||||
|
status?: number;
|
||||||
|
statusStr?: string;
|
||||||
|
userId?: number;
|
||||||
|
username?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取操作日志列表 */
|
||||||
|
export const getOperationLogListApi = (params?: OperationLogsQuery) => {
|
||||||
|
return http.request<ResponseData<PageDTO<OperationLogDTO>>>(
|
||||||
|
"get",
|
||||||
|
"/logs/operationLogs",
|
||||||
|
{
|
||||||
|
params
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteOperationLogApi = (data: Array<number>) => {
|
||||||
|
return http.request<ResponseData<void>>("delete", "/logs/operationLogs", {
|
||||||
|
params: {
|
||||||
|
// 需要将数组转换为字符串 否则Axios会将参数变成 noticeIds[0]:1 noticeIds[1]:2 这种格式,后端接收参数不成功
|
||||||
|
operationIds: data.toString()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@ -9,7 +9,7 @@ import { MotionPlugin } from "@vueuse/motion";
|
|||||||
import { injectResponsiveStorage } from "@/utils/responsive";
|
import { injectResponsiveStorage } from "@/utils/responsive";
|
||||||
|
|
||||||
import Table from "@pureadmin/table";
|
import Table from "@pureadmin/table";
|
||||||
// import PureDescriptions from "@pureadmin/descriptions";
|
import PureDescriptions from "@pureadmin/descriptions";
|
||||||
|
|
||||||
// 引入重置样式
|
// 引入重置样式
|
||||||
import "./style/reset.scss";
|
import "./style/reset.scss";
|
||||||
@ -53,7 +53,7 @@ getServerConfig(app).then(async config => {
|
|||||||
.use(MotionPlugin)
|
.use(MotionPlugin)
|
||||||
.use(ElementPlus)
|
.use(ElementPlus)
|
||||||
// .use(useEcharts);
|
// .use(useEcharts);
|
||||||
.use(Table);
|
.use(Table)
|
||||||
// .use(PureDescriptions);
|
.use(PureDescriptions);
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { PaginationProps } from "@pureadmin/table";
|
import { PaginationProps, TableColumn } from "@pureadmin/table";
|
||||||
import { Sort } from "element-plus";
|
import { Sort } from "element-plus";
|
||||||
|
import { utils, writeFile } from "xlsx";
|
||||||
|
import { message } from "./message";
|
||||||
|
|
||||||
export class CommonUtils {
|
export class CommonUtils {
|
||||||
static getBeginTimeSafely(timeRange: string[]): string {
|
static getBeginTimeSafely(timeRange: string[]): string {
|
||||||
@ -50,6 +52,68 @@ export class CommonUtils {
|
|||||||
baseQuery.orderDirection = sort.order;
|
baseQuery.orderDirection = sort.order;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 适用于BaseQuery中固定的时间参数 beginTime和endTime参数 */
|
||||||
|
static fillTimeRangeParams(baseQuery: any, timeRange: string[]) {
|
||||||
|
if (timeRange == null || timeRange.length == 0 || timeRange === undefined) {
|
||||||
|
baseQuery["beginTime"] = undefined;
|
||||||
|
baseQuery["endTime"] = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (baseQuery == null || baseQuery === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
baseQuery["beginTime"] = CommonUtils.getBeginTimeSafely(timeRange);
|
||||||
|
baseQuery["endTime"] = CommonUtils.getEndTimeSafely(timeRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
static exportExcel(
|
||||||
|
columns: TableColumnList,
|
||||||
|
originalDataList: any[],
|
||||||
|
excelName: string
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
!Array.isArray(columns) ||
|
||||||
|
!Array.isArray(originalDataList) ||
|
||||||
|
typeof excelName !== "string"
|
||||||
|
) {
|
||||||
|
message("参数异常,导出失败", { type: "error" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// columns和dataList为空的话 弹出提示 不执行导出
|
||||||
|
if (columns.length === 0 || originalDataList.length === 0) {
|
||||||
|
message("无法导出空列表", { type: "warning" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const titleList: string[] = [];
|
||||||
|
const dataKeyList: string[] = [];
|
||||||
|
// 把columns里面的label取出来作为excel的列标题,把prop取出来等下从dataList里面根据作为key取对象中的值
|
||||||
|
columns.forEach((column: TableColumn) => {
|
||||||
|
if (column.label && column.prop) {
|
||||||
|
titleList.push(column.label);
|
||||||
|
dataKeyList.push(column.prop as string);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const excelDataList: string[][] = originalDataList.map(item => {
|
||||||
|
const arr = [];
|
||||||
|
dataKeyList.forEach(dataKey => {
|
||||||
|
arr.push(item[dataKey]);
|
||||||
|
});
|
||||||
|
return arr;
|
||||||
|
});
|
||||||
|
|
||||||
|
excelDataList.unshift(titleList);
|
||||||
|
|
||||||
|
const workSheet = utils.aoa_to_sheet(excelDataList);
|
||||||
|
const workBook = utils.book_new();
|
||||||
|
utils.book_append_sheet(workBook, workSheet, excelName);
|
||||||
|
writeFile(workBook, `${excelName}.xlsx`);
|
||||||
|
}
|
||||||
|
|
||||||
// 私有构造函数,防止类被实例化
|
// 私有构造函数,防止类被实例化
|
||||||
private constructor() {}
|
private constructor() {}
|
||||||
}
|
}
|
||||||
|
@ -125,6 +125,8 @@ class PureHttp {
|
|||||||
.catch(() => {
|
.catch(() => {
|
||||||
message("取消重新登录", { type: "info" });
|
message("取消重新登录", { type: "info" });
|
||||||
});
|
});
|
||||||
|
NProgress.done();
|
||||||
|
return Promise.reject(response.data.msg);
|
||||||
} else {
|
} else {
|
||||||
// 其余情况弹出错误提示框
|
// 其余情况弹出错误提示框
|
||||||
message(response.data.msg, { type: "error" });
|
message(response.data.msg, { type: "error" });
|
||||||
|
93
src/views/system/log/operationLog/description.vue
Normal file
93
src/views/system/log/operationLog/description.vue
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useUserStoreHook } from "@/store/modules/user";
|
||||||
|
import { OperationLogDTO } from "../../../../api/system/log";
|
||||||
|
|
||||||
|
/** TODO 有其他方式 来换掉这个props 父子组件传值吗? */
|
||||||
|
const props = defineProps<OperationLogDTO>();
|
||||||
|
|
||||||
|
const operationLogStatusMap =
|
||||||
|
useUserStoreHook().dictionaryMap["sysOperationLog.status"];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-descriptions
|
||||||
|
direction="horizontal"
|
||||||
|
:column="2"
|
||||||
|
:labelStyle="'white-space:nowrap;'"
|
||||||
|
:contentStyle="'word-break:break-all;'"
|
||||||
|
:size="'large'"
|
||||||
|
>
|
||||||
|
<!-- 开头前两列设置宽度 -->
|
||||||
|
<el-descriptions-item label="操作编号:" :width="'25%'">{{
|
||||||
|
props.operationId
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="请求模块:" :width="'25%'">{{
|
||||||
|
props.requestModule
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item :span="2" label="操作类型:">{{
|
||||||
|
props.businessTypeStr
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="操作人:">{{
|
||||||
|
props.username
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="操作人ID:">{{
|
||||||
|
props.userId
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="操作人类型:">{{
|
||||||
|
props.operatorTypeStr
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="操作人部门:">{{
|
||||||
|
props.deptName
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="操作人IP:">{{
|
||||||
|
props.operatorIp
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item :span="2" label="操作人地址:">{{
|
||||||
|
props.operatorLocation
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
|
||||||
|
<el-descriptions-item label="请求链接:">{{
|
||||||
|
props.requestUrl
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="请求方式:">{{
|
||||||
|
props.requestMethod
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item :span="2" label="请求参数:">
|
||||||
|
<!-- 长度可能较长的字符串使用el-text包住 避免超出框 -->
|
||||||
|
<el-text>
|
||||||
|
{{ props.operationParam }}
|
||||||
|
</el-text>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item :span="2" label="调用方法:">
|
||||||
|
<el-text>
|
||||||
|
{{ props.calledMethod }}
|
||||||
|
</el-text>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item :span="2" label="返回结果:">
|
||||||
|
<el-text>
|
||||||
|
{{ props.operationResult }}
|
||||||
|
</el-text>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item :span="2" label="错误详情:">
|
||||||
|
<el-text>
|
||||||
|
{{ props.errorStack }}
|
||||||
|
</el-text>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="状态:"
|
||||||
|
><el-tag
|
||||||
|
:type="operationLogStatusMap[props.status].cssTag"
|
||||||
|
effect="plain"
|
||||||
|
>
|
||||||
|
{{ operationLogStatusMap[props.status].label }}
|
||||||
|
</el-tag></el-descriptions-item
|
||||||
|
>
|
||||||
|
<el-descriptions-item label="操作时间:">{{
|
||||||
|
props.operationTime
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</template>
|
||||||
|
<style>
|
||||||
|
.el-descriptions {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
226
src/views/system/log/operationLog/index.vue
Normal file
226
src/views/system/log/operationLog/index.vue
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { useOperationLogHook } from "./utils/hook";
|
||||||
|
import { PureTableBar } from "@/components/RePureTableBar";
|
||||||
|
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||||
|
|
||||||
|
import Delete from "@iconify-icons/ep/delete";
|
||||||
|
import View from "@iconify-icons/ep/view";
|
||||||
|
import Search from "@iconify-icons/ep/search";
|
||||||
|
import Refresh from "@iconify-icons/ep/refresh";
|
||||||
|
import { useUserStoreHook } from "@/store/modules/user";
|
||||||
|
// 这个导入声明好长 看看如何优化
|
||||||
|
import { CommonUtils } from "../../../../utils/common";
|
||||||
|
|
||||||
|
/** 组件name最好和菜单表中的router_name一致 */
|
||||||
|
defineOptions({
|
||||||
|
name: "SystemOperationLog"
|
||||||
|
});
|
||||||
|
|
||||||
|
const businessTypeList =
|
||||||
|
useUserStoreHook().dictionaryList["sysOperationLog.businessType"];
|
||||||
|
const operationStatusList =
|
||||||
|
useUserStoreHook().dictionaryList["sysOperationLog.status"];
|
||||||
|
const tableRef = ref();
|
||||||
|
|
||||||
|
const searchFormRef = ref();
|
||||||
|
const {
|
||||||
|
searchFormParams,
|
||||||
|
pageLoading,
|
||||||
|
columns,
|
||||||
|
dataList,
|
||||||
|
pagination,
|
||||||
|
timeRange,
|
||||||
|
defaultSort,
|
||||||
|
multipleSelection,
|
||||||
|
onSearch,
|
||||||
|
resetForm,
|
||||||
|
openDialog,
|
||||||
|
getOperationLogList,
|
||||||
|
handleDelete,
|
||||||
|
handleBulkDelete
|
||||||
|
} = useOperationLogHook();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="main">
|
||||||
|
<!-- 搜索栏 -->
|
||||||
|
<el-form
|
||||||
|
ref="searchFormRef"
|
||||||
|
:inline="true"
|
||||||
|
:model="searchFormParams"
|
||||||
|
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]"
|
||||||
|
>
|
||||||
|
<el-form-item label="系统模块:" prop="requestModule">
|
||||||
|
<el-input
|
||||||
|
v-model="searchFormParams.requestModule"
|
||||||
|
placeholder="请输入系统模块"
|
||||||
|
clearable
|
||||||
|
class="!w-[200px]"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="操作类型:" prop="businessType">
|
||||||
|
<el-select
|
||||||
|
v-model="searchFormParams.businessType"
|
||||||
|
placeholder="请选择状态"
|
||||||
|
clearable
|
||||||
|
class="!w-[180px]"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="dict in businessTypeList"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="操作人员:" prop="username">
|
||||||
|
<el-input
|
||||||
|
v-model="searchFormParams.username"
|
||||||
|
placeholder="请输入创建者"
|
||||||
|
clearable
|
||||||
|
class="!w-[180px]"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态:" prop="status">
|
||||||
|
<el-select
|
||||||
|
v-model="searchFormParams.status"
|
||||||
|
placeholder="请选择状态"
|
||||||
|
clearable
|
||||||
|
class="!w-[180px]"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="dict in operationStatusList"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<label class="el-form-item__label is-required font-bold"
|
||||||
|
>操作时间:</label
|
||||||
|
>
|
||||||
|
<!-- TODO 如何消除这个v-model的warning -->
|
||||||
|
<el-date-picker
|
||||||
|
class="!w-[240px]"
|
||||||
|
v-model="timeRange"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
type="daterange"
|
||||||
|
range-separator="-"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:icon="useRenderIcon(Search)"
|
||||||
|
:loading="pageLoading"
|
||||||
|
@click="onSearch"
|
||||||
|
>
|
||||||
|
搜索
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
:icon="useRenderIcon(Refresh)"
|
||||||
|
@click="resetForm(searchFormRef, tableRef)"
|
||||||
|
>
|
||||||
|
重置
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<!-- table bar 包裹 table -->
|
||||||
|
<PureTableBar title="通知列表" :columns="columns" @refresh="onSearch">
|
||||||
|
<!-- 表格操作栏 -->
|
||||||
|
<template #buttons>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
:icon="useRenderIcon(Delete)"
|
||||||
|
@click="handleBulkDelete(tableRef)"
|
||||||
|
>
|
||||||
|
批量删除
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="CommonUtils.exportExcel(columns, dataList, '操作日志列表')"
|
||||||
|
>单页导出</el-button
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="CommonUtils.exportExcel(columns, dataList, '操作日志列表')"
|
||||||
|
>全部导出</el-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
<template v-slot="{ size, dynamicColumns }">
|
||||||
|
<pure-table
|
||||||
|
border
|
||||||
|
ref="tableRef"
|
||||||
|
align-whole="center"
|
||||||
|
showOverflowTooltip
|
||||||
|
table-layout="auto"
|
||||||
|
:loading="pageLoading"
|
||||||
|
:size="size"
|
||||||
|
adaptive
|
||||||
|
:data="dataList"
|
||||||
|
:columns="dynamicColumns"
|
||||||
|
:default-sort="defaultSort"
|
||||||
|
:pagination="pagination"
|
||||||
|
:paginationSmall="size === 'small' ? true : false"
|
||||||
|
:header-cell-style="{
|
||||||
|
background: 'var(--el-table-row-hover-bg-color)',
|
||||||
|
color: 'var(--el-text-color-primary)'
|
||||||
|
}"
|
||||||
|
@page-size-change="getOperationLogList"
|
||||||
|
@page-current-change="getOperationLogList"
|
||||||
|
@sort-change="getOperationLogList"
|
||||||
|
@selection-change="
|
||||||
|
rows => (multipleSelection = rows.map(item => item.operationId))
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template #operation="{ row }">
|
||||||
|
<el-button
|
||||||
|
class="reset-margin"
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
:size="size"
|
||||||
|
:icon="useRenderIcon(View)"
|
||||||
|
@click="openDialog(row)"
|
||||||
|
>
|
||||||
|
详情
|
||||||
|
</el-button>
|
||||||
|
<el-popconfirm
|
||||||
|
:title="`是否确认删除编号为${row.operationId}的这条日志`"
|
||||||
|
@confirm="handleDelete(row)"
|
||||||
|
>
|
||||||
|
<template #reference>
|
||||||
|
<el-button
|
||||||
|
class="reset-margin"
|
||||||
|
link
|
||||||
|
type="danger"
|
||||||
|
:size="size"
|
||||||
|
:icon="useRenderIcon(Delete)"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-popconfirm>
|
||||||
|
</template>
|
||||||
|
</pure-table>
|
||||||
|
</template>
|
||||||
|
</PureTableBar>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
:deep(.el-dropdown-menu__item i) {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-form {
|
||||||
|
:deep(.el-form-item) {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
254
src/views/system/log/operationLog/utils/hook.tsx
Normal file
254
src/views/system/log/operationLog/utils/hook.tsx
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
import dayjs from "dayjs";
|
||||||
|
import descriptionForm from "../description.vue";
|
||||||
|
import { message } from "@/utils/message";
|
||||||
|
import { addDialog, closeDialog } from "@/components/ReDialog";
|
||||||
|
import { ElMessageBox, Sort } from "element-plus";
|
||||||
|
import { OperationLogsQuery, getOperationLogListApi } from "@/api/system/log";
|
||||||
|
import { reactive, ref, onMounted, h, toRaw } from "vue";
|
||||||
|
import { useUserStoreHook } from "@/store/modules/user";
|
||||||
|
import { deleteOperationLogApi } from "@/api/system/log";
|
||||||
|
import { CommonUtils } from "@/utils/common";
|
||||||
|
|
||||||
|
const operationLogStatusMap =
|
||||||
|
useUserStoreHook().dictionaryMap["sysOperationLog.status"];
|
||||||
|
const businessTypeMap =
|
||||||
|
useUserStoreHook().dictionaryMap["sysOperationLog.businessType"];
|
||||||
|
|
||||||
|
export function useOperationLogHook() {
|
||||||
|
const defaultSort: Sort = {
|
||||||
|
prop: "operationTime",
|
||||||
|
order: "descending"
|
||||||
|
};
|
||||||
|
|
||||||
|
const pagination: PaginationProps = {
|
||||||
|
total: 0,
|
||||||
|
pageSize: 10,
|
||||||
|
currentPage: 1,
|
||||||
|
background: true
|
||||||
|
};
|
||||||
|
|
||||||
|
const timeRange = ref([]);
|
||||||
|
|
||||||
|
const searchFormParams = reactive<OperationLogsQuery>({
|
||||||
|
beginTime: undefined,
|
||||||
|
endTime: undefined,
|
||||||
|
businessType: undefined,
|
||||||
|
requestModule: undefined,
|
||||||
|
status: undefined,
|
||||||
|
username: undefined,
|
||||||
|
timeRangeColumn: defaultSort.prop
|
||||||
|
});
|
||||||
|
|
||||||
|
const dataList = ref([]);
|
||||||
|
const pageLoading = ref(true);
|
||||||
|
const multipleSelection = ref([]);
|
||||||
|
|
||||||
|
const columns: TableColumnList = [
|
||||||
|
{
|
||||||
|
type: "selection",
|
||||||
|
align: "left"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "操作编号",
|
||||||
|
prop: "operationId",
|
||||||
|
minWidth: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "业务模块",
|
||||||
|
prop: "requestModule",
|
||||||
|
minWidth: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "操作类型",
|
||||||
|
prop: "businessType",
|
||||||
|
minWidth: 120,
|
||||||
|
cellRenderer: ({ row, props }) => (
|
||||||
|
<el-tag
|
||||||
|
size={props.size}
|
||||||
|
type={businessTypeMap[row.businessType].cssTag}
|
||||||
|
effect="plain"
|
||||||
|
>
|
||||||
|
{businessTypeMap[row.businessType].label}
|
||||||
|
</el-tag>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "请求方式",
|
||||||
|
prop: "requestMethod",
|
||||||
|
minWidth: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "操作人员",
|
||||||
|
prop: "username",
|
||||||
|
minWidth: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "登录地址",
|
||||||
|
prop: "operatorIp",
|
||||||
|
minWidth: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "状态",
|
||||||
|
prop: "status",
|
||||||
|
minWidth: 120,
|
||||||
|
cellRenderer: ({ row, props }) => (
|
||||||
|
<el-tag
|
||||||
|
size={props.size}
|
||||||
|
type={operationLogStatusMap[row.status].cssTag}
|
||||||
|
effect="plain"
|
||||||
|
>
|
||||||
|
{operationLogStatusMap[row.status].label}
|
||||||
|
</el-tag>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "状态名",
|
||||||
|
prop: "statusStr",
|
||||||
|
minWidth: 120,
|
||||||
|
hide: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "操作时间",
|
||||||
|
minWidth: 160,
|
||||||
|
prop: "operationTime",
|
||||||
|
sortable: "custom",
|
||||||
|
formatter: ({ operationTime }) =>
|
||||||
|
dayjs(operationTime).format("YYYY-MM-DD HH:mm:ss")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "操作",
|
||||||
|
fixed: "right",
|
||||||
|
width: 140,
|
||||||
|
slot: "operation"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
async function onSearch() {
|
||||||
|
// 点击搜索的时候 需要重置分页
|
||||||
|
pagination.currentPage = 1;
|
||||||
|
getOperationLogList();
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetForm(formEl, tableRef) {
|
||||||
|
if (!formEl) return;
|
||||||
|
// 清空查询参数
|
||||||
|
formEl.resetFields();
|
||||||
|
// 清空排序
|
||||||
|
searchFormParams.orderColumn = undefined;
|
||||||
|
searchFormParams.orderDirection = undefined;
|
||||||
|
// 清空时间查询 TODO 这块有点繁琐 有可以优化的地方吗?
|
||||||
|
// Form组件的resetFields方法无法清除datepicker里面的数据。
|
||||||
|
timeRange.value = [];
|
||||||
|
searchFormParams.beginTime = undefined;
|
||||||
|
searchFormParams.endTime = undefined;
|
||||||
|
tableRef.getTableRef().clearSort();
|
||||||
|
// 重置分页并查询
|
||||||
|
onSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getOperationLogList(sort: Sort = defaultSort) {
|
||||||
|
pageLoading.value = true;
|
||||||
|
if (sort != null) {
|
||||||
|
CommonUtils.fillSortParams(searchFormParams, sort);
|
||||||
|
}
|
||||||
|
CommonUtils.fillPaginationParams(searchFormParams, pagination);
|
||||||
|
CommonUtils.fillTimeRangeParams(searchFormParams, timeRange.value);
|
||||||
|
|
||||||
|
const { data } = await getOperationLogListApi(toRaw(searchFormParams));
|
||||||
|
dataList.value = data.rows;
|
||||||
|
pagination.total = data.total;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
pageLoading.value = false;
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDelete(row) {
|
||||||
|
await deleteOperationLogApi([row.operationId]).then(() => {
|
||||||
|
message(`您删除了操作编号为${row.operationId}的这条数据`, {
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
// 刷新列表
|
||||||
|
getOperationLogList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleBulkDelete(tableRef) {
|
||||||
|
if (multipleSelection.value.length === 0) {
|
||||||
|
message("请选择需要删除的数据", { type: "warning" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
`确认要<strong>删除</strong>编号为<strong style='color:var(--el-color-primary)'>[ ${multipleSelection.value} ]</strong>的日志吗?`,
|
||||||
|
"系统提示",
|
||||||
|
{
|
||||||
|
confirmButtonText: "确定",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: "warning",
|
||||||
|
dangerouslyUseHTMLString: true,
|
||||||
|
draggable: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(async () => {
|
||||||
|
await deleteOperationLogApi(multipleSelection.value).then(() => {
|
||||||
|
message(`您删除了日志编号为[ ${multipleSelection.value} ]的数据`, {
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
// 刷新列表
|
||||||
|
getOperationLogList();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
message("取消删除", {
|
||||||
|
type: "info"
|
||||||
|
});
|
||||||
|
// 清空checkbox选择的数据
|
||||||
|
tableRef.getTableRef().clearSelection();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDialog(row) {
|
||||||
|
addDialog({
|
||||||
|
title: "日志详情",
|
||||||
|
width: "60%",
|
||||||
|
draggable: true,
|
||||||
|
fullscreenIcon: false,
|
||||||
|
closeOnClickModal: true,
|
||||||
|
contentRenderer: () => h(descriptionForm, toRaw(row)),
|
||||||
|
footerButtons: [
|
||||||
|
{
|
||||||
|
label: "关闭",
|
||||||
|
text: true,
|
||||||
|
size: "large",
|
||||||
|
bg: true,
|
||||||
|
btnClick: ({ dialog: { options, index } }) => {
|
||||||
|
closeDialog(options, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getOperationLogList();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
searchFormParams,
|
||||||
|
pageLoading,
|
||||||
|
columns,
|
||||||
|
dataList,
|
||||||
|
pagination,
|
||||||
|
defaultSort,
|
||||||
|
timeRange,
|
||||||
|
multipleSelection,
|
||||||
|
onSearch,
|
||||||
|
// exportExcel,
|
||||||
|
getOperationLogList,
|
||||||
|
resetForm,
|
||||||
|
openDialog,
|
||||||
|
handleDelete,
|
||||||
|
handleBulkDelete
|
||||||
|
};
|
||||||
|
}
|
@ -190,7 +190,7 @@ export function useNoticeHook() {
|
|||||||
)
|
)
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
await deleteSystemNoticeApi(multipleSelection.value).then(() => {
|
await deleteSystemNoticeApi(multipleSelection.value).then(() => {
|
||||||
message(`您删除了通知编号为[ ${multipleSelection.value} ]的条数据`, {
|
message(`您删除了通知编号为[ ${multipleSelection.value} ]的数据`, {
|
||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
// 刷新列表
|
// 刷新列表
|
||||||
|
Loading…
x
Reference in New Issue
Block a user