Files
UserbaseFront/src/views/oplog/OperationLogList.vue
T
chenxing 3b0f29b0d1
build / build (push) Successful in 1m58s
feat(接入规范验收修订轮): 操作列收口text形态+通道撑满高度+主题色批量落地
- 操作列:group-user/操作日志/开放接口日志/推送日志四页手写 h('a') 迁 actionColumn({mode:"text"}),颜色归通道 var(--brand);推送日志「重试」改 disabledWhen 置灰保位
- group-user 状态列 h('Tag')/h('Badge') 字符串组件名改 import 真组件(Vue3 坑)
- 高度:日志三页根节点改 page-wrapper yh-fill;TablePageAlovaNew 对齐 flow-center 同构版(.table-region + useElementHeight 实测 + 页长记忆);RoleAuthzList 转 tree-wrapper 骨架(外边界/间隙 16)
- 主题色:当前学校/当前组/当前角色、应用管理左类型面板、角色管理卡片操作位硬编码蓝改 var(--brand) 系令牌
- 顶栏房子图标钮删除(§4:首页入口收敛面包屑「首页」)
- 含 2026-10-10 试点未提交件:tableActions.ts 七函数、main.scss .op-cell、AppList/ModuleManage/RoleUserManage 迁移
2026-10-10 23:39:32 +08:00

189 lines
6.2 KiB
Vue

<template>
<div class="page-wrapper yh-fill">
<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" />
<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" />
</div>
<OperationLogDetailModal v-model="detailVisible" :row="currentRow" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import TablePageAlovaNew from '@/components/TablePageAlovaNew.vue';
import OperationLogDetailModal from './OperationLogDetailModal.vue';
import Utils from '@/utils';
import { actionColumn } from '@/utils/tableActions';
defineOptions({ name: 'OperationLogList' });
const tableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null);
interface DataParams {
keyword?: string;
success?: string;
logLevel?: number;
startTime?: string;
endTime?: string;
}
const dataParams = ref<DataParams>({
keyword: 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).SysOperationLog.operationLogList({
params: clean({
keyword: dataParams.value.keyword?.trim() || undefined,
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 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: 'username', width: 140 },
{
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: 'responseSize',
width: 90,
align: 'right',
render: (h: any, { row }: any) => h('span', row.responseSize != null && row.responseSize > 0 ? Utils.getFileSize(row.responseSize) : '-'),
},
{
title: '操作时间',
key: 'createTime',
width: 180,
render: (h: any, { row }: any) => h('span', row.createTime ? Utils.dateFormat(row.createTime, 'yyyy-MM-dd HH:mm:ss') : '-'),
},
// 文字链形态:颜色走通道 .operating-link = var(--brand),随主题档位变化
actionColumn([
{ key: 'detail', label: '查看详情', on: (r: any) => openDetail(r) },
], { mode: 'text', width: 100, fixed: 'right' }),
];
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>