feat(log): 三类日志列表加大小列,详情按 50KB 门槛切换在线预览/下载
build / build (push) Successful in 1m57s

- 推送/操作/开放接口日志列表各加"大小"列(payload_size/response_size,Utils.getFileSize 格式化)
- 详情弹窗:下载按钮常驻;Utils.canPreview(≤50KB) 才显示"在线预览",点击 fetch 复用各自 /Download/{id} 取原文渲染
- oapilog 详情从无条件内联改为同一套门槛逻辑
- utils 新增 PREVIEW_MAX_BYTES 与 canPreview
This commit is contained in:
chenxing
2026-09-18 09:24:38 +08:00
parent 628a914e19
commit d012a99e28
7 changed files with 209 additions and 6 deletions
+14
View File
@@ -67,6 +67,20 @@ export default class Utils {
} else return "";
}
/**
* 在线预览大小上限(字节):日志大字段(请求体/响应)超过此值时隐藏"在线预览"、只保留"下载"。
* 浏览器 JSON 美化 + <pre> 渲染在数百 KB 起会卡顿,50KB 为体验无损的保守阈值。
*/
public static readonly PREVIEW_MAX_BYTES = 50 * 1024;
/**
* 是否允许在线预览:有内容(size > 0)且字节数不超过阈值。
* size 为 null/undefined/0(未记录请求体、saveResponse=false、响应为空)→ 不可预览,仅下载。
*/
public static canPreview(size?: number | null): boolean {
return size != null && size > 0 && size <= Utils.PREVIEW_MAX_BYTES;
}
/**
* 删除指定键
*/
+70 -2
View File
@@ -20,7 +20,13 @@
<pre class="json-pre">{{ formatJson(row?.requestParams) }}</pre>
</FormItem>
<FormItem label="响应结果:">
<pre class="json-pre">{{ formatJson(row?.responseData) }}</pre>
<template v-if="row?.id">
<a href="javascript:;" class="download-link" @click.prevent="handleDownload">下载</a>
<a v-if="canPreview" href="javascript:;" class="download-link" style="margin-left: 12px"
@click.prevent="handlePreview">在线预览</a>
<span v-if="sizeText" class="sub-text">({{ sizeText }})</span>
</template>
<span v-else>-</span>
</FormItem>
<FormItem label="创建时间:">
<span>{{ formatTime(row?.createTime) }}</span>
@@ -31,10 +37,18 @@
<Button type="primary" @click="show = false">确定</Button>
</template>
</Modal>
<Modal v-model="previewVisible" title="响应结果预览" width="760" :mask-closable="true">
<pre class="json-pre">{{ previewText }}</pre>
<template #footer>
<Button @click="previewVisible = false">关闭</Button>
</template>
</Modal>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { computed, ref } from 'vue';
import useCurrentInstance from '@/utils/hook/useCurrentInstance';
import Utils from '@/utils';
interface Props {
@@ -81,6 +95,32 @@ function formatTime(val: any): string {
if (!val) return '-';
return Utils.dateFormat(val, 'yyyy-MM-dd HH:mm:ss');
}
const { $conf } = useCurrentInstance();
const previewVisible = ref(false);
const previewText = ref('');
const canPreview = computed(() => Utils.canPreview(props.row?.responseSize));
const sizeText = computed(() =>
props.row?.responseSize != null && props.row.responseSize > 0 ? Utils.getFileSize(props.row.responseSize) : ''
);
function handleDownload() {
if (!props.row?.id) return;
location.href = `${$conf.serviceHost}/api/oapilog/Download/${props.row.id}`;
}
async function handlePreview() {
if (!props.row?.id) return;
previewText.value = '加载中…';
previewVisible.value = true;
try {
const res = await fetch(`${$conf.serviceHost}/api/oapilog/Download/${props.row.id}`, { credentials: 'include' });
previewText.value = formatJson(await res.text());
} catch (e: any) {
previewText.value = '预览加载失败:' + (e?.message || e);
}
}
</script>
<style lang="scss" scoped>
@@ -116,4 +156,32 @@ function formatTime(val: any): string {
line-height: 1.6;
}
}
.download-link {
color: #577afb;
&:hover {
color: #3d5afe;
}
}
.sub-text {
margin-left: 8px;
color: #999;
font-size: 12px;
}
.json-pre {
margin: 0;
padding: 10px 12px;
background: #f7f8fa;
border: 1px solid #e8eaec;
border-radius: 4px;
max-height: 420px;
overflow: auto;
white-space: pre-wrap;
word-break: break-all;
font-size: 13px;
line-height: 1.6;
}
</style>
+7
View File
@@ -151,6 +151,13 @@ const columns = [
align: 'right',
render: (h: any, { row }: any) => h('span', row.costTime != null ? `${row.costTime} ms` : '-'),
},
{
title: '响应',
key: 'responseSize',
width: 90,
align: 'right',
render: (h: any, { row }: any) => h('span', row.responseSize != null && row.responseSize > 0 ? Utils.getFileSize(row.responseSize) : '-'),
},
{
title: '调用时间',
key: 'createTime',
+47 -2
View File
@@ -20,7 +20,12 @@
<pre class="json-pre">{{ formatJson(row?.requestParams) }}</pre>
</FormItem>
<FormItem label="数据:">
<a v-if="row?.id" href="javascript:;" class="download-link" @click.prevent="handleDownload">下载</a>
<template v-if="row?.id">
<a href="javascript:;" class="download-link" @click.prevent="handleDownload">下载</a>
<a v-if="canPreview" href="javascript:;" class="download-link" style="margin-left: 12px"
@click.prevent="handlePreview">在线预览</a>
<span v-if="sizeText" class="sub-text">({{ sizeText }})</span>
</template>
<span v-else>-</span>
</FormItem>
<FormItem label="创建时间:">
@@ -32,10 +37,17 @@
<Button type="primary" @click="show = false">确定</Button>
</template>
</Modal>
<Modal v-model="previewVisible" title="响应结果预览" width="760" :mask-closable="true">
<pre class="json-pre">{{ previewText }}</pre>
<template #footer>
<Button @click="previewVisible = false">关闭</Button>
</template>
</Modal>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { computed, ref } from 'vue';
import useCurrentInstance from '@/utils/hook/useCurrentInstance';
import Utils from '@/utils';
@@ -83,6 +95,25 @@ function handleDownload() {
if (!props.row?.id) return;
location.href = `${$conf.serviceHost}/api/oplog/Download/${props.row.id}`;
}
const previewVisible = ref(false);
const previewText = ref('');
const canPreview = computed(() => Utils.canPreview(props.row?.responseSize));
const sizeText = computed(() =>
props.row?.responseSize != null && props.row.responseSize > 0 ? Utils.getFileSize(props.row.responseSize) : ''
);
async function handlePreview() {
if (!props.row?.id) return;
previewText.value = '加载中…';
previewVisible.value = true;
try {
const res = await fetch(`${$conf.serviceHost}/api/oplog/Download/${props.row.id}`, { credentials: 'include' });
previewText.value = formatJson(await res.text());
} catch (e: any) {
previewText.value = '预览加载失败:' + (e?.message || e);
}
}
</script>
<style lang="scss" scoped>
@@ -126,4 +157,18 @@ function handleDownload() {
}
}
}
.json-pre {
margin: 0;
padding: 10px 12px;
background: #f7f8fa;
border: 1px solid #e8eaec;
border-radius: 4px;
max-height: 420px;
overflow: auto;
white-space: pre-wrap;
word-break: break-all;
font-size: 13px;
line-height: 1.6;
}
</style>
+7
View File
@@ -152,6 +152,13 @@ const columns = [
align: 'right',
render: (h: any, { row }: any) => h('span', row.costTime != null ? `${row.costTime} ms` : '-'),
},
{
title: '响应',
key: 'responseSize',
width: 90,
align: 'right',
render: (h: any, { row }: any) => h('span', row.responseSize != null && row.responseSize > 0 ? Utils.getFileSize(row.responseSize) : '-'),
},
{
title: '操作时间ed',
key: 'createTime',
+7
View File
@@ -156,6 +156,13 @@ const columns = [
align: 'right',
render: (h: any, { row }: any) => h('span', row.costMs != null ? `${row.costMs}` : '-'),
},
{
title: '请求体',
key: 'payloadSize',
width: 100,
align: 'right',
render: (h: any, { row }: any) => h('span', row.payloadSize != null && row.payloadSize > 0 ? Utils.getFileSize(row.payloadSize) : '-'),
},
{
title: '操作',
key: 'action',
+57 -2
View File
@@ -43,7 +43,12 @@
<span>{{ formatTime(row?.createdAt) }}</span>
</FormItem>
<FormItem label="请求体:">
<a v-if="row?.id" href="javascript:;" class="download-link" @click.prevent="handleDownload">下载请求体</a>
<template v-if="row?.id">
<a href="javascript:;" class="download-link" @click.prevent="handleDownload">下载请求体</a>
<a v-if="canPreview" href="javascript:;" class="download-link" style="margin-left: 12px"
@click.prevent="handlePreview">在线预览</a>
<span v-if="sizeText" class="sub-text">({{ sizeText }})</span>
</template>
<span v-else>-</span>
</FormItem>
</Form>
@@ -51,10 +56,17 @@
<Button type="primary" @click="show = false">确定</Button>
</template>
</Modal>
<Modal v-model="previewVisible" title="请求体预览" width="760" :mask-closable="true">
<pre class="json-pre">{{ previewText }}</pre>
<template #footer>
<Button @click="previewVisible = false">关闭</Button>
</template>
</Modal>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { computed, ref } from 'vue';
import useCurrentInstance from '@/utils/hook/useCurrentInstance';
import Utils from '@/utils';
@@ -87,11 +99,40 @@ function onVisibleChange(val: boolean) {
const { $conf } = useCurrentInstance();
const previewVisible = ref(false);
const previewText = ref('');
const canPreview = computed(() => Utils.canPreview(props.row?.payloadSize));
const sizeText = computed(() =>
props.row?.payloadSize != null && props.row.payloadSize > 0 ? Utils.getFileSize(props.row.payloadSize) : ''
);
function handleDownload() {
if (!props.row?.id) return;
location.href = `${$conf.serviceHost}/api/push/Download/${props.row.id}`;
}
async function handlePreview() {
if (!props.row?.id) return;
previewText.value = '加载中…';
previewVisible.value = true;
try {
const res = await fetch(`${$conf.serviceHost}/api/push/Download/${props.row.id}`, { credentials: 'include' });
previewText.value = formatJson(await res.text());
} catch (e: any) {
previewText.value = '预览加载失败:' + (e?.message || e);
}
}
function formatJson(val: string): string {
if (!val) return '-';
try {
return JSON.stringify(JSON.parse(val), null, 2);
} catch {
return val;
}
}
function formatTime(val: any): string {
if (!val) return '-';
return Utils.dateFormat(val, 'yyyy-MM-dd HH:mm:ss');
@@ -135,4 +176,18 @@ function formatTime(val: any): string {
}
}
}
.json-pre {
margin: 0;
padding: 10px 12px;
background: #f7f8fa;
border: 1px solid #e8eaec;
border-radius: 4px;
max-height: 420px;
overflow: auto;
white-space: pre-wrap;
word-break: break-all;
font-size: 13px;
line-height: 1.6;
}
</style>