feat: M1-04 content safety — reports + violations management tabs
All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 10s
All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 10s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
bf79178546
commit
d71bb5f4d8
@ -17,6 +17,8 @@ function CSPage() {
|
|||||||
|
|
||||||
const { data: words } = useQuery({ queryKey: ['safety', 'words'], queryFn: (): Promise<any> => api.get('/admin-api/content-safety/words') })
|
const { data: words } = useQuery({ queryKey: ['safety', 'words'], queryFn: (): Promise<any> => api.get('/admin-api/content-safety/words') })
|
||||||
const { data: checks } = useQuery({ queryKey: ['safety', 'checks'], queryFn: (): Promise<any> => api.get('/admin-api/content-safety/checks') })
|
const { data: checks } = useQuery({ queryKey: ['safety', 'checks'], queryFn: (): Promise<any> => api.get('/admin-api/content-safety/checks') })
|
||||||
|
const { data: reports } = useQuery({ queryKey: ['safety', 'reports'], queryFn: (): Promise<any> => api.get('/admin-api/content-safety/reports') })
|
||||||
|
const { data: violations } = useQuery({ queryKey: ['safety', 'violations'], queryFn: (): Promise<any> => api.get('/admin-api/content-safety/violations') })
|
||||||
|
|
||||||
const addWord = async () => {
|
const addWord = async () => {
|
||||||
await api.post('/admin-api/content-safety/words', { word: newWord, category, riskLevel: risk })
|
await api.post('/admin-api/content-safety/words', { word: newWord, category, riskLevel: risk })
|
||||||
@ -29,6 +31,24 @@ function CSPage() {
|
|||||||
onOk: async () => { await api.delete(`/admin-api/content-safety/words/${id}`); qc.invalidateQueries({ queryKey: ['safety'] }) },
|
onOk: async () => { await api.delete(`/admin-api/content-safety/words/${id}`); qc.invalidateQueries({ queryKey: ['safety'] }) },
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const handleReport = (id: string, action: string) => modal.confirm({
|
||||||
|
title: action === 'confirmed' ? '确认违规' : '驳回举报',
|
||||||
|
content: action === 'confirmed' ? '确认后将创建违规记录' : '驳回后将关闭此举报',
|
||||||
|
onOk: async () => {
|
||||||
|
await api.post(`/admin-api/content-safety/reports/${id}/handle`, { action, note: `Admin ${action}` })
|
||||||
|
message.success('已处理'); qc.invalidateQueries({ queryKey: ['safety'] })
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const applyPenalty = (id: string, penalty: string) => modal.confirm({
|
||||||
|
title: '应用处罚',
|
||||||
|
content: `确认对违规记录应用「${penalty}」处罚?`,
|
||||||
|
onOk: async () => {
|
||||||
|
await api.post(`/admin-api/content-safety/violations/${id}/penalty`, { penalty })
|
||||||
|
message.success('处罚已应用'); qc.invalidateQueries({ queryKey: ['safety'] })
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const wordCols = [
|
const wordCols = [
|
||||||
{ title: '词汇', dataIndex: 'word', width: 150 },
|
{ title: '词汇', dataIndex: 'word', width: 150 },
|
||||||
{ title: '分类', dataIndex: 'category', width: 100 },
|
{ title: '分类', dataIndex: 'category', width: 100 },
|
||||||
@ -46,6 +66,35 @@ function CSPage() {
|
|||||||
{ title: '结果', dataIndex: 'result', width: 80, render: (v: string) => <Tag color={v==='passed'?'green':v==='blocked'?'red':'gold'}>{v}</Tag> },
|
{ title: '结果', dataIndex: 'result', width: 80, render: (v: string) => <Tag color={v==='passed'?'green':v==='blocked'?'red':'gold'}>{v}</Tag> },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const reportCols = [
|
||||||
|
{ title: '时间', dataIndex: 'createdAt', width: 130, render: (d: string) => dayjs(d).format('MM-DD HH:mm') },
|
||||||
|
{ title: '类型', dataIndex: 'targetType', width: 110 },
|
||||||
|
{ title: '目标ID', dataIndex: 'targetId', width: 120, ellipsis: true },
|
||||||
|
{ title: '原因', dataIndex: 'reason', ellipsis: true },
|
||||||
|
{ title: '状态', dataIndex: 'status', width: 80, render: (v: string) => v === 'pending' ? <Tag color="gold">待处理</Tag> : v === 'confirmed' ? <Tag color="red">已确认</Tag> : <Tag>已驳回</Tag> },
|
||||||
|
{ title: '操作', width: 160, render: (_: any, r: any) => r.status === 'pending' ? (
|
||||||
|
<Space size="small">
|
||||||
|
<Button type="link" size="small" onClick={() => handleReport(r.id, 'confirmed')}>确认</Button>
|
||||||
|
<Button type="link" size="small" danger onClick={() => handleReport(r.id, 'dismissed')}>驳回</Button>
|
||||||
|
</Space>
|
||||||
|
) : <Text type="secondary">-</Text> },
|
||||||
|
]
|
||||||
|
|
||||||
|
const violationCols = [
|
||||||
|
{ title: '时间', dataIndex: 'createdAt', width: 130, render: (d: string) => dayjs(d).format('MM-DD HH:mm') },
|
||||||
|
{ title: '用户', dataIndex: 'userId', width: 140, ellipsis: true },
|
||||||
|
{ title: '类型', dataIndex: 'contentType', width: 100 },
|
||||||
|
{ title: '内容', dataIndex: 'content', ellipsis: true, render: (v: string) => <Text style={{ fontSize: 12 }}>{v?.slice(0, 60)}</Text> },
|
||||||
|
{ title: '风险', dataIndex: 'riskLevel', width: 70, render: (v: string) => <Tag color={v==='critical'?'red':v==='high'?'orange':'blue'}>{v}</Tag> },
|
||||||
|
{ title: '处罚', dataIndex: 'penalty', width: 80, render: (v: string) => v === 'none' ? <Tag>无</Tag> : <Tag color="red">{v}</Tag> },
|
||||||
|
{ title: '操作', width: 120, render: (_: any, r: any) => r.penalty === 'none' ? (
|
||||||
|
<Space size="small">
|
||||||
|
<Button type="link" size="small" onClick={() => applyPenalty(r.id, 'warning')}>警告</Button>
|
||||||
|
<Button type="link" size="small" danger onClick={() => applyPenalty(r.id, 'restrict')}>限制</Button>
|
||||||
|
</Space>
|
||||||
|
) : <Text type="secondary">{r.penalty}</Text> },
|
||||||
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
|
||||||
@ -59,6 +108,8 @@ function CSPage() {
|
|||||||
<Tabs items={[
|
<Tabs items={[
|
||||||
{ key: 'words', label: '敏感词库', children: <Table dataSource={words || []} columns={wordCols} rowKey="id" pagination={false} size="small" /> },
|
{ key: 'words', label: '敏感词库', children: <Table dataSource={words || []} columns={wordCols} rowKey="id" pagination={false} size="small" /> },
|
||||||
{ key: 'checks', label: '审核记录', children: <Table dataSource={checks || []} columns={checkCols} rowKey="id" pagination={{ pageSize: 20 }} size="small" /> },
|
{ key: 'checks', label: '审核记录', children: <Table dataSource={checks || []} columns={checkCols} rowKey="id" pagination={{ pageSize: 20 }} size="small" /> },
|
||||||
|
{ key: 'reports', label: '举报管理', children: <Table dataSource={reports || []} columns={reportCols} rowKey="id" pagination={{ pageSize: 20 }} size="small" /> },
|
||||||
|
{ key: 'violations', label: '违规记录', children: <Table dataSource={violations || []} columns={violationCols} rowKey="id" pagination={{ pageSize: 20 }} size="small" /> },
|
||||||
]} />
|
]} />
|
||||||
|
|
||||||
<Modal title="新增敏感词" open={addOpen} onOk={addWord} onCancel={() => setAddOpen(false)} okText="添加">
|
<Modal title="新增敏感词" open={addOpen} onOk={addWord} onCancel={() => setAddOpen(false)} okText="添加">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user