|
|
@@ -0,0 +1,292 @@ |
|
|
|
import React, { useState, useEffect, useRef } from 'react'; |
|
|
|
import { useSetState } from 'ahooks'; |
|
|
|
import { Space, Table, Button, Input, FloatButton, Divider, Tag, Card, Tooltip } from 'antd'; |
|
|
|
import type { TableColumnsType, InputRef } from 'antd'; |
|
|
|
import type { ColumnType, TableProps } from 'antd/es/table'; |
|
|
|
import { t } from '@/utils/i18n'; |
|
|
|
import { DownloadOutlined, SearchOutlined, ExclamationCircleFilled } from '@ant-design/icons'; |
|
|
|
import { antdUtils } from '@/utils/antd'; |
|
|
|
import { useRequest } from '@/hooks/use-request'; |
|
|
|
import { formatDate } from '@/utils/formatTime' |
|
|
|
import apiLogService from '@/request/service/api-log'; |
|
|
|
import { |
|
|
|
ApiErrorLogPageReqVO, |
|
|
|
ApiErrorLogExportReqVO, |
|
|
|
ApiErrorLogVO |
|
|
|
} from '@/models'; |
|
|
|
|
|
|
|
|
|
|
|
type DataIndex = keyof ApiErrorLogVO; |
|
|
|
|
|
|
|
const mapOperation = (operation: number) => { |
|
|
|
if (operation === 1) { |
|
|
|
return (<Tag color="purple">通知</Tag>) |
|
|
|
} else if (operation === 2) { |
|
|
|
return (<Tag color="purple">新增</Tag>) |
|
|
|
} else if (operation === 3) { |
|
|
|
return (<Tag color="yellow">修改</Tag>) |
|
|
|
} else if (operation === 4) { |
|
|
|
return (<Tag color="red">删除</Tag>) |
|
|
|
} else if (operation === 5) { |
|
|
|
return (<Tag color="blue">导出</Tag>) |
|
|
|
} else { |
|
|
|
return (<Tag color="red">未知({operation})</Tag>) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
export default () => { |
|
|
|
const [dataSource, setDataSource] = useState<ApiErrorLogVO[]>([]); |
|
|
|
|
|
|
|
const { runAsync: getPageData } = useRequest(apiLogService.getApiErrorLogPageApi, { manual: true }); |
|
|
|
const { runAsync: exportOperateLog } = useRequest(apiLogService.exportApiErrorLogApi, { manual: true }); |
|
|
|
const { runAsync: updateApiErrorLogStatus } = useRequest(apiLogService.updateApiErrorLogPageApi, { manual: true }); |
|
|
|
|
|
|
|
const [searchState, setSearchState] = useSetState<ApiErrorLogPageReqVO>({ |
|
|
|
pageNo: 1, |
|
|
|
pageSize: 10 |
|
|
|
}); |
|
|
|
const [total, setTotal] = useState(0) |
|
|
|
const searchInput = useRef<InputRef>(null); |
|
|
|
const [onSearching, setOnSearching] = useState(false); |
|
|
|
|
|
|
|
const load = async () => { |
|
|
|
console.log(searchState) |
|
|
|
const [error, { code, msg, data }] = await getPageData(searchState); |
|
|
|
setOnSearching(false); |
|
|
|
if (error || code !== 0) { |
|
|
|
antdUtils.message?.open({ type: 'error', content: msg ?? '操作失败' }); |
|
|
|
return |
|
|
|
} |
|
|
|
setTotal(data.total); |
|
|
|
setDataSource(data.list); |
|
|
|
}; |
|
|
|
|
|
|
|
const getColumnSearchProps = (dataIndex: DataIndex, placeholder: string): ColumnType<ApiErrorLogVO> => ({ |
|
|
|
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters, close }) => ( |
|
|
|
<div style={{ padding: 8 }} onKeyDown={(e) => e.stopPropagation()}> |
|
|
|
<Input.Search |
|
|
|
ref={searchInput} |
|
|
|
placeholder={placeholder} |
|
|
|
value={selectedKeys[0]} |
|
|
|
onChange={(e) => { |
|
|
|
setSelectedKeys(e.target.value && e.target.value !== '' ? [e.target.value] : []) |
|
|
|
}} |
|
|
|
onSearch={(value) => { |
|
|
|
if (value === '' && clearFilters) { |
|
|
|
clearFilters!!() |
|
|
|
} |
|
|
|
confirm(); |
|
|
|
}} |
|
|
|
onPressEnter={() => confirm()} |
|
|
|
allowClear |
|
|
|
style={{ marginBottom: 8, display: 'block' }} |
|
|
|
enterButton="搜索" |
|
|
|
size="middle" |
|
|
|
loading={onSearching} |
|
|
|
/> |
|
|
|
</div> |
|
|
|
), |
|
|
|
filterIcon: (filtered: boolean) => ( |
|
|
|
<SearchOutlined style={{ color: filtered ? 'primaryColor' : undefined }} /> |
|
|
|
), |
|
|
|
onFilterDropdownOpenChange: (visible) => { |
|
|
|
if (visible) { |
|
|
|
setTimeout(() => searchInput.current?.select(), 100); |
|
|
|
} |
|
|
|
}, |
|
|
|
}); |
|
|
|
|
|
|
|
const handleUpdateApiErrorLogStatus = async function (item: ApiErrorLogVO, status: number) { |
|
|
|
antdUtils.modal?.confirm({ |
|
|
|
title: '温馨提示', |
|
|
|
icon: <ExclamationCircleFilled />, |
|
|
|
content: `确认标记为已${status === 1 ? '处理' : '忽略'}?`, |
|
|
|
okText: '确定', |
|
|
|
cancelText: '取消', |
|
|
|
onOk() { |
|
|
|
return new Promise(async (resolve) => { |
|
|
|
const [error, { code, msg }] = await updateApiErrorLogStatus(item.id, status); |
|
|
|
if (error || code !== 0) { |
|
|
|
antdUtils.message?.open({ type: 'error', content: msg ?? '操作失败' }) |
|
|
|
} else { |
|
|
|
antdUtils.message?.open({ type: 'success', content: '操作成功' }) |
|
|
|
} |
|
|
|
await load(); |
|
|
|
resolve('') |
|
|
|
}).catch(() => antdUtils.message?.open({ |
|
|
|
type: 'error', |
|
|
|
content: '操作失败', |
|
|
|
})); |
|
|
|
}, |
|
|
|
onCancel() { |
|
|
|
}, |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
const columns: TableColumnsType<ApiErrorLogVO> = [ |
|
|
|
{ |
|
|
|
title: '日志编号', |
|
|
|
dataIndex: 'id', |
|
|
|
key: 'id', |
|
|
|
align: 'center', |
|
|
|
fixed: 'left', |
|
|
|
width: 100, |
|
|
|
}, |
|
|
|
{ |
|
|
|
title: '用户编号', |
|
|
|
dataIndex: 'userId', |
|
|
|
key: 'userId', |
|
|
|
align: 'center', |
|
|
|
width: 100, |
|
|
|
...getColumnSearchProps('userId', "请输入用户编号") |
|
|
|
}, |
|
|
|
{ |
|
|
|
title: '用户类型', |
|
|
|
dataIndex: 'userType', |
|
|
|
key: 'userType', |
|
|
|
fixed: 'left', |
|
|
|
filterSearch: true, |
|
|
|
align: 'center', |
|
|
|
width: 100, |
|
|
|
render: (value: number) => { |
|
|
|
if (value === 1) { |
|
|
|
return <Tag color="purple">会员</Tag> |
|
|
|
} else if (value === 2) { |
|
|
|
return <Tag color="blue">管理员</Tag> |
|
|
|
} else { |
|
|
|
return <Tag color="red">未知</Tag> |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
{ |
|
|
|
title: '应用名', |
|
|
|
dataIndex: 'applicationName', |
|
|
|
key: 'applicationName', |
|
|
|
align: 'center', |
|
|
|
width: 120, |
|
|
|
...getColumnSearchProps('applicationName', "请输入应用名") |
|
|
|
}, |
|
|
|
{ |
|
|
|
title: '请求方法', |
|
|
|
dataIndex: 'requestMethod', |
|
|
|
key: 'requestMethod', |
|
|
|
align: 'center', |
|
|
|
width: 100, |
|
|
|
}, |
|
|
|
{ |
|
|
|
title: '请求地址', |
|
|
|
dataIndex: 'requestUrl', |
|
|
|
key: 'requestUrl', |
|
|
|
align: 'center', |
|
|
|
width: 150, |
|
|
|
...getColumnSearchProps('requestUrl', "请输入请求地址") |
|
|
|
}, |
|
|
|
{ |
|
|
|
title: '异常时间', |
|
|
|
dataIndex: 'exceptionTime', |
|
|
|
key: 'exceptionTime', |
|
|
|
align: 'center', |
|
|
|
width: 150, |
|
|
|
render: (value: number) => { |
|
|
|
return formatDate(new Date(value), "YYYY-mm-dd HH:MM:SS") |
|
|
|
} |
|
|
|
}, |
|
|
|
{ |
|
|
|
title: '异常名', |
|
|
|
dataIndex: 'exceptionName', |
|
|
|
key: 'exceptionName', |
|
|
|
align: 'center', |
|
|
|
width: 150, |
|
|
|
}, |
|
|
|
{ |
|
|
|
title: '处理状态', |
|
|
|
dataIndex: 'processStatus', |
|
|
|
key: 'processStatus', |
|
|
|
align: 'center', |
|
|
|
width: 150, |
|
|
|
render: (value: number) => { |
|
|
|
if (value === 0) { |
|
|
|
return <Tag color="red">未处理</Tag> |
|
|
|
} else if (value === 1) { |
|
|
|
return <Tag color="purple">已处理</Tag> |
|
|
|
} else if (value === 2) { |
|
|
|
return <Tag color="blue">已忽略</Tag> |
|
|
|
} else { |
|
|
|
return <Tag color="purple">内置</Tag> |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
{ |
|
|
|
title: t("QkOmYwne" /* 操作 */), |
|
|
|
key: 'action', |
|
|
|
fixed: 'right', |
|
|
|
align: 'center', |
|
|
|
width: 250, |
|
|
|
render: (value: ApiErrorLogVO, record) => ( |
|
|
|
<Space size="small" split={(<Divider type='vertical' />)}> |
|
|
|
<a onClick={() => { |
|
|
|
|
|
|
|
}}> 详情 </a> |
|
|
|
{ |
|
|
|
(value.processStatus === 0 ? (<a onClick={() => { |
|
|
|
handleUpdateApiErrorLogStatus(value, 1) |
|
|
|
}}> 已处理 </a>) : null) |
|
|
|
} |
|
|
|
{ |
|
|
|
(value.processStatus === 0 ? (<a onClick={() => { |
|
|
|
handleUpdateApiErrorLogStatus(value, 2) |
|
|
|
}}> 已忽略 </a>) : null) |
|
|
|
} |
|
|
|
</Space> |
|
|
|
), |
|
|
|
}, |
|
|
|
]; |
|
|
|
|
|
|
|
const exportLogs = async () => { |
|
|
|
await exportOperateLog() |
|
|
|
} |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
load(); |
|
|
|
}, [searchState]); |
|
|
|
|
|
|
|
const onChange: TableProps<ApiErrorLogVO>['onChange'] = (pagination, filters, sorter, extra) => { |
|
|
|
const state: ApiErrorLogPageReqVO = { |
|
|
|
applicationName: filters.applicationName ? filters.applicationName[0] as string : undefined, |
|
|
|
requestUrl: filters.requestUrl ? filters.requestUrl[0] as string : undefined, |
|
|
|
userId: filters.userId ? parseInt(filters.userId[0] as string) : undefined, |
|
|
|
pageNo: pagination.current, |
|
|
|
pageSize: pagination.pageSize |
|
|
|
} |
|
|
|
setOnSearching(true); |
|
|
|
setSearchState(state); |
|
|
|
}; |
|
|
|
|
|
|
|
return ( |
|
|
|
<> |
|
|
|
<div> |
|
|
|
<Card className='mt-[4px] dark:bg-[rgb(33,41,70)] bg-white roundle-lg px[12px]'> |
|
|
|
<Table rowKey="id" |
|
|
|
scroll={{ x: true }} |
|
|
|
columns={columns} |
|
|
|
dataSource={dataSource} |
|
|
|
onChange={onChange} |
|
|
|
className='bg-transparent' |
|
|
|
pagination={{ |
|
|
|
position: ['bottomRight'], |
|
|
|
current: searchState.pageNo, |
|
|
|
pageSize: searchState.pageSize, |
|
|
|
total |
|
|
|
}} |
|
|
|
/> |
|
|
|
</Card> |
|
|
|
<FloatButton |
|
|
|
type='primary' |
|
|
|
tooltip={<div>导出日志</div>} |
|
|
|
icon={<DownloadOutlined />} |
|
|
|
onClick={exportLogs} |
|
|
|
/> |
|
|
|
</div> |
|
|
|
</> |
|
|
|
); |
|
|
|
}; |