feat(admin): Add activity logs page and fix AI chat markdown rendering
- Add paginated activity logs API with filters (date, module, action, keyword) - Add ActivityLogsPage with table, filters, and detail modal - Add markdown rendering support for AI chat messages - Remove prototype placeholder content from chat sidebar Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -24,6 +24,8 @@ import UserDetailPage from './modules/admin/pages/UserDetailPage'
|
||||
// 系统知识库管理
|
||||
import SystemKbListPage from './modules/admin/pages/SystemKbListPage'
|
||||
import SystemKbDetailPage from './modules/admin/pages/SystemKbDetailPage'
|
||||
// 运营日志
|
||||
import ActivityLogsPage from './pages/admin/ActivityLogsPage'
|
||||
// 个人中心页面
|
||||
import ProfilePage from './pages/user/ProfilePage'
|
||||
|
||||
@@ -115,6 +117,8 @@ function App() {
|
||||
{/* 系统知识库 */}
|
||||
<Route path="system-kb" element={<SystemKbListPage />} />
|
||||
<Route path="system-kb/:id" element={<SystemKbDetailPage />} />
|
||||
{/* 运营日志 */}
|
||||
<Route path="activity-logs" element={<ActivityLogsPage />} />
|
||||
{/* 系统配置 */}
|
||||
<Route path="system" element={<div className="text-center py-20">🚧 系统配置页面开发中...</div>} />
|
||||
</Route>
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
MenuUnfoldOutlined,
|
||||
BellOutlined,
|
||||
BookOutlined,
|
||||
FileTextOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import type { MenuProps } from 'antd'
|
||||
import { useAuth } from '../auth'
|
||||
@@ -94,6 +95,11 @@ const AdminLayout = () => {
|
||||
icon: <TeamOutlined />,
|
||||
label: '租户管理',
|
||||
},
|
||||
{
|
||||
key: '/admin/activity-logs',
|
||||
icon: <FileTextOutlined />,
|
||||
label: '运营日志',
|
||||
},
|
||||
{
|
||||
key: '/admin/users',
|
||||
icon: <UserOutlined />,
|
||||
|
||||
@@ -28,6 +28,7 @@ import { ThinkingBlock } from '@/shared/components/Chat';
|
||||
import { getAccessToken } from '@/framework/auth/api';
|
||||
import { AGENTS, AGENT_PROMPTS, BRAND_COLORS } from '../constants';
|
||||
import type { AgentConfig, Conversation, Message } from '../types';
|
||||
import { MarkdownContent } from '../protocol-agent/components/MarkdownContent';
|
||||
import '../styles/chat-workspace.css';
|
||||
|
||||
/**
|
||||
@@ -651,14 +652,6 @@ export const ChatWorkspace: React.FC<ChatWorkspaceProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 用户信息 */}
|
||||
<div className="sidebar-user">
|
||||
<div className="user-avatar">U</div>
|
||||
<div className="user-info">
|
||||
<div className="user-name">Dr. Wang</div>
|
||||
<div className="user-plan">专业版会员</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* 主对话区 */}
|
||||
@@ -750,7 +743,11 @@ export const ChatWorkspace: React.FC<ChatWorkspaceProps> = ({
|
||||
{/* 消息内容(有内容时才显示气泡) */}
|
||||
{(msg.content || (msg.role === 'assistant' && isStreaming && index === messages.length - 1)) && (
|
||||
<div className={`message-bubble ${msg.role}`}>
|
||||
{msg.content}
|
||||
{msg.role === 'assistant' ? (
|
||||
<MarkdownContent content={msg.content || ''} />
|
||||
) : (
|
||||
msg.content
|
||||
)}
|
||||
{msg.role === 'assistant' && isStreaming && index === messages.length - 1 && (
|
||||
<span className="typing-cursor">▊</span>
|
||||
)}
|
||||
|
||||
@@ -946,3 +946,93 @@
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* ============================================ */
|
||||
/* MarkdownContent 组件样式 */
|
||||
/* ============================================ */
|
||||
.message-bubble .markdown-content {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.message-bubble .markdown-content p {
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.message-bubble .markdown-content p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.message-bubble .markdown-content .md-divider {
|
||||
border: none;
|
||||
border-top: 1px solid #E5E7EB;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.message-bubble .markdown-content h1,
|
||||
.message-bubble .markdown-content h2,
|
||||
.message-bubble .markdown-content h3 {
|
||||
margin: 16px 0 8px 0;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.message-bubble .markdown-content h1:first-child,
|
||||
.message-bubble .markdown-content h2:first-child,
|
||||
.message-bubble .markdown-content h3:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.message-bubble .markdown-content h1 {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.message-bubble .markdown-content h2 {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.message-bubble .markdown-content h3 {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.message-bubble .markdown-content ul,
|
||||
.message-bubble .markdown-content ol {
|
||||
margin: 8px 0 12px 0;
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
.message-bubble .markdown-content ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
.message-bubble .markdown-content ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
.message-bubble .markdown-content li {
|
||||
margin: 6px 0;
|
||||
line-height: 1.6;
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
.message-bubble .markdown-content li::marker {
|
||||
color: #4F6EF2;
|
||||
}
|
||||
|
||||
.message-bubble .markdown-content strong {
|
||||
font-weight: 700;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.message-bubble .markdown-content em {
|
||||
font-style: italic;
|
||||
color: #4B5563;
|
||||
}
|
||||
|
||||
.message-bubble .markdown-content code {
|
||||
background: rgba(79, 110, 242, 0.1);
|
||||
color: #4F6EF2;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Monaco', 'Consolas', 'Courier New', monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
|
||||
423
frontend-v2/src/pages/admin/ActivityLogsPage.tsx
Normal file
423
frontend-v2/src/pages/admin/ActivityLogsPage.tsx
Normal file
@@ -0,0 +1,423 @@
|
||||
/**
|
||||
* 运营日志页面
|
||||
*
|
||||
* 提供完整的运营日志查看、筛选、搜索功能
|
||||
*
|
||||
* @date 2026-01-28
|
||||
*/
|
||||
|
||||
import { useState, useCallback } from 'react'
|
||||
import {
|
||||
Card,
|
||||
Table,
|
||||
Tag,
|
||||
Input,
|
||||
Select,
|
||||
DatePicker,
|
||||
Button,
|
||||
Space,
|
||||
Tooltip,
|
||||
Modal,
|
||||
Typography,
|
||||
} from 'antd'
|
||||
import type { ColumnsType, TablePaginationConfig } from 'antd/es/table'
|
||||
import {
|
||||
SearchOutlined,
|
||||
ReloadOutlined,
|
||||
MessageOutlined,
|
||||
BookOutlined,
|
||||
FileTextOutlined,
|
||||
SyncOutlined,
|
||||
LoginOutlined,
|
||||
CloudUploadOutlined,
|
||||
DeleteOutlined,
|
||||
EyeOutlined,
|
||||
FilterOutlined,
|
||||
ClearOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import dayjs from 'dayjs'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
import { fetchActivityLogs, type ActivityLog } from './api/activityApi'
|
||||
|
||||
const { RangePicker } = DatePicker
|
||||
const { Text, Paragraph } = Typography
|
||||
|
||||
// ==================== 常量映射 ====================
|
||||
|
||||
const MODULE_OPTIONS = [
|
||||
{ label: '全部模块', value: '' },
|
||||
{ label: '系统', value: 'SYSTEM' },
|
||||
{ label: 'AI问答', value: 'AIA' },
|
||||
{ label: '个人知识库', value: 'PKB' },
|
||||
{ label: '智能文献', value: 'ASL' },
|
||||
{ label: '数据清洗', value: 'DC' },
|
||||
{ label: '智能循证', value: 'RVW' },
|
||||
{ label: '智能IIT', value: 'IIT' },
|
||||
]
|
||||
|
||||
const ACTION_OPTIONS = [
|
||||
{ label: '全部动作', value: '' },
|
||||
{ label: '登录', value: 'login' },
|
||||
{ label: '登出', value: 'logout' },
|
||||
{ label: '对话', value: 'chat' },
|
||||
{ label: '上传', value: 'upload' },
|
||||
{ label: '导出', value: 'export' },
|
||||
{ label: '创建', value: 'create' },
|
||||
{ label: '删除', value: 'delete' },
|
||||
{ label: '查看', value: 'view' },
|
||||
]
|
||||
|
||||
const MODULE_ICONS: Record<string, React.ReactNode> = {
|
||||
'SYSTEM': <LoginOutlined />,
|
||||
'AIA': <MessageOutlined />,
|
||||
'PKB': <BookOutlined />,
|
||||
'ASL': <SearchOutlined />,
|
||||
'DC': <FileTextOutlined />,
|
||||
'RVW': <FileTextOutlined />,
|
||||
'IIT': <SyncOutlined />,
|
||||
}
|
||||
|
||||
const MODULE_COLORS: Record<string, string> = {
|
||||
'SYSTEM': '#8c8c8c',
|
||||
'AIA': '#1890ff',
|
||||
'PKB': '#52c41a',
|
||||
'ASL': '#722ed1',
|
||||
'DC': '#fa8c16',
|
||||
'RVW': '#eb2f96',
|
||||
'IIT': '#13c2c2',
|
||||
}
|
||||
|
||||
const ACTION_ICONS: Record<string, React.ReactNode> = {
|
||||
'login': <LoginOutlined />,
|
||||
'logout': <LoginOutlined />,
|
||||
'chat': <MessageOutlined />,
|
||||
'upload': <CloudUploadOutlined />,
|
||||
'delete': <DeleteOutlined />,
|
||||
'export': <FileTextOutlined />,
|
||||
'create': <FileTextOutlined />,
|
||||
}
|
||||
|
||||
// ==================== 组件 ====================
|
||||
|
||||
export default function ActivityLogsPage() {
|
||||
// 筛选状态
|
||||
const [page, setPage] = useState(1)
|
||||
const [pageSize, setPageSize] = useState(20)
|
||||
const [dateRange, setDateRange] = useState<[Dayjs | null, Dayjs | null] | null>(null)
|
||||
const [moduleFilter, setModuleFilter] = useState<string>('')
|
||||
const [actionFilter, setActionFilter] = useState<string>('')
|
||||
const [keyword, setKeyword] = useState<string>('')
|
||||
const [searchKeyword, setSearchKeyword] = useState<string>('')
|
||||
|
||||
// 详情弹窗
|
||||
const [detailVisible, setDetailVisible] = useState(false)
|
||||
const [selectedLog, setSelectedLog] = useState<ActivityLog | null>(null)
|
||||
|
||||
// 构建查询参数
|
||||
const queryParams = {
|
||||
page,
|
||||
pageSize,
|
||||
startDate: dateRange?.[0]?.format('YYYY-MM-DD'),
|
||||
endDate: dateRange?.[1]?.format('YYYY-MM-DD'),
|
||||
module: moduleFilter || undefined,
|
||||
action: actionFilter || undefined,
|
||||
keyword: searchKeyword || undefined,
|
||||
}
|
||||
|
||||
// 查询数据
|
||||
const { data, isLoading, refetch } = useQuery({
|
||||
queryKey: ['activityLogs', queryParams],
|
||||
queryFn: () => fetchActivityLogs(queryParams),
|
||||
})
|
||||
|
||||
// 处理分页变化
|
||||
const handleTableChange = useCallback((pagination: TablePaginationConfig) => {
|
||||
setPage(pagination.current || 1)
|
||||
setPageSize(pagination.pageSize || 20)
|
||||
}, [])
|
||||
|
||||
// 处理搜索
|
||||
const handleSearch = useCallback(() => {
|
||||
setSearchKeyword(keyword)
|
||||
setPage(1) // 重置到第一页
|
||||
}, [keyword])
|
||||
|
||||
// 清空筛选
|
||||
const handleClearFilters = useCallback(() => {
|
||||
setDateRange(null)
|
||||
setModuleFilter('')
|
||||
setActionFilter('')
|
||||
setKeyword('')
|
||||
setSearchKeyword('')
|
||||
setPage(1)
|
||||
}, [])
|
||||
|
||||
// 显示详情
|
||||
const handleShowDetail = useCallback((log: ActivityLog) => {
|
||||
setSelectedLog(log)
|
||||
setDetailVisible(true)
|
||||
}, [])
|
||||
|
||||
// 表格列定义
|
||||
const columns: ColumnsType<ActivityLog> = [
|
||||
{
|
||||
title: '时间',
|
||||
dataIndex: 'createdAt',
|
||||
key: 'createdAt',
|
||||
width: 170,
|
||||
render: (val: string) => (
|
||||
<Text style={{ fontSize: 13 }}>
|
||||
{dayjs(val).format('YYYY-MM-DD HH:mm:ss')}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '租户',
|
||||
dataIndex: 'tenantName',
|
||||
key: 'tenantName',
|
||||
width: 120,
|
||||
ellipsis: true,
|
||||
render: (val: string | null) => (
|
||||
<Tooltip title={val}>
|
||||
<Text style={{ fontSize: 13 }}>{val || '-'}</Text>
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '用户',
|
||||
dataIndex: 'userName',
|
||||
key: 'userName',
|
||||
width: 100,
|
||||
ellipsis: true,
|
||||
render: (val: string | null) => (
|
||||
<Tooltip title={val}>
|
||||
<Text style={{ fontSize: 13 }}>{val || '-'}</Text>
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '模块',
|
||||
dataIndex: 'module',
|
||||
key: 'module',
|
||||
width: 100,
|
||||
render: (val: string) => (
|
||||
<Tag
|
||||
icon={MODULE_ICONS[val]}
|
||||
color={MODULE_COLORS[val] || 'default'}
|
||||
>
|
||||
{val}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '功能',
|
||||
dataIndex: 'feature',
|
||||
key: 'feature',
|
||||
width: 120,
|
||||
ellipsis: true,
|
||||
render: (val: string) => (
|
||||
<Tooltip title={val}>
|
||||
<Text style={{ fontSize: 13 }}>{val}</Text>
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '动作',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
width: 80,
|
||||
render: (val: string) => (
|
||||
<Space size={4}>
|
||||
{ACTION_ICONS[val]}
|
||||
<span style={{ fontSize: 13 }}>{val}</span>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '详情',
|
||||
dataIndex: 'info',
|
||||
key: 'info',
|
||||
ellipsis: true,
|
||||
render: (val: string | null) => (
|
||||
<Tooltip title={val} placement="topLeft">
|
||||
<Text
|
||||
style={{ fontSize: 13, color: '#666' }}
|
||||
ellipsis
|
||||
>
|
||||
{val || '-'}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 70,
|
||||
fixed: 'right',
|
||||
render: (_, record) => (
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
icon={<EyeOutlined />}
|
||||
onClick={() => handleShowDetail(record)}
|
||||
>
|
||||
查看
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
{/* 页面标题 */}
|
||||
<div className="mb-4">
|
||||
<h2 className="text-xl font-semibold text-gray-800">运营日志</h2>
|
||||
<p className="text-gray-500 text-sm mt-1">查看系统所有用户操作记录</p>
|
||||
</div>
|
||||
|
||||
{/* 筛选区域 */}
|
||||
<Card className="mb-4" size="small">
|
||||
<div className="flex flex-wrap gap-3 items-center">
|
||||
<Space>
|
||||
<FilterOutlined />
|
||||
<span className="text-gray-600">筛选:</span>
|
||||
</Space>
|
||||
|
||||
<RangePicker
|
||||
value={dateRange}
|
||||
onChange={(dates) => {
|
||||
setDateRange(dates as [Dayjs | null, Dayjs | null])
|
||||
setPage(1)
|
||||
}}
|
||||
placeholder={['开始日期', '结束日期']}
|
||||
style={{ width: 240 }}
|
||||
/>
|
||||
|
||||
<Select
|
||||
value={moduleFilter}
|
||||
onChange={(val) => {
|
||||
setModuleFilter(val)
|
||||
setPage(1)
|
||||
}}
|
||||
options={MODULE_OPTIONS}
|
||||
style={{ width: 130 }}
|
||||
placeholder="选择模块"
|
||||
/>
|
||||
|
||||
<Select
|
||||
value={actionFilter}
|
||||
onChange={(val) => {
|
||||
setActionFilter(val)
|
||||
setPage(1)
|
||||
}}
|
||||
options={ACTION_OPTIONS}
|
||||
style={{ width: 120 }}
|
||||
placeholder="选择动作"
|
||||
/>
|
||||
|
||||
<Input.Search
|
||||
value={keyword}
|
||||
onChange={(e) => setKeyword(e.target.value)}
|
||||
onSearch={handleSearch}
|
||||
placeholder="搜索用户/租户名称"
|
||||
style={{ width: 200 }}
|
||||
enterButton={<SearchOutlined />}
|
||||
/>
|
||||
|
||||
<Button
|
||||
icon={<ClearOutlined />}
|
||||
onClick={handleClearFilters}
|
||||
>
|
||||
清空筛选
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={() => refetch()}
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* 数据表格 */}
|
||||
<Card>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={data?.data || []}
|
||||
rowKey="id"
|
||||
loading={isLoading}
|
||||
scroll={{ x: 1100 }}
|
||||
pagination={{
|
||||
current: page,
|
||||
pageSize: pageSize,
|
||||
total: data?.pagination.total || 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条记录`,
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
}}
|
||||
onChange={handleTableChange}
|
||||
size="middle"
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* 详情弹窗 */}
|
||||
<Modal
|
||||
title="日志详情"
|
||||
open={detailVisible}
|
||||
onCancel={() => setDetailVisible(false)}
|
||||
footer={[
|
||||
<Button key="close" onClick={() => setDetailVisible(false)}>
|
||||
关闭
|
||||
</Button>
|
||||
]}
|
||||
width={600}
|
||||
>
|
||||
{selectedLog && (
|
||||
<div className="space-y-3">
|
||||
<div className="flex">
|
||||
<span className="w-20 text-gray-500">时间:</span>
|
||||
<span>{dayjs(selectedLog.createdAt).format('YYYY-MM-DD HH:mm:ss')}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="w-20 text-gray-500">租户:</span>
|
||||
<span>{selectedLog.tenantName || '-'}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="w-20 text-gray-500">用户:</span>
|
||||
<span>{selectedLog.userName || '-'}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="w-20 text-gray-500">模块:</span>
|
||||
<Tag
|
||||
icon={MODULE_ICONS[selectedLog.module]}
|
||||
color={MODULE_COLORS[selectedLog.module] || 'default'}
|
||||
>
|
||||
{selectedLog.module}
|
||||
</Tag>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="w-20 text-gray-500">功能:</span>
|
||||
<span>{selectedLog.feature}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="w-20 text-gray-500">动作:</span>
|
||||
<span>{selectedLog.action}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="w-20 text-gray-500">详情:</span>
|
||||
<Paragraph
|
||||
style={{ margin: 0, flex: 1 }}
|
||||
ellipsis={{ rows: 5, expandable: true }}
|
||||
>
|
||||
{selectedLog.info || '-'}
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
73
frontend-v2/src/pages/admin/api/activityApi.ts
Normal file
73
frontend-v2/src/pages/admin/api/activityApi.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 运营日志 API
|
||||
*
|
||||
* @date 2026-01-28
|
||||
*/
|
||||
|
||||
import apiClient from '../../../common/api/axios'
|
||||
|
||||
// ==================== 类型定义 ====================
|
||||
|
||||
export interface ActivityLog {
|
||||
id: string
|
||||
createdAt: string
|
||||
tenantId: string
|
||||
tenantName: string | null
|
||||
userId: string
|
||||
userName: string | null
|
||||
module: string
|
||||
feature: string
|
||||
action: string
|
||||
info: string | null
|
||||
}
|
||||
|
||||
export interface Pagination {
|
||||
page: number
|
||||
pageSize: number
|
||||
total: number
|
||||
totalPages: number
|
||||
}
|
||||
|
||||
export interface ActivityLogsResponse {
|
||||
success: boolean
|
||||
data: ActivityLog[]
|
||||
pagination: Pagination
|
||||
}
|
||||
|
||||
export interface ActivityLogsParams {
|
||||
page?: number
|
||||
pageSize?: number
|
||||
startDate?: string
|
||||
endDate?: string
|
||||
module?: string
|
||||
action?: string
|
||||
keyword?: string
|
||||
}
|
||||
|
||||
// ==================== API 函数 ====================
|
||||
|
||||
/**
|
||||
* 获取运营日志列表(分页)
|
||||
*/
|
||||
export async function fetchActivityLogs(params: ActivityLogsParams = {}): Promise<{
|
||||
data: ActivityLog[]
|
||||
pagination: Pagination
|
||||
}> {
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.page) queryParams.append('page', String(params.page))
|
||||
if (params.pageSize) queryParams.append('pageSize', String(params.pageSize))
|
||||
if (params.startDate) queryParams.append('startDate', params.startDate)
|
||||
if (params.endDate) queryParams.append('endDate', params.endDate)
|
||||
if (params.module) queryParams.append('module', params.module)
|
||||
if (params.action) queryParams.append('action', params.action)
|
||||
if (params.keyword) queryParams.append('keyword', params.keyword)
|
||||
|
||||
const url = `/api/admin/stats/logs${queryParams.toString() ? '?' + queryParams.toString() : ''}`
|
||||
const res = await apiClient.get<ActivityLogsResponse>(url)
|
||||
|
||||
return {
|
||||
data: res.data.data,
|
||||
pagination: res.data.pagination,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user