feat: 完善代理商端业务逻辑与前后端框架
主要更新: - 更新代理商端文档,明确项目由品牌方分配流程 - 新增Brief配置详情页(已配置)设计稿 - 完善工作台紧急待办中品牌新任务功能 - 整理Pencil设计文件中代理商端页面顺序 - 新增后端FastAPI框架及核心API - 新增前端Next.js页面和组件库 - 添加.gitignore排除构建和缓存文件 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
d52509d630
commit
e4959d584f
@@ -0,0 +1,255 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Plus, Shield, AlertTriangle, Ban, Building2 } from 'lucide-react'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||
import { Button } from '@/components/ui/Button'
|
||||
import { Input } from '@/components/ui/Input'
|
||||
import { Modal } from '@/components/ui/Modal'
|
||||
import { Select } from '@/components/ui/Select'
|
||||
import { ErrorTag, WarningTag, SuccessTag } from '@/components/ui/Tag'
|
||||
|
||||
// 模拟规则数据
|
||||
const mockRules = {
|
||||
forbiddenWords: [
|
||||
{ id: '1', word: '最好', category: '极限词', severity: 'high' },
|
||||
{ id: '2', word: '第一', category: '极限词', severity: 'high' },
|
||||
{ id: '3', word: '最佳', category: '极限词', severity: 'high' },
|
||||
{ id: '4', word: '100%有效', category: '虚假宣称', severity: 'high' },
|
||||
{ id: '5', word: '立即见效', category: '虚假宣称', severity: 'medium' },
|
||||
{ id: '6', word: '永久', category: '极限词', severity: 'medium' },
|
||||
],
|
||||
competitors: [
|
||||
{ id: '1', name: '竞品A', logoUrl: '' },
|
||||
{ id: '2', name: '竞品B', logoUrl: '' },
|
||||
{ id: '3', name: '竞品C', logoUrl: '' },
|
||||
],
|
||||
whitelist: [
|
||||
{ id: '1', term: '品牌专属术语1', reason: '品牌授权使用' },
|
||||
{ id: '2', term: '特定产品名', reason: '官方产品名称' },
|
||||
],
|
||||
}
|
||||
|
||||
const categoryOptions = [
|
||||
{ value: 'absolute_term', label: '极限词' },
|
||||
{ value: 'false_claim', label: '虚假宣称' },
|
||||
{ value: 'platform_rule', label: '平台规则' },
|
||||
{ value: 'custom', label: '自定义' },
|
||||
]
|
||||
|
||||
const severityOptions = [
|
||||
{ value: 'high', label: '高风险' },
|
||||
{ value: 'medium', label: '中风险' },
|
||||
{ value: 'low', label: '低风险' },
|
||||
]
|
||||
|
||||
function SeverityTag({ severity }: { severity: string }) {
|
||||
if (severity === 'high') return <ErrorTag>高风险</ErrorTag>
|
||||
if (severity === 'medium') return <WarningTag>中风险</WarningTag>
|
||||
return <SuccessTag>低风险</SuccessTag>
|
||||
}
|
||||
|
||||
export default function RulesPage() {
|
||||
const [activeTab, setActiveTab] = useState<'forbidden' | 'competitors' | 'whitelist'>('forbidden')
|
||||
const [showAddModal, setShowAddModal] = useState(false)
|
||||
const [newWord, setNewWord] = useState('')
|
||||
const [newCategory, setNewCategory] = useState('absolute_term')
|
||||
const [newSeverity, setNewSeverity] = useState('high')
|
||||
|
||||
const handleAddWord = () => {
|
||||
if (!newWord.trim()) return
|
||||
// TODO: 调用 API 添加
|
||||
setShowAddModal(false)
|
||||
setNewWord('')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-2xl font-bold text-gray-900">规则配置</h1>
|
||||
</div>
|
||||
|
||||
{/* 标签页 */}
|
||||
<div className="flex gap-2 border-b">
|
||||
<button
|
||||
type="button"
|
||||
className={`px-4 py-2 border-b-2 transition-colors ${
|
||||
activeTab === 'forbidden'
|
||||
? 'border-blue-600 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
onClick={() => setActiveTab('forbidden')}
|
||||
>
|
||||
<Ban size={16} className="inline mr-2" />
|
||||
违禁词 ({mockRules.forbiddenWords.length})
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`px-4 py-2 border-b-2 transition-colors ${
|
||||
activeTab === 'competitors'
|
||||
? 'border-blue-600 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
onClick={() => setActiveTab('competitors')}
|
||||
>
|
||||
<Building2 size={16} className="inline mr-2" />
|
||||
竞品列表 ({mockRules.competitors.length})
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`px-4 py-2 border-b-2 transition-colors ${
|
||||
activeTab === 'whitelist'
|
||||
? 'border-blue-600 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
onClick={() => setActiveTab('whitelist')}
|
||||
>
|
||||
<Shield size={16} className="inline mr-2" />
|
||||
白名单 ({mockRules.whitelist.length})
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 违禁词列表 */}
|
||||
{activeTab === 'forbidden' && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center justify-between">
|
||||
<span>违禁词列表</span>
|
||||
<Button size="sm" icon={Plus} onClick={() => setShowAddModal(true)}>
|
||||
添加
|
||||
</Button>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b text-left text-sm text-gray-500">
|
||||
<th className="pb-3 font-medium">词汇</th>
|
||||
<th className="pb-3 font-medium">分类</th>
|
||||
<th className="pb-3 font-medium">风险等级</th>
|
||||
<th className="pb-3 font-medium">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{mockRules.forbiddenWords.map((word) => (
|
||||
<tr key={word.id} className="border-b last:border-0">
|
||||
<td className="py-3 font-medium text-gray-900">{word.word}</td>
|
||||
<td className="py-3 text-gray-600">{word.category}</td>
|
||||
<td className="py-3"><SeverityTag severity={word.severity} /></td>
|
||||
<td className="py-3">
|
||||
<Button size="sm" variant="ghost">删除</Button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 竞品列表 */}
|
||||
{activeTab === 'competitors' && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center justify-between">
|
||||
<span>竞品列表</span>
|
||||
<Button size="sm" icon={Plus}>添加竞品</Button>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-gray-500 mb-4">
|
||||
系统将在视频中检测以下竞品的 Logo 或品牌名称
|
||||
</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
{mockRules.competitors.map((competitor) => (
|
||||
<div key={competitor.id} className="p-4 border rounded-lg flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 bg-gray-100 rounded-full flex items-center justify-center">
|
||||
<Building2 size={20} className="text-gray-400" />
|
||||
</div>
|
||||
<span className="font-medium">{competitor.name}</span>
|
||||
</div>
|
||||
<Button size="sm" variant="ghost">删除</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 白名单 */}
|
||||
{activeTab === 'whitelist' && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center justify-between">
|
||||
<span>白名单</span>
|
||||
<Button size="sm" icon={Plus}>添加</Button>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-gray-500 mb-4">
|
||||
白名单中的词汇即使命中违禁词也不会触发告警
|
||||
</p>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b text-left text-sm text-gray-500">
|
||||
<th className="pb-3 font-medium">词汇</th>
|
||||
<th className="pb-3 font-medium">原因</th>
|
||||
<th className="pb-3 font-medium">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{mockRules.whitelist.map((item) => (
|
||||
<tr key={item.id} className="border-b last:border-0">
|
||||
<td className="py-3 font-medium text-gray-900">{item.term}</td>
|
||||
<td className="py-3 text-gray-600">{item.reason}</td>
|
||||
<td className="py-3">
|
||||
<Button size="sm" variant="ghost">删除</Button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 添加违禁词弹窗 */}
|
||||
<Modal
|
||||
isOpen={showAddModal}
|
||||
onClose={() => setShowAddModal(false)}
|
||||
title="添加违禁词"
|
||||
size="sm"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<Input
|
||||
label="违禁词"
|
||||
placeholder="输入违禁词"
|
||||
value={newWord}
|
||||
onChange={(e) => setNewWord(e.target.value)}
|
||||
/>
|
||||
<Select
|
||||
label="分类"
|
||||
options={categoryOptions}
|
||||
value={newCategory}
|
||||
onChange={(e) => setNewCategory(e.target.value)}
|
||||
/>
|
||||
<Select
|
||||
label="风险等级"
|
||||
options={severityOptions}
|
||||
value={newSeverity}
|
||||
onChange={(e) => setNewSeverity(e.target.value)}
|
||||
/>
|
||||
<div className="flex gap-3 justify-end pt-4">
|
||||
<Button variant="ghost" onClick={() => setShowAddModal(false)}>取消</Button>
|
||||
<Button onClick={handleAddWord}>添加</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user