- 3 消息页 + 2 资料编辑页 + 3 设置页 + 2 资料展示页 - api.ts 新增 Profile/Messages/ChangePassword 等类型和方法 - SSEContext 事件映射修复 + 断线重连修复 - 剩余页面加 USE_MOCK 双模式,52/55 页面已完成 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
286 lines
11 KiB
TypeScript
286 lines
11 KiB
TypeScript
'use client'
|
||
|
||
import React, { useState, useEffect, useCallback } from 'react'
|
||
import { useRouter } from 'next/navigation'
|
||
import { ArrowLeft, Camera, Check, Copy } from 'lucide-react'
|
||
import { ResponsiveLayout } from '@/components/layout/ResponsiveLayout'
|
||
import { Button } from '@/components/ui/Button'
|
||
import { Input } from '@/components/ui/Input'
|
||
import { cn } from '@/lib/utils'
|
||
import { useToast } from '@/components/ui/Toast'
|
||
import { USE_MOCK } from '@/contexts/AuthContext'
|
||
import { api } from '@/lib/api'
|
||
|
||
// 模拟用户数据
|
||
const mockUser = {
|
||
name: '李小红',
|
||
initial: '李',
|
||
creatorId: 'CR123456', // 达人ID(系统生成,不可修改)
|
||
phone: '138****8888',
|
||
email: 'lixiaohong@example.com',
|
||
douyinAccount: '@xiaohong_creator',
|
||
bio: '专注美妆护肤分享,与你一起变美~',
|
||
}
|
||
|
||
export default function ProfileEditPage() {
|
||
const router = useRouter()
|
||
const toast = useToast()
|
||
const [isSaving, setIsSaving] = useState(false)
|
||
const [idCopied, setIdCopied] = useState(false)
|
||
const [formData, setFormData] = useState({
|
||
name: mockUser.name,
|
||
phone: mockUser.phone,
|
||
email: mockUser.email,
|
||
douyinAccount: mockUser.douyinAccount,
|
||
bio: mockUser.bio,
|
||
})
|
||
const [creatorId, setCreatorId] = useState(mockUser.creatorId)
|
||
|
||
const loadData = useCallback(async () => {
|
||
if (USE_MOCK) return
|
||
try {
|
||
const profile = await api.getProfile()
|
||
setFormData({
|
||
name: profile.name || '',
|
||
phone: profile.phone || '',
|
||
email: profile.email || '',
|
||
douyinAccount: profile.creator?.douyin_account || '',
|
||
bio: profile.creator?.bio || '',
|
||
})
|
||
if (profile.creator?.id) setCreatorId(profile.creator.id)
|
||
} catch {}
|
||
}, [])
|
||
|
||
useEffect(() => { loadData() }, [loadData])
|
||
|
||
// 复制达人ID
|
||
const handleCopyId = async () => {
|
||
try {
|
||
await navigator.clipboard.writeText(creatorId)
|
||
setIdCopied(true)
|
||
setTimeout(() => setIdCopied(false), 2000)
|
||
} catch {
|
||
toast.error('复制失败,请重试')
|
||
}
|
||
}
|
||
|
||
// 处理输入变化
|
||
const handleInputChange = (field: string, value: string) => {
|
||
setFormData(prev => ({ ...prev, [field]: value }))
|
||
}
|
||
|
||
// 保存
|
||
const handleSave = async () => {
|
||
setIsSaving(true)
|
||
if (USE_MOCK) {
|
||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||
} else {
|
||
try {
|
||
await api.updateProfile({
|
||
name: formData.name,
|
||
phone: formData.phone,
|
||
bio: formData.bio,
|
||
douyin_account: formData.douyinAccount,
|
||
})
|
||
} catch (err: any) {
|
||
toast.error(err.message || '保存失败')
|
||
setIsSaving(false)
|
||
return
|
||
}
|
||
}
|
||
setIsSaving(false)
|
||
router.back()
|
||
}
|
||
|
||
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 lg:flex-row gap-6 flex-1 min-h-0 overflow-y-auto lg:overflow-visible">
|
||
{/* 头像编辑卡片 */}
|
||
<div className="lg:w-[360px] lg:flex-shrink-0">
|
||
<div className="bg-bg-card rounded-2xl p-6 card-shadow flex flex-col items-center gap-5">
|
||
{/* 头像 */}
|
||
<div className="relative">
|
||
<div
|
||
className="w-24 h-24 rounded-full flex items-center justify-center"
|
||
style={{
|
||
background: 'linear-gradient(135deg, #6366F1 0%, #4F46E5 100%)',
|
||
}}
|
||
>
|
||
<span className="text-[40px] font-bold text-white">{formData.name?.[0] || '?'}</span>
|
||
</div>
|
||
{/* 相机按钮 */}
|
||
<button
|
||
type="button"
|
||
className="absolute bottom-0 right-0 w-8 h-8 rounded-full bg-accent-indigo flex items-center justify-center shadow-lg hover:bg-accent-indigo/90 transition-colors"
|
||
>
|
||
<Camera size={16} className="text-white" />
|
||
</button>
|
||
</div>
|
||
<p className="text-sm text-text-secondary">点击更换头像</p>
|
||
|
||
{/* 提示 */}
|
||
<div className="w-full p-4 rounded-xl bg-bg-elevated">
|
||
<p className="text-[13px] text-text-tertiary leading-relaxed">
|
||
支持 JPG、PNG 格式,文件大小不超过 5MB,建议使用正方形图片以获得最佳显示效果。
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 表单卡片 */}
|
||
<div className="flex-1 flex flex-col gap-5">
|
||
<div className="bg-bg-card rounded-2xl p-6 card-shadow flex flex-col gap-5">
|
||
{/* 达人ID(只读) */}
|
||
<div className="flex flex-col gap-2">
|
||
<label className="text-sm font-medium text-text-primary">达人ID</label>
|
||
<div className="flex gap-3">
|
||
<div className="flex-1 px-4 py-3 rounded-xl border border-border-default bg-bg-elevated/50 flex items-center justify-between">
|
||
<span className="font-mono font-medium text-accent-indigo">{creatorId}</span>
|
||
<button
|
||
type="button"
|
||
onClick={handleCopyId}
|
||
className="flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-bg-elevated transition-colors"
|
||
>
|
||
{idCopied ? (
|
||
<>
|
||
<Check size={14} className="text-accent-green" />
|
||
<span className="text-xs text-accent-green">已复制</span>
|
||
</>
|
||
) : (
|
||
<>
|
||
<Copy size={14} className="text-text-tertiary" />
|
||
<span className="text-xs text-text-tertiary">复制</span>
|
||
</>
|
||
)}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<p className="text-xs text-text-tertiary">系统自动生成的唯一标识,供代理商邀请时使用</p>
|
||
</div>
|
||
|
||
{/* 昵称 */}
|
||
<div className="flex flex-col gap-2">
|
||
<label className="text-sm font-medium text-text-primary">昵称</label>
|
||
<Input
|
||
value={formData.name}
|
||
onChange={(e) => handleInputChange('name', e.target.value)}
|
||
placeholder="请输入昵称"
|
||
/>
|
||
</div>
|
||
|
||
{/* 手机号 */}
|
||
<div className="flex flex-col gap-2">
|
||
<label className="text-sm font-medium text-text-primary">手机号</label>
|
||
<div className="flex gap-3">
|
||
<Input
|
||
value={formData.phone}
|
||
onChange={(e) => handleInputChange('phone', e.target.value)}
|
||
placeholder="请输入手机号"
|
||
className="flex-1"
|
||
disabled
|
||
/>
|
||
<Button variant="secondary" size="md">
|
||
更换
|
||
</Button>
|
||
</div>
|
||
<p className="text-xs text-text-tertiary">手机号用于登录和接收重要通知</p>
|
||
</div>
|
||
|
||
{/* 邮箱 */}
|
||
<div className="flex flex-col gap-2">
|
||
<label className="text-sm font-medium text-text-primary">邮箱</label>
|
||
<Input
|
||
type="email"
|
||
value={formData.email}
|
||
onChange={(e) => handleInputChange('email', e.target.value)}
|
||
placeholder="请输入邮箱"
|
||
/>
|
||
</div>
|
||
|
||
{/* 抖音账号 */}
|
||
<div className="flex flex-col gap-2">
|
||
<label className="text-sm font-medium text-text-primary">抖音账号</label>
|
||
<div className="flex gap-3">
|
||
<Input
|
||
value={formData.douyinAccount}
|
||
onChange={(e) => handleInputChange('douyinAccount', e.target.value)}
|
||
placeholder="请输入抖音账号"
|
||
className="flex-1"
|
||
disabled
|
||
/>
|
||
<Button variant="secondary" size="md">
|
||
重新绑定
|
||
</Button>
|
||
</div>
|
||
<p className="text-xs text-text-tertiary">已认证的抖音账号,用于发布视频</p>
|
||
</div>
|
||
|
||
{/* 个人简介 */}
|
||
<div className="flex flex-col gap-2">
|
||
<label className="text-sm font-medium text-text-primary">个人简介</label>
|
||
<textarea
|
||
value={formData.bio}
|
||
onChange={(e) => handleInputChange('bio', e.target.value)}
|
||
placeholder="介绍一下自己吧..."
|
||
rows={3}
|
||
className={cn(
|
||
'w-full px-4 py-3 rounded-xl border border-border-default',
|
||
'bg-bg-elevated text-text-primary text-[15px]',
|
||
'placeholder:text-text-tertiary',
|
||
'focus:outline-none focus:border-accent-indigo focus:ring-2 focus:ring-accent-indigo/20',
|
||
'transition-all resize-none'
|
||
)}
|
||
/>
|
||
<p className="text-xs text-text-tertiary text-right">{formData.bio.length}/100</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 保存按钮 */}
|
||
<div className="flex justify-end gap-3">
|
||
<Button variant="secondary" size="lg" onClick={() => router.back()}>
|
||
取消
|
||
</Button>
|
||
<Button
|
||
variant="primary"
|
||
size="lg"
|
||
onClick={handleSave}
|
||
disabled={isSaving}
|
||
className="min-w-[120px]"
|
||
>
|
||
{isSaving ? (
|
||
<span className="flex items-center gap-2">
|
||
<span className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
||
保存中...
|
||
</span>
|
||
) : (
|
||
<span className="flex items-center gap-2">
|
||
<Check size={18} />
|
||
保存修改
|
||
</span>
|
||
)}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</ResponsiveLayout>
|
||
)
|
||
}
|