Files
erp/apps/web/src/pages/settings/AuditLogViewer.tsx
iven 89fc482d99
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled
feat(web): 采用 UI UX Pro Max Soft UI Evolution 设计系统
从 Pinterest 风格切换到 Soft UI Evolution 设计系统,使用 UI UX Pro Max
推理引擎生成适合跨行业 ERP 业务用户的专业设计方案。

设计变更:
- 主色从 Pinterest Red (#e60023) 切换到 Trust Blue (#2563EB)
- 字体从系统默认切换到 Noto Sans SC(中文优先)
- 圆角从 16-20px 调整到 10-12px(专业但不夸张)
- 中性色从暖橄榄调切换到 Slate 石板蓝调
- 成功色 #103c25 → #059669,警告色 #b56e1a → #d97706
- 暗色模式从暖黑 (#1a1a18) 切换到深海军蓝 (#0f172a)

涉及文件:DESIGN.md + index.css + App.tsx + 24 个组件文件
2026-04-20 23:27:24 +08:00

207 lines
5.8 KiB
TypeScript

import { useState, useEffect, useCallback } from 'react';
import { Table, Select, Input, Tag, message, theme } from 'antd';
import type { ColumnsType, TablePaginationConfig } from 'antd/es/table';
import { listAuditLogs, type AuditLogItem, type AuditLogQuery } from '../../api/auditLogs';
const RESOURCE_TYPE_OPTIONS = [
{ value: 'user', label: '用户' },
{ value: 'role', label: '角色' },
{ value: 'organization', label: '组织' },
{ value: 'department', label: '部门' },
{ value: 'position', label: '岗位' },
{ value: 'process_instance', label: '流程实例' },
{ value: 'dictionary', label: '字典' },
{ value: 'menu', label: '菜单' },
{ value: 'setting', label: '设置' },
{ value: 'numbering_rule', label: '编号规则' },
];
const ACTION_STYLES: Record<string, { bg: string; color: string; text: string }> = {
create: { bg: '#ECFDF5', color: '#059669', text: '创建' },
update: { bg: '#eff6ff', color: '#2563eb', text: '更新' },
delete: { bg: '#FEF2F2', color: '#dc2626', text: '删除' },
};
function formatDateTime(value: string): string {
return new Date(value).toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
}
export default function AuditLogViewer() {
const [logs, setLogs] = useState<AuditLogItem[]>([]);
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(false);
const [query, setQuery] = useState<AuditLogQuery>({ page: 1, page_size: 20 });
const { token } = theme.useToken();
const isDark = token.colorBgContainer === '#111827' || token.colorBgContainer === 'rgb(17, 24, 39)';
const fetchLogs = useCallback(async (params: AuditLogQuery) => {
setLoading(true);
try {
const result = await listAuditLogs(params);
setLogs(result.data);
setTotal(result.total);
} catch {
message.error('加载审计日志失败');
}
setLoading(false);
}, []);
useEffect(() => {
fetchLogs(query);
}, [query, fetchLogs]);
const handleFilterChange = (field: keyof AuditLogQuery, value: string | undefined) => {
setQuery((prev) => ({
...prev,
[field]: value || undefined,
page: 1,
}));
};
const handleTableChange = (pagination: TablePaginationConfig) => {
setQuery((prev) => ({
...prev,
page: pagination.current,
page_size: pagination.pageSize,
}));
};
const columns: ColumnsType<AuditLogItem> = [
{
title: '操作',
dataIndex: 'action',
key: 'action',
width: 100,
render: (action: string) => {
const info = ACTION_STYLES[action] || { bg: '#f8fafc', color: '#475569', text: action };
return (
<Tag style={{
background: info.bg,
border: 'none',
color: info.color,
fontWeight: 500,
}}>
{info.text}
</Tag>
);
},
},
{
title: '资源类型',
dataIndex: 'resource_type',
key: 'resource_type',
width: 120,
render: (v: string) => (
<Tag style={{
background: isDark ? '#0f172a' : '#f8fafc',
border: 'none',
color: isDark ? '#CBD5E1' : '#475569',
}}>
{v}
</Tag>
),
},
{
title: '资源 ID',
dataIndex: 'resource_id',
key: 'resource_id',
width: 200,
ellipsis: true,
render: (v: string) => (
<span style={{ fontFamily: 'monospace', fontSize: 12, color: isDark ? '#94a3b8' : '#475569' }}>
{v}
</span>
),
},
{
title: '操作用户',
dataIndex: 'user_id',
key: 'user_id',
width: 200,
ellipsis: true,
render: (v: string) => (
<span style={{ fontFamily: 'monospace', fontSize: 12, color: isDark ? '#94a3b8' : '#475569' }}>
{v}
</span>
),
},
{
title: '时间',
dataIndex: 'created_at',
key: 'created_at',
width: 180,
render: (value: string) => (
<span style={{ color: isDark ? '#475569' : '#94a3b8', fontSize: 13 }}>
{formatDateTime(value)}
</span>
),
},
];
return (
<div>
{/* 筛选工具栏 */}
<div style={{
display: 'flex',
alignItems: 'center',
gap: 12,
marginBottom: 16,
padding: 12,
background: isDark ? '#111827' : '#FFFFFF',
borderRadius: 10,
border: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`,
}}>
<Select
allowClear
placeholder="资源类型"
style={{ width: 160 }}
options={RESOURCE_TYPE_OPTIONS}
value={query.resource_type}
onChange={(value) => handleFilterChange('resource_type', value)}
/>
<Input
allowClear
placeholder="操作用户 ID"
style={{ width: 240 }}
value={query.user_id ?? ''}
onChange={(e) => handleFilterChange('user_id', e.target.value)}
/>
<span style={{ fontSize: 13, color: isDark ? '#475569' : '#94a3b8', marginLeft: 'auto' }}>
{total}
</span>
</div>
{/* 表格 */}
<div style={{
background: isDark ? '#111827' : '#FFFFFF',
borderRadius: 12,
border: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`,
overflow: 'hidden',
}}>
<Table
rowKey="id"
columns={columns}
dataSource={logs}
loading={loading}
onChange={handleTableChange}
pagination={{
current: query.page,
pageSize: query.page_size,
total,
showSizeChanger: true,
showTotal: (t) => `${t}`,
}}
scroll={{ x: 900 }}
/>
</div>
</div>
);
}