添加开放接口日志
deploy-userbase-front / build-and-deploy (push) Successful in 3m0s

This commit is contained in:
chenxing
2026-08-20 10:23:47 +08:00
parent 35529fc671
commit 049965f51b
7 changed files with 493 additions and 15 deletions
-15
View File
@@ -1,15 +0,0 @@
<template>
<div class="page-wrapper">
<div class="main-container">
<Card :bordered="false" dis-hover>
<p slot="title">接口日志</p>
<Alert type="info" show-icon>功能开发中,敬请期待...</Alert>
</Card>
</div>
</div>
</template>
<script lang="ts" setup>
import { useDefaultStore } from "@/store";
const store = useDefaultStore();
</script>
+136
View File
@@ -0,0 +1,136 @@
<template>
<Modal v-model="show" :mask-closable="false" width="700" title="查看开放接口日志详情" @on-visible-change="onVisibleChange">
<Form :label-width="120" class="oapilog-detail-form">
<FormItem label="id:">
<span>{{ row?.id ?? '-' }}</span>
</FormItem>
<FormItem label="接口名称:">
<span>{{ row?.requestUri ?? '-' }}</span>
</FormItem>
<FormItem label="调用方应用:">
<span>{{ appText }}</span>
</FormItem>
<FormItem label="调用IP:">
<span>{{ row?.requestIp ?? '-' }}</span>
</FormItem>
<FormItem label="调用结果:">
<span :class="resultClass">{{ resultText }}</span>
</FormItem>
<FormItem label="调用参数:">
<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>
<span v-else>-</span>
</FormItem>
<FormItem label="创建时间:">
<span>{{ formatTime(row?.createTime) }}</span>
</FormItem>
</Form>
<template #footer>
<Button @click="show = false">取消</Button>
<Button type="primary" @click="show = false">确定</Button>
</template>
</Modal>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import useCurrentInstance from '@/utils/hook/useCurrentInstance';
import Utils from '@/utils';
interface Props {
modelValue: boolean;
row?: Record<string, any> | null;
}
const props = withDefaults(defineProps<Props>(), {
row: null,
});
const emit = defineEmits(['update:modelValue']);
const show = computed({
get: () => props.modelValue,
set: val => emit('update:modelValue', val),
});
const { $conf } = useCurrentInstance();
const appText = computed(() => {
if (!props.row?.callerAppName && !props.row?.callerAppKey) return '-';
const name = props.row.callerAppName || '-';
const key = props.row.callerAppKey ?? '';
return `${name}(appKey: ${key})`;
});
const isSuccess = computed(() => props.row?.statusCode === 200);
const resultText = computed(() => isSuccess.value ? '执行成功' : '执行失败');
const resultClass = computed(() => isSuccess.value ? 'text-success' : 'text-error');
function onVisibleChange(val: boolean) {
if (!val) emit('update:modelValue', false);
}
function formatJson(val: any): string {
if (!val) return '-';
try {
return JSON.stringify(JSON.parse(val), null, 2);
} catch {
return String(val);
}
}
function formatTime(val: any): string {
if (!val) return '-';
return Utils.dateFormat(val, 'yyyy-MM-dd HH:mm:ss');
}
function handleDownload() {
if (!props.row?.id) return;
location.href = `${$conf.serviceHost}/api/oapilog/Download/${props.row.id}`;
}
</script>
<style lang="scss" scoped>
.oapilog-detail-form {
:deep(.ivu-form-item) {
margin-bottom: 16px;
}
:deep(.ivu-form-item-label) {
color: #515a6e;
font-weight: 500;
}
.text-success {
color: #19be6b;
}
.text-error {
color: #ed4014;
}
.json-pre {
margin: 0;
padding: 10px 12px;
background: #f7f8fa;
border: 1px solid #e8eaec;
border-radius: 4px;
max-height: 200px;
overflow: auto;
white-space: pre-wrap;
word-break: break-all;
font-size: 13px;
line-height: 1.6;
}
.download-link {
color: #577afb;
&:hover {
color: #3d5afe;
}
}
}
</style>
+199
View File
@@ -0,0 +1,199 @@
<template>
<div class="main-wrapper">
<div class="main-container">
<div class="search-wrap fix">
<Input v-model="dataParams.keyword" placeholder="接口名称 / 应用名" clearable suffix="ios-search"
style="width: 220px" @on-search="handleSearch" @on-clear="handleSearch" />
<Input v-model="dataParams.callerAppKey" placeholder="应用Key" clearable style="width: 130px"
@on-enter="handleSearch" @on-clear="handleSearch" />
<Select v-model="dataParams.success" class="ml-12" clearable placeholder="调用结果" style="width: 120px"
@on-change="handleSearch">
<Option value="true">成功</Option>
<Option value="false">失败</Option>
</Select>
<Select v-model="dataParams.logLevel" class="ml-12" clearable placeholder="重要程度" style="width: 120px"
@on-change="handleSearch">
<Option :value="1">重要</Option>
<Option :value="2">次要</Option>
<Option :value="3">一般</Option>
</Select>
<DatePicker v-model="startDate" type="date" placeholder="开始时间" format="yyyy-MM-dd" :editable="false"
:clearable="true" class="ml-12" style="width: 140px" @on-change="v => onStartChange(v)" />
<DatePicker v-model="endDate" type="date" placeholder="结束时间" format="yyyy-MM-dd" :editable="false"
:clearable="true" class="ml-12" style="width: 140px" @on-change="v => onEndChange(v)" />
<Button type="primary" class="ml-12" @click="handleSearch">查询</Button>
</div>
<TablePageAlovaNew ref="tableRef" :dataApi="tableApi" :columns="columns" :tableHeight="tableHeight" />
</div>
<OapiLogDetailModal v-model="detailVisible" :row="currentRow" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import useCurrentInstance from '@/utils/hook/useCurrentInstance';
import TablePageAlovaNew from '@/components/TablePageAlovaNew.vue';
import OapiLogDetailModal from './OapiLogDetailModal.vue';
import Utils from '@/utils';
defineOptions({ name: 'OapiLogList' });
const { $utils } = useCurrentInstance();
const tableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null);
interface DataParams {
keyword?: string;
callerAppKey?: number;
success?: string;
logLevel?: number;
startTime?: string;
endTime?: string;
}
const dataParams = ref<DataParams>({
keyword: undefined,
callerAppKey: undefined,
success: undefined,
logLevel: undefined,
startTime: undefined,
endTime: undefined,
});
const startDate = ref<Date | null>(null);
const endDate = ref<Date | null>(null);
function onStartChange(val: string) {
dataParams.value.startTime = val ? `${val} 00:00:00` : undefined;
handleSearch();
}
function onEndChange(val: string) {
dataParams.value.endTime = val ? `${val} 23:59:59` : undefined;
handleSearch();
}
function handleSearch() {
tableRef.value?.refreshData();
}
function clean(obj: Record<string, any>) {
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined && v !== null && v !== ''));
}
const tableApi = (params: { pageIndex: number; pageSize: number }) => {
return (Apis as any).SysOapiLog.oapiLogList({
params: clean({
keyword: dataParams.value.keyword?.trim() || undefined,
callerAppKey: dataParams.value.callerAppKey,
success: dataParams.value.success,
logLevel: dataParams.value.logLevel,
startTime: dataParams.value.startTime,
endTime: dataParams.value.endTime,
pageIndex: params.pageIndex,
pageSize: params.pageSize,
}),
cacheFor: null,
});
};
const tableHeight = ref($utils.useTableHeight({ isOperat: false, isPage: true, isSearch: true }));
const logLevelMap: Record<number, { text: string; className: string }> = {
1: { text: '重要', className: 'text-error' },
2: { text: '次要', className: 'text-warning' },
3: { text: '一般', className: 'text-dark' },
};
const operationTypeMap: Record<string, string> = {
DELETE: '删除',
CREATE: '新增',
UPDATE: '更新',
QUERY: '查询',
};
const columns = [
{ type: 'index', title: '序号', width: 70, align: 'center' },
{ title: '接口名称', key: 'requestUri', minWidth: 220, ellipsis: true, tooltip: true },
{ title: '调用方应用', key: 'callerAppName', width: 150, ellipsis: true, tooltip: true },
{
title: '操作类型',
key: 'operationType',
width: 100,
align: 'center',
render: (h: any, { row }: any) => h('span', operationTypeMap[row.operationType] ?? row.operationType ?? '-'),
},
{
title: '重要程度',
key: 'logLevel',
width: 100,
align: 'center',
render: (h: any, { row }: any) => {
const cfg = logLevelMap[row.logLevel];
if (!cfg) return h('span', '-');
return h('span', { class: cfg.className }, cfg.text);
},
},
{
title: '状态码',
key: 'statusCode',
width: 100,
align: 'center',
render: (h: any, { row }: any) => h('span', {
style: { color: row.statusCode === 200 ? '#19be6b' : '#ed4014' },
}, row.statusCode ?? '-'),
},
{
title: '耗时(ms)',
key: 'costTime',
width: 110,
align: 'right',
render: (h: any, { row }: any) => h('span', row.costTime != null ? `${row.costTime} ms` : '-'),
},
{
title: '调用时间',
key: 'createTime',
width: 180,
render: (h: any, { row }: any) => h('span', row.createTime ? Utils.dateFormat(row.createTime, 'yyyy-MM-dd HH:mm:ss') : '-'),
},
{
title: '操作',
key: 'action',
width: 100,
align: 'center',
fixed: 'right' as const,
render: (h: any, { row }: any) =>
h('a', {
style: { color: '#577afb' },
onClick: () => openDetail(row),
}, '查看详情'),
},
];
const detailVisible = ref(false);
const currentRow = ref<Record<string, any> | null>(null);
function openDetail(row: any) {
currentRow.value = row;
detailVisible.value = true;
}
</script>
<style lang="scss" scoped>
.search-wrap {
.ivu-select,
.ivu-input-wrapper,
.ivu-date-picker {
vertical-align: middle;
}
}
</style>