422 lines
11 KiB
TypeScript
422 lines
11 KiB
TypeScript
/**
|
|
* KRA - System Error Log Page
|
|
* 系统错误日志页面 - 查看、删除错误日志
|
|
*/
|
|
import React, { useState, useEffect } from 'react';
|
|
import {
|
|
Card,
|
|
Table,
|
|
Button,
|
|
Space,
|
|
Form,
|
|
Input,
|
|
DatePicker,
|
|
Drawer,
|
|
Descriptions,
|
|
Tag,
|
|
message,
|
|
Tooltip,
|
|
Popconfirm,
|
|
} from 'antd';
|
|
import type { ColumnsType } from 'antd/es/table';
|
|
import {
|
|
SearchOutlined,
|
|
ReloadOutlined,
|
|
DeleteOutlined,
|
|
InfoCircleOutlined,
|
|
QuestionCircleOutlined,
|
|
DownOutlined,
|
|
UpOutlined,
|
|
RobotOutlined,
|
|
} from '@ant-design/icons';
|
|
import { PageContainer } from '@ant-design/pro-components';
|
|
import {
|
|
getSysErrorList,
|
|
getSysErrorById,
|
|
deleteSysError,
|
|
deleteSysErrorByIds,
|
|
} from '@/services/kratos/sysError';
|
|
import { formatDate } from '@/utils/date';
|
|
import styles from './index.less';
|
|
|
|
const { RangePicker } = DatePicker;
|
|
|
|
interface SysError {
|
|
ID: number;
|
|
CreatedAt: string;
|
|
form: string;
|
|
level: string;
|
|
status: string;
|
|
info: string;
|
|
solution?: string;
|
|
}
|
|
|
|
// Level tag mapping
|
|
const levelTagMap: Record<string, string> = {
|
|
fatal: 'error',
|
|
error: 'warning',
|
|
};
|
|
|
|
const levelLabelMap: Record<string, string> = {
|
|
fatal: '致命错误',
|
|
error: '一般错误',
|
|
};
|
|
|
|
// Status tag mapping
|
|
const statusTagMap: Record<string, string> = {
|
|
'未处理': 'default',
|
|
'处理中': 'processing',
|
|
'处理完成': 'success',
|
|
'处理失败': 'error',
|
|
};
|
|
|
|
const statusLabelMap: Record<string, string> = {
|
|
'未处理': '未处理',
|
|
'处理中': '处理中',
|
|
'处理完成': '处理完成',
|
|
'处理失败': '处理失败',
|
|
};
|
|
|
|
const SysErrorPage: React.FC = () => {
|
|
const [form] = Form.useForm();
|
|
const [loading, setLoading] = useState(false);
|
|
const [dataSource, setDataSource] = useState<SysError[]>([]);
|
|
const [total, setTotal] = useState(0);
|
|
const [current, setCurrent] = useState(1);
|
|
const [pageSize, setPageSize] = useState(10);
|
|
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
|
const [showAllQuery, setShowAllQuery] = useState(false);
|
|
|
|
// Detail drawer
|
|
const [detailVisible, setDetailVisible] = useState(false);
|
|
const [detailData, setDetailData] = useState<SysError | null>(null);
|
|
|
|
// Fetch table data
|
|
const fetchData = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const values = form.getFieldsValue();
|
|
const params: any = {
|
|
page: current,
|
|
pageSize,
|
|
...values,
|
|
};
|
|
if (values.createdAtRange) {
|
|
params.startCreatedAt = values.createdAtRange[0]?.format('YYYY-MM-DD HH:mm:ss');
|
|
params.endCreatedAt = values.createdAtRange[1]?.format('YYYY-MM-DD HH:mm:ss');
|
|
delete params.createdAtRange;
|
|
}
|
|
const res = await getSysErrorList(params);
|
|
if (res.code === 0) {
|
|
setDataSource(res.data?.list || []);
|
|
setTotal(res.data?.total || 0);
|
|
}
|
|
} catch (error) {
|
|
console.error('获取错误日志列表失败:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
fetchData();
|
|
}, [current, pageSize]);
|
|
|
|
// Search
|
|
const handleSearch = () => {
|
|
setCurrent(1);
|
|
fetchData();
|
|
};
|
|
|
|
// Reset
|
|
const handleReset = () => {
|
|
form.resetFields();
|
|
setCurrent(1);
|
|
fetchData();
|
|
};
|
|
|
|
// Delete single row
|
|
const handleDelete = async (record: SysError) => {
|
|
try {
|
|
const res = await deleteSysError({ ID: record.ID });
|
|
if (res.code === 0) {
|
|
message.success('删除成功');
|
|
fetchData();
|
|
}
|
|
} catch (error) {
|
|
message.error('删除失败');
|
|
}
|
|
};
|
|
|
|
// Batch delete
|
|
const handleBatchDelete = async () => {
|
|
if (selectedRowKeys.length === 0) {
|
|
message.warning('请选择要删除的数据');
|
|
return;
|
|
}
|
|
try {
|
|
const res = await deleteSysErrorByIds({ IDs: selectedRowKeys as number[] });
|
|
if (res.code === 0) {
|
|
message.success('删除成功');
|
|
setSelectedRowKeys([]);
|
|
fetchData();
|
|
}
|
|
} catch (error) {
|
|
message.error('删除失败');
|
|
}
|
|
};
|
|
|
|
// View details
|
|
const handleViewDetail = async (record: SysError) => {
|
|
try {
|
|
const res = await getSysErrorById({ ID: record.ID });
|
|
if (res.code === 0) {
|
|
setDetailData(res.data?.reSysError || res.data);
|
|
setDetailVisible(true);
|
|
}
|
|
} catch (error) {
|
|
message.error('获取详情失败');
|
|
}
|
|
};
|
|
|
|
// Get AI solution (placeholder - would need backend support)
|
|
const handleGetSolution = async (id: number) => {
|
|
message.info('AI方案功能需要后端支持');
|
|
};
|
|
|
|
|
|
const columns: ColumnsType<SysError> = [
|
|
{
|
|
title: '日期',
|
|
dataIndex: 'CreatedAt',
|
|
key: 'CreatedAt',
|
|
width: 180,
|
|
sorter: true,
|
|
render: (text) => formatDate(text),
|
|
},
|
|
{
|
|
title: '错误来源',
|
|
dataIndex: 'form',
|
|
key: 'form',
|
|
width: 120,
|
|
},
|
|
{
|
|
title: '错误等级',
|
|
dataIndex: 'level',
|
|
key: 'level',
|
|
width: 120,
|
|
render: (level) => (
|
|
<Tag color={levelTagMap[level] || 'default'}>
|
|
{levelLabelMap[level] || '一般错误'}
|
|
</Tag>
|
|
),
|
|
},
|
|
{
|
|
title: '处理状态',
|
|
dataIndex: 'status',
|
|
key: 'status',
|
|
width: 140,
|
|
render: (status) => (
|
|
<Tag color={statusTagMap[status] || 'default'}>
|
|
{statusLabelMap[status] || '未处理'}
|
|
</Tag>
|
|
),
|
|
},
|
|
{
|
|
title: '错误内容',
|
|
dataIndex: 'info',
|
|
key: 'info',
|
|
width: 240,
|
|
ellipsis: {
|
|
showTitle: false,
|
|
},
|
|
render: (text) => (
|
|
<Tooltip placement="topLeft" title={text}>
|
|
{text}
|
|
</Tooltip>
|
|
),
|
|
},
|
|
{
|
|
title: '解决方案',
|
|
dataIndex: 'solution',
|
|
key: 'solution',
|
|
width: 120,
|
|
ellipsis: {
|
|
showTitle: false,
|
|
},
|
|
render: (text) => (
|
|
<Tooltip placement="topLeft" title={text}>
|
|
{text || '-'}
|
|
</Tooltip>
|
|
),
|
|
},
|
|
{
|
|
title: '操作',
|
|
key: 'action',
|
|
fixed: 'right',
|
|
width: 280,
|
|
render: (_, record) => (
|
|
<Space>
|
|
{record.status !== '处理中' && (
|
|
<Button
|
|
type="link"
|
|
icon={<RobotOutlined />}
|
|
onClick={() => handleGetSolution(record.ID)}
|
|
>
|
|
方案
|
|
</Button>
|
|
)}
|
|
<Button
|
|
type="link"
|
|
icon={<InfoCircleOutlined />}
|
|
onClick={() => handleViewDetail(record)}
|
|
>
|
|
查看
|
|
</Button>
|
|
<Popconfirm
|
|
title="确定要删除吗?"
|
|
onConfirm={() => handleDelete(record)}
|
|
okText="确定"
|
|
cancelText="取消"
|
|
>
|
|
<Button type="link" danger icon={<DeleteOutlined />}>
|
|
删除
|
|
</Button>
|
|
</Popconfirm>
|
|
</Space>
|
|
),
|
|
},
|
|
];
|
|
|
|
return (
|
|
<PageContainer>
|
|
{/* Search Form */}
|
|
<Card className={styles.searchCard}>
|
|
<Form form={form} layout="inline" onFinish={handleSearch}>
|
|
<Form.Item
|
|
name="createdAtRange"
|
|
label={
|
|
<span>
|
|
创建日期
|
|
<Tooltip title="搜索范围是开始日期(包含)至结束日期(不包含)">
|
|
<QuestionCircleOutlined style={{ marginLeft: 4 }} />
|
|
</Tooltip>
|
|
</span>
|
|
}
|
|
>
|
|
<RangePicker showTime style={{ width: 380 }} />
|
|
</Form.Item>
|
|
<Form.Item name="form" label="错误来源">
|
|
<Input placeholder="搜索条件" />
|
|
</Form.Item>
|
|
<Form.Item name="info" label="错误内容">
|
|
<Input placeholder="搜索条件" />
|
|
</Form.Item>
|
|
{showAllQuery && (
|
|
<>
|
|
{/* Additional query fields can be added here */}
|
|
</>
|
|
)}
|
|
<Form.Item>
|
|
<Space>
|
|
<Button type="primary" icon={<SearchOutlined />} htmlType="submit">
|
|
查询
|
|
</Button>
|
|
<Button icon={<ReloadOutlined />} onClick={handleReset}>
|
|
重置
|
|
</Button>
|
|
<Button
|
|
type="link"
|
|
icon={showAllQuery ? <UpOutlined /> : <DownOutlined />}
|
|
onClick={() => setShowAllQuery(!showAllQuery)}
|
|
>
|
|
{showAllQuery ? '收起' : '展开'}
|
|
</Button>
|
|
</Space>
|
|
</Form.Item>
|
|
</Form>
|
|
</Card>
|
|
|
|
{/* Table */}
|
|
<Card>
|
|
<div className={styles.toolbar}>
|
|
<Space>
|
|
<Popconfirm
|
|
title="确定要删除选中的数据吗?"
|
|
onConfirm={handleBatchDelete}
|
|
okText="确定"
|
|
cancelText="取消"
|
|
disabled={selectedRowKeys.length === 0}
|
|
>
|
|
<Button
|
|
icon={<DeleteOutlined />}
|
|
disabled={selectedRowKeys.length === 0}
|
|
>
|
|
删除
|
|
</Button>
|
|
</Popconfirm>
|
|
</Space>
|
|
</div>
|
|
<Table
|
|
rowKey="ID"
|
|
columns={columns}
|
|
dataSource={dataSource}
|
|
loading={loading}
|
|
rowSelection={{
|
|
selectedRowKeys,
|
|
onChange: setSelectedRowKeys,
|
|
}}
|
|
pagination={{
|
|
current,
|
|
pageSize,
|
|
total,
|
|
showSizeChanger: true,
|
|
showQuickJumper: true,
|
|
showTotal: (t) => `共 ${t} 条`,
|
|
onChange: (page, size) => {
|
|
setCurrent(page);
|
|
setPageSize(size);
|
|
},
|
|
}}
|
|
scroll={{ x: 1200 }}
|
|
/>
|
|
</Card>
|
|
|
|
{/* Detail Drawer */}
|
|
<Drawer
|
|
title="查看"
|
|
open={detailVisible}
|
|
onClose={() => setDetailVisible(false)}
|
|
width={600}
|
|
destroyOnClose
|
|
>
|
|
{detailData && (
|
|
<Descriptions column={2} bordered layout="vertical">
|
|
<Descriptions.Item label="错误来源">
|
|
{detailData.form}
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label="错误等级">
|
|
<Tag color={levelTagMap[detailData.level] || 'default'}>
|
|
{levelLabelMap[detailData.level] || '一般错误'}
|
|
</Tag>
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label="处理状态">
|
|
<Tag color={statusTagMap[detailData.status] || 'default'}>
|
|
{statusLabelMap[detailData.status] || '未处理'}
|
|
</Tag>
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label="错误内容" span={2}>
|
|
<pre className={styles.preContent}>{detailData.info}</pre>
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label="解决方案" span={2}>
|
|
<pre className={styles.preContent}>{detailData.solution || '-'}</pre>
|
|
</Descriptions.Item>
|
|
</Descriptions>
|
|
)}
|
|
</Drawer>
|
|
</PageContainer>
|
|
);
|
|
};
|
|
|
|
export default SysErrorPage;
|