This commit is contained in:
张成
2026-04-01 14:23:57 +08:00
parent 09368d2a95
commit 084c437096
8 changed files with 268 additions and 2 deletions

View File

@@ -0,0 +1,11 @@
class ApiCallLogServer {
async page(row) {
return window.framework.http.post("/biz_api_call_log/page", row);
}
async exportRows(row) {
return window.framework.http.post("/biz_api_call_log/export", row);
}
}
export default new ApiCallLogServer();

View File

@@ -8,6 +8,7 @@ import SubscriptionTokens from '../views/subscription/tokens.vue'
import SubscriptionPayment from '../views/subscription/payment.vue'
import SubscriptionUsage from '../views/subscription/usage.vue'
import SubscriptionAuditLog from '../views/subscription/audit_log.vue'
import SubscriptionApiCallLog from '../views/subscription/api_call_log.vue'
const componentMap = {
// 与 sys_menu.component 一致:库中常见为 home/index 或 home/index.vue
@@ -22,6 +23,7 @@ const componentMap = {
'subscription/payment': SubscriptionPayment,
'subscription/usage': SubscriptionUsage,
'subscription/audit': SubscriptionAuditLog,
'subscription/api_call_log': SubscriptionApiCallLog,
}
export default componentMap;

View File

@@ -0,0 +1,202 @@
<template>
<div class="content-view">
<div class="table-head-tool">
<Form ref="formInline" :model="param.seachOption" inline :label-width="80">
<FormItem label="筛选">
<Select v-model="param.seachOption.key" style="width: 140px" @on-change="onSearchKeyChange">
<Option value="user_id">user_id</Option>
<Option value="token_id">token_id</Option>
<Option value="api_path">api_path</Option>
<Option value="http_method">http_method</Option>
<Option value="status_code">status_code</Option>
<Option value="response_body">response_body</Option>
</Select>
<Input v-model="param.seachOption.value" class="ml10" style="width: 260px" placeholder="支持数值或路径片段" />
</FormItem>
<FormItem>
<Button type="primary" @click="load(1)">查询</Button>
<Dropdown trigger="click" class="ml10" @on-click="on_toolbar_more">
<Button type="default">更多 <Icon type="ios-arrow-down" /></Button>
<DropdownMenu slot="list">
<DropdownItem name="export">导出 CSV</DropdownItem>
</DropdownMenu>
</Dropdown>
</FormItem>
</Form>
</div>
<div class="table-body">
<Table :columns="columns" :data="rows" border stripe />
<Modal v-model="detailVisible" title="响应结果(日志内已截断)" width="760" footer-hide>
<Input v-model="detailBody" type="textarea" :rows="20" readonly class="resp-detail-ta" />
</Modal>
<div class="table-page-bar">
<Page
:total="total"
:current="param.pageOption.page"
:page-size="param.pageOption.pageSize"
show-total
@on-change="onPage"
@on-page-size-change="onSize"
/>
</div>
</div>
</div>
</template>
<script>
import apiCallLogServer from '@/api/subscription/api_call_log_server.js'
import { downloadCsvFromRows } from '@/utils/csvExport.js'
export default {
name: 'SubscriptionApiCallLog',
data() {
return {
detailVisible: false,
detailBody: '',
rows: [],
total: 0,
param: {
seachOption: { key: 'user_id', value: '' },
pageOption: { page: 1, pageSize: 20, total: 0 },
},
}
},
computed: {
columns() {
return [
{ title: 'ID', key: 'id', width: 80 },
{ title: '用户', key: 'user_id', width: 96 },
{ title: 'Token', key: 'token_id', width: 96 },
{ title: '接口路径', key: 'api_path', minWidth: 200, ellipsis: true, tooltip: true },
{ title: '方法', key: 'http_method', width: 88 },
{ title: 'HTTP状态', key: 'status_code', width: 96 },
{
title: '响应结果',
key: 'response_body',
minWidth: 220,
render: (h, p) => {
const s = p.row.response_body == null ? '' : String(p.row.response_body)
if (!s) return h('span', { class: 'muted' }, '—')
const short = s.length > 72 ? `${s.slice(0, 72)}` : s
return h('div', { class: 'resp-cell' }, [
h('span', { class: 'resp-preview', attrs: { title: s } }, short),
h(
'Button',
{
props: { type: 'primary', size: 'small' },
class: 'ml8',
on: {
click: (e) => {
e.stopPropagation()
this.open_response_detail(s)
},
},
},
'查看'
),
])
},
},
{ title: '耗时ms', key: 'response_time', width: 96 },
{ title: '统计日', key: 'call_date', width: 120 },
{ title: '创建时间', key: 'created_at', minWidth: 168 },
]
},
},
mounted() {
this.load(1)
},
methods: {
onSearchKeyChange() {
this.param.seachOption.value = ''
},
open_response_detail(text) {
this.detailBody = text
this.detailVisible = true
},
async load(page) {
if (page) this.param.pageOption.page = page
const res = await apiCallLogServer.page({ param: this.param })
if (res && res.code === 0) {
this.rows = res.data.rows || []
this.total = res.data.count || 0
} else {
this.$Message.error((res && res.message) || '加载失败')
}
},
onPage(p) {
this.param.pageOption.page = p
this.load()
},
onSize(s) {
this.param.pageOption.pageSize = s
this.load(1)
},
on_toolbar_more(name) {
if (name === 'export') this.doExport()
},
async doExport() {
const res = await apiCallLogServer.exportRows({ param: this.param })
if (res && res.code === 0 && res.data && res.data.rows) {
downloadCsvFromRows(res.data.rows, 'biz_api_call_log.csv')
this.$Message.success('已导出')
} else {
this.$Message.error((res && res.message) || '导出失败')
}
},
},
}
</script>
<style lang="less" scoped>
.content-view {
width: 100%;
max-width: 1720px;
margin: 0 auto;
padding: 26px 36px 36px;
box-sizing: border-box;
}
.table-head-tool {
margin-bottom: 16px;
}
.ml10 {
margin-left: 10px;
}
.table-page-bar {
margin-top: 12px;
text-align: right;
}
.muted {
color: #c5c8ce;
}
.resp-cell {
display: flex;
align-items: flex-start;
gap: 8px;
}
.resp-preview {
flex: 1;
min-width: 0;
font-size: 12px;
line-height: 1.4;
word-break: break-all;
}
.ml8 {
margin-left: 8px;
flex-shrink: 0;
}
.resp-detail-ta :deep(textarea) {
font-family: Consolas, 'Courier New', monospace;
font-size: 12px;
line-height: 1.45;
}
</style>