From 050fb554f03a967eeb0e1bb41bbd1e64243049c2 Mon Sep 17 00:00:00 2001 From: WangDL Date: Sun, 24 May 2026 18:19:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20M4-09=20=E2=80=94=20compliance=20admin?= =?UTF-8?q?=20page=20(policies,=20agreements,=20filings,=20data=20requests?= =?UTF-8?q?,=20security)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ComplianceAdmin page with 6 tabs: privacy policies, user agreements, filings, data deletion, data export, security events Co-Authored-By: Claude Opus 4.7 --- src/App.tsx | 2 + src/config/menu.tsx | 1 + src/pages/ComplianceAdmin.tsx | 134 ++++++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 src/pages/ComplianceAdmin.tsx diff --git a/src/App.tsx b/src/App.tsx index fa33ef5..cac0523 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -34,6 +34,7 @@ const KnowledgeOpsPage = lazy(() => import('./pages/KnowledgeOps')) const ChatLogsPage = lazy(() => import('./pages/ChatLogs')) const HermesSettings = lazy(() => import('./pages/HermesSettings')) const ReleaseAdmin = lazy(() => import('./pages/ReleaseAdmin')) +const ComplianceAdmin = lazy(() => import('./pages/ComplianceAdmin')) const TaskAssistant = lazy(() => import('./pages/TaskAssistant')) const Placeholder = lazy(() => import('./pages/Placeholder')) const ForbiddenPage = lazy(() => import('./pages/403')) @@ -184,6 +185,7 @@ function App() { }>} /> }>} /> }>} /> + }>} /> }>} /> } /> diff --git a/src/config/menu.tsx b/src/config/menu.tsx index 58a55d0..11a7618 100644 --- a/src/config/menu.tsx +++ b/src/config/menu.tsx @@ -29,6 +29,7 @@ export const adminMenuItems: AdminMenuItem[] = [ { path: '/imports', name: '文档导入', icon: }, { path: '/files', name: '文件与 COS', icon: }, { path: '/reporting', name: '报表导出', icon: , requiredRole: 'ADMIN' }, + { path: '/compliance', name: '合规安全', icon: , requiredRole: 'ADMIN' }, { path: '/audit', name: '审计日志', icon: , requiredRole: 'ADMIN' }, { path: '/billing', name: 'API 用量', icon: , requiredRole: 'SUPER_ADMIN' }, { path: '/git', name: '项目中心', icon: }, diff --git a/src/pages/ComplianceAdmin.tsx b/src/pages/ComplianceAdmin.tsx new file mode 100644 index 0000000..8aab0cc --- /dev/null +++ b/src/pages/ComplianceAdmin.tsx @@ -0,0 +1,134 @@ +import { Table, Button, Modal, Form, Input, Tag, Space, Typography, Tabs, DatePicker } from 'antd' +import { PlusOutlined, EditOutlined, SafetyOutlined, CheckOutlined } from '@ant-design/icons' +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' +import { api } from '@/services/http-client' +import { message } from 'antd' +import dayjs from 'dayjs' + +const { Title } = Typography +const { TextArea } = Input + +const statusColors: Record = { pending: 'blue', approved: 'green', rejected: 'red', PENDING: 'blue', APPROVED: 'green', REJECTED: 'red' } + +export default function ComplianceAdmin() { + const [modalOpen, setModalOpen] = useState(false) + const [modalType, setModalType] = useState('') + const [editing, setEditing] = useState(null) + const [form] = Form.useForm() + const qc = useQueryClient() + + const { data: policies, isLoading: pLoading } = useQuery({ + queryKey: ['compliance', 'policies'], + queryFn: () => api.get('/admin-api/compliance/privacy-policies').then(d => d ?? []), + }) + + const { data: agreements, isLoading: aLoading } = useQuery({ + queryKey: ['compliance', 'agreements'], + queryFn: () => api.get('/admin-api/compliance/user-agreements').then(d => d ?? []), + }) + + const { data: filings, isLoading: fLoading } = useQuery({ + queryKey: ['compliance', 'filings'], + queryFn: () => api.get('/admin-api/compliance/filings').then(d => d ?? []), + }) + + const { data: deletions, isLoading: dLoading } = useQuery({ + queryKey: ['compliance', 'deletions'], + queryFn: () => api.get('/admin-api/compliance/deletion-requests').then(d => d ?? []), + }) + + const { data: exports_, isLoading: eLoading } = useQuery({ + queryKey: ['compliance', 'exports'], + queryFn: () => api.get('/admin-api/compliance/export-requests').then(d => d ?? []), + }) + + const { data: securityEvents, isLoading: sLoading } = useQuery({ + queryKey: ['compliance', 'security-events'], + queryFn: () => api.get('/admin-api/compliance/security-events').then(d => d ?? []), + }) + + const saveMutation = useMutation({ + mutationFn: (values: any) => { + if (modalType === 'policy') return editing + ? api.patch(`/admin-api/compliance/privacy-policies/${editing.id}`, values) + : api.post('/admin-api/compliance/privacy-policies', values) + if (modalType === 'agreement') return editing + ? api.patch(`/admin-api/compliance/user-agreements/${editing.id}`, values) + : api.post('/admin-api/compliance/user-agreements', values) + return api.post('/admin-api/compliance/filings', values) + }, + onSuccess: () => { + message.success('已保存') + qc.invalidateQueries({ queryKey: ['compliance'] }) + setModalOpen(false); setEditing(null) + }, + }) + + const approveDeletion = useMutation({ + mutationFn: (id: string) => api.post(`/admin-api/compliance/deletion-requests/${id}/approve`), + onSuccess: () => { message.success('已批准'); qc.invalidateQueries({ queryKey: ['compliance', 'deletions'] }) }, + }) + + const openCreate = (type: string) => { setModalType(type); setEditing(null); form.resetFields(); setModalOpen(true) } + + const docColumns = [ + { title: '版本', dataIndex: 'version', width: 80 }, + { title: '标题', dataIndex: 'title', width: 200, ellipsis: true }, + { title: '生效时间', dataIndex: 'effectiveAt', width: 110, render: (d: string) => d ? new Date(d).toLocaleDateString() : '-' }, + { title: '状态', dataIndex: 'published', width: 60, render: (p: boolean) => {p ? '已发布' : '草稿'} }, + { title: '操作', width: 60, render: (_: any, r: any) => : null }, + ] + + const securityColumns = [ + { title: '类型', dataIndex: 'eventType', width: 100 }, + { title: '严重程度', dataIndex: 'severity', width: 80, render: (s: string) => {s} }, + { title: '已处理', dataIndex: 'handled', width: 60, render: (h: boolean) => h ? '是' : '否' }, + { title: '时间', dataIndex: 'createdAt', width: 110, render: (d: string) => d ? new Date(d).toLocaleString() : '-' }, + ] + + const tabs = [ + { key: 'policies', label: '隐私政策', children:
}, + { key: 'agreements', label: '用户协议', children:
}, + { key: 'filings', label: '备案台账', children:
}, + { key: 'deletions', label: '数据删除', children:
}, + { key: 'exports', label: '数据导出', children:
{s} }, + { title: '创建时间', dataIndex: 'createdAt', width: 110, render: (d: string) => new Date(d).toLocaleDateString() }, + ]} rowKey="id" loading={eLoading} size="small" /> }, + { key: 'security', label: '安全事件', children:
}, + ] + + return ( +
+ <SafetyOutlined /> 合规与安全 + + { setModalOpen(false); setEditing(null) }} onOk={() => form.submit()} confirmLoading={saveMutation.isPending}> +
+ + + {modalType === 'filing' && } + {modalType !== 'filing' &&