XuqmGroup-Web/tenant-platform/src/views/logs/OperationLogView.vue

163 行
4.3 KiB
Vue

<template>
<div>
<h2 style="margin-bottom: 24px">操作日志</h2>
<el-card shadow="never">
<div class="toolbar responsive-toolbar">
<el-select v-model="filters.moduleType" placeholder="模块" style="width: 180px" clearable @change="handleModuleChange">
<el-option label="全部模块" value="" />
<el-option label="应用管理" value="APP" />
<el-option label="子账号管理" value="SUB_ACCOUNT" />
<el-option label="服务管理" value="SERVICE" />
<el-option label="密钥验证" value="APP_SECRET" />
<el-option label="邮箱验证" value="EMAIL_VERIFY" />
</el-select>
<el-button :loading="loading" @click="loadLogs">刷新</el-button>
</div>
<div class="table-wrap">
<el-table :data="logs" v-loading="loading" border stripe>
<el-table-column prop="createdAt" label="时间" width="180">
<template #default="{ row }">{{ formatTime(row.createdAt) }}</template>
</el-table-column>
<el-table-column prop="moduleType" label="模块" width="140">
<template #default="{ row }">
<el-tag size="small" effect="plain">{{ moduleLabel(row.moduleType) }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="action" label="动作" width="180" />
<el-table-column prop="resourceType" label="资源类型" width="140" />
<el-table-column prop="resourceId" label="资源ID" min-width="220" show-overflow-tooltip />
<el-table-column prop="operator" label="操作人" width="180" />
<el-table-column label="详情" min-width="280">
<template #default="{ row }">
<span class="detail">{{ formatDetail(row.detailJson) }}</span>
</template>
</el-table-column>
</el-table>
</div>
<el-pagination
style="margin-top: 16px"
layout="total, prev, pager, next"
:total="total"
:page-size="pageSize"
:current-page="page + 1"
@current-change="handlePageChange"
/>
</el-card>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import { operationLogApi, type TenantOperationLog } from '@/api/operationLog'
const loading = ref(false)
const logs = ref<TenantOperationLog[]>([])
const total = ref(0)
const page = ref(0)
const pageSize = ref(20)
const filters = reactive({
moduleType: '',
})
onMounted(() => {
loadLogs()
})
async function loadLogs() {
loading.value = true
try {
const res = await operationLogApi.list({
moduleType: filters.moduleType || undefined,
page: page.value,
size: pageSize.value,
})
const data = res.data.data
logs.value = data.content ?? []
total.value = data.totalElements ?? 0
} finally {
loading.value = false
}
}
function handlePageChange(nextPage: number) {
page.value = nextPage - 1
loadLogs()
}
function handleModuleChange() {
page.value = 0
loadLogs()
}
function moduleLabel(moduleType: string) {
return {
APP: '应用管理',
SUB_ACCOUNT: '子账号管理',
SERVICE: '服务管理',
APP_SECRET: '应用密钥',
EMAIL_VERIFY: '邮箱验证',
}[moduleType] ?? moduleType
}
function formatDetail(detailJson?: string) {
if (!detailJson) return '-'
try {
const parsed = JSON.parse(detailJson)
return typeof parsed === 'string' ? parsed : JSON.stringify(parsed, null, 0)
} catch {
return detailJson
}
}
function formatTime(value: string | number | null | undefined) {
if (value === null || value === undefined || value === '') return '-'
const date = new Date(value)
if (Number.isNaN(date.getTime())) return String(value)
return date.toLocaleString('zh-CN')
}
</script>
<style scoped>
.toolbar {
display: flex;
gap: 12px;
margin-bottom: 16px;
align-items: center;
}
.responsive-toolbar {
flex-wrap: wrap;
}
.table-wrap {
overflow-x: auto;
}
.detail {
display: inline-block;
max-width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 767px) {
.responsive-toolbar {
align-items: stretch;
}
.responsive-toolbar :deep(.el-select),
.responsive-toolbar :deep(.el-button) {
width: 100%;
}
.table-wrap :deep(.el-table) {
min-width: 900px;
}
}
</style>