up.
This commit is contained in:
parent
e2d79ce1ef
commit
d281084e1d
@ -7,7 +7,7 @@ import type { OperationRecord, OperationRecordParam } from './model';
|
|||||||
*/
|
*/
|
||||||
export async function pageOperationRecords(params: OperationRecordParam) {
|
export async function pageOperationRecords(params: OperationRecordParam) {
|
||||||
const res = await request.get<ApiResult<PageResult<OperationRecord>>>(
|
const res = await request.get<ApiResult<PageResult<OperationRecord>>>(
|
||||||
'/system/request-record/page',
|
'/system/operate-record/page',
|
||||||
{ params }
|
{ params }
|
||||||
);
|
);
|
||||||
if (res.data.code === 0) {
|
if (res.data.code === 0) {
|
||||||
@ -15,17 +15,3 @@ export async function pageOperationRecords(params: OperationRecordParam) {
|
|||||||
}
|
}
|
||||||
return Promise.reject(new Error(res.data.message));
|
return Promise.reject(new Error(res.data.message));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询操作日志列表
|
|
||||||
*/
|
|
||||||
export async function listOperationRecords(params?: OperationRecordParam) {
|
|
||||||
const res = await request.get<ApiResult<OperationRecord[]>>(
|
|
||||||
'/system/request-record',
|
|
||||||
{ params }
|
|
||||||
);
|
|
||||||
if (res.data.code === 0 && res.data.data) {
|
|
||||||
return res.data.data;
|
|
||||||
}
|
|
||||||
return Promise.reject(new Error(res.data.message));
|
|
||||||
}
|
|
||||||
|
|||||||
@ -8,38 +8,16 @@ export interface OperationRecord {
|
|||||||
id?: number;
|
id?: number;
|
||||||
/** 用户id */
|
/** 用户id */
|
||||||
userId?: number;
|
userId?: number;
|
||||||
/** 操作模块 */
|
/** 操作数据 */
|
||||||
module: string;
|
data: string;
|
||||||
/** 操作功能 */
|
/** 操作功能 */
|
||||||
description: string;
|
description: string;
|
||||||
/** 请求地址 */
|
/** ContextId */
|
||||||
url: string;
|
contextId: string;
|
||||||
/** 请求方式 */
|
|
||||||
requestMethod: string;
|
|
||||||
/** 调用方法 */
|
|
||||||
method: string;
|
|
||||||
/** 请求参数 */
|
|
||||||
params: string;
|
|
||||||
/** 返回结果 */
|
|
||||||
result: string;
|
|
||||||
/** 异常信息 */
|
|
||||||
error: string;
|
|
||||||
/** 消耗时间, 单位毫秒 */
|
|
||||||
spendTime: number;
|
|
||||||
/** 操作系统 */
|
|
||||||
os: string;
|
|
||||||
/** 设备名称 */
|
|
||||||
device: string;
|
|
||||||
/** 浏览器类型 */
|
|
||||||
browser: string;
|
|
||||||
/** ip地址 */
|
|
||||||
ip: string;
|
|
||||||
/** 状态, 0成功, 1异常 */
|
|
||||||
status: number;
|
|
||||||
/** 操作时间 */
|
/** 操作时间 */
|
||||||
createTime: string;
|
createTime: string;
|
||||||
/** 用户昵称 */
|
/** 地址 */
|
||||||
nickname: string;
|
location: string;
|
||||||
/** 用户账号 */
|
/** 用户账号 */
|
||||||
username: string;
|
username: string;
|
||||||
}
|
}
|
||||||
@ -51,11 +29,9 @@ export interface OperationRecordParam extends PageParam {
|
|||||||
/** 用户昵称 */
|
/** 用户昵称 */
|
||||||
username?: string;
|
username?: string;
|
||||||
/** 操作模块 */
|
/** 操作模块 */
|
||||||
module?: string;
|
location?: string;
|
||||||
/** 开始时间 */
|
/** 开始时间 */
|
||||||
createTimeStart?: string;
|
createTimeStart?: string;
|
||||||
/** 截至时间 */
|
/** 截至时间 */
|
||||||
createTimeEnd?: string;
|
createTimeEnd?: string;
|
||||||
/** 状态 */
|
|
||||||
status?: number;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -220,7 +220,6 @@ export function doWithTransition(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将字节数转换为易读的存储单位(GB, MB, KB, B)
|
* 将字节数转换为易读的存储单位(GB, MB, KB, B)
|
||||||
* @param bytes 字节数量
|
* @param bytes 字节数量
|
||||||
@ -250,3 +249,78 @@ export function formatBytes(bytes: number, precision: number = 2): string {
|
|||||||
|
|
||||||
return `${formattedValue}${unit}`;
|
return `${formattedValue}${unit}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化JSON字符串
|
||||||
|
* @param {string} jsonString - 需要格式化的JSON字符串
|
||||||
|
* @param {number} indentSpaces - 缩进空格数(默认为2)
|
||||||
|
* @returns {string} 格式化后的JSON字符串
|
||||||
|
* @throws {Error} 当输入不是有效的JSON时抛出错误
|
||||||
|
*/
|
||||||
|
export function formatJson(jsonString, indentSpaces = 2) {
|
||||||
|
// 验证输入是否为字符串
|
||||||
|
|
||||||
|
// 解析JSON字符串为JavaScript对象
|
||||||
|
let parsedObj;
|
||||||
|
if (typeof jsonString === 'string') {
|
||||||
|
// throw new Error('输入必须是字符串');
|
||||||
|
try {
|
||||||
|
parsedObj = JSON.parse(jsonString);
|
||||||
|
} catch (e: any) {
|
||||||
|
throw new Error('无效的JSON格式: ' + e.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parsedObj = jsonString;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归函数来处理格式化
|
||||||
|
function format(obj, depth) {
|
||||||
|
// 处理基本类型
|
||||||
|
if (obj === null) {
|
||||||
|
return 'null';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof obj === 'boolean' || typeof obj === 'number') {
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof obj === 'string') {
|
||||||
|
return '"' + obj + '"';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理数组
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.length === 0) {
|
||||||
|
return '[]';
|
||||||
|
}
|
||||||
|
|
||||||
|
const indent = ' '.repeat(depth * indentSpaces);
|
||||||
|
const innerIndent = ' '.repeat((depth + 1) * indentSpaces);
|
||||||
|
const items: any = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < obj.length; i++) {
|
||||||
|
items.push(innerIndent + format(obj[i], depth + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return '[\n' + items.join(',\n') + '\n' + indent + ']';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理对象
|
||||||
|
const indent = ' '.repeat(depth * indentSpaces);
|
||||||
|
const innerIndent = ' '.repeat((depth + 1) * indentSpaces);
|
||||||
|
const keys = Object.keys(obj);
|
||||||
|
|
||||||
|
if (keys.length === 0) {
|
||||||
|
return '{}';
|
||||||
|
}
|
||||||
|
|
||||||
|
const items: any = [];
|
||||||
|
for (const key of keys) {
|
||||||
|
items.push(innerIndent + '"' + key + '": ' + format(obj[key], depth + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return '{\n' + items.join(',\n') + '\n' + indent + '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
return format(parsedObj, 0);
|
||||||
|
}
|
||||||
|
|||||||
@ -8,102 +8,49 @@
|
|||||||
class="detail-table"
|
class="detail-table"
|
||||||
>
|
>
|
||||||
<el-descriptions-item label="操作人">
|
<el-descriptions-item label="操作人">
|
||||||
<div>{{ data.nickname }}({{ data.username }})</div>
|
<div>{{ data.contextId }}</div>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="IP地址">
|
<el-descriptions-item label="地址">
|
||||||
<div>{{ data.ip }}</div>
|
<div>{{ data.location }}</div>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="操作模块">
|
<el-descriptions-item label="操作描述">
|
||||||
<div>{{ data.module }}</div>
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="操作功能">
|
|
||||||
<div>{{ data.description }}</div>
|
<div>{{ data.description }}</div>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="操作时间">
|
<el-descriptions-item label="操作时间">
|
||||||
<div>{{ data.createTime }}</div>
|
<div>{{ data.createTime }}</div>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="请求耗时">
|
<el-descriptions-item label="事件" :span="2">
|
||||||
<div v-if="!isNaN(data.spendTime)">{{ data.spendTime / 1000 }}s</div>
|
<div style="word-break: break-all">{{ data.event }}</div>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="请求方式">
|
<el-descriptions-item label="数据" :span="2">
|
||||||
<div>{{ data.requestMethod }}</div>
|
<monaco-editor
|
||||||
</el-descriptions-item>
|
v-model="previewData"
|
||||||
<el-descriptions-item label="请求状态">
|
language="json"
|
||||||
<el-tag
|
style="height: 460px"
|
||||||
v-if="data.status === 0"
|
/>
|
||||||
size="small"
|
|
||||||
type="success"
|
|
||||||
:disable-transitions="true"
|
|
||||||
>
|
|
||||||
正常
|
|
||||||
</el-tag>
|
|
||||||
<el-tag
|
|
||||||
v-else-if="data.status === 1"
|
|
||||||
size="small"
|
|
||||||
type="danger"
|
|
||||||
:disable-transitions="true"
|
|
||||||
>
|
|
||||||
异常
|
|
||||||
</el-tag>
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="请求地址" :span="2">
|
|
||||||
<div style="word-break: break-all">{{ data.url }}</div>
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="调用方法" :span="2">
|
|
||||||
<div style="word-break: break-all">{{ data.method }}</div>
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="请求参数" :span="2">
|
|
||||||
<ele-ellipsis :max-line="4" :tooltip="ellipsisTooltipProps">
|
|
||||||
{{ data.params }}
|
|
||||||
</ele-ellipsis>
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item v-if="data.status === 0" label="返回结果" :span="2">
|
|
||||||
<ele-ellipsis :max-line="4" :tooltip="ellipsisTooltipProps">
|
|
||||||
{{ data.result }}
|
|
||||||
</ele-ellipsis>
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item v-else label="异常信息" :span="2">
|
|
||||||
<ele-ellipsis :max-line="4" :tooltip="ellipsisTooltipProps">
|
|
||||||
{{ data.error }}
|
|
||||||
</ele-ellipsis>
|
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
</ele-modal>
|
</ele-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive } from 'vue';
|
|
||||||
import type { EleTooltipProps } from 'ele-admin-plus/es/ele-app/plus';
|
|
||||||
import type { OperationRecord } from '@/api/system/operation-record/model';
|
import type { OperationRecord } from '@/api/system/operation-record/model';
|
||||||
import { useMobile } from '@/utils/use-mobile';
|
import { useMobile } from '@/utils/use-mobile';
|
||||||
|
import MonacoEditor from '@/components/MonacoEditor/index.vue';
|
||||||
|
import { formatJson } from '@/utils/common';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
defineProps<{
|
const props = defineProps<{
|
||||||
/** 修改回显的数据 */
|
/** 修改回显的数据 */
|
||||||
data: OperationRecord;
|
data: OperationRecord;
|
||||||
}>();
|
}>();
|
||||||
|
const previewData = computed(() => {
|
||||||
|
return formatJson(props.data);
|
||||||
|
});
|
||||||
|
|
||||||
/** 弹窗是否打开 */
|
/** 弹窗是否打开 */
|
||||||
const visible = defineModel({ type: Boolean });
|
const visible = defineModel({ type: Boolean });
|
||||||
|
|
||||||
/** 文字省略组件的提示组件的属性 */
|
|
||||||
const ellipsisTooltipProps = reactive<EleTooltipProps>({
|
|
||||||
popperStyle: {
|
|
||||||
width: '580px',
|
|
||||||
maxWidth: '90%',
|
|
||||||
wordBreak: 'break-all'
|
|
||||||
},
|
|
||||||
bodyStyle: {
|
|
||||||
maxWidth: 'calc(100vw - 32px)',
|
|
||||||
maxHeight: '252px',
|
|
||||||
overflowY: 'auto',
|
|
||||||
'--ele-scrollbar-color': '#5e5e5e',
|
|
||||||
'--ele-scrollbar-hover-color': '#707070',
|
|
||||||
'--ele-scrollbar-size': '8px'
|
|
||||||
},
|
|
||||||
offset: 4,
|
|
||||||
placement: 'top'
|
|
||||||
});
|
|
||||||
|
|
||||||
/** 是否是移动端 */
|
/** 是否是移动端 */
|
||||||
const { mobile } = useMobile();
|
const { mobile } = useMobile();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :lg="6" :md="12" :sm="12" :xs="24">
|
<el-col :lg="6" :md="12" :sm="12" :xs="24">
|
||||||
<el-form-item label="操作模块">
|
<el-form-item label="地址">
|
||||||
<el-input
|
<el-input
|
||||||
clearable
|
clearable
|
||||||
v-model.trim="form.module"
|
v-model.trim="form.module"
|
||||||
|
|||||||
@ -12,34 +12,6 @@
|
|||||||
:export-config="{ fileName: '操作日志数据' }"
|
:export-config="{ fileName: '操作日志数据' }"
|
||||||
cache-key="systemOperationRecordTable"
|
cache-key="systemOperationRecordTable"
|
||||||
>
|
>
|
||||||
<template #toolbar>
|
|
||||||
<el-button
|
|
||||||
type="primary"
|
|
||||||
class="ele-btn-icon"
|
|
||||||
:icon="DownloadOutlined"
|
|
||||||
@click="exportData"
|
|
||||||
>
|
|
||||||
导出
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
<template #status="{ row }">
|
|
||||||
<el-tag
|
|
||||||
v-if="row.status === 0"
|
|
||||||
size="small"
|
|
||||||
type="success"
|
|
||||||
:disable-transitions="true"
|
|
||||||
>
|
|
||||||
正常
|
|
||||||
</el-tag>
|
|
||||||
<el-tag
|
|
||||||
v-else-if="row.status === 1"
|
|
||||||
size="small"
|
|
||||||
type="danger"
|
|
||||||
:disable-transitions="true"
|
|
||||||
>
|
|
||||||
异常
|
|
||||||
</el-tag>
|
|
||||||
</template>
|
|
||||||
<template #action="{ row }">
|
<template #action="{ row }">
|
||||||
<el-link type="primary" underline="never" @click="openDetail(row)">
|
<el-link type="primary" underline="never" @click="openDetail(row)">
|
||||||
详情
|
详情
|
||||||
@ -54,21 +26,14 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { EleMessage } from 'ele-admin-plus';
|
|
||||||
import type { EleProTable } from 'ele-admin-plus';
|
import type { EleProTable } from 'ele-admin-plus';
|
||||||
import type {
|
import type {
|
||||||
DatasourceFunction,
|
DatasourceFunction,
|
||||||
Columns
|
Columns
|
||||||
} from 'ele-admin-plus/es/ele-pro-table/types';
|
} from 'ele-admin-plus/es/ele-pro-table/types';
|
||||||
import ExcelJS from 'exceljs';
|
|
||||||
import { download } from '@/utils/common';
|
|
||||||
import { DownloadOutlined } from '@/components/icons';
|
|
||||||
import OperationRecordSearch from './components/operation-record-search.vue';
|
import OperationRecordSearch from './components/operation-record-search.vue';
|
||||||
import OperationRecordDetail from './components/operation-record-detail.vue';
|
import OperationRecordDetail from './components/operation-record-detail.vue';
|
||||||
import {
|
import { pageOperationRecords } from '@/api/system/operation-record';
|
||||||
pageOperationRecords,
|
|
||||||
listOperationRecords
|
|
||||||
} from '@/api/system/operation-record';
|
|
||||||
import type {
|
import type {
|
||||||
OperationRecord,
|
OperationRecord,
|
||||||
OperationRecordParam
|
OperationRecordParam
|
||||||
@ -85,73 +50,32 @@
|
|||||||
type: 'index',
|
type: 'index',
|
||||||
columnKey: 'index',
|
columnKey: 'index',
|
||||||
width: 50,
|
width: 50,
|
||||||
align: 'center' /* ,
|
align: 'center'
|
||||||
fixed: 'left' */
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'username',
|
prop: 'contextId',
|
||||||
label: '账号',
|
label: 'ContextId',
|
||||||
sortable: 'custom',
|
width: 280
|
||||||
minWidth: 110
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'nickname',
|
prop: 'operateUserId',
|
||||||
label: '用户名',
|
label: '操作人',
|
||||||
sortable: 'custom',
|
width: 110
|
||||||
minWidth: 110
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'module',
|
prop: 'location',
|
||||||
label: '操作模块',
|
label: '地址',
|
||||||
sortable: 'custom',
|
minWidth: 160
|
||||||
minWidth: 110
|
},
|
||||||
|
{
|
||||||
|
prop: 'event',
|
||||||
|
label: '事件',
|
||||||
|
width: 120
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'description',
|
prop: 'description',
|
||||||
label: '操作功能',
|
label: '操作描述',
|
||||||
sortable: 'custom',
|
minWidth: 210
|
||||||
minWidth: 110
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'url',
|
|
||||||
label: '请求地址',
|
|
||||||
sortable: 'custom',
|
|
||||||
minWidth: 110
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'requestMethod',
|
|
||||||
label: '请求方式',
|
|
||||||
sortable: 'custom',
|
|
||||||
width: 110,
|
|
||||||
align: 'center'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'status',
|
|
||||||
label: '状态',
|
|
||||||
sortable: 'custom',
|
|
||||||
width: 100,
|
|
||||||
align: 'center',
|
|
||||||
slot: 'status',
|
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
text: '正常',
|
|
||||||
value: '0'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '异常',
|
|
||||||
value: '1'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
filterMultiple: false,
|
|
||||||
formatter: (row) => (row.status == 0 ? '正常' : '异常')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'spendTime',
|
|
||||||
label: '耗时',
|
|
||||||
sortable: 'custom',
|
|
||||||
width: 100,
|
|
||||||
formatter: (row) => `${row.spendTime / 1000}s`,
|
|
||||||
align: 'center'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'createTime',
|
prop: 'createTime',
|
||||||
@ -221,74 +145,4 @@
|
|||||||
current.value = row;
|
current.value = row;
|
||||||
showInfo.value = true;
|
showInfo.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 导出数据 */
|
|
||||||
const exportData = () => {
|
|
||||||
// 请求查询全部(不分页)的接口
|
|
||||||
const loading = EleMessage.loading({
|
|
||||||
message: '请求中..',
|
|
||||||
plain: true
|
|
||||||
});
|
|
||||||
tableRef.value?.fetch?.(({ where, orders, filters }) => {
|
|
||||||
listOperationRecords({ ...where, ...orders, ...filters })
|
|
||||||
.then((data) => {
|
|
||||||
const workbook = new ExcelJS.Workbook();
|
|
||||||
const sheet = workbook.addWorksheet('Sheet1');
|
|
||||||
sheet.addRow([
|
|
||||||
'账号',
|
|
||||||
'用户名',
|
|
||||||
'操作模块',
|
|
||||||
'操作功能',
|
|
||||||
'请求地址',
|
|
||||||
'请求方式',
|
|
||||||
'状态',
|
|
||||||
'耗时',
|
|
||||||
'操作时间'
|
|
||||||
]);
|
|
||||||
data.forEach((d) => {
|
|
||||||
sheet.addRow([
|
|
||||||
d.username,
|
|
||||||
d.nickname,
|
|
||||||
d.module,
|
|
||||||
d.description,
|
|
||||||
d.url,
|
|
||||||
d.requestMethod,
|
|
||||||
['正常', '异常'][d.status],
|
|
||||||
d.spendTime / 1000 + 's',
|
|
||||||
d.createTime
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
// 设置列宽
|
|
||||||
[16, 16, 18, 20, 28, 14, 14, 14, 24].forEach((width, index) => {
|
|
||||||
sheet.getColumn(index + 1).width = width;
|
|
||||||
});
|
|
||||||
// 设置样式
|
|
||||||
sheet.eachRow({ includeEmpty: true }, (row, rowIndex) => {
|
|
||||||
row.height = 20;
|
|
||||||
row.eachCell({ includeEmpty: true }, (cell) => {
|
|
||||||
cell.border = {
|
|
||||||
top: { style: 'thin' },
|
|
||||||
left: { style: 'thin' },
|
|
||||||
bottom: { style: 'thin' },
|
|
||||||
right: { style: 'thin' }
|
|
||||||
};
|
|
||||||
cell.alignment = {
|
|
||||||
vertical: 'middle',
|
|
||||||
horizontal: 'center'
|
|
||||||
};
|
|
||||||
cell.font = { size: 12, bold: rowIndex === 1 };
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// 下载文件
|
|
||||||
workbook.xlsx.writeBuffer().then((data) => {
|
|
||||||
download(data, '操作日志.xlsx');
|
|
||||||
loading.close();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
loading.close();
|
|
||||||
EleMessage.error({ message: e.message, plain: true });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -14,11 +14,18 @@
|
|||||||
<div>{{ data.requestIp }}</div>
|
<div>{{ data.requestIp }}</div>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="请求时间">
|
<el-descriptions-item label="请求时间">
|
||||||
<div>{{ data.requestTime }}<span style="color:#9e9e9e;"> - {{ data.requestEndTime }}</span></div>
|
<div
|
||||||
|
>{{ data.requestTime
|
||||||
|
}}<span style="color: #9e9e9e">
|
||||||
|
- {{ data.requestEndTime }}</span
|
||||||
|
></div
|
||||||
|
>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
|
|
||||||
<el-descriptions-item label="接口耗时">
|
<el-descriptions-item label="接口耗时">
|
||||||
<div v-if="!isNaN(data.runningTime)">{{ data.runningTime / 1000 }}s</div>
|
<div v-if="!isNaN(data.runningTime)"
|
||||||
|
>{{ data.runningTime / 1000 }}s</div
|
||||||
|
>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="请求方式">
|
<el-descriptions-item label="请求方式">
|
||||||
<div>{{ data.requestMethod }}</div>
|
<div>{{ data.requestMethod }}</div>
|
||||||
@ -33,7 +40,9 @@
|
|||||||
</el-tag>
|
</el-tag>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="请求地址" :span="2">
|
<el-descriptions-item label="请求地址" :span="2">
|
||||||
<div style="word-break: break-all">{{ data.requestDomain }}{{ data.requestPath }}</div>
|
<div style="word-break: break-all"
|
||||||
|
>{{ data.requestDomain }}{{ data.requestPath }}</div
|
||||||
|
>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="规则名称" :span="2">
|
<el-descriptions-item label="规则名称" :span="2">
|
||||||
<div style="word-break: break-all">{{ data.ruleName }}</div>
|
<div style="word-break: break-all">{{ data.ruleName }}</div>
|
||||||
@ -45,13 +54,21 @@
|
|||||||
<ele-ellipsis :max-line="4" :tooltip="ellipsisTooltipProps">
|
<ele-ellipsis :max-line="4" :tooltip="ellipsisTooltipProps">
|
||||||
{{ data.requestHeaders }}
|
{{ data.requestHeaders }}
|
||||||
</ele-ellipsis>
|
</ele-ellipsis>
|
||||||
<el-button size="small" @click="preview('请求头数据', 'json', data.requestHeaders)">预览</el-button>
|
<el-button
|
||||||
|
size="small"
|
||||||
|
@click="preview('请求头数据', 'json', data.requestHeaders)"
|
||||||
|
>预览</el-button
|
||||||
|
>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="请求参数" :span="2">
|
<el-descriptions-item label="请求参数" :span="2">
|
||||||
<ele-ellipsis :max-line="4" :tooltip="ellipsisTooltipProps">
|
<ele-ellipsis :max-line="4" :tooltip="ellipsisTooltipProps">
|
||||||
{{ data.requestBody }}
|
{{ data.requestBody }}
|
||||||
</ele-ellipsis>
|
</ele-ellipsis>
|
||||||
<el-button size="small" @click="preview('请求参数', data.responseType, data.requestBody)">预览</el-button>
|
<el-button
|
||||||
|
size="small"
|
||||||
|
@click="preview('请求参数', data.responseType, data.requestBody)"
|
||||||
|
>预览</el-button
|
||||||
|
>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="返回类型" :span="1">
|
<el-descriptions-item label="返回类型" :span="1">
|
||||||
<ele-ellipsis :max-line="4" :tooltip="ellipsisTooltipProps">
|
<ele-ellipsis :max-line="4" :tooltip="ellipsisTooltipProps">
|
||||||
@ -60,22 +77,23 @@
|
|||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="运行消耗" :span="1">
|
<el-descriptions-item label="运行消耗" :span="1">
|
||||||
<ele-ellipsis :max-line="4" :tooltip="ellipsisTooltipProps">
|
<ele-ellipsis :max-line="4" :tooltip="ellipsisTooltipProps">
|
||||||
{{ formatBytes(data.runningMemoryUsage) }} / {{ data.runningMemoryLimit }}
|
{{ formatBytes(data.runningMemoryUsage) }} /
|
||||||
|
{{ data.runningMemoryLimit }}
|
||||||
</ele-ellipsis>
|
</ele-ellipsis>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="返回结果" :span="2">
|
<el-descriptions-item label="返回结果" :span="2">
|
||||||
<ele-ellipsis :max-line="4" :tooltip="ellipsisTooltipProps">
|
<ele-ellipsis :max-line="4" :tooltip="ellipsisTooltipProps">
|
||||||
{{ data.responseData }}
|
{{ data.responseData }}
|
||||||
</ele-ellipsis>
|
</ele-ellipsis>
|
||||||
<el-button size="small" @click="preview('返回结果',data.responseType, data.responseData)">预览</el-button>
|
<el-button
|
||||||
|
size="small"
|
||||||
|
@click="preview('返回结果', data.responseType, data.responseData)"
|
||||||
|
>预览</el-button
|
||||||
|
>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
|
|
||||||
<el-dialog
|
<el-dialog v-model="previewVisible" :title="previewTitle" width="860">
|
||||||
v-model="previewVisible"
|
|
||||||
:title="previewTitle"
|
|
||||||
width="860"
|
|
||||||
>
|
|
||||||
<monaco-editor
|
<monaco-editor
|
||||||
v-model="previewData"
|
v-model="previewData"
|
||||||
language="json"
|
language="json"
|
||||||
@ -90,8 +108,8 @@
|
|||||||
import type { EleTooltipProps } from 'ele-admin-plus/es/ele-app/plus';
|
import type { EleTooltipProps } from 'ele-admin-plus/es/ele-app/plus';
|
||||||
import type { OperationRecord } from '@/api/system/operation-record/model';
|
import type { OperationRecord } from '@/api/system/operation-record/model';
|
||||||
import { useMobile } from '@/utils/use-mobile';
|
import { useMobile } from '@/utils/use-mobile';
|
||||||
import {formatBytes} from "@/utils/common";
|
import { formatBytes, formatJson } from '@/utils/common';
|
||||||
import MonacoEditor from "@/components/MonacoEditor/index.vue";
|
import MonacoEditor from '@/components/MonacoEditor/index.vue';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
/** 修改回显的数据 */
|
/** 修改回显的数据 */
|
||||||
@ -124,93 +142,17 @@
|
|||||||
const { mobile } = useMobile();
|
const { mobile } = useMobile();
|
||||||
|
|
||||||
const previewVisible = ref(false);
|
const previewVisible = ref(false);
|
||||||
const previewData = ref("");
|
const previewData = ref('');
|
||||||
const previewTitle = ref("");
|
const previewTitle = ref('');
|
||||||
const preview = (title, type, data) => {
|
const preview = (title, type, data) => {
|
||||||
previewTitle.value = title;
|
previewTitle.value = title;
|
||||||
if(type == "think\\response\\Json" || type == 'json'){
|
if (type == 'think\\response\\Json' || type == 'json') {
|
||||||
previewData.value = formatJson(data);
|
previewData.value = formatJson(data);
|
||||||
} else {
|
} else {
|
||||||
previewData.value = data;
|
previewData.value = data;
|
||||||
}
|
}
|
||||||
previewVisible.value = true;
|
previewVisible.value = true;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* 格式化JSON字符串
|
|
||||||
* @param {string} jsonString - 需要格式化的JSON字符串
|
|
||||||
* @param {number} indentSpaces - 缩进空格数(默认为2)
|
|
||||||
* @returns {string} 格式化后的JSON字符串
|
|
||||||
* @throws {Error} 当输入不是有效的JSON时抛出错误
|
|
||||||
*/
|
|
||||||
function formatJson(jsonString, indentSpaces = 2) {
|
|
||||||
// 验证输入是否为字符串
|
|
||||||
|
|
||||||
// 解析JSON字符串为JavaScript对象
|
|
||||||
let parsedObj;
|
|
||||||
if (typeof jsonString === 'string') {
|
|
||||||
// throw new Error('输入必须是字符串');
|
|
||||||
try {
|
|
||||||
parsedObj = JSON.parse(jsonString);
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error('无效的JSON格式: ' + e.message);
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
parsedObj = jsonString;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 递归函数来处理格式化
|
|
||||||
function format(obj, depth) {
|
|
||||||
// 处理基本类型
|
|
||||||
if (obj === null) {
|
|
||||||
return 'null';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof obj === 'boolean' || typeof obj === 'number') {
|
|
||||||
return obj.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof obj === 'string') {
|
|
||||||
return '"' + obj + '"';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理数组
|
|
||||||
if (Array.isArray(obj)) {
|
|
||||||
if (obj.length === 0) {
|
|
||||||
return '[]';
|
|
||||||
}
|
|
||||||
|
|
||||||
let indent = ' '.repeat(depth * indentSpaces);
|
|
||||||
let innerIndent = ' '.repeat((depth + 1) * indentSpaces);
|
|
||||||
let items = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < obj.length; i++) {
|
|
||||||
items.push(innerIndent + format(obj[i], depth + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
return '[\n' + items.join(',\n') + '\n' + indent + ']';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理对象
|
|
||||||
let indent = ' '.repeat(depth * indentSpaces);
|
|
||||||
let innerIndent = ' '.repeat((depth + 1) * indentSpaces);
|
|
||||||
let keys = Object.keys(obj);
|
|
||||||
|
|
||||||
if (keys.length === 0) {
|
|
||||||
return '{}';
|
|
||||||
}
|
|
||||||
|
|
||||||
let items = [];
|
|
||||||
for (let key of keys) {
|
|
||||||
items.push(innerIndent + '"' + key + '": ' + format(obj[key], depth + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
return '{\n' + items.join(',\n') + '\n' + indent + '}';
|
|
||||||
}
|
|
||||||
|
|
||||||
return format(parsedObj, 0);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user