Просмотр исходного кода

feat: 实现审核页面,更新 store 和 API 集成

Ryuiso 1 месяц назад
Родитель
Сommit
8207e508f8

+ 23 - 1
jk-rag-platform/src/apis/index.ts

@@ -730,9 +730,31 @@ export const getTenantSize = async ()=>{
     return api.get('/system/tenant/getTenantSize');
 }
 // http://localhost:8080/deepseek/api/app/auditHistory/list
-export const fetchAuditHistoryList = async (data: any) => { 
+export const fetchAuditHistoryList = async (data: any) => {
     return api.post('/deepseek/api/app/auditHistory/list', { data: data });
 }
+
+// ==================== 审核管理相关 API ====================
+
+// 获取审核列表
+export const fetchAuditList = async (data: any) => {
+    return api.post('/deepseek/api/app/audit/list', data);
+};
+
+// 获取审核统计数据
+export const fetchAuditStats = async () => {
+    return api.get('/deepseek/api/app/audit/stats');
+};
+
+// 审核操作(通过/拒绝)
+export const auditAction = async (data: { auditId: string; action: string; opinion: string }) => {
+    return api.put('/deepseek/api/app/audit/auditAction', data);
+};
+
+// 获取审核详情
+export const fetchAuditDetail = async (auditId: string) => {
+    return api.get(`/deepseek/api/app/audit/detail/${auditId}`);
+};
 // POST http://localhost:8080/deepseek/api/document/checkQuoteFile 检查引用文件是否存在
 export const checkQuoteFileApi = async (data: any) => {
     return api.post('/deepseek/api/document/checkQuoteFile', data);

+ 19 - 12
jk-rag-platform/src/components/common/AppCard/index.scss

@@ -146,14 +146,17 @@
     &.bg-green { background: #F0FDF4; }
     &.bg-orange { background: #FFF7ED; }
 
-    .card-icon {
+    .card-icon,
+    svg {
         width: 100%;
         height: 100%;
         object-fit: contain;
-    }
 
-    .iconify {
-        font-size: $icon-3xl;
+        // fill 风格图标 - 移除描边
+        &[fill="none"] {
+            fill: currentColor;
+            stroke: none;
+        }
     }
 }
 
@@ -188,8 +191,9 @@
             transform: scale(1.05);
         }
 
-        .iconify {
-            font-size: $icon-lg;
+        svg {
+            width: 16px;
+            height: 16px;
         }
     }
 }
@@ -273,8 +277,9 @@
         font-size: $font-sm;
         color: $text-hint;
 
-        .iconify {
-            font-size: $icon-md;
+        svg {
+            width: 14px;
+            height: 14px;
         }
     }
 }
@@ -296,8 +301,9 @@
     font-size: $font-xs;
     color: $text-hint;
 
-    .iconify {
-        font-size: $icon-md;
+    svg {
+        width: 14px;
+        height: 14px;
         color: $success-color;
     }
 }
@@ -313,8 +319,9 @@
     text-overflow: ellipsis;
     max-width: 200px;
 
-    .iconify {
-        font-size: $icon-md;
+    svg {
+        width: 14px;
+        height: 14px;
     }
 }
 

+ 24 - 16
jk-rag-platform/src/components/common/StatsGrid/index.scss

@@ -49,27 +49,34 @@
     align-items: center;
     justify-content: center;
 
-    &.bg-blue { 
-        background: $icon-bg-blue; 
+    &.bg-blue {
+        background: $icon-bg-blue;
     }
-    &.bg-indigo { 
-        background: $icon-bg-indigo; 
+    &.bg-indigo {
+        background: $icon-bg-indigo;
     }
-    &.bg-orange { 
-        background: $icon-bg-amber; 
+    &.bg-orange {
+        background: $icon-bg-amber;
     }
-    &.bg-green { 
-        background: $success-light; 
+    &.bg-green {
+        background: $success-light;
     }
 
-    .iconify {
-        font-size: $icon-lg;  // 18px - 紧凑图标
+    svg {
+        width: 20px;
+        height: 20px;
 
-        &.icon-blue { color: $primary-color; }
-        &.icon-indigo { color: $info-dark; }
-        &.icon-orange { color: $warning-color; }
-        &.icon-green { color: $success-color; }
+        // 使用 fill 风格 - 移除描边,使用实心填充
+        &[fill="none"] {
+            fill: currentColor;
+            stroke: none;
+        }
     }
+
+    .icon-blue { color: $primary-color; }
+    .icon-indigo { color: $info-dark; }
+    .icon-orange { color: $warning-color; }
+    .icon-green { color: $success-color; }
 }
 
 .stat-card-trend {
@@ -115,8 +122,9 @@
             height: $icon-xl;
             border-radius: $radius-md;  // 6px
 
-            .iconify {
-                font-size: $icon-md;  // 16px
+            svg {
+                width: 18px;
+                height: 18px;
             }
         }
 

+ 2 - 2
jk-rag-platform/src/components/common/StatsGrid/index.tsx

@@ -21,13 +21,13 @@ const iconMap: Record<string, React.ComponentType<any>> = {
 
 const StatCard: React.FC<StatCardProps> = (props) => {
     const { icon, iconBgColor, title, value, trend, trendColor = 'gray' } = props;
-    const IconComponent = iconMap[icon] || BarChart;
+    const IconComponent = iconMap[icon] || GraphUp;
 
     return (
         <div className='stat-card'>
             <div className='stat-card-header'>
                 <div className={`stat-card-icon bg-${iconBgColor}`}>
-                    <IconComponent width="24" height="24" className={`icon-${iconBgColor}`} />
+                    <IconComponent width="20" height="20" className={`icon-${iconBgColor}`} />
                 </div>
                 {trend && (
                     <span className={`stat-card-trend text-${trendColor}`}>{trend}</span>

+ 401 - 201
jk-rag-platform/src/pages/system/audit/index.tsx

@@ -1,246 +1,446 @@
 import * as React from 'react';
-
-import { Table, TableColumnsType, TablePaginationConfig, Drawer, Button } from 'antd';
-import { StepForwardOutlined, PlusOutlined } from '@ant-design/icons';
-import dayjs from 'dayjs';
-import { GuideTips } from '@/components/common';
-import store from './store';
-import { Record } from './types';
+import { useState, useEffect } from 'react';
+import { Table, Button, Input, Space, Modal, Form, Drawer, message, Pagination, Tag } from 'antd';
+import { SearchOutlined, EyeOutlined, CheckCircleOutlined, CloseCircleOutlined } from 'iconoir-react';
+import { useAuditStore } from './store';
 import './style.scss';
-import LocalStorage from '@/LocalStorage';
-import InfoModal from './components/InfoModal';
-import PreviewModal from './components/PreviewModal';
-import AuditHistory from './components/auditHistory';
 
-const KnowledgeLibList: React.FC = () => {
-    const {
-        state,
-        onChangePagination,
-        onClickCreate,
-        onClickModify,
-        onClickfetchTakaiApplicationDetail,
-        infoModalOnClickConfirm,
-        infoModalOnClickCancel,
-        infoModalOnClickClose,
-        init,
-        reset
-    } = store;
+interface AuditItem {
+    id: string;
+    appId: string;
+    appName: string;
+    appDescription: string;
+    creatorId: string;
+    creator: string;
+    department: string;
+    submitTime: string;
+    auditStatus: 'pending' | 'approved' | 'rejected';
+    auditor: string | null;
+    auditTime: string | null;
+    auditOpinion: string | null;
+    knowledgeBaseCount: number;
+    documentCount: number;
+    sliceCount: number;
+}
+
+const AuditPage: React.FC = () => {
     const {
         listLoading,
         list,
-        infoModalId,
-        infoModalOpen,
-        page
-    } = state;
-
-    const [drawerFlag, setDrawerFlag] = React.useState<boolean>(false);
-    const [drawerData, setDrawerData] = React.useState<any>({});
-    const [historyOpen, setHistoryOpen] = React.useState<boolean>(false);
-
-    React.useEffect(() => {
-        const userInfo = LocalStorage.getUserInfo();
-        const userId = (userInfo?.id ?? '').toString();
-        init(userId);
-
-         // 监听面包屑创建知识库事件
-        const handleKnowledgeLibCreate = (event: CustomEvent) => {
-            if (event.detail.platform === 'auditHistory') {
-                // onClickCreate();
-                setHistoryOpen(true);
-            }
-        };
-        
-        window.addEventListener('auditHistory', handleKnowledgeLibCreate as EventListener);
+        page,
+        stats,
+        fetchAuditList,
+        fetchAuditStats,
+        onAuditPass,
+        onAuditReject,
+        setQuery,
+    } = useAuditStore();
+
+    const [detailVisible, setDetailVisible] = useState(false);
+    const [auditModalVisible, setAuditModalVisible] = useState(false);
+    const [currentAudit, setCurrentAudit] = useState<AuditItem | null>(null);
+    const [auditActionType, setAuditActionType] = useState<'approve' | 'reject'>('approve');
+    const [auditForm] = Form.useForm();
+    const [keyword, setKeyword] = useState('');
+    const [statusFilter, setStatusFilter] = useState<string>('all');
 
+    useEffect(() => {
+        fetchAuditList();
+        fetchAuditStats();
+    }, [page.pageNum, page.pageSize]);
+
+    // 处理审核操作
+    const handleAudit = async () => {
+        try {
+            const values = await auditForm.validateFields();
+            if (auditActionType === 'approve') {
+                await onAuditPass(currentAudit?.id || '', values.opinion || '');
+            } else {
+                await onAuditReject(currentAudit?.id || '', values.opinion || '');
+            }
+            setAuditModalVisible(false);
+        } catch (error: any) {
+            if (error?.response?.status !== 200) {
+                console.error('审核操作失败:', error);
+            }
+        }
+    };
 
+    // 查看详情
+    const handleViewDetail = (record: AuditItem) => {
+        setCurrentAudit(record);
+        setDetailVisible(true);
+    };
 
+    // 打开审核弹窗
+    const handleOpenAuditModal = (record: AuditItem, action: 'approve' | 'reject') => {
+        setCurrentAudit(record);
+        setAuditActionType(action);
+        auditForm.resetFields();
+        setAuditModalVisible(true);
+    };
 
-        return () => {
-            reset();
-            window.removeEventListener('auditHistory', handleKnowledgeLibCreate as EventListener);
+    // 状态标签渲染
+    const renderStatusBadge = (status: string) => {
+        const statusMap: Record<string, { text: string; color: string }> = {
+            pending: { text: '待审核', color: '#F59E0B' },
+            approved: { text: '已通过', color: '#059669' },
+            rejected: { text: '已拒绝', color: '#DC2626' },
         };
-    }, []);
+        const config = statusMap[status] || { text: status, color: '#6B7280' };
+        return <Tag color={config.color}>{config.text}</Tag>;
+    };
 
-    const columns: TableColumnsType<Record> = [
+    // 表格列定义
+    const columns = [
         {
-            title: '序号',
-            dataIndex: 'index',
+            title: '应用名称',
+            dataIndex: 'appName',
+            key: 'appName',
+            width: 200,
+            render: (text: string, record: AuditItem) => (
+                <div>
+                    <div style={{ fontWeight: 500, color: '#1F2937' }}>{text}</div>
+                    <div style={{ fontSize: '12px', color: '#6B7280', marginTop: '4px' }}>
+                        {record.appDescription}
+                    </div>
+                </div>
+            ),
+        },
+        {
+            title: '创建人',
+            dataIndex: 'creator',
+            key: 'creator',
+            width: 100,
+            render: (text: string, record: AuditItem) => (
+                <div>
+                    <div>{text}</div>
+                    <div style={{ fontSize: '12px', color: '#9CA3AF' }}>{record.department}</div>
+                </div>
+            ),
+        },
+        {
+            title: '提交时间',
+            dataIndex: 'submitTime',
+            key: 'submitTime',
+            width: 160,
+        },
+        {
+            title: '知识库',
+            key: 'knowledgeBase',
             width: 80,
-            render: (text, record, index) => {
-                return index + 1;
-            }
+            render: (_: any, record: AuditItem) => (
+                <span>{record.knowledgeBaseCount} 个</span>
+            ),
         },
         {
-            title: '知识名称',
-            dataIndex: 'name',
-            render: (text, record) => {
-                // const previewUrl = `/preview/${record.url}`; // 根据实际字段构造 URL
-                return (
-                    <a
-                        href={record.url}
-                        target="_blank"
-                        rel="noopener noreferrer"
-                        onClick={(e) => {
-                            e.stopPropagation(); // 防止 Table 默认事件干扰
-                        }}
-                    >
-                        {text}
-                    </a>
-                );
-            }
+            title: '文档',
+            key: 'document',
+            width: 70,
+            render: (_: any, record: AuditItem) => (
+                <span>{record.documentCount} 篇</span>
+            ),
         },
         {
-            title: '状态',
-            dataIndex: 'status',
-            render: (text) => {
-                if (text === '1') {
-                    return '待审核';
-                }else if(text === '2'){
-                    return '审核中';
-                }else if(text === '3'){
-                    return '审核通过';
-                }else if(text === '4'||text === '5'){   
-                    return '审核拒绝';
-                }
-            }
+            title: '切片',
+            key: 'slice',
+            width: 70,
+            render: (_: any, record: AuditItem) => (
+                <span>{record.sliceCount} 条</span>
+            ),
         },
         {
-            title: '审核',
-            dataIndex: 'userName',
-            render: (text) => {
-                return `${text}`;
-            }
+            title: '审核状态',
+            dataIndex: 'auditStatus',
+            key: 'auditStatus',
+            width: 100,
+            render: renderStatusBadge,
         },
         {
-            title: '审核意见',
-            dataIndex: 'comment',
-            render: (text) => {
-                if(text){
-                    return `${text}`;
-                }else{
-                    return '--';
-                }
-            }
+            title: '审核人',
+            dataIndex: 'auditor',
+            key: 'auditor',
+            width: 90,
+            render: (text: string | null) => text || '-',
         },
         {
-            title: '创建时间',
-            dataIndex: 'createTime',
-            width: 200,
-            render: (text) => {
-                if (text) {
-                    return dayjs(text).format('YYYY-MM-DD HH:mm:ss');
-                } else {
-                    return '--';
-                }
-            }
+            title: '审核时间',
+            dataIndex: 'auditTime',
+            key: 'auditTime',
+            width: 160,
+            render: (text: string | null) => text || '-',
         },
         {
             title: '操作',
-            dataIndex: 'operation',
-            width: 150,
-            fixed: 'right',
-            render: (text, record) => {
-                return (
-                    <>
-                        <a
-                            style={{ marginRight: 16 }}
-                            onClick={() => {
-                                // onClickfetchTakaiApplicationDetail(record.appId);
-                                setDrawerFlag(true)
-                                setDrawerData(record)
-                            }}
-                            title='审核'
-                        >
-                            查看
-                        </a >
-                        <a
-                            style={{ marginRight: 16 }}
-                            onClick={() => {
-                                onClickModify(record.appId);
-                            }}
-                            title='审核'
-                        >
-                            <StepForwardOutlined />审核
-                        </a >
-                    </>
-                )
-            }
-        }
-    ];
-
-    const paginationConfig: TablePaginationConfig = {
-        // 显示数据总量
-        showTotal: (total: number) => {
-            return `共 ${total} 条`;
-        },
-        // 展示分页条数切换
-        showSizeChanger: true,
-        // 指定每页显示条数
-        pageSizeOptions: ['10', '20', '50', '100'],
-        // 快速跳转至某页
-        showQuickJumper: true,
-        current: page.pageNum,
-        pageSize: page.pageSize,
-        total: page.total,
-        onChange: async (page, pageSize) => {
-            await onChangePagination(page, pageSize);
+            key: 'action',
+            width: 200,
+            render: (_: any, record: AuditItem) => (
+                <Space>
+                    <Button
+                        size="small"
+                        icon={<EyeOutlined />}
+                        onClick={() => handleViewDetail(record)}
+                    >
+                        详情
+                    </Button>
+                    {record.auditStatus === 'pending' && (
+                        <>
+                            <Button
+                                size="small"
+                                type="primary"
+                                icon={<CheckCircleOutlined />}
+                                style={{ background: '#059669', borderColor: '#059669' }}
+                                onClick={() => handleOpenAuditModal(record, 'approve')}
+                            >
+                                通过
+                            </Button>
+                            <Button
+                                size="small"
+                                danger
+                                icon={<CloseCircleOutlined />}
+                                onClick={() => handleOpenAuditModal(record, 'reject')}
+                            >
+                                拒绝
+                            </Button>
+                        </>
+                    )}
+                </Space>
+            ),
         },
-    };
+    ];
 
     return (
-        <div className="page-container">
-            {/* 标题区域 */}
-            <div className="list-header">
-                <div className='list-header-title'>
-                    <h1>应用审核</h1>
-                    <p>审核和管理应用</p>
+        <div className="page-container audit-page">
+            {/* 统计卡片 */}
+            <div className="audit-stats">
+                <div className="content-section" style={{ textAlign: 'center' }}>
+                    <div style={{ fontSize: '28px', fontWeight: 'bold', color: '#1F2937' }}>
+                        {stats?.total || 0}
+                    </div>
+                    <div style={{ fontSize: '13px', color: '#6B7280', marginTop: '4px' }}>
+                        总审核数
+                    </div>
                 </div>
-                <div className='list-header-actions'>
-                    <Button
-                        type='primary'
-                        icon={<PlusOutlined />}
-                        onClick={onClickCreate}
-                    >
-                        创建
-                    </Button>
+                <div className="content-section" style={{ textAlign: 'center' }}>
+                    <div style={{ fontSize: '28px', fontWeight: 'bold', color: '#F59E0B' }}>
+                        {stats?.pending || 0}
+                    </div>
+                    <div style={{ fontSize: '13px', color: '#6B7280', marginTop: '4px' }}>
+                        待审核
+                    </div>
+                </div>
+                <div className="content-section" style={{ textAlign: 'center' }}>
+                    <div style={{ fontSize: '28px', fontWeight: 'bold', color: '#059669' }}>
+                        {stats?.approved || 0}
+                    </div>
+                    <div style={{ fontSize: '13px', color: '#6B7280', marginTop: '4px' }}>
+                        已通过
+                    </div>
+                </div>
+                <div className="content-section" style={{ textAlign: 'center' }}>
+                    <div style={{ fontSize: '28px', fontWeight: 'bold', color: '#DC2626' }}>
+                        {stats?.rejected || 0}
+                    </div>
+                    <div style={{ fontSize: '13px', color: '#6B7280', marginTop: '4px' }}>
+                        已拒绝
+                    </div>
                 </div>
             </div>
 
-            {/* 表格区域 - 使用单个 content-section */}
-            <div className="content-section">
+            {/* 审核列表 */}
+            <div className="audit-table content-section">
+                <div className="table-header">
+                    <div className="header-title">
+                        <h3>审核列表</h3>
+                        <p>管理和审核 RAG 应用上线申请</p>
+                    </div>
+                    <div className="header-actions">
+                        <Input
+                            className="search-input"
+                            placeholder="搜索应用名称或创建人"
+                            prefix={<SearchOutlined />}
+                            value={keyword}
+                            onChange={(e) => setKeyword(e.target.value)}
+                            onPressEnter={() => fetchAuditList()}
+                            allowClear
+                        />
+                    </div>
+                </div>
+
+                <div className="filter-tags">
+                    <span
+                        className={`filter-tag ${statusFilter === 'all' ? 'active' : ''}`}
+                        onClick={() => {
+                            setStatusFilter('all');
+                            setQuery({ status: '' });
+                        }}
+                    >
+                        全部
+                    </span>
+                    <span
+                        className={`filter-tag ${statusFilter === 'pending' ? 'active' : ''}`}
+                        onClick={() => {
+                            setStatusFilter('pending');
+                            setQuery({ status: 'pending' });
+                            fetchAuditList();
+                        }}
+                    >
+                        待审核
+                    </span>
+                    <span
+                        className={`filter-tag ${statusFilter === 'approved' ? 'active' : ''}`}
+                        onClick={() => {
+                            setStatusFilter('approved');
+                            setQuery({ status: 'approved' });
+                            fetchAuditList();
+                        }}
+                    >
+                        已通过
+                    </span>
+                    <span
+                        className={`filter-tag ${statusFilter === 'rejected' ? 'active' : ''}`}
+                        onClick={() => {
+                            setStatusFilter('rejected');
+                            setQuery({ status: 'rejected' });
+                            fetchAuditList();
+                        }}
+                    >
+                        已拒绝
+                    </span>
+                </div>
+
                 <Table
-                    scroll={{ x: 'max-content' }}
-                    rowKey={(record) => record.createTime}
-                    loading={listLoading}
                     columns={columns}
                     dataSource={list}
-                    pagination={paginationConfig}
+                    rowKey="id"
+                    loading={listLoading}
+                    pagination={false}
+                    scroll={{ x: 1400 }}
                 />
+
+                <div className="pagination-container" style={{ marginTop: '16px', display: 'flex', justifyContent: 'flex-end' }}>
+                    <Pagination
+                        current={page.pageNum}
+                        pageSize={page.pageSize}
+                        total={page.total}
+                        onChange={(pageNum, pageSize) => {
+                            useAuditStore.getState().setPage({ ...page, pageNum, pageSize });
+                            fetchAuditList();
+                        }}
+                        showSizeChanger
+                        showTotal={(t) => `共 ${t} 条`}
+                        pageSizeOptions={['10', '20', '50', '100']}
+                    />
+                </div>
             </div>
-        </div>
-        {
-            infoModalOpen &&
-            <InfoModal
-                id={infoModalId}
-                open={infoModalOpen}
-                onClickConfirm={infoModalOnClickConfirm}
-                onClickCancel={infoModalOnClickCancel}
-                onClickClose={infoModalOnClickClose}
-            />
-        }
-        {
+
+            {/* 审核操作弹窗 */}
+            <Modal
+                title={auditActionType === 'approve' ? '审核通过' : '审核拒绝'}
+                open={auditModalVisible}
+                onOk={handleAudit}
+                onCancel={() => setAuditModalVisible(false)}
+                okText="确认"
+                cancelText="取消"
+                okButtonProps={{
+                    danger: auditActionType === 'reject',
+                    style: auditActionType === 'reject' ? {} : { background: '#059669', borderColor: '#059669' },
+                }}
+            >
+                <div style={{ marginBottom: '16px' }}>
+                    <strong>应用名称:</strong>{currentAudit?.appName}
+                </div>
+                <div style={{ marginBottom: '16px' }}>
+                    <strong>创建人:</strong>{currentAudit?.creator} ({currentAudit?.department})
+                </div>
+                <div style={{ marginBottom: '16px' }}>
+                    <strong>应用描述:</strong>
+                    <p style={{ color: '#6B7280', fontSize: '13px' }}>{currentAudit?.appDescription}</p>
+                </div>
+                <Form form={auditForm} layout="vertical">
+                    <Form.Item
+                        label="审核意见"
+                        name="opinion"
+                        rules={[{ required: true, message: `请输入${auditActionType === 'approve' ? '通过' : '拒绝'}意见` }]}
+                    >
+                        <Input.TextArea
+                            rows={4}
+                            placeholder={auditActionType === 'reject'
+                                ? '请输入拒绝原因(必填)'
+                                : '请输入审核意见(必填)'}
+                        />
+                    </Form.Item>
+                </Form>
+            </Modal>
+
+            {/* 详情抽屉 */}
             <Drawer
-                title={drawerData.name}
-                closable={{ 'aria-label': 'Close Button' }}
-                onClose={() => { setDrawerFlag(false) }}
-                width="80%"
-                open={drawerFlag}
+                title="应用详情"
+                placement="right"
+                width={600}
+                open={detailVisible}
+                onClose={() => setDetailVisible(false)}
+                className="detail-drawer"
             >
-                {drawerFlag&&<PreviewModal isComponent={true} AuditAppId={drawerData.appId} />}
+                {currentAudit && (
+                    <>
+                        <div className="detail-section">
+                            <h4 className="section-title">基本信息</h4>
+                            <div className="detail-row">
+                                <span className="label">应用名称:</span>
+                                <span className="value">{currentAudit.appName}</span>
+                            </div>
+                            <div className="detail-row">
+                                <span className="label">应用描述:</span>
+                                <span className="value">{currentAudit.appDescription}</span>
+                            </div>
+                            <div className="detail-row">
+                                <span className="label">创建人:</span>
+                                <span className="value">{currentAudit.creator}</span>
+                            </div>
+                            <div className="detail-row">
+                                <span className="label">所属部门:</span>
+                                <span className="value">{currentAudit.department}</span>
+                            </div>
+                            <div className="detail-row">
+                                <span className="label">提交时间:</span>
+                                <span className="value">{currentAudit.submitTime}</span>
+                            </div>
+                            <div className="detail-row">
+                                <span className="label">审核状态:</span>
+                                <span className="value">{renderStatusBadge(currentAudit.auditStatus)}</span>
+                            </div>
+                        </div>
+
+                        <div className="detail-section">
+                            <h4 className="section-title">资源统计</h4>
+                            <div className="detail-row">
+                                <span className="label">知识库:</span>
+                                <span className="value">{currentAudit.knowledgeBaseCount} 个</span>
+                            </div>
+                            <div className="detail-row">
+                                <span className="label">文档:</span>
+                                <span className="value">{currentAudit.documentCount} 篇</span>
+                            </div>
+                            <div className="detail-row">
+                                <span className="label">切片:</span>
+                                <span className="value">{currentAudit.sliceCount} 条</span>
+                            </div>
+                        </div>
+
+                        {currentAudit.auditOpinion && (
+                            <div className="detail-section">
+                                <h4 className="section-title">审核意见</h4>
+                                <p style={{ color: '#6B7280', fontSize: '13px' }}>
+                                    {currentAudit.auditOpinion}
+                                </p>
+                            </div>
+                        )}
+                    </>
+                )}
             </Drawer>
-        }
-        <AuditHistory open={historyOpen} onClose={() => setHistoryOpen(false)} />
+        </div>
     );
 };
 
-export default KnowledgeLibList;
+export default AuditPage;

+ 61 - 24
jk-rag-platform/src/pages/system/audit/store.ts

@@ -1,6 +1,6 @@
 import { create } from 'zustand';
 import { message } from 'antd';
-import { apis } from '@/apis';
+import { fetchAuditList, fetchAuditStats, auditAction } from '@/apis';
 
 interface PageState {
     pageNum: number;
@@ -15,6 +15,12 @@ interface AuditState {
     page: PageState;
     infoModalId: string;
     infoModalOpen: boolean;
+    stats: {
+        total: number;
+        pending: number;
+        approved: number;
+        rejected: number;
+    } | null;
 }
 
 interface AuditActions {
@@ -24,12 +30,14 @@ interface AuditActions {
     setPage: (page: PageState) => void;
     setInfoModalId: (id: string) => void;
     setInfoModalOpen: (open: boolean) => void;
+    setStats: (stats: any) => void;
 }
 
 export type AuditStore = AuditState & AuditActions & {
     fetchAuditList: () => Promise<void>;
-    onAuditPass: (id: string) => Promise<void>;
-    onAuditReject: (id: string, reason: string) => Promise<void>;
+    fetchAuditStats: () => Promise<void>;
+    onAuditPass: (auditId: string, opinion: string) => Promise<void>;
+    onAuditReject: (auditId: string, opinion: string) => Promise<void>;
 };
 
 const initialState: AuditState = {
@@ -43,64 +51,93 @@ const initialState: AuditState = {
     },
     infoModalId: '',
     infoModalOpen: false,
+    stats: null,
 };
 
 export const useAuditStore = create<AuditStore>((set, get) => ({
     ...initialState,
-    
+
     setQuery: (query) => set({ query }),
-    
+
     setListLoading: (loading) => set({ listLoading: loading }),
-    
+
     setList: (list) => set({ list }),
-    
+
     setPage: (page) => set({ page }),
-    
+
     setInfoModalId: (id) => set({ infoModalId: id }),
-    
+
     setInfoModalOpen: (open) => set({ infoModalOpen: open }),
-    
+
+    setStats: (stats) => set({ stats }),
+
     fetchAuditList: async () => {
         set({ listLoading: true });
         try {
             const { query, page } = get();
-            const res: any = await apis.fetchAuditList({
+            const res: any = await fetchAuditList({
                 ...query,
                 pageNum: page.pageNum,
                 pageSize: page.pageSize,
             });
-            
+
             if (res.code === 200) {
                 set({
-                    list: res.rows || [],
+                    list: res.data?.list || [],
                     page: {
                         ...page,
-                        total: res.total || 0,
+                        total: res.data?.total || 0,
                     },
                 });
             }
         } catch (error) {
             console.error('获取审核列表失败:', error);
+            message.error('获取审核列表失败');
         } finally {
             set({ listLoading: false });
         }
     },
-    
-    onAuditPass: async (id: string) => {
+
+    fetchAuditStats: async () => {
+        try {
+            const res: any = await fetchAuditStats();
+            if (res.code === 200) {
+                set({ stats: res.data || null });
+            }
+        } catch (error) {
+            console.error('获取统计数据失败:', error);
+        }
+    },
+
+    onAuditPass: async (auditId: string, opinion: string) => {
         try {
-            await apis.auditPass(id);
-            message.success('审核通过');
-            get().fetchAuditList();
+            const res: any = await auditAction({
+                auditId,
+                action: 'approve',
+                opinion,
+            });
+            if (res.code === 200) {
+                message.success('审核通过');
+                get().fetchAuditList();
+                get().fetchAuditStats();
+            }
         } catch (error) {
             message.error('审核失败');
         }
     },
-    
-    onAuditReject: async (id: string, reason: string) => {
+
+    onAuditReject: async (auditId: string, opinion: string) => {
         try {
-            await apis.auditReject(id, reason);
-            message.success('已拒绝');
-            get().fetchAuditList();
+            const res: any = await auditAction({
+                auditId,
+                action: 'reject',
+                opinion,
+            });
+            if (res.code === 200) {
+                message.success('已拒绝');
+                get().fetchAuditList();
+                get().fetchAuditStats();
+            }
         } catch (error) {
             message.error('操作失败');
         }

+ 240 - 2
jk-rag-platform/src/pages/system/audit/style.scss

@@ -1,2 +1,240 @@
-// 系统管理 - 审计日志页面样式
-// 注意:.content-section 已在全局 global.less 中定义,此处不需要重复
+@import '@/styles/variables.scss';
+
+.audit-page {
+    .audit-stats {
+        display: grid;
+        grid-template-columns: repeat(4, 1fr);
+        gap: $spacing-4;
+        margin-bottom: $spacing-4;
+
+        @media (max-width: $screen-lg) {
+            grid-template-columns: repeat(2, 1fr);
+        }
+
+        @media (max-width: $screen-md) {
+            grid-template-columns: 1fr;
+        }
+    }
+
+    .audit-table {
+        background: $bg-secondary;
+        border-radius: $radius-lg;
+        padding: $spacing-4;
+
+        .table-header {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            margin-bottom: $spacing-4;
+
+            .header-title {
+                h3 {
+                    font-size: $font-lg;
+                    font-weight: $font-weight-semibold;
+                    color: $text-primary;
+                    margin: 0 0 $spacing-1 0;
+                }
+
+                p {
+                    font-size: $font-sm;
+                    color: $text-secondary;
+                    margin: 0;
+                }
+            }
+
+            .header-actions {
+                display: flex;
+                gap: $spacing-3;
+                align-items: center;
+
+                .search-input {
+                    width: 240px;
+                }
+            }
+        }
+
+        .filter-tags {
+            display: flex;
+            gap: $spacing-2;
+            margin-bottom: $spacing-4;
+            flex-wrap: wrap;
+
+            .filter-tag {
+                padding: $spacing-2 $spacing-3;
+                background: $bg-tertiary;
+                border-radius: $radius-md;
+                font-size: $font-sm;
+                color: $text-secondary;
+                cursor: pointer;
+                transition: $transition-base;
+
+                &:hover {
+                    background: $bg-hover;
+                    color: $text-primary;
+                }
+
+                &.active {
+                    background: $tag-bg-blue;
+                    color: $tag-text-blue;
+                }
+            }
+        }
+
+        .status-badge {
+            display: inline-flex;
+            align-items: center;
+            gap: $spacing-1;
+            padding: $spacing-1 $spacing-2;
+            border-radius: $radius-md;
+            font-size: $font-sm;
+
+            &.pending {
+                background: $tag-bg-amber;
+                color: $tag-text-amber;
+            }
+
+            &.approved {
+                background: $tag-bg-teal;
+                color: $tag-text-teal;
+            }
+
+            &.rejected {
+                background: $tag-bg-rose;
+                color: $tag-text-rose;
+            }
+        }
+
+        .action-buttons {
+            display: flex;
+            gap: $spacing-2;
+
+            .audit-btn {
+                padding: $spacing-1 $spacing-3;
+                font-size: $font-sm;
+                border-radius: $radius-md;
+
+                &.approve {
+                    background: $success-color;
+                    color: #fff;
+                    border: none;
+
+                    &:hover {
+                        background: $success-light;
+                    }
+                }
+
+                &.reject {
+                    background: $error-color;
+                    color: #fff;
+                    border: none;
+
+                    &:hover {
+                        background: $error-light;
+                    }
+                }
+            }
+        }
+    }
+
+    .detail-drawer {
+        .detail-section {
+            margin-bottom: $spacing-4;
+
+            .section-title {
+                font-size: $font-md;
+                font-weight: $font-weight-semibold;
+                color: $text-primary;
+                margin-bottom: $spacing-3;
+                padding-bottom: $spacing-2;
+                border-bottom: 1px solid $border-base;
+            }
+
+            .detail-row {
+                display: flex;
+                margin-bottom: $spacing-2;
+
+                .label {
+                    width: 100px;
+                    font-size: $font-sm;
+                    color: $text-secondary;
+                    flex-shrink: 0;
+                }
+
+                .value {
+                    font-size: $font-base;
+                    color: $text-primary;
+                }
+            }
+
+            .tag-list {
+                display: flex;
+                flex-wrap: wrap;
+                gap: $spacing-2;
+
+                .tag {
+                    padding: $spacing-1 $spacing-2;
+                    background: $tag-bg-blue;
+                    color: $tag-text-blue;
+                    border-radius: $radius-md;
+                    font-size: $font-xs;
+                }
+            }
+        }
+
+        .history-timeline {
+            .timeline-item {
+                display: flex;
+                gap: $spacing-3;
+                padding-bottom: $spacing-3;
+                margin-bottom: $spacing-3;
+                border-left: 2px solid $border-base;
+                padding-left: $spacing-4;
+                position: relative;
+
+                &::before {
+                    content: '';
+                    position: absolute;
+                    left: -6px;
+                    top: 0;
+                    width: 10px;
+                    height: 10px;
+                    border-radius: 50%;
+                    background: $primary-color;
+                }
+
+                &.approved::before {
+                    background: $success-color;
+                }
+
+                &.rejected::before {
+                    background: $error-color;
+                }
+
+                .timeline-content {
+                    flex: 1;
+
+                    .timeline-title {
+                        font-size: $font-base;
+                        font-weight: $font-weight-medium;
+                        color: $text-primary;
+                        margin-bottom: $spacing-1;
+                    }
+
+                    .timeline-time {
+                        font-size: $font-xs;
+                        color: $text-hint;
+                        margin-bottom: $spacing-1;
+                    }
+
+                    .timeline-remark {
+                        font-size: $font-sm;
+                        color: $text-secondary;
+                        background: $bg-tertiary;
+                        padding: $spacing-2;
+                        border-radius: $radius-md;
+                    }
+                }
+            }
+        }
+    }
+}