Files
Ticket/web/src/features/logs/operation/index.vue
T
2026-04-30 10:47:26 +08:00

142 lines
3.9 KiB
Vue

<template>
<div class="page-shell">
<section class="page-card table-fill">
<div class="page-toolbar">
<n-form inline :show-label="false" class="toolbar-form">
<n-form-item>
<n-input v-model:value="query.keyword" clearable placeholder="用户/路径" />
</n-form-item>
<n-form-item>
<n-select
v-model:value="query.status"
clearable
placeholder="状态"
:options="statusOptions"
style="width: 140px"
/>
</n-form-item>
<n-form-item>
<n-space>
<n-button type="primary" @click="refetch">查询</n-button>
<n-button @click="reset">重置</n-button>
</n-space>
</n-form-item>
</n-form>
</div>
<div class="card-body card-body-fill table-fill">
<n-data-table
remote
size="small"
:columns="columns"
:data="rows"
:loading="operationLogQuery.isLoading.value"
:pagination="pagination"
:row-key="(row: OperationLogItem) => row.id"
@update:page="onPage"
@update:page-size="onPageSize"
/>
</div>
</section>
</div>
</template>
<script setup lang="ts">
import { computed, h, reactive } from 'vue'
import { NTag, type DataTableColumns } from 'naive-ui'
import { useQuery } from '@tanstack/vue-query'
import { listOperationLogsApi } from '@/api/logs'
import type { OperationLogItem } from '@/types/logs'
import { httpMethodLabel, operationTypeLabel, statusLabel, statusTagType } from '@/utils/display'
import { renderPagePrefix } from '@/utils/pagination'
const query = reactive({
page: 1,
pageSize: 20,
keyword: '',
status: null as string | null
})
const statusOptions = [
{ label: '成功', value: 'SUCCESS' },
{ label: '失败', value: 'FAIL' }
]
const operationLogQuery = useQuery({
queryKey: computed(() => ['logs', 'operation', { ...query }]),
queryFn: () =>
listOperationLogsApi({
page: query.page,
pageSize: query.pageSize,
keyword: query.keyword || undefined,
status: query.status || undefined
})
})
const rows = computed(() => operationLogQuery.data.value?.items ?? [])
const pagination = computed(() => ({
page: query.page,
pageSize: query.pageSize,
itemCount: operationLogQuery.data.value?.total ?? 0,
showSizePicker: true,
pageSizes: [10, 20, 50],
prefix: renderPagePrefix
}))
function refetch() {
query.page = 1
operationLogQuery.refetch()
}
function reset() {
query.keyword = ''
query.status = null
query.page = 1
operationLogQuery.refetch()
}
function onPage(page: number) {
query.page = page
}
function onPageSize(pageSize: number) {
query.pageSize = pageSize
query.page = 1
}
const columns = computed<DataTableColumns<OperationLogItem>>(() => [
{ title: '时间', key: 'createdAt', width: 170 },
{ title: '用户', key: 'username', minWidth: 110, render: (row) => row.username || '-' },
{
title: '操作',
key: 'operationName',
minWidth: 180,
render: (row) =>
h('div', { class: 'leading-5' }, [
h('div', { class: 'font-medium text-slate-900' }, row.operationName),
h('div', { class: 'text-xs text-slate-500' }, operationTypeLabel(row.operationType))
])
},
{ title: '方法', key: 'httpMethod', width: 90, render: (row) => httpMethodLabel(row.httpMethod) },
{ title: '路径', key: 'requestPath', minWidth: 220 },
{
title: '状态',
key: 'status',
width: 90,
render: (row) =>
h(
NTag,
{ type: statusTagType(row.status), size: 'small' },
{ default: () => statusLabel(row.status) }
)
},
{ title: '耗时', key: 'costMs', width: 90, render: (row) => `${row.costMs} ms` },
{
title: '错误信息',
key: 'errorMessage',
minWidth: 180,
render: (row) => row.errorMessage || '-'
}
])
</script>