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) {
|
||||
const res = await request.get<ApiResult<PageResult<OperationRecord>>>(
|
||||
'/system/request-record/page',
|
||||
'/system/operate-record/page',
|
||||
{ params }
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
@ -15,17 +15,3 @@ export async function pageOperationRecords(params: OperationRecordParam) {
|
||||
}
|
||||
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 */
|
||||
userId?: number;
|
||||
/** 操作模块 */
|
||||
module: string;
|
||||
/** 操作数据 */
|
||||
data: string;
|
||||
/** 操作功能 */
|
||||
description: string;
|
||||
/** 请求地址 */
|
||||
url: 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;
|
||||
/** ContextId */
|
||||
contextId: string;
|
||||
/** 操作时间 */
|
||||
createTime: string;
|
||||
/** 用户昵称 */
|
||||
nickname: string;
|
||||
/** 地址 */
|
||||
location: string;
|
||||
/** 用户账号 */
|
||||
username: string;
|
||||
}
|
||||
@ -51,11 +29,9 @@ export interface OperationRecordParam extends PageParam {
|
||||
/** 用户昵称 */
|
||||
username?: string;
|
||||
/** 操作模块 */
|
||||
module?: string;
|
||||
location?: string;
|
||||
/** 开始时间 */
|
||||
createTimeStart?: string;
|
||||
/** 截至时间 */
|
||||
createTimeEnd?: string;
|
||||
/** 状态 */
|
||||
status?: number;
|
||||
}
|
||||
|
||||
@ -220,7 +220,6 @@ export function doWithTransition(
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将字节数转换为易读的存储单位(GB, MB, KB, B)
|
||||
* @param bytes 字节数量
|
||||
@ -250,3 +249,78 @@ export function formatBytes(bytes: number, precision: number = 2): string {
|
||||
|
||||
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"
|
||||
>
|
||||
<el-descriptions-item label="操作人">
|
||||
<div>{{ data.nickname }}({{ data.username }})</div>
|
||||
<div>{{ data.contextId }}</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="IP地址">
|
||||
<div>{{ data.ip }}</div>
|
||||
<el-descriptions-item label="地址">
|
||||
<div>{{ data.location }}</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="操作模块">
|
||||
<div>{{ data.module }}</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="操作功能">
|
||||
<el-descriptions-item label="操作描述">
|
||||
<div>{{ data.description }}</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="操作时间">
|
||||
<div>{{ data.createTime }}</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="请求耗时">
|
||||
<div v-if="!isNaN(data.spendTime)">{{ data.spendTime / 1000 }}s</div>
|
||||
<el-descriptions-item label="事件" :span="2">
|
||||
<div style="word-break: break-all">{{ data.event }}</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="请求方式">
|
||||
<div>{{ data.requestMethod }}</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="请求状态">
|
||||
<el-tag
|
||||
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 label="数据" :span="2">
|
||||
<monaco-editor
|
||||
v-model="previewData"
|
||||
language="json"
|
||||
style="height: 460px"
|
||||
/>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<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 { 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;
|
||||
}>();
|
||||
const previewData = computed(() => {
|
||||
return formatJson(props.data);
|
||||
});
|
||||
|
||||
/** 弹窗是否打开 */
|
||||
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();
|
||||
</script>
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :lg="6" :md="12" :sm="12" :xs="24">
|
||||
<el-form-item label="操作模块">
|
||||
<el-form-item label="地址">
|
||||
<el-input
|
||||
clearable
|
||||
v-model.trim="form.module"
|
||||
|
||||
@ -12,34 +12,6 @@
|
||||
:export-config="{ fileName: '操作日志数据' }"
|
||||
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 }">
|
||||
<el-link type="primary" underline="never" @click="openDetail(row)">
|
||||
详情
|
||||
@ -54,21 +26,14 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { EleMessage } from 'ele-admin-plus';
|
||||
import type { EleProTable } from 'ele-admin-plus';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
Columns
|
||||
} 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 OperationRecordDetail from './components/operation-record-detail.vue';
|
||||
import {
|
||||
pageOperationRecords,
|
||||
listOperationRecords
|
||||
} from '@/api/system/operation-record';
|
||||
import { pageOperationRecords } from '@/api/system/operation-record';
|
||||
import type {
|
||||
OperationRecord,
|
||||
OperationRecordParam
|
||||
@ -85,73 +50,32 @@
|
||||
type: 'index',
|
||||
columnKey: 'index',
|
||||
width: 50,
|
||||
align: 'center' /* ,
|
||||
fixed: 'left' */
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
prop: 'username',
|
||||
label: '账号',
|
||||
sortable: 'custom',
|
||||
minWidth: 110
|
||||
prop: 'contextId',
|
||||
label: 'ContextId',
|
||||
width: 280
|
||||
},
|
||||
{
|
||||
prop: 'nickname',
|
||||
label: '用户名',
|
||||
sortable: 'custom',
|
||||
minWidth: 110
|
||||
prop: 'operateUserId',
|
||||
label: '操作人',
|
||||
width: 110
|
||||
},
|
||||
{
|
||||
prop: 'module',
|
||||
label: '操作模块',
|
||||
sortable: 'custom',
|
||||
minWidth: 110
|
||||
prop: 'location',
|
||||
label: '地址',
|
||||
minWidth: 160
|
||||
},
|
||||
{
|
||||
prop: 'event',
|
||||
label: '事件',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
prop: 'description',
|
||||
label: '操作功能',
|
||||
sortable: 'custom',
|
||||
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'
|
||||
label: '操作描述',
|
||||
minWidth: 210
|
||||
},
|
||||
{
|
||||
prop: 'createTime',
|
||||
@ -221,74 +145,4 @@
|
||||
current.value = row;
|
||||
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>
|
||||
|
||||
@ -14,11 +14,18 @@
|
||||
<div>{{ data.requestIp }}</div>
|
||||
</el-descriptions-item>
|
||||
<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 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 label="请求方式">
|
||||
<div>{{ data.requestMethod }}</div>
|
||||
@ -33,7 +40,9 @@
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<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 label="规则名称" :span="2">
|
||||
<div style="word-break: break-all">{{ data.ruleName }}</div>
|
||||
@ -45,13 +54,21 @@
|
||||
<ele-ellipsis :max-line="4" :tooltip="ellipsisTooltipProps">
|
||||
{{ data.requestHeaders }}
|
||||
</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 label="请求参数" :span="2">
|
||||
<ele-ellipsis :max-line="4" :tooltip="ellipsisTooltipProps">
|
||||
{{ data.requestBody }}
|
||||
</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 label="返回类型" :span="1">
|
||||
<ele-ellipsis :max-line="4" :tooltip="ellipsisTooltipProps">
|
||||
@ -60,22 +77,23 @@
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="运行消耗" :span="1">
|
||||
<ele-ellipsis :max-line="4" :tooltip="ellipsisTooltipProps">
|
||||
{{ formatBytes(data.runningMemoryUsage) }} / {{ data.runningMemoryLimit }}
|
||||
{{ formatBytes(data.runningMemoryUsage) }} /
|
||||
{{ data.runningMemoryLimit }}
|
||||
</ele-ellipsis>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="返回结果" :span="2">
|
||||
<ele-ellipsis :max-line="4" :tooltip="ellipsisTooltipProps">
|
||||
{{ data.responseData }}
|
||||
</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>
|
||||
|
||||
<el-dialog
|
||||
v-model="previewVisible"
|
||||
:title="previewTitle"
|
||||
width="860"
|
||||
>
|
||||
<el-dialog v-model="previewVisible" :title="previewTitle" width="860">
|
||||
<monaco-editor
|
||||
v-model="previewData"
|
||||
language="json"
|
||||
@ -90,8 +108,8 @@
|
||||
import type { EleTooltipProps } from 'ele-admin-plus/es/ele-app/plus';
|
||||
import type { OperationRecord } from '@/api/system/operation-record/model';
|
||||
import { useMobile } from '@/utils/use-mobile';
|
||||
import {formatBytes} from "@/utils/common";
|
||||
import MonacoEditor from "@/components/MonacoEditor/index.vue";
|
||||
import { formatBytes, formatJson } from '@/utils/common';
|
||||
import MonacoEditor from '@/components/MonacoEditor/index.vue';
|
||||
|
||||
defineProps<{
|
||||
/** 修改回显的数据 */
|
||||
@ -124,93 +142,17 @@
|
||||
const { mobile } = useMobile();
|
||||
|
||||
const previewVisible = ref(false);
|
||||
const previewData = ref("");
|
||||
const previewTitle = ref("");
|
||||
const previewData = ref('');
|
||||
const previewTitle = ref('');
|
||||
const preview = (title, type, data) => {
|
||||
previewTitle.value = title;
|
||||
if(type == "think\\response\\Json" || type == 'json'){
|
||||
if (type == 'think\\response\\Json' || type == 'json') {
|
||||
previewData.value = formatJson(data);
|
||||
} else {
|
||||
previewData.value = data;
|
||||
}
|
||||
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>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user