feat: 完善品牌方和代理商前端功能
品牌方功能: - 项目看板: 添加截止日期编辑功能 - 项目详情: 添加代理商管理、截止日期编辑、最近任务显示代理商 - 项目创建: 代理商选择支持搜索(名称/ID/公司名) - 代理商管理: 通过ID邀请、添加备注/分配项目/移除操作 - Brief配置: 新增项目级Brief和规则配置页面 - 系统设置: 完善账户安全(密码/2FA/邮箱/手机/设备管理)、数据导出、退出登录 代理商功能: - 个人中心: 新增代理商ID展示、公司信息(企业验证)、个人信息编辑 - 账户设置: 密码修改、手机/邮箱绑定、两步验证 - 通知设置: 分类型和渠道的通知开关 - 审核历史: 搜索筛选和统计展示 - 帮助反馈: FAQ分类搜索和客服联系 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
ae74c515c7
commit
964797d2e9
@@ -0,0 +1,240 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||
import { Button } from '@/components/ui/Button'
|
||||
import { Input } from '@/components/ui/Input'
|
||||
import {
|
||||
ArrowLeft,
|
||||
Lock,
|
||||
Shield,
|
||||
Smartphone,
|
||||
Mail,
|
||||
Key,
|
||||
Eye,
|
||||
EyeOff,
|
||||
CheckCircle,
|
||||
AlertTriangle
|
||||
} from 'lucide-react'
|
||||
|
||||
export default function AgencyAccountSettingsPage() {
|
||||
const router = useRouter()
|
||||
const [showOldPassword, setShowOldPassword] = useState(false)
|
||||
const [showNewPassword, setShowNewPassword] = useState(false)
|
||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false)
|
||||
const [passwordForm, setPasswordForm] = useState({
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
})
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
|
||||
// 模拟账号安全状态
|
||||
const securityStatus = {
|
||||
phone: { bound: true, value: '138****8888' },
|
||||
email: { bound: true, value: 'zhang@starmedia.com' },
|
||||
twoFactor: { enabled: false },
|
||||
}
|
||||
|
||||
const handleChangePassword = async () => {
|
||||
if (!passwordForm.oldPassword || !passwordForm.newPassword || !passwordForm.confirmPassword) {
|
||||
alert('请填写完整密码信息')
|
||||
return
|
||||
}
|
||||
if (passwordForm.newPassword !== passwordForm.confirmPassword) {
|
||||
alert('两次输入的新密码不一致')
|
||||
return
|
||||
}
|
||||
if (passwordForm.newPassword.length < 8) {
|
||||
alert('新密码长度不能少于8位')
|
||||
return
|
||||
}
|
||||
setIsSaving(true)
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
setIsSaving(false)
|
||||
alert('密码修改成功')
|
||||
setPasswordForm({ oldPassword: '', newPassword: '', confirmPassword: '' })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* 顶部导航 */}
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.back()}
|
||||
className="p-2 rounded-lg hover:bg-bg-elevated transition-colors"
|
||||
>
|
||||
<ArrowLeft size={20} className="text-text-secondary" />
|
||||
</button>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-text-primary">账户设置</h1>
|
||||
<p className="text-sm text-text-secondary mt-0.5">管理密码和账号安全</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* 修改密码 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Lock size={18} className="text-accent-indigo" />
|
||||
修改密码
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div>
|
||||
<label className="text-sm text-text-secondary mb-1.5 block">当前密码</label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type={showOldPassword ? 'text' : 'password'}
|
||||
value={passwordForm.oldPassword}
|
||||
onChange={(e) => setPasswordForm({ ...passwordForm, oldPassword: e.target.value })}
|
||||
placeholder="请输入当前密码"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowOldPassword(!showOldPassword)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-text-tertiary hover:text-text-secondary"
|
||||
>
|
||||
{showOldPassword ? <EyeOff size={18} /> : <Eye size={18} />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-text-secondary mb-1.5 block">新密码</label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type={showNewPassword ? 'text' : 'password'}
|
||||
value={passwordForm.newPassword}
|
||||
onChange={(e) => setPasswordForm({ ...passwordForm, newPassword: e.target.value })}
|
||||
placeholder="请输入新密码(至少8位)"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowNewPassword(!showNewPassword)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-text-tertiary hover:text-text-secondary"
|
||||
>
|
||||
{showNewPassword ? <EyeOff size={18} /> : <Eye size={18} />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-text-secondary mb-1.5 block">确认新密码</label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type={showConfirmPassword ? 'text' : 'password'}
|
||||
value={passwordForm.confirmPassword}
|
||||
onChange={(e) => setPasswordForm({ ...passwordForm, confirmPassword: e.target.value })}
|
||||
placeholder="请再次输入新密码"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-text-tertiary hover:text-text-secondary"
|
||||
>
|
||||
{showConfirmPassword ? <EyeOff size={18} /> : <Eye size={18} />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="primary"
|
||||
className="w-full"
|
||||
onClick={handleChangePassword}
|
||||
disabled={isSaving}
|
||||
>
|
||||
{isSaving ? '保存中...' : '确认修改'}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 账号安全 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Shield size={18} className="text-accent-green" />
|
||||
账号安全
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{/* 手机绑定 */}
|
||||
<div className="flex items-center justify-between p-4 rounded-xl bg-bg-elevated">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-lg bg-accent-indigo/15 flex items-center justify-center">
|
||||
<Smartphone size={20} className="text-accent-indigo" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-text-primary">手机绑定</p>
|
||||
<p className="text-sm text-text-secondary">
|
||||
{securityStatus.phone.bound ? securityStatus.phone.value : '未绑定'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{securityStatus.phone.bound ? (
|
||||
<CheckCircle size={18} className="text-accent-green" />
|
||||
) : (
|
||||
<AlertTriangle size={18} className="text-accent-amber" />
|
||||
)}
|
||||
<Button variant="secondary" size="sm">
|
||||
{securityStatus.phone.bound ? '更换' : '绑定'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 邮箱绑定 */}
|
||||
<div className="flex items-center justify-between p-4 rounded-xl bg-bg-elevated">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-lg bg-accent-blue/15 flex items-center justify-center">
|
||||
<Mail size={20} className="text-accent-blue" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-text-primary">邮箱绑定</p>
|
||||
<p className="text-sm text-text-secondary">
|
||||
{securityStatus.email.bound ? securityStatus.email.value : '未绑定'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{securityStatus.email.bound ? (
|
||||
<CheckCircle size={18} className="text-accent-green" />
|
||||
) : (
|
||||
<AlertTriangle size={18} className="text-accent-amber" />
|
||||
)}
|
||||
<Button variant="secondary" size="sm">
|
||||
{securityStatus.email.bound ? '更换' : '绑定'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 两步验证 */}
|
||||
<div className="flex items-center justify-between p-4 rounded-xl bg-bg-elevated">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-lg bg-accent-amber/15 flex items-center justify-center">
|
||||
<Key size={20} className="text-accent-amber" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-text-primary">两步验证</p>
|
||||
<p className="text-sm text-text-secondary">
|
||||
{securityStatus.twoFactor.enabled ? '已开启' : '未开启,建议开启以增强安全'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{securityStatus.twoFactor.enabled ? (
|
||||
<CheckCircle size={18} className="text-accent-green" />
|
||||
) : (
|
||||
<AlertTriangle size={18} className="text-accent-amber" />
|
||||
)}
|
||||
<Button variant="secondary" size="sm">
|
||||
{securityStatus.twoFactor.enabled ? '关闭' : '开启'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
|
||||
import { Button } from '@/components/ui/Button'
|
||||
import {
|
||||
ArrowLeft,
|
||||
BellRing,
|
||||
MessageSquare,
|
||||
Mail,
|
||||
Smartphone,
|
||||
FileText,
|
||||
Users,
|
||||
AlertTriangle
|
||||
} from 'lucide-react'
|
||||
|
||||
// 通知设置类型
|
||||
interface NotificationSetting {
|
||||
id: string
|
||||
icon: React.ElementType
|
||||
iconColor: string
|
||||
title: string
|
||||
description: string
|
||||
email: boolean
|
||||
push: boolean
|
||||
sms: boolean
|
||||
}
|
||||
|
||||
// 通知设置数据
|
||||
const initialSettings: NotificationSetting[] = [
|
||||
{
|
||||
id: 'review',
|
||||
icon: FileText,
|
||||
iconColor: 'text-accent-indigo',
|
||||
title: '审核任务通知',
|
||||
description: '有新任务待审核时通知',
|
||||
email: true,
|
||||
push: true,
|
||||
sms: false,
|
||||
},
|
||||
{
|
||||
id: 'appeal',
|
||||
icon: MessageSquare,
|
||||
iconColor: 'text-accent-amber',
|
||||
title: '申诉通知',
|
||||
description: '达人提交申诉时通知',
|
||||
email: true,
|
||||
push: true,
|
||||
sms: true,
|
||||
},
|
||||
{
|
||||
id: 'creator',
|
||||
icon: Users,
|
||||
iconColor: 'text-accent-green',
|
||||
title: '达人动态',
|
||||
description: '达人提交内容、完成任务时通知',
|
||||
email: false,
|
||||
push: true,
|
||||
sms: false,
|
||||
},
|
||||
{
|
||||
id: 'urgent',
|
||||
icon: AlertTriangle,
|
||||
iconColor: 'text-accent-coral',
|
||||
title: '紧急通知',
|
||||
description: '任务即将超时、品牌方催促等',
|
||||
email: true,
|
||||
push: true,
|
||||
sms: true,
|
||||
},
|
||||
{
|
||||
id: 'system',
|
||||
icon: BellRing,
|
||||
iconColor: 'text-accent-blue',
|
||||
title: '系统通知',
|
||||
description: '系统更新、维护公告等',
|
||||
email: true,
|
||||
push: false,
|
||||
sms: false,
|
||||
},
|
||||
]
|
||||
|
||||
// 开关组件
|
||||
function Toggle({ checked, onChange }: { checked: boolean; onChange: (checked: boolean) => void }) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onChange(!checked)}
|
||||
className={`relative w-11 h-6 rounded-full transition-colors ${
|
||||
checked ? 'bg-accent-indigo' : 'bg-bg-elevated'
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`absolute top-1 w-4 h-4 rounded-full bg-white shadow transition-transform ${
|
||||
checked ? 'left-6' : 'left-1'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default function AgencyNotificationSettingsPage() {
|
||||
const router = useRouter()
|
||||
const [settings, setSettings] = useState(initialSettings)
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
|
||||
const updateSetting = (id: string, field: 'email' | 'push' | 'sms', value: boolean) => {
|
||||
setSettings(prev =>
|
||||
prev.map(s =>
|
||||
s.id === id ? { ...s, [field]: value } : s
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
setIsSaving(true)
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
setIsSaving(false)
|
||||
alert('通知设置已保存')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* 顶部导航 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.back()}
|
||||
className="p-2 rounded-lg hover:bg-bg-elevated transition-colors"
|
||||
>
|
||||
<ArrowLeft size={20} className="text-text-secondary" />
|
||||
</button>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-text-primary">消息设置</h1>
|
||||
<p className="text-sm text-text-secondary mt-0.5">管理您的通知偏好</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="primary" onClick={handleSave} disabled={isSaving}>
|
||||
{isSaving ? '保存中...' : '保存设置'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 通知渠道说明 */}
|
||||
<div className="flex gap-6 p-4 rounded-xl bg-bg-elevated">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 rounded-lg bg-accent-indigo/15 flex items-center justify-center">
|
||||
<BellRing size={16} className="text-accent-indigo" />
|
||||
</div>
|
||||
<span className="text-sm text-text-secondary">App推送</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 rounded-lg bg-accent-blue/15 flex items-center justify-center">
|
||||
<Mail size={16} className="text-accent-blue" />
|
||||
</div>
|
||||
<span className="text-sm text-text-secondary">邮件通知</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 rounded-lg bg-accent-green/15 flex items-center justify-center">
|
||||
<Smartphone size={16} className="text-accent-green" />
|
||||
</div>
|
||||
<span className="text-sm text-text-secondary">短信通知</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 通知设置列表 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<BellRing size={18} className="text-accent-indigo" />
|
||||
通知类型设置
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-1">
|
||||
{/* 表头 */}
|
||||
<div className="flex items-center py-3 px-4 text-sm text-text-tertiary border-b border-border-subtle">
|
||||
<div className="flex-1">通知类型</div>
|
||||
<div className="w-20 text-center">App推送</div>
|
||||
<div className="w-20 text-center">邮件</div>
|
||||
<div className="w-20 text-center">短信</div>
|
||||
</div>
|
||||
|
||||
{/* 设置项 */}
|
||||
{settings.map((setting) => {
|
||||
const Icon = setting.icon
|
||||
return (
|
||||
<div
|
||||
key={setting.id}
|
||||
className="flex items-center py-4 px-4 hover:bg-bg-elevated/50 rounded-lg transition-colors"
|
||||
>
|
||||
<div className="flex-1 flex items-center gap-3">
|
||||
<div className={`w-10 h-10 rounded-lg bg-opacity-15 flex items-center justify-center`}
|
||||
style={{ backgroundColor: `${setting.iconColor.replace('text-', '')}15` }}
|
||||
>
|
||||
<Icon size={20} className={setting.iconColor} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-text-primary">{setting.title}</p>
|
||||
<p className="text-sm text-text-tertiary">{setting.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-20 flex justify-center">
|
||||
<Toggle
|
||||
checked={setting.push}
|
||||
onChange={(v) => updateSetting(setting.id, 'push', v)}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-20 flex justify-center">
|
||||
<Toggle
|
||||
checked={setting.email}
|
||||
onChange={(v) => updateSetting(setting.id, 'email', v)}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-20 flex justify-center">
|
||||
<Toggle
|
||||
checked={setting.sms}
|
||||
onChange={(v) => updateSetting(setting.id, 'sms', v)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 免打扰设置 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>免打扰设置</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-between p-4 rounded-xl bg-bg-elevated">
|
||||
<div>
|
||||
<p className="font-medium text-text-primary">夜间免打扰</p>
|
||||
<p className="text-sm text-text-tertiary">22:00 - 08:00 期间不发送推送通知</p>
|
||||
</div>
|
||||
<Toggle checked={true} onChange={() => {}} />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user