Your Name e4959d584f feat: 完善代理商端业务逻辑与前后端框架
主要更新:
- 更新代理商端文档,明确项目由品牌方分配流程
- 新增Brief配置详情页(已配置)设计稿
- 完善工作台紧急待办中品牌新任务功能
- 整理Pencil设计文件中代理商端页面顺序
- 新增后端FastAPI框架及核心API
- 新增前端Next.js页面和组件库
- 添加.gitignore排除构建和缓存文件

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 19:27:31 +08:00

256 lines
9.4 KiB
TypeScript

'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>
)
}