feat: 完成代理商/品牌方前端及文档更新
代理商端前端: - 新增达人管理页面(含任务申诉次数管理) - 新增消息中心(含申诉次数申请审批) - 新增 Brief 管理(列表、详情) - 新增审核中心(脚本审核、视频审核) - 新增数据报表页面 品牌方端前端: - 优化首页仪表盘布局 - 新增项目管理(列表、详情、创建) - 新增代理商管理页面 - 新增审核中心(脚本终审、视频终审) - 新增系统设置页面 文档更新: - 申诉次数改为按任务分配(每任务初始1次) - 更新 PRD、FeatureSummary、User_Role_Interfaces 等文档 - 更新 UI 设计规范和开发计划 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
2f9b7f05fd
commit
4753626e5a
@@ -0,0 +1,298 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||
import { Button } from '@/components/ui/Button'
|
||||
import { Modal } from '@/components/ui/Modal'
|
||||
import { SuccessTag, PendingTag, WarningTag } from '@/components/ui/Tag'
|
||||
import {
|
||||
Search,
|
||||
Plus,
|
||||
Users,
|
||||
TrendingUp,
|
||||
TrendingDown,
|
||||
Mail,
|
||||
Copy,
|
||||
CheckCircle,
|
||||
Clock,
|
||||
MoreVertical
|
||||
} from 'lucide-react'
|
||||
|
||||
// 模拟代理商列表
|
||||
const mockAgencies = [
|
||||
{
|
||||
id: 'agency-001',
|
||||
name: '星耀传媒',
|
||||
email: 'contact@xingyao.com',
|
||||
status: 'active',
|
||||
creatorCount: 50,
|
||||
projectCount: 8,
|
||||
passRate: 92,
|
||||
trend: 'up',
|
||||
joinedAt: '2025-06-15',
|
||||
},
|
||||
{
|
||||
id: 'agency-002',
|
||||
name: '创意无限',
|
||||
email: 'hello@chuangyi.com',
|
||||
status: 'active',
|
||||
creatorCount: 35,
|
||||
projectCount: 5,
|
||||
passRate: 88,
|
||||
trend: 'up',
|
||||
joinedAt: '2025-08-20',
|
||||
},
|
||||
{
|
||||
id: 'agency-003',
|
||||
name: '美妆达人MCN',
|
||||
email: 'biz@meizhuang.com',
|
||||
status: 'active',
|
||||
creatorCount: 28,
|
||||
projectCount: 4,
|
||||
passRate: 75,
|
||||
trend: 'down',
|
||||
joinedAt: '2025-10-10',
|
||||
},
|
||||
{
|
||||
id: 'agency-004',
|
||||
name: '时尚风向标',
|
||||
email: 'info@shishang.com',
|
||||
status: 'pending',
|
||||
creatorCount: 0,
|
||||
projectCount: 0,
|
||||
passRate: 0,
|
||||
trend: 'stable',
|
||||
joinedAt: '2026-02-01',
|
||||
},
|
||||
]
|
||||
|
||||
function StatusTag({ status }: { status: string }) {
|
||||
if (status === 'active') return <SuccessTag>已激活</SuccessTag>
|
||||
if (status === 'pending') return <PendingTag>待接受</PendingTag>
|
||||
return <WarningTag>已暂停</WarningTag>
|
||||
}
|
||||
|
||||
export default function AgenciesManagePage() {
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [showInviteModal, setShowInviteModal] = useState(false)
|
||||
const [inviteEmail, setInviteEmail] = useState('')
|
||||
const [inviteLink, setInviteLink] = useState('')
|
||||
|
||||
const filteredAgencies = mockAgencies.filter(agency =>
|
||||
agency.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
agency.email.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
)
|
||||
|
||||
const handleInvite = () => {
|
||||
if (!inviteEmail.trim()) {
|
||||
alert('请输入代理商邮箱')
|
||||
return
|
||||
}
|
||||
// 模拟生成邀请链接
|
||||
const link = `https://miaosi.app/invite/agency/${Date.now()}`
|
||||
setInviteLink(link)
|
||||
}
|
||||
|
||||
const handleCopyLink = () => {
|
||||
navigator.clipboard.writeText(inviteLink)
|
||||
alert('链接已复制')
|
||||
}
|
||||
|
||||
const handleSendInvite = () => {
|
||||
alert(`邀请已发送至 ${inviteEmail}`)
|
||||
setShowInviteModal(false)
|
||||
setInviteEmail('')
|
||||
setInviteLink('')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* 页面标题 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-text-primary">代理商管理</h1>
|
||||
<p className="text-sm text-text-secondary mt-1">管理合作代理商,查看代理商绩效数据</p>
|
||||
</div>
|
||||
<Button onClick={() => setShowInviteModal(true)}>
|
||||
<Plus size={16} />
|
||||
邀请代理商
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 统计卡片 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<Card>
|
||||
<CardContent className="py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-text-secondary">总代理商</p>
|
||||
<p className="text-2xl font-bold text-text-primary">{mockAgencies.length}</p>
|
||||
</div>
|
||||
<div className="w-10 h-10 rounded-lg bg-accent-indigo/20 flex items-center justify-center">
|
||||
<Users size={20} className="text-accent-indigo" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-text-secondary">已激活</p>
|
||||
<p className="text-2xl font-bold text-accent-green">{mockAgencies.filter(a => a.status === 'active').length}</p>
|
||||
</div>
|
||||
<div className="w-10 h-10 rounded-lg bg-accent-green/20 flex items-center justify-center">
|
||||
<CheckCircle size={20} className="text-accent-green" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-text-secondary">待接受</p>
|
||||
<p className="text-2xl font-bold text-yellow-400">{mockAgencies.filter(a => a.status === 'pending').length}</p>
|
||||
</div>
|
||||
<div className="w-10 h-10 rounded-lg bg-yellow-500/20 flex items-center justify-center">
|
||||
<Clock size={20} className="text-yellow-400" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-text-secondary">平均通过率</p>
|
||||
<p className="text-2xl font-bold text-text-primary">
|
||||
{Math.round(mockAgencies.filter(a => a.status === 'active').reduce((sum, a) => sum + a.passRate, 0) / mockAgencies.filter(a => a.status === 'active').length)}%
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-10 h-10 rounded-lg bg-purple-500/20 flex items-center justify-center">
|
||||
<TrendingUp size={20} className="text-purple-400" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 搜索 */}
|
||||
<div className="relative max-w-md">
|
||||
<Search size={18} className="absolute left-3 top-1/2 -translate-y-1/2 text-text-tertiary" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="搜索代理商名称或邮箱..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-full pl-10 pr-4 py-2 border border-border-subtle rounded-lg bg-bg-elevated text-text-primary focus:outline-none focus:ring-2 focus:ring-accent-indigo"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 代理商列表 */}
|
||||
<Card>
|
||||
<CardContent className="p-0">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-border-subtle text-left text-sm text-text-secondary bg-bg-elevated">
|
||||
<th className="px-6 py-4 font-medium">代理商</th>
|
||||
<th className="px-6 py-4 font-medium">状态</th>
|
||||
<th className="px-6 py-4 font-medium">达人数</th>
|
||||
<th className="px-6 py-4 font-medium">项目数</th>
|
||||
<th className="px-6 py-4 font-medium">通过率</th>
|
||||
<th className="px-6 py-4 font-medium">加入时间</th>
|
||||
<th className="px-6 py-4 font-medium">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredAgencies.map((agency) => (
|
||||
<tr key={agency.id} className="border-b border-border-subtle last:border-0 hover:bg-bg-elevated/50">
|
||||
<td className="px-6 py-4">
|
||||
<div>
|
||||
<div className="font-medium text-text-primary">{agency.name}</div>
|
||||
<div className="text-sm text-text-tertiary">{agency.email}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<StatusTag status={agency.status} />
|
||||
</td>
|
||||
<td className="px-6 py-4 text-text-primary">{agency.creatorCount}</td>
|
||||
<td className="px-6 py-4 text-text-primary">{agency.projectCount}</td>
|
||||
<td className="px-6 py-4">
|
||||
{agency.status === 'active' ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`font-medium ${agency.passRate >= 90 ? 'text-accent-green' : agency.passRate >= 80 ? 'text-accent-indigo' : 'text-orange-400'}`}>
|
||||
{agency.passRate}%
|
||||
</span>
|
||||
{agency.trend === 'up' && <TrendingUp size={14} className="text-accent-green" />}
|
||||
{agency.trend === 'down' && <TrendingDown size={14} className="text-accent-coral" />}
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-text-tertiary">-</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-sm text-text-tertiary">{agency.joinedAt}</td>
|
||||
<td className="px-6 py-4">
|
||||
<Button variant="ghost" size="sm">
|
||||
<MoreVertical size={16} />
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 邀请代理商弹窗 */}
|
||||
<Modal isOpen={showInviteModal} onClose={() => { setShowInviteModal(false); setInviteEmail(''); setInviteLink(''); }} title="邀请代理商">
|
||||
<div className="space-y-4">
|
||||
<p className="text-text-secondary text-sm">输入代理商邮箱,系统将发送邀请链接。</p>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-text-primary mb-1">代理商邮箱</label>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="email"
|
||||
value={inviteEmail}
|
||||
onChange={(e) => setInviteEmail(e.target.value)}
|
||||
placeholder="agency@example.com"
|
||||
className="flex-1 px-4 py-2 border border-border-subtle rounded-lg bg-bg-elevated text-text-primary focus:outline-none focus:ring-2 focus:ring-accent-indigo"
|
||||
/>
|
||||
<Button variant="secondary" onClick={handleInvite}>
|
||||
生成链接
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{inviteLink && (
|
||||
<div className="p-4 bg-bg-elevated rounded-lg">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm font-medium text-text-primary">邀请链接</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCopyLink}
|
||||
className="flex items-center gap-1 text-sm text-accent-indigo hover:underline"
|
||||
>
|
||||
<Copy size={14} />
|
||||
复制
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-sm text-text-secondary break-all">{inviteLink}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-3 justify-end pt-4">
|
||||
<Button variant="ghost" onClick={() => { setShowInviteModal(false); setInviteEmail(''); setInviteLink(''); }}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={handleSendInvite} disabled={!inviteEmail.trim()}>
|
||||
<Mail size={16} />
|
||||
发送邀请
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user