Your Name f02b3f4098 feat: 前端对接 Profile/Messages/Settings 页面 API
- 3 消息页 + 2 资料编辑页 + 3 设置页 + 2 资料展示页
- api.ts 新增 Profile/Messages/ChangePassword 等类型和方法
- SSEContext 事件映射修复 + 断线重连修复
- 剩余页面加 USE_MOCK 双模式,52/55 页面已完成

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 10:27:59 +08:00

231 lines
8.2 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 { useState, useEffect, useCallback } from 'react'
import { useRouter } from 'next/navigation'
import { useToast } from '@/components/ui/Toast'
import { USE_MOCK } from '@/contexts/AuthContext'
import { api } from '@/lib/api'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
import { Button } from '@/components/ui/Button'
import { Input } from '@/components/ui/Input'
import {
ArrowLeft,
CircleUser,
Camera,
Copy,
Check
} from 'lucide-react'
// 模拟用户数据
const mockUserData = {
avatar: '星',
name: '张经理',
agencyId: 'AG789012',
phone: '138****8888',
email: 'zhang@starmedia.com',
position: '运营总监',
department: '内容审核部',
}
export default function AgencyProfileEditPage() {
const router = useRouter()
const toast = useToast()
const [formData, setFormData] = useState(mockUserData)
const [isSaving, setIsSaving] = useState(false)
const [copied, setCopied] = useState(false)
const loadData = useCallback(async () => {
if (USE_MOCK) return
try {
const profile = await api.getProfile()
setFormData({
avatar: profile.name?.[0] || '?',
name: profile.name || '',
agencyId: profile.agency?.id || '--',
phone: profile.phone || '',
email: profile.email || '',
position: profile.agency?.contact_name || '',
department: '',
})
} catch {}
}, [])
useEffect(() => { loadData() }, [loadData])
const handleCopyId = async () => {
try {
await navigator.clipboard.writeText(formData.agencyId)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
} catch {
toast.error('复制失败,请重试')
}
}
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,
contact_name: formData.position,
})
} catch (err: any) {
toast.error(err.message || '保存失败')
setIsSaving(false)
return
}
}
setIsSaving(false)
toast.success('个人信息已保存')
router.back()
}
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="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* 左侧:头像 */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent className="flex flex-col items-center">
<div className="relative">
<div
className="w-32 h-32 rounded-full flex items-center justify-center"
style={{
background: 'linear-gradient(135deg, #6366F1 0%, #4F46E5 100%)',
}}
>
<span className="text-5xl font-bold text-white">{formData.avatar}</span>
</div>
<button
type="button"
className="absolute bottom-0 right-0 w-10 h-10 rounded-full bg-accent-indigo flex items-center justify-center text-white shadow-lg hover:bg-accent-indigo/80 transition-colors"
>
<Camera size={18} />
</button>
</div>
<p className="text-sm text-text-tertiary mt-4"></p>
<p className="text-xs text-text-tertiary mt-1"> JPGPNG 2MB</p>
</CardContent>
</Card>
{/* 右侧:基本信息 */}
<div className="lg:col-span-2">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<CircleUser size={18} className="text-accent-indigo" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-5">
{/* 代理商ID只读 */}
<div>
<label className="text-sm text-text-secondary mb-1.5 block">ID</label>
<div className="flex items-center gap-2">
<div className="flex-1 px-4 py-2.5 rounded-xl bg-bg-elevated border border-border-subtle">
<span className="font-mono font-medium text-accent-indigo">{formData.agencyId}</span>
</div>
<button
type="button"
onClick={handleCopyId}
className="px-4 py-2.5 rounded-xl bg-bg-elevated border border-border-subtle hover:bg-bg-page transition-colors flex items-center gap-2"
>
{copied ? (
<>
<Check size={16} className="text-accent-green" />
<span className="text-accent-green text-sm"></span>
</>
) : (
<>
<Copy size={16} className="text-text-secondary" />
<span className="text-text-secondary text-sm"></span>
</>
)}
</button>
</div>
<p className="text-xs text-text-tertiary mt-1">ID不可修改使</p>
</div>
{/* 姓名 */}
<div>
<label className="text-sm text-text-secondary mb-1.5 block"></label>
<Input
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
placeholder="请输入姓名"
/>
</div>
{/* 职位和部门 */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="text-sm text-text-secondary mb-1.5 block"></label>
<Input
value={formData.position}
onChange={(e) => setFormData({ ...formData, position: e.target.value })}
placeholder="请输入职位"
/>
</div>
<div>
<label className="text-sm text-text-secondary mb-1.5 block"></label>
<Input
value={formData.department}
onChange={(e) => setFormData({ ...formData, department: e.target.value })}
placeholder="请输入部门"
/>
</div>
</div>
{/* 联系方式 */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="text-sm text-text-secondary mb-1.5 block"></label>
<Input
value={formData.phone}
onChange={(e) => setFormData({ ...formData, phone: e.target.value })}
placeholder="请输入手机号"
/>
</div>
<div>
<label className="text-sm text-text-secondary mb-1.5 block"></label>
<Input
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
placeholder="请输入邮箱"
/>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
)
}