Your Name 964797d2e9 feat: 完善品牌方和代理商前端功能
品牌方功能:
- 项目看板: 添加截止日期编辑功能
- 项目详情: 添加代理商管理、截止日期编辑、最近任务显示代理商
- 项目创建: 代理商选择支持搜索(名称/ID/公司名)
- 代理商管理: 通过ID邀请、添加备注/分配项目/移除操作
- Brief配置: 新增项目级Brief和规则配置页面
- 系统设置: 完善账户安全(密码/2FA/邮箱/手机/设备管理)、数据导出、退出登录

代理商功能:
- 个人中心: 新增代理商ID展示、公司信息(企业验证)、个人信息编辑
- 账户设置: 密码修改、手机/邮箱绑定、两步验证
- 通知设置: 分类型和渠道的通知开关
- 审核历史: 搜索筛选和统计展示
- 帮助反馈: FAQ分类搜索和客服联系

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 17:40:11 +08:00

293 lines
9.1 KiB
TypeScript

'use client'
import React, { useState } from 'react'
import { useRouter } from 'next/navigation'
import {
CircleUser,
Settings,
BellRing,
History,
MessageCircleQuestion,
ChevronRight,
LogOut,
Copy,
Check,
Building2,
Users,
FileCheck
} from 'lucide-react'
import { cn } from '@/lib/utils'
// 代理商数据
const mockAgency = {
name: '星辰传媒',
initial: '星',
agencyId: 'AG789012', // 代理商ID
companyName: '上海星辰文化传媒有限公司',
role: '官方认证代理商',
stats: {
creators: 156, // 管理达人数
reviewed: 1280, // 累计审核数
passRate: 88, // 通过率
monthlyTasks: 45, // 本月任务
},
}
// 菜单项数据
const menuItems = [
{
id: 'company',
icon: Building2,
iconColor: 'text-accent-indigo',
bgColor: 'bg-accent-indigo',
title: '公司信息',
subtitle: '公司名称、营业执照、联系方式',
},
{
id: 'personal',
icon: CircleUser,
iconColor: 'text-accent-blue',
bgColor: 'bg-accent-blue',
title: '个人信息',
subtitle: '头像、昵称、负责人信息',
},
{
id: 'account',
icon: Settings,
iconColor: 'text-accent-green',
bgColor: 'bg-accent-green',
title: '账户设置',
subtitle: '修改密码、账号安全',
},
{
id: 'notification',
icon: BellRing,
iconColor: 'text-accent-amber',
bgColor: 'bg-accent-amber',
title: '消息设置',
subtitle: '通知开关、提醒偏好',
},
{
id: 'history',
icon: History,
iconColor: 'text-accent-coral',
bgColor: 'bg-accent-coral',
title: '审核历史',
subtitle: '查看历史审核记录',
},
{
id: 'help',
icon: MessageCircleQuestion,
iconColor: 'text-text-secondary',
bgColor: 'bg-bg-elevated',
title: '帮助与反馈',
subtitle: '常见问题、联系客服',
},
]
// 代理商卡片组件
function AgencyCard() {
const [copied, setCopied] = useState(false)
// 复制代理商ID
const handleCopyId = async () => {
try {
await navigator.clipboard.writeText(mockAgency.agencyId)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
} catch (err) {
console.error('复制失败:', err)
}
}
return (
<div className="bg-bg-card rounded-2xl p-6 card-shadow flex flex-col gap-5">
{/* 头像和信息 */}
<div className="flex items-center gap-5">
{/* 头像 */}
<div
className="w-20 h-20 rounded-full flex items-center justify-center"
style={{
background: 'linear-gradient(135deg, #6366F1 0%, #4F46E5 100%)',
}}
>
<span className="text-[32px] font-bold text-white">{mockAgency.initial}</span>
</div>
{/* 代理商信息 */}
<div className="flex flex-col gap-1.5">
<span className="text-xl font-semibold text-text-primary">{mockAgency.name}</span>
<span className="text-sm text-text-secondary">{mockAgency.role}</span>
{/* 代理商ID */}
<div className="flex items-center gap-2 mt-1">
<span className="text-xs text-text-tertiary">ID:</span>
<div className="flex items-center gap-1.5 px-2 py-1 rounded-md bg-bg-elevated">
<span className="text-xs font-mono font-medium text-accent-indigo">{mockAgency.agencyId}</span>
<button
type="button"
onClick={handleCopyId}
className="p-0.5 hover:bg-bg-card rounded transition-colors"
title={copied ? '已复制' : '复制代理商ID'}
>
{copied ? (
<Check size={12} className="text-accent-green" />
) : (
<Copy size={12} className="text-text-tertiary hover:text-text-secondary" />
)}
</button>
</div>
{copied && (
<span className="text-xs text-accent-green animate-fade-in"></span>
)}
</div>
</div>
</div>
{/* 公司名称 */}
<div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-bg-elevated">
<Building2 size={16} className="text-text-tertiary" />
<span className="text-sm text-text-secondary">{mockAgency.companyName}</span>
</div>
{/* 统计数据 */}
<div className="grid grid-cols-4 gap-4 pt-4 border-t border-border-subtle">
<div className="flex flex-col items-center gap-1">
<div className="flex items-center gap-1">
<Users size={14} className="text-accent-indigo" />
<span className="text-xl font-bold text-text-primary">{mockAgency.stats.creators}</span>
</div>
<span className="text-xs text-text-secondary"></span>
</div>
<div className="flex flex-col items-center gap-1">
<div className="flex items-center gap-1">
<FileCheck size={14} className="text-accent-blue" />
<span className="text-xl font-bold text-text-primary">{mockAgency.stats.reviewed}</span>
</div>
<span className="text-xs text-text-secondary"></span>
</div>
<div className="flex flex-col items-center gap-1">
<span className="text-xl font-bold text-accent-green">{mockAgency.stats.passRate}%</span>
<span className="text-xs text-text-secondary"></span>
</div>
<div className="flex flex-col items-center gap-1">
<span className="text-xl font-bold text-accent-amber">{mockAgency.stats.monthlyTasks}</span>
<span className="text-xs text-text-secondary"></span>
</div>
</div>
</div>
)
}
// 菜单项组件
function MenuItem({ item, onClick }: { item: typeof menuItems[0]; onClick: () => void }) {
const Icon = item.icon
const isPlainBg = item.bgColor === 'bg-bg-elevated'
return (
<button
type="button"
onClick={onClick}
className="flex items-center justify-between py-4 w-full text-left hover:bg-bg-elevated/30 transition-colors rounded-lg px-2 -mx-2"
>
<div className="flex items-center gap-4">
{/* 图标背景 */}
<div
className={cn(
'w-10 h-10 rounded-[10px] flex items-center justify-center',
isPlainBg ? item.bgColor : `${item.bgColor}/15`
)}
>
<Icon size={20} className={item.iconColor} />
</div>
{/* 文字 */}
<div className="flex flex-col gap-0.5">
<span className="text-[15px] font-medium text-text-primary">{item.title}</span>
<span className="text-[13px] text-text-tertiary">{item.subtitle}</span>
</div>
</div>
<ChevronRight size={20} className="text-text-tertiary" />
</button>
)
}
// 菜单卡片组件
function MenuCard({ onMenuClick }: { onMenuClick: (id: string) => void }) {
return (
<div className="bg-bg-card rounded-2xl p-6 card-shadow flex flex-col">
{menuItems.map((item, index) => (
<div key={item.id}>
<MenuItem item={item} onClick={() => onMenuClick(item.id)} />
{index < menuItems.length - 1 && (
<div className="h-px bg-border-subtle" />
)}
</div>
))}
</div>
)
}
// 退出卡片组件
function LogoutCard({ onLogout }: { onLogout: () => void }) {
return (
<div className="bg-bg-card rounded-2xl p-6 card-shadow">
<button
type="button"
onClick={onLogout}
className="w-full flex items-center justify-center gap-2 py-4 rounded-xl border-[1.5px] border-accent-coral text-accent-coral font-medium hover:bg-accent-coral/10 transition-colors"
>
<LogOut size={20} />
<span>退</span>
</button>
</div>
)
}
export default function AgencyProfilePage() {
const router = useRouter()
// 菜单项点击处理
const handleMenuClick = (menuId: string) => {
const routes: Record<string, string> = {
company: '/agency/profile/company',
personal: '/agency/profile/edit',
account: '/agency/settings/account',
notification: '/agency/settings/notification',
history: '/agency/review/history',
help: '/agency/help',
}
const route = routes[menuId]
if (route) {
router.push(route)
}
}
// 退出登录
const handleLogout = () => {
// TODO: 实际退出逻辑
router.push('/login')
}
return (
<div className="space-y-6">
{/* 顶部栏 */}
<div className="flex flex-col gap-1">
<h1 className="text-2xl font-bold text-text-primary"></h1>
<p className="text-sm text-text-secondary"></p>
</div>
{/* 内容区 - 响应式布局 */}
<div className="flex flex-col lg:flex-row gap-6">
{/* 代理商卡片 */}
<div className="lg:w-[400px] lg:flex-shrink-0">
<AgencyCard />
</div>
{/* 菜单和退出 */}
<div className="flex-1 flex flex-col gap-5">
<MenuCard onMenuClick={handleMenuClick} />
<LogoutCard onLogout={handleLogout} />
</div>
</div>
</div>
)
}