feat: 添加操作日志
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { http } from "@/utils/http";
|
||||
import type { UserLoginLogInfo } from "types/monitor";
|
||||
import type { OperationLogInfo, UserLoginLogInfo } from "types/monitor";
|
||||
import { filterEmptyObject } from "./utils";
|
||||
|
||||
// --------------------------登录日志相关--------------------------------------
|
||||
@@ -28,3 +28,21 @@ export const getUserLoginLogAPI = (params: {
|
||||
export const deleteUserOnlineAPI = (id: string) => {
|
||||
return http.request<null>("delete", `/api/log/logout/${id}`);
|
||||
};
|
||||
|
||||
// ------------------------操作日志相关----------------------------------------
|
||||
|
||||
/**
|
||||
* 获取用户操作日志
|
||||
*/
|
||||
export const getUserOperationsAPI = (params: {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}) => {
|
||||
return http.request<QueryListResult<OperationLogInfo>>(
|
||||
"GET",
|
||||
"/api/log/operation",
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
134
src/views/monitor/operation/components/form.vue
Normal file
134
src/views/monitor/operation/components/form.vue
Normal file
@@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<!-- 操作日志基本信息 -->
|
||||
<el-card class="mb-4">
|
||||
<el-descriptions title="操作日志详情" border>
|
||||
<el-descriptions-item label="操作名称">{{
|
||||
log.operation_name
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="操作类型">{{
|
||||
log.operation_type
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="请求路径">{{
|
||||
log.request_path
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="请求方法">{{
|
||||
log.request_method
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="操作人ID">{{
|
||||
log.operator_id
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="操作人姓名">{{
|
||||
log.operator_name
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="操作人昵称">{{
|
||||
log.operator_nickname
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="部门ID">{{
|
||||
log.department_id
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="部门名称">{{
|
||||
log.department_name
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="操作时间">{{
|
||||
log.operation_time
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="耗时"
|
||||
>{{ log.cost_time }} 秒</el-descriptions-item
|
||||
>
|
||||
<el-descriptions-item label="IP地址">{{
|
||||
log.host
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="地理位置">{{
|
||||
log.location
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="浏览器">{{
|
||||
log.browser
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="操作系统">{{
|
||||
log.os
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="User-Agent">{{
|
||||
log.user_agent
|
||||
}}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
|
||||
<!-- 折叠面板 -->
|
||||
<el-card>
|
||||
<el-collapse v-model="activeCollapse">
|
||||
<!-- 请求参数 -->
|
||||
<el-collapse-item title="请求参数" name="requestParams">
|
||||
<vue-json-pretty :data="parsedRequestParams" />
|
||||
</el-collapse-item>
|
||||
<!-- 响应结果 -->
|
||||
<el-collapse-item title="响应结果" name="responseResult">
|
||||
<vue-json-pretty :data="parsedResponseResult" />
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from "vue";
|
||||
import VueJsonPretty from "vue-json-pretty";
|
||||
import "vue-json-pretty/lib/styles.css";
|
||||
|
||||
// 操作日志数据
|
||||
const log = ref({
|
||||
id: "9d40633c-677b-4adc-9a2e-7676fc6924df",
|
||||
operation_name: "获取用户列表",
|
||||
operation_type: 1,
|
||||
request_path: "/user/list",
|
||||
request_method: "GET",
|
||||
request_params: "{}",
|
||||
response_result:
|
||||
'{"code": 200, "msg": "操作成功", "success": true, "time": "2025-01-29T00:26:23.979927", "data": {"result": [{"id": "8242939b-01af-4e4f-be6a-045ff95b51b6", "create_time": "2025-01-20T21:43:45.800419+08:00", "update_time": "2025-01-27T02:05:34.436735+08:00", "username": "admin", "email": "zhangsan@example.com", "phone": "13888888888", "nickname": "张三", "gender": 0, "status": 1, "department_id": "74aae292-f318-4e07-93b8-3772d9792351"}], "total": 1, "page": 1}}',
|
||||
host: "127.0.0.1",
|
||||
location: "",
|
||||
browser: "Edge 132",
|
||||
os: "Windows 10",
|
||||
user_agent:
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0",
|
||||
operator_id: "8242939b-01af-4e4f-be6a-045ff95b51b6",
|
||||
operator_name: "admin",
|
||||
operator_nickname: "张三",
|
||||
department_id: "74aae292-f318-4e07-93b8-3772d9792351",
|
||||
department_name: "系统管理",
|
||||
status: 1,
|
||||
operation_time: "2025-01-29T00:26:23.982990+08:00",
|
||||
cost_time: 0.7223844528198242
|
||||
});
|
||||
|
||||
// 解析请求参数
|
||||
const parsedRequestParams = computed(() => {
|
||||
try {
|
||||
return JSON.parse(log.value.request_params);
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
});
|
||||
|
||||
// 解析响应结果
|
||||
const parsedResponseResult = computed(() => {
|
||||
try {
|
||||
return JSON.parse(log.value.response_result);
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
});
|
||||
|
||||
// 折叠面板的激活项
|
||||
const activeCollapse = ref(["requestParams", "responseResult"]);
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.el-descriptions {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.el-collapse {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
308
src/views/monitor/operation/hook.tsx
Normal file
308
src/views/monitor/operation/hook.tsx
Normal file
@@ -0,0 +1,308 @@
|
||||
import dayjs from "dayjs";
|
||||
import Detail from "./components/form.vue";
|
||||
import { message } from "@/utils/message";
|
||||
import { addDialog } from "@/components/ReDialog";
|
||||
import { getKeyList } from "@pureadmin/utils";
|
||||
import { getUserOperationsAPI } from "@/api/monitor";
|
||||
import { usePublicHooks } from "@/views/system/hooks";
|
||||
import type { PaginationProps } from "@pureadmin/table";
|
||||
import { type Ref, reactive, ref, onMounted, h } from "vue";
|
||||
import type { OperationLogInfo } from "types/monitor";
|
||||
|
||||
export function useOperation(tableRef: Ref) {
|
||||
const form = reactive({
|
||||
module: "",
|
||||
status: "",
|
||||
operatingTime: ""
|
||||
});
|
||||
const dataList = ref<OperationLogInfo[]>([]);
|
||||
const loading = ref(true);
|
||||
const selectedNum = ref(0);
|
||||
const { tagStyle } = usePublicHooks();
|
||||
|
||||
const pagination = reactive<PaginationProps>({
|
||||
total: 0,
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
background: true
|
||||
});
|
||||
|
||||
const getOperationType = (type: number) => {
|
||||
switch (type) {
|
||||
case 1:
|
||||
return "primary";
|
||||
case 2:
|
||||
return "success";
|
||||
case 3:
|
||||
return "warning";
|
||||
case 4:
|
||||
return "danger";
|
||||
case 5:
|
||||
return "success";
|
||||
case 6:
|
||||
return "primary";
|
||||
case 7:
|
||||
return "success";
|
||||
case 8:
|
||||
return "danger";
|
||||
default:
|
||||
return "info";
|
||||
}
|
||||
};
|
||||
const getOperationName = (type: number) => {
|
||||
switch (type) {
|
||||
case 1:
|
||||
return "查询";
|
||||
case 2:
|
||||
return "新增";
|
||||
case 3:
|
||||
return "修改";
|
||||
case 4:
|
||||
return "删除";
|
||||
case 5:
|
||||
return "授权";
|
||||
case 6:
|
||||
return "导出";
|
||||
case 7:
|
||||
return "导入";
|
||||
case 8:
|
||||
return "强退";
|
||||
default:
|
||||
return "其他";
|
||||
}
|
||||
};
|
||||
const getRequestType = (method: string) => {
|
||||
switch (method) {
|
||||
case "GET":
|
||||
return "primary";
|
||||
case "POST":
|
||||
return "success";
|
||||
case "PUT":
|
||||
return "warning";
|
||||
case "DELETE":
|
||||
return "danger";
|
||||
case "PATCH":
|
||||
return "info";
|
||||
default:
|
||||
return "info";
|
||||
}
|
||||
};
|
||||
const columns: TableColumnList = [
|
||||
{
|
||||
label: "勾选列", // 如果需要表格多选,此处label必须设置
|
||||
type: "selection",
|
||||
fixed: "left",
|
||||
reserveSelection: true // 数据刷新后保留选项
|
||||
},
|
||||
{
|
||||
label: "操作人员账号",
|
||||
prop: "operator_name",
|
||||
minWidth: 100
|
||||
},
|
||||
{
|
||||
label: "操作人员昵称",
|
||||
prop: "operator_nickname",
|
||||
minWidth: 100
|
||||
},
|
||||
{
|
||||
label: "操作名称",
|
||||
prop: "operation_name",
|
||||
minWidth: 100
|
||||
},
|
||||
{
|
||||
label: "操作类型",
|
||||
prop: "operation_type",
|
||||
cellRenderer: ({ row, props }) => (
|
||||
<el-tag size={props.size} type={getOperationType(row.operation_type)}>
|
||||
{getOperationName(row.operation_type)}
|
||||
</el-tag>
|
||||
)
|
||||
},
|
||||
{
|
||||
label: "操作IP",
|
||||
prop: "host"
|
||||
},
|
||||
{
|
||||
label: "操作地点",
|
||||
prop: "location"
|
||||
},
|
||||
{
|
||||
label: "请求方法",
|
||||
prop: "request_method",
|
||||
cellRenderer: ({ row, props }) => (
|
||||
<el-tag size={props.size} type={getRequestType(row.request_method)}>
|
||||
{row.request_method}
|
||||
</el-tag>
|
||||
)
|
||||
},
|
||||
{
|
||||
label: "请求路径",
|
||||
prop: "request_path"
|
||||
},
|
||||
{
|
||||
label: "请求耗时",
|
||||
prop: "cost_time",
|
||||
cellRenderer: ({ row, props }) => (
|
||||
<el-tag
|
||||
size={props.size}
|
||||
type={row.cost_time < 1000 ? "success" : "warning"}
|
||||
effect="plain"
|
||||
>
|
||||
{row.cost_time.toFixed(2)} ms
|
||||
</el-tag>
|
||||
)
|
||||
},
|
||||
{
|
||||
label: "操作系统",
|
||||
prop: "os",
|
||||
hide: true
|
||||
},
|
||||
{
|
||||
label: "浏览器类型",
|
||||
prop: "browser",
|
||||
hide: true
|
||||
},
|
||||
{
|
||||
label: "操作状态",
|
||||
prop: "status",
|
||||
cellRenderer: ({ row, props }) => (
|
||||
<el-tag size={props.size} style={tagStyle.value(row.status)}>
|
||||
{row.status === 1 ? "成功" : "失败"}
|
||||
</el-tag>
|
||||
)
|
||||
},
|
||||
{
|
||||
label: "操作时间",
|
||||
prop: "operation_time",
|
||||
minWidth: 180,
|
||||
formatter: ({ operation_time }) =>
|
||||
dayjs(operation_time).format("YYYY-MM-DD HH:mm:ss")
|
||||
},
|
||||
{
|
||||
label: "操作",
|
||||
fixed: "right",
|
||||
width: 100,
|
||||
slot: "operation"
|
||||
}
|
||||
];
|
||||
|
||||
const handleSizeChange = async (val: number) => {
|
||||
const res = await getUserOperationsAPI({
|
||||
page: pagination.currentPage,
|
||||
pageSize: val
|
||||
});
|
||||
if (res.success) {
|
||||
dataList.value = res.data.result;
|
||||
pagination.total = res.data.total;
|
||||
pagination.currentPage = res.data.page;
|
||||
}
|
||||
};
|
||||
|
||||
const handleCurrentChange = async (val: number) => {
|
||||
const res = await getUserOperationsAPI({
|
||||
page: val,
|
||||
pageSize: pagination.pageSize
|
||||
});
|
||||
if (res.success) {
|
||||
dataList.value = res.data.result;
|
||||
pagination.total = res.data.total;
|
||||
pagination.currentPage = res.data.page;
|
||||
}
|
||||
};
|
||||
|
||||
/** 当CheckBox选择项发生变化时会触发该事件 */
|
||||
const handleSelectionChange = val => {
|
||||
selectedNum.value = val.length;
|
||||
// 重置表格高度
|
||||
tableRef.value.setAdaptive();
|
||||
};
|
||||
|
||||
/** 取消选择 */
|
||||
const onSelectionCancel = () => {
|
||||
selectedNum.value = 0;
|
||||
// 用于多选表格,清空用户的选择
|
||||
tableRef.value.getTableRef().clearSelection();
|
||||
};
|
||||
|
||||
/** 批量删除 */
|
||||
const onbatchDel = () => {
|
||||
// 返回当前选中的行
|
||||
const curSelected = tableRef.value.getTableRef().getSelectionRows();
|
||||
// 接下来根据实际业务,通过选中行的某项数据,比如下面的id,调用接口进行批量删除
|
||||
message(`已删除序号为 ${getKeyList(curSelected, "id")} 的数据`, {
|
||||
type: "success"
|
||||
});
|
||||
tableRef.value.getTableRef().clearSelection();
|
||||
onSearch();
|
||||
};
|
||||
|
||||
/** 清空日志 */
|
||||
const clearAll = async () => {
|
||||
// 根据实际业务,调用接口删除所有日志数据
|
||||
message("已删除所有日志数据", {
|
||||
type: "success"
|
||||
});
|
||||
await onSearch();
|
||||
};
|
||||
|
||||
const onSearch = async () => {
|
||||
loading.value = true;
|
||||
const res = await getUserOperationsAPI({
|
||||
page: pagination.currentPage,
|
||||
pageSize: pagination.pageSize
|
||||
});
|
||||
if (res.success) {
|
||||
dataList.value = res.data.result;
|
||||
pagination.total = res.data.total;
|
||||
pagination.currentPage = res.data.page;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 500);
|
||||
};
|
||||
|
||||
const resetForm = formEl => {
|
||||
if (!formEl) return;
|
||||
formEl.resetFields();
|
||||
onSearch();
|
||||
};
|
||||
|
||||
const onDetailHandle = () => {
|
||||
addDialog({
|
||||
title: `详情`,
|
||||
props: {},
|
||||
width: "40%",
|
||||
draggable: true,
|
||||
fullscreenIcon: true,
|
||||
closeOnClickModal: false,
|
||||
contentRenderer: () => h(Detail, {}),
|
||||
beforeSure: (done, {}) => {
|
||||
done();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await onSearch();
|
||||
});
|
||||
|
||||
return {
|
||||
form,
|
||||
loading,
|
||||
columns,
|
||||
dataList,
|
||||
pagination,
|
||||
selectedNum,
|
||||
onSearch,
|
||||
clearAll,
|
||||
resetForm,
|
||||
onbatchDel,
|
||||
getOperationName,
|
||||
onDetailHandle,
|
||||
handleSizeChange,
|
||||
onSelectionCancel,
|
||||
handleCurrentChange,
|
||||
handleSelectionChange
|
||||
};
|
||||
}
|
||||
320
src/views/monitor/operation/index.vue
Normal file
320
src/views/monitor/operation/index.vue
Normal file
@@ -0,0 +1,320 @@
|
||||
<script setup lang="ts">
|
||||
import dayjs from "dayjs";
|
||||
import { ref, reactive, computed } from "vue";
|
||||
import { useOperation } from "./hook";
|
||||
import { getPickerShortcuts } from "../utils";
|
||||
import { PureTableBar } from "@/components/RePureTableBar";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import VueJsonPretty from "vue-json-pretty";
|
||||
import "vue-json-pretty/lib/styles.css";
|
||||
import View from "@iconify-icons/ep/view";
|
||||
import Delete from "@iconify-icons/ep/delete";
|
||||
import Refresh from "@iconify-icons/ep/refresh";
|
||||
import { OperationLogInfo } from "types/monitor";
|
||||
|
||||
defineOptions({
|
||||
name: "OperationLog"
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const formRef = ref();
|
||||
const tableRef = ref();
|
||||
|
||||
/**
|
||||
* 抽屉状态
|
||||
*/
|
||||
const drawerStatus = ref<boolean>(false);
|
||||
|
||||
// 折叠面板的激活项
|
||||
const activeCollapse = ref(["requestinfo"]);
|
||||
|
||||
const logInfo = reactive<OperationLogInfo>({
|
||||
id: "",
|
||||
operation_name: "",
|
||||
operation_type: 0,
|
||||
request_path: "",
|
||||
request_method: "",
|
||||
request_params: "{}",
|
||||
response_result: "{}",
|
||||
host: "",
|
||||
location: "",
|
||||
browser: "",
|
||||
os: "",
|
||||
user_agent: "",
|
||||
operator_id: "",
|
||||
operator_name: "",
|
||||
operator_nickname: "",
|
||||
department_id: "",
|
||||
department_name: "",
|
||||
status: 1,
|
||||
operation_time: "",
|
||||
cost_time: 0
|
||||
});
|
||||
|
||||
/**
|
||||
* 操作日志详情展开
|
||||
*/
|
||||
const onDetailHandle = (row: OperationLogInfo) => {
|
||||
activeCollapse.value = ["requestinfo"];
|
||||
drawerStatus.value = true;
|
||||
Object.assign(logInfo, row);
|
||||
};
|
||||
|
||||
// 解析请求参数
|
||||
const parsedRequestParams = computed(() => {
|
||||
try {
|
||||
return JSON.parse(logInfo.request_params);
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
});
|
||||
|
||||
// 解析响应结果
|
||||
const parsedResponseResult = computed(() => {
|
||||
try {
|
||||
return JSON.parse(logInfo.response_result);
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
form,
|
||||
loading,
|
||||
columns,
|
||||
dataList,
|
||||
pagination,
|
||||
selectedNum,
|
||||
onSearch,
|
||||
clearAll,
|
||||
resetForm,
|
||||
onbatchDel,
|
||||
getOperationName,
|
||||
handleSizeChange,
|
||||
onSelectionCancel,
|
||||
handleCurrentChange,
|
||||
handleSelectionChange
|
||||
} = useOperation(tableRef);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="main">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:inline="true"
|
||||
:model="form"
|
||||
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto"
|
||||
>
|
||||
<el-form-item label="所属模块" prop="module">
|
||||
<el-input
|
||||
v-model="form.module"
|
||||
placeholder="请输入所属模块"
|
||||
clearable
|
||||
class="!w-[170px]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="操作状态" prop="status">
|
||||
<el-select
|
||||
v-model="form.status"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
class="!w-[150px]"
|
||||
>
|
||||
<el-option label="成功" value="1" />
|
||||
<el-option label="失败" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="操作时间" prop="operatingTime">
|
||||
<el-date-picker
|
||||
v-model="form.operatingTime"
|
||||
:shortcuts="getPickerShortcuts()"
|
||||
type="datetimerange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期时间"
|
||||
end-placeholder="结束日期时间"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="useRenderIcon('ri:search-line')"
|
||||
:loading="loading"
|
||||
@click="onSearch"
|
||||
>
|
||||
{{ t("buttons:Search") }}
|
||||
</el-button>
|
||||
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
|
||||
{{ t("buttons:Reset") }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<PureTableBar title="操作日志" :columns="columns" @refresh="onSearch">
|
||||
<template #buttons>
|
||||
<!-- <el-popconfirm title="确定要删除所有日志数据吗?" @confirm="clearAll">
|
||||
<template #reference>
|
||||
<el-button type="danger" :icon="useRenderIcon(Delete)">
|
||||
清空日志
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popconfirm> -->
|
||||
</template>
|
||||
<template v-slot="{ size, dynamicColumns }">
|
||||
<div
|
||||
v-if="selectedNum > 0"
|
||||
v-motion-fade
|
||||
class="bg-[var(--el-fill-color-light)] w-full h-[46px] mb-2 pl-4 flex items-center"
|
||||
>
|
||||
<div class="flex-auto">
|
||||
<span
|
||||
style="font-size: var(--el-font-size-base)"
|
||||
class="text-[rgba(42,46,54,0.5)] dark:text-[rgba(220,220,242,0.5)]"
|
||||
>
|
||||
已选 {{ selectedNum }} 项
|
||||
</span>
|
||||
<el-button type="primary" text @click="onSelectionCancel">
|
||||
{{ t("buttons:Deselect") }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-popconfirm title="是否确认删除?" @confirm="onbatchDel">
|
||||
<template #reference>
|
||||
<el-button type="danger" text class="mr-1">
|
||||
{{ t("buttons:DeleteInBatches") }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</div>
|
||||
<pure-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
align-whole="center"
|
||||
table-layout="auto"
|
||||
:loading="loading"
|
||||
:size="size"
|
||||
adaptive
|
||||
:adaptiveConfig="{ offsetBottom: 108 }"
|
||||
:data="dataList"
|
||||
:columns="dynamicColumns"
|
||||
:pagination="{ ...pagination, size }"
|
||||
:header-cell-style="{
|
||||
background: 'var(--el-fill-color-light)',
|
||||
color: 'var(--el-text-color-primary)'
|
||||
}"
|
||||
@selection-change="handleSelectionChange"
|
||||
@page-size-change="handleSizeChange"
|
||||
@page-current-change="handleCurrentChange"
|
||||
>
|
||||
<template #operation="{ row }">
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(View)"
|
||||
@click="onDetailHandle(row)"
|
||||
>
|
||||
{{ t("buttons:Details") }}
|
||||
</el-button>
|
||||
</template>
|
||||
</pure-table>
|
||||
</template>
|
||||
</PureTableBar>
|
||||
<el-drawer
|
||||
v-model="drawerStatus"
|
||||
title="操作日志详情"
|
||||
:size="`50%`"
|
||||
show-close
|
||||
>
|
||||
<el-collapse v-model="activeCollapse" :accordion="false">
|
||||
<!-- 请求基本信息 -->
|
||||
<el-collapse-item title="基本信息" name="requestinfo">
|
||||
<el-descriptions title="日志基本信息" border>
|
||||
<el-descriptions-item align="center" label="操作名称">{{
|
||||
logInfo.operation_name
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item align="center" label="操作类型">{{
|
||||
getOperationName(logInfo.operation_type)
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item align="center" label="请求路径">{{
|
||||
logInfo.request_path
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item align="center" label="请求方法">{{
|
||||
logInfo.request_method
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item align="center" label="操作人账号">{{
|
||||
logInfo.operator_name
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item align="center" label="操作人昵称">{{
|
||||
logInfo.operator_nickname
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item align="center" label="所属部门">{{
|
||||
logInfo.department_name
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item align="center" label="操作时间">{{
|
||||
dayjs(logInfo.operation_time).format("YYYY-MM-DD HH:mm:ss")
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item align="center" label="耗时"
|
||||
>{{ logInfo.cost_time.toFixed(2) }} ms</el-descriptions-item
|
||||
>
|
||||
<el-descriptions-item align="center" label="请求IP">{{
|
||||
logInfo.host
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item align="center" label="请求位置">{{
|
||||
logInfo.location
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item align="center" label="请求浏览器">{{
|
||||
logInfo.browser
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item align="center" label="请求系统">{{
|
||||
logInfo.os
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item align="center" label="请求头">{{
|
||||
logInfo.user_agent
|
||||
}}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-collapse-item>
|
||||
<!-- 请求参数 -->
|
||||
<el-collapse-item title="请求参数" name="requestParams">
|
||||
<vue-json-pretty
|
||||
:data="parsedRequestParams"
|
||||
showLineNumber
|
||||
collapsedOnClickBrackets
|
||||
showSelectController
|
||||
showIcon
|
||||
virtual
|
||||
showLength
|
||||
/>
|
||||
</el-collapse-item>
|
||||
<!-- 响应结果 -->
|
||||
<el-collapse-item title="响应结果" name="responseResult">
|
||||
<vue-json-pretty
|
||||
:data="parsedResponseResult"
|
||||
showLineNumber
|
||||
collapsedOnClickBrackets
|
||||
showSelectController
|
||||
showIcon
|
||||
virtual
|
||||
showLength
|
||||
/>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-dropdown-menu__item i) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin: 24px 24px 0 !important;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user