liuyun f1cb5723b2 feat: wire all management pages to backend (config/dict/org/resource/role/audit/login-log)
- 401 自动登出:http 会话过期回调 + main.ts 注册(清 menu/tabs + 跳登录)
- 参数配置/字典/组织/资源/角色/操作日志/登录日志对接真实接口
- 角色管理新增权限分配树(el-tree 勾选资源,/roles/:code/resources)
- 操作日志/登录日志只读页(ty-search-form + ty-table + useRequest)
- listOrgs 迁到 api/org.ts

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-11 12:45:53 +08:00

116 lines
3.1 KiB
Vue

<template>
<div class="log-page">
<el-card class="log-page__search" shadow="never">
<ty-search-form :schema="searchSchema" :model-value="searchQuery" @search="handleSearch" @reset="handleReset" />
</el-card>
<el-card class="log-page__table" shadow="never">
<div class="log-page__table-body">
<ty-table
:columns="columns"
:data="list"
:loading="loading"
:total="total"
v-model:page="page"
v-model:page-size="pageSize"
/>
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from 'vue'
import type { JsonSchema, TableColumn } from '@/types/crud'
import { useRequest } from '@/composables/useRequest'
import { listAuditLogs } from '@/api/audit-log'
const STATUS_MAP = {
success: { label: '成功', type: 'success' },
fail: { label: '失败', type: 'danger' },
} as const
const columns: TableColumn[] = [
{ prop: 'username', label: '操作人', width: 100 },
{ prop: 'module', label: '模块', width: 100 },
{ prop: 'action', label: '动作', width: 100 },
{ prop: 'method', label: '方法', width: 80, align: 'center' },
{ prop: 'path', label: '路径', minWidth: 180 },
{ prop: 'status', label: '状态', width: 80, align: 'center', type: 'tag', tagMap: STATUS_MAP },
{ prop: 'ip', label: 'IP', width: 130 },
{ prop: 'durationMs', label: '耗时(ms)', width: 90, align: 'center' },
{ prop: 'createdAt', label: '时间', width: 160, type: 'datetime' },
]
const searchSchema: JsonSchema = {
type: 'object',
properties: {
username: { type: 'string', title: '操作人', placeholder: '请输入操作人' },
module: { type: 'string', title: '模块', placeholder: '请输入模块' },
status: {
type: 'string',
title: '状态',
enum: ['success', 'fail'],
enumNames: ['成功', '失败'],
placeholder: '全部',
},
},
}
const page = ref(1)
const pageSize = ref(10)
const searchQuery = reactive({ username: '', module: '', status: '' })
const appliedQuery = reactive({ username: '', module: '', status: '' })
const { data, loading, run } = useRequest(
(signal: AbortSignal) => listAuditLogs({ page: page.value, pageSize: pageSize.value, ...appliedQuery }, signal),
{ manual: true },
)
const list = computed(() => data.value?.list ?? [])
const total = computed(() => data.value?.total ?? 0)
function handleSearch(): void {
appliedQuery.username = searchQuery.username
appliedQuery.module = searchQuery.module
appliedQuery.status = searchQuery.status
page.value = 1
run()
}
function handleReset(): void {
appliedQuery.username = ''
appliedQuery.module = ''
appliedQuery.status = ''
page.value = 1
run()
}
onMounted(run)
watch([page, pageSize], () => {
run()
})
</script>
<style scoped lang="scss">
.log-page {
@apply flex h-full flex-col;
}
.log-page__search {
@apply mb-md flex-shrink-0;
}
.log-page__table {
@apply flex min-h-0 flex-1 flex-col;
:deep(.el-card__body) {
@apply flex min-h-0 flex-1 flex-col;
}
}
.log-page__table-body {
@apply min-h-0 flex-1;
}
</style>