refactor: 清理无用模块、修复前后端对齐、添加注册页面

- 删除后端 risk_exceptions 模块(API/Model/Schema/迁移/测试)
- 删除后端 metrics 模块(API/测试)
- 删除后端 ManualTask 模型和相关 Schema
- 修复搜索接口响应缺少 total 字段的问题
- 统一 Platform 枚举(前端去掉后端不支持的 weibo/wechat)
- 新增前端注册页面 /register,登录页添加注册链接

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-02-09 14:51:17 +08:00
co-authored by Claude Opus 4.6
parent a32102f583
commit 4a3c7e7923
22 changed files with 725 additions and 1298 deletions
+8
View File
@@ -162,6 +162,14 @@ function LoginForm() {
</button>
</form>
{/* 注册链接 */}
<p className="text-center text-sm text-text-secondary">
{' '}
<Link href="/register" className="text-accent-indigo hover:underline font-medium">
</Link>
</p>
{/* Demo 登录 */}
<div className="pt-6 border-t border-border-subtle">
<p className="text-sm text-text-tertiary text-center mb-4">Demo </p>
+233
View File
@@ -0,0 +1,233 @@
'use client'
import { useState } from 'react'
import { useRouter } from 'next/navigation'
import { useAuth } from '@/contexts/AuthContext'
import { ShieldCheck, AlertCircle, ArrowLeft, Mail, Lock, User, Phone } from 'lucide-react'
import Link from 'next/link'
import type { UserRole } from '@/lib/api'
const roleOptions: { value: UserRole; label: string; desc: string }[] = [
{ value: 'brand', label: '品牌方', desc: '创建项目、管理代理商、配置审核规则' },
{ value: 'agency', label: '代理商', desc: '管理达人、分配任务、审核内容' },
{ value: 'creator', label: '达人', desc: '上传脚本和视频、查看审核结果' },
]
export default function RegisterPage() {
const router = useRouter()
const { register } = useAuth()
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [phone, setPhone] = useState('')
const [password, setPassword] = useState('')
const [confirmPassword, setConfirmPassword] = useState('')
const [role, setRole] = useState<UserRole>('creator')
const [error, setError] = useState('')
const [isLoading, setIsLoading] = useState(false)
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError('')
if (!name.trim()) {
setError('请输入用户名')
return
}
if (!email && !phone) {
setError('请填写邮箱或手机号')
return
}
if (password.length < 6) {
setError('密码至少 6 位')
return
}
if (password !== confirmPassword) {
setError('两次密码不一致')
return
}
setIsLoading(true)
const result = await register({
name: name.trim(),
email: email || undefined,
phone: phone || undefined,
password,
role,
})
if (result.success) {
switch (role) {
case 'creator':
router.push('/creator')
break
case 'agency':
router.push('/agency')
break
case 'brand':
router.push('/brand')
break
}
} else {
setError(result.error || '注册失败')
}
setIsLoading(false)
}
return (
<div className="min-h-screen bg-bg-page flex flex-col items-center justify-center px-6 py-12">
<div className="w-full max-w-sm space-y-8">
{/* 返回 */}
<Link
href="/login"
className="inline-flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors"
>
<ArrowLeft className="w-4 h-4" />
</Link>
{/* Logo */}
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-accent-indigo to-[#4F46E5] flex items-center justify-center shadow-[0px_8px_24px_-4px_rgba(99,102,241,0.4)]">
<ShieldCheck className="w-7 h-7 text-white" />
</div>
<div>
<span className="text-2xl font-bold text-text-primary"></span>
<p className="text-sm text-text-secondary"></p>
</div>
</div>
{/* 注册表单 */}
<form onSubmit={handleSubmit} className="space-y-5">
{error && (
<div className="flex items-center gap-2 p-3 bg-accent-coral/10 text-accent-coral rounded-lg text-sm">
<AlertCircle size={16} />
{error}
</div>
)}
{/* 角色选择 */}
<div className="space-y-2">
<label className="block text-sm font-medium text-text-primary"></label>
<div className="grid grid-cols-3 gap-2">
{roleOptions.map((opt) => (
<button
key={opt.value}
type="button"
onClick={() => setRole(opt.value)}
className={`p-3 rounded-xl border text-center transition-all ${
role === opt.value
? 'border-accent-indigo bg-accent-indigo/10 text-accent-indigo'
: 'border-border-subtle bg-bg-card text-text-secondary hover:bg-bg-elevated'
}`}
>
<div className="font-medium text-sm">{opt.label}</div>
</button>
))}
</div>
<p className="text-xs text-text-tertiary">
{roleOptions.find((o) => o.value === role)?.desc}
</p>
</div>
{/* 用户名 */}
<div className="space-y-2">
<label className="block text-sm font-medium text-text-primary"></label>
<div className="relative">
<User className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-text-tertiary" />
<input
type="text"
placeholder="请输入用户名"
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full pl-12 pr-4 py-3.5 bg-bg-elevated border border-border-subtle rounded-xl text-text-primary placeholder-text-tertiary focus:outline-none focus:ring-2 focus:ring-accent-indigo focus:border-transparent transition-all"
required
/>
</div>
</div>
{/* 邮箱 */}
<div className="space-y-2">
<label className="block text-sm font-medium text-text-primary"></label>
<div className="relative">
<Mail className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-text-tertiary" />
<input
type="email"
placeholder="请输入邮箱"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full pl-12 pr-4 py-3.5 bg-bg-elevated border border-border-subtle rounded-xl text-text-primary placeholder-text-tertiary focus:outline-none focus:ring-2 focus:ring-accent-indigo focus:border-transparent transition-all"
/>
</div>
</div>
{/* 手机号 */}
<div className="space-y-2">
<label className="block text-sm font-medium text-text-primary">
<span className="text-text-tertiary font-normal">()</span>
</label>
<div className="relative">
<Phone className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-text-tertiary" />
<input
type="tel"
placeholder="请输入手机号"
value={phone}
onChange={(e) => setPhone(e.target.value)}
className="w-full pl-12 pr-4 py-3.5 bg-bg-elevated border border-border-subtle rounded-xl text-text-primary placeholder-text-tertiary focus:outline-none focus:ring-2 focus:ring-accent-indigo focus:border-transparent transition-all"
/>
</div>
</div>
{/* 密码 */}
<div className="space-y-2">
<label className="block text-sm font-medium text-text-primary"></label>
<div className="relative">
<Lock className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-text-tertiary" />
<input
type="password"
placeholder="至少 6 位密码"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full pl-12 pr-4 py-3.5 bg-bg-elevated border border-border-subtle rounded-xl text-text-primary placeholder-text-tertiary focus:outline-none focus:ring-2 focus:ring-accent-indigo focus:border-transparent transition-all"
required
/>
</div>
</div>
{/* 确认密码 */}
<div className="space-y-2">
<label className="block text-sm font-medium text-text-primary"></label>
<div className="relative">
<Lock className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-text-tertiary" />
<input
type="password"
placeholder="请再次输入密码"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="w-full pl-12 pr-4 py-3.5 bg-bg-elevated border border-border-subtle rounded-xl text-text-primary placeholder-text-tertiary focus:outline-none focus:ring-2 focus:ring-accent-indigo focus:border-transparent transition-all"
required
/>
</div>
</div>
<button
type="submit"
disabled={isLoading}
className="w-full py-3.5 rounded-xl bg-gradient-to-r from-accent-indigo to-[#4F46E5] text-white font-semibold text-base shadow-[0px_8px_24px_-4px_rgba(99,102,241,0.4)] hover:opacity-90 transition-opacity disabled:opacity-50"
>
{isLoading ? '注册中...' : '注册'}
</button>
</form>
{/* 底部链接 */}
<p className="text-center text-sm text-text-secondary">
{' '}
<Link href="/login" className="text-accent-indigo hover:underline font-medium">
</Link>
</p>
</div>
</div>
)
}