Your Name 2f9b7f05fd feat(creator): 完成达人端前端页面开发
- 新增申诉中心页面(列表、详情、新建申诉)
- 新增申诉次数管理页面(按任务显示配额,支持向代理商申请)
- 新增个人中心页面(达人ID复制、菜单导航)
- 新增个人信息编辑、账户设置、消息通知设置页面
- 新增帮助中心和历史记录页面
- 新增脚本提交和视频提交页面
- 优化消息中心页面(消息详情跳转)
- 优化任务详情页面布局和交互
- 更新 ResponsiveLayout、Sidebar、ReviewSteps 通用组件

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 15:38:01 +08:00

384 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import React, { useState } from 'react'
import { useRouter } from 'next/navigation'
import {
ArrowLeft,
Key,
ShieldCheck,
Smartphone,
ChevronRight,
Check,
X,
Eye,
EyeOff,
} from 'lucide-react'
import { ResponsiveLayout } from '@/components/layout/ResponsiveLayout'
import { Button } from '@/components/ui/Button'
import { Input } from '@/components/ui/Input'
import { Modal } from '@/components/ui/Modal'
import { cn } from '@/lib/utils'
// 模拟登录设备数据
const mockDevices = [
{
id: '1',
name: 'iPhone 15 Pro',
type: 'mobile',
location: '北京',
lastActive: '当前设备',
isCurrent: true,
},
{
id: '2',
name: 'MacBook Pro',
type: 'desktop',
location: '北京',
lastActive: '2小时前',
isCurrent: false,
},
{
id: '3',
name: 'iPad Air',
type: 'tablet',
location: '上海',
lastActive: '3天前',
isCurrent: false,
},
]
// 设置项组件
function SettingItem({
icon: Icon,
iconColor,
title,
description,
badge,
onClick,
showArrow = true,
}: {
icon: React.ElementType
iconColor: string
title: string
description: string
badge?: React.ReactNode
onClick?: () => void
showArrow?: boolean
}) {
return (
<button
type="button"
onClick={onClick}
className="flex items-center justify-between py-5 px-5 w-full text-left hover:bg-bg-elevated/30 transition-colors border-b border-border-subtle last:border-b-0"
>
<div className="flex items-center gap-4">
<div className={cn('w-10 h-10 rounded-xl flex items-center justify-center', `${iconColor}/15`)}>
<Icon size={20} className={iconColor} />
</div>
<div className="flex flex-col gap-1">
<span className="text-[15px] font-medium text-text-primary">{title}</span>
<span className="text-[13px] text-text-tertiary">{description}</span>
</div>
</div>
<div className="flex items-center gap-2">
{badge}
{showArrow && <ChevronRight size={20} className="text-text-tertiary" />}
</div>
</button>
)
}
// 修改密码弹窗
function ChangePasswordModal({
isOpen,
onClose,
}: {
isOpen: boolean
onClose: () => void
}) {
const [step, setStep] = useState(1)
const [showPasswords, setShowPasswords] = useState({
current: false,
new: false,
confirm: false,
})
const [formData, setFormData] = useState({
currentPassword: '',
newPassword: '',
confirmPassword: '',
})
const [isSaving, setIsSaving] = useState(false)
const handleSubmit = async () => {
setIsSaving(true)
await new Promise(resolve => setTimeout(resolve, 1500))
setIsSaving(false)
setStep(2)
}
const handleClose = () => {
setStep(1)
setFormData({ currentPassword: '', newPassword: '', confirmPassword: '' })
onClose()
}
return (
<Modal isOpen={isOpen} onClose={handleClose} title="修改密码">
{step === 1 ? (
<div className="flex flex-col gap-5">
{/* 当前密码 */}
<div className="flex flex-col gap-2">
<label className="text-sm font-medium text-text-primary"></label>
<div className="relative">
<Input
type={showPasswords.current ? 'text' : 'password'}
value={formData.currentPassword}
onChange={(e) => setFormData(prev => ({ ...prev, currentPassword: e.target.value }))}
placeholder="请输入当前密码"
/>
<button
type="button"
onClick={() => setShowPasswords(prev => ({ ...prev, current: !prev.current }))}
className="absolute right-3 top-1/2 -translate-y-1/2 text-text-tertiary hover:text-text-secondary"
>
{showPasswords.current ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
</div>
</div>
{/* 新密码 */}
<div className="flex flex-col gap-2">
<label className="text-sm font-medium text-text-primary"></label>
<div className="relative">
<Input
type={showPasswords.new ? 'text' : 'password'}
value={formData.newPassword}
onChange={(e) => setFormData(prev => ({ ...prev, newPassword: e.target.value }))}
placeholder="请输入新密码8-20位"
/>
<button
type="button"
onClick={() => setShowPasswords(prev => ({ ...prev, new: !prev.new }))}
className="absolute right-3 top-1/2 -translate-y-1/2 text-text-tertiary hover:text-text-secondary"
>
{showPasswords.new ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
</div>
<p className="text-xs text-text-tertiary">8-20</p>
</div>
{/* 确认密码 */}
<div className="flex flex-col gap-2">
<label className="text-sm font-medium text-text-primary"></label>
<div className="relative">
<Input
type={showPasswords.confirm ? 'text' : 'password'}
value={formData.confirmPassword}
onChange={(e) => setFormData(prev => ({ ...prev, confirmPassword: e.target.value }))}
placeholder="请再次输入新密码"
/>
<button
type="button"
onClick={() => setShowPasswords(prev => ({ ...prev, confirm: !prev.confirm }))}
className="absolute right-3 top-1/2 -translate-y-1/2 text-text-tertiary hover:text-text-secondary"
>
{showPasswords.confirm ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
</div>
</div>
{/* 按钮 */}
<div className="flex gap-3 pt-2">
<Button variant="secondary" size="lg" className="flex-1" onClick={handleClose}>
</Button>
<Button
variant="primary"
size="lg"
className="flex-1"
onClick={handleSubmit}
disabled={!formData.currentPassword || !formData.newPassword || !formData.confirmPassword || isSaving}
>
{isSaving ? '提交中...' : '确认修改'}
</Button>
</div>
</div>
) : (
<div className="flex flex-col items-center gap-4 py-6">
<div className="w-16 h-16 rounded-full bg-accent-green/15 flex items-center justify-center">
<Check size={32} className="text-accent-green" />
</div>
<div className="text-center">
<p className="text-lg font-semibold text-text-primary"></p>
<p className="text-sm text-text-secondary mt-1">使</p>
</div>
<Button variant="primary" size="lg" className="w-full mt-2" onClick={handleClose}>
</Button>
</div>
)}
</Modal>
)
}
// 设备管理弹窗
function DeviceManageModal({
isOpen,
onClose,
}: {
isOpen: boolean
onClose: () => void
}) {
const [devices, setDevices] = useState(mockDevices)
const handleRemoveDevice = (deviceId: string) => {
setDevices(prev => prev.filter(d => d.id !== deviceId))
}
return (
<Modal isOpen={isOpen} onClose={onClose} title="登录设备管理">
<div className="flex flex-col gap-4">
<p className="text-sm text-text-secondary">使</p>
<div className="flex flex-col gap-3">
{devices.map((device) => (
<div
key={device.id}
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-xl bg-bg-card flex items-center justify-center">
<Smartphone size={20} className="text-text-secondary" />
</div>
<div className="flex flex-col gap-0.5">
<div className="flex items-center gap-2">
<span className="text-[15px] font-medium text-text-primary">{device.name}</span>
{device.isCurrent && (
<span className="px-2 py-0.5 rounded-full bg-accent-green/15 text-accent-green text-xs font-medium">
</span>
)}
</div>
<span className="text-[13px] text-text-tertiary">
{device.location} · {device.lastActive}
</span>
</div>
</div>
{!device.isCurrent && (
<button
type="button"
onClick={() => handleRemoveDevice(device.id)}
className="text-accent-coral hover:text-accent-coral/80 text-sm font-medium"
>
</button>
)}
</div>
))}
</div>
<Button variant="secondary" size="lg" className="w-full mt-2" onClick={onClose}>
</Button>
</div>
</Modal>
)
}
export default function AccountSettingsPage() {
const router = useRouter()
const [showPasswordModal, setShowPasswordModal] = useState(false)
const [showDeviceModal, setShowDeviceModal] = useState(false)
const [twoFactorEnabled, setTwoFactorEnabled] = useState(true)
return (
<ResponsiveLayout role="creator">
<div className="flex flex-col gap-6 h-full">
{/* 顶部栏 */}
<div className="flex items-center gap-4">
<button
type="button"
onClick={() => router.back()}
className="w-10 h-10 rounded-xl bg-bg-elevated flex items-center justify-center hover:bg-bg-elevated/80 transition-colors"
>
<ArrowLeft size={20} className="text-text-secondary" />
</button>
<div className="flex flex-col gap-1">
<h1 className="text-2xl lg:text-[28px] font-bold text-text-primary"></h1>
<p className="text-sm lg:text-[15px] text-text-secondary"></p>
</div>
</div>
{/* 内容区 */}
<div className="flex flex-col gap-5 flex-1 min-h-0 overflow-y-auto lg:max-w-2xl">
{/* 账户安全 */}
<div className="flex flex-col gap-3">
<h2 className="text-base font-semibold text-text-primary px-1"></h2>
<div className="bg-bg-card rounded-2xl card-shadow overflow-hidden">
<SettingItem
icon={Key}
iconColor="text-accent-indigo"
title="修改密码"
description="定期更新密码以保障账户安全"
onClick={() => setShowPasswordModal(true)}
/>
<SettingItem
icon={ShieldCheck}
iconColor="text-accent-green"
title="两步验证"
description="启用双因素认证增强安全性"
badge={
<span
className={cn(
'px-2 py-1 rounded text-xs font-medium',
twoFactorEnabled
? 'bg-accent-green/15 text-accent-green'
: 'bg-bg-elevated text-text-tertiary'
)}
>
{twoFactorEnabled ? '已开启' : '未开启'}
</span>
}
onClick={() => setTwoFactorEnabled(!twoFactorEnabled)}
/>
<SettingItem
icon={Smartphone}
iconColor="text-accent-blue"
title="登录设备管理"
description="查看和管理已登录的设备"
onClick={() => setShowDeviceModal(true)}
/>
</div>
</div>
{/* 危险区域 */}
<div className="flex flex-col gap-3">
<h2 className="text-base font-semibold text-accent-coral px-1"></h2>
<div className="bg-bg-card rounded-2xl card-shadow p-5">
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
<div className="flex flex-col gap-1">
<span className="text-[15px] font-medium text-text-primary"></span>
<span className="text-[13px] text-text-tertiary">
</span>
</div>
<Button
variant="secondary"
size="md"
className="border-accent-coral text-accent-coral hover:bg-accent-coral/10 lg:flex-shrink-0"
>
</Button>
</div>
</div>
</div>
</div>
</div>
{/* 弹窗 */}
<ChangePasswordModal isOpen={showPasswordModal} onClose={() => setShowPasswordModal(false)} />
<DeviceManageModal isOpen={showDeviceModal} onClose={() => setShowDeviceModal(false)} />
</ResponsiveLayout>
)
}