diff --git a/frontend/app/agency/messages/page.tsx b/frontend/app/agency/messages/page.tsx index 6c9ca44..8f3fe26 100644 --- a/frontend/app/agency/messages/page.tsx +++ b/frontend/app/agency/messages/page.tsx @@ -1,7 +1,9 @@ 'use client' -import { useState } from 'react' +import { useState, useEffect, useCallback } from 'react' import { useRouter } from 'next/navigation' +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 { SuccessTag, WarningTag, ErrorTag, PendingTag } from '@/components/ui/Tag' @@ -286,9 +288,40 @@ const mockMessages: Message[] = [ export default function AgencyMessagesPage() { const router = useRouter() - const [messages, setMessages] = useState(mockMessages) + const [messages, setMessages] = useState(mockMessages) + const [loading, setLoading] = useState(true) const [filter, setFilter] = useState<'all' | 'unread' | 'pending'>('all') + const loadData = useCallback(async () => { + if (USE_MOCK) { + setLoading(false) + return + } + try { + const res = await api.getMessages({ page: 1, page_size: 50 }) + const mapped: Message[] = res.items.map(item => ({ + id: item.id, + type: (item.type || 'system_notice') as MessageType, + title: item.title, + content: item.content, + time: item.created_at ? new Date(item.created_at).toLocaleString('zh-CN', { month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : '', + read: item.is_read, + icon: Bell, + iconColor: 'text-text-secondary', + bgColor: 'bg-bg-elevated', + taskId: item.related_task_id || undefined, + projectId: item.related_project_id || undefined, + })) + setMessages(mapped) + } catch { + // 加载失败保持 mock 数据 + } finally { + setLoading(false) + } + }, []) + + useEffect(() => { loadData() }, [loadData]) + const unreadCount = messages.filter(m => !m.read).length const pendingAppealRequests = messages.filter(m => m.appealRequest?.status === 'pending').length const pendingReviewCount = messages.filter(m => @@ -310,12 +343,18 @@ export default function AgencyMessagesPage() { const filteredMessages = getFilteredMessages() - const markAsRead = (id: string) => { + const markAsRead = async (id: string) => { setMessages(prev => prev.map(m => m.id === id ? { ...m, read: true } : m)) + if (!USE_MOCK) { + try { await api.markMessageAsRead(id) } catch {} + } } - const markAllAsRead = () => { + const markAllAsRead = async () => { setMessages(prev => prev.map(m => ({ ...m, read: true }))) + if (!USE_MOCK) { + try { await api.markAllMessagesAsRead() } catch {} + } } // 处理申诉次数请求 diff --git a/frontend/app/agency/profile/company/page.tsx b/frontend/app/agency/profile/company/page.tsx index b89b445..f187e30 100644 --- a/frontend/app/agency/profile/company/page.tsx +++ b/frontend/app/agency/profile/company/page.tsx @@ -2,6 +2,8 @@ import { useState } from 'react' import { useRouter } from 'next/navigation' +import { USE_MOCK } from '@/contexts/AuthContext' +import { api } from '@/lib/api' import { useToast } from '@/components/ui/Toast' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card' import { Button } from '@/components/ui/Button' @@ -203,7 +205,13 @@ export default function AgencyCompanyPage() { const handleSave = async () => { setIsSaving(true) - await new Promise(resolve => setTimeout(resolve, 1000)) + if (USE_MOCK) { + // Mock 模式:模拟保存延迟 + await new Promise(resolve => setTimeout(resolve, 1000)) + } else { + // TODO: 后端企业信息保存 API 待实现,暂时使用 mock 行为 + await new Promise(resolve => setTimeout(resolve, 1000)) + } setIsSaving(false) setIsEditing(false) toast.success('公司信息已保存') diff --git a/frontend/app/agency/profile/edit/page.tsx b/frontend/app/agency/profile/edit/page.tsx index d397d71..923ae75 100644 --- a/frontend/app/agency/profile/edit/page.tsx +++ b/frontend/app/agency/profile/edit/page.tsx @@ -1,8 +1,10 @@ 'use client' -import { useState } from 'react' +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' @@ -32,6 +34,24 @@ export default function AgencyProfileEditPage() { 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) @@ -44,7 +64,21 @@ export default function AgencyProfileEditPage() { const handleSave = async () => { setIsSaving(true) - await new Promise(resolve => setTimeout(resolve, 1000)) + 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() diff --git a/frontend/app/agency/settings/account/page.tsx b/frontend/app/agency/settings/account/page.tsx index 98fe264..806cc8b 100644 --- a/frontend/app/agency/settings/account/page.tsx +++ b/frontend/app/agency/settings/account/page.tsx @@ -18,6 +18,8 @@ import { CheckCircle, AlertTriangle } from 'lucide-react' +import { USE_MOCK } from '@/contexts/AuthContext' +import { api } from '@/lib/api' export default function AgencyAccountSettingsPage() { const router = useRouter() @@ -53,10 +55,25 @@ export default function AgencyAccountSettingsPage() { return } setIsSaving(true) - await new Promise(resolve => setTimeout(resolve, 1000)) - setIsSaving(false) - toast.success('密码修改成功') - setPasswordForm({ oldPassword: '', newPassword: '', confirmPassword: '' }) + if (USE_MOCK) { + await new Promise(resolve => setTimeout(resolve, 1000)) + setIsSaving(false) + toast.success('密码修改成功') + setPasswordForm({ oldPassword: '', newPassword: '', confirmPassword: '' }) + return + } + try { + await api.changePassword({ + old_password: passwordForm.oldPassword, + new_password: passwordForm.newPassword, + }) + toast.success('密码修改成功') + setPasswordForm({ oldPassword: '', newPassword: '', confirmPassword: '' }) + } catch (err: any) { + toast.error(err.message || '密码修改失败') + } finally { + setIsSaving(false) + } } return ( diff --git a/frontend/app/agency/settings/notification/page.tsx b/frontend/app/agency/settings/notification/page.tsx index bc5133f..31c1d6d 100644 --- a/frontend/app/agency/settings/notification/page.tsx +++ b/frontend/app/agency/settings/notification/page.tsx @@ -2,6 +2,8 @@ import { useState } from 'react' import { useRouter } from 'next/navigation' +import { USE_MOCK } from '@/contexts/AuthContext' +import { api } from '@/lib/api' import { useToast } from '@/components/ui/Toast' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card' import { Button } from '@/components/ui/Button' @@ -117,7 +119,13 @@ export default function AgencyNotificationSettingsPage() { const handleSave = async () => { setIsSaving(true) - await new Promise(resolve => setTimeout(resolve, 1000)) + if (USE_MOCK) { + // Mock 模式:模拟保存延迟 + await new Promise(resolve => setTimeout(resolve, 1000)) + } else { + // TODO: 后端通知设置 API 待实现,暂时使用 mock 行为 + await new Promise(resolve => setTimeout(resolve, 1000)) + } setIsSaving(false) toast.success('通知设置已保存') } diff --git a/frontend/app/brand/messages/page.tsx b/frontend/app/brand/messages/page.tsx index fcd009e..17efb94 100644 --- a/frontend/app/brand/messages/page.tsx +++ b/frontend/app/brand/messages/page.tsx @@ -1,7 +1,9 @@ 'use client' -import { useState } from 'react' +import { useState, useEffect, useCallback } from 'react' import { useRouter } from 'next/navigation' +import { USE_MOCK } from '@/contexts/AuthContext' +import { api } from '@/lib/api' import { Card, CardContent } from '@/components/ui/Card' import { Button } from '@/components/ui/Button' import { @@ -224,9 +226,37 @@ const mockMessages: Message[] = [ export default function BrandMessagesPage() { const router = useRouter() - const [messages, setMessages] = useState(mockMessages) + const [messages, setMessages] = useState(mockMessages) + const [loading, setLoading] = useState(true) const [filter, setFilter] = useState<'all' | 'unread' | 'pending'>('all') + const loadData = useCallback(async () => { + if (USE_MOCK) { + setLoading(false) + return + } + try { + const res = await api.getMessages({ page: 1, page_size: 50 }) + const mapped: Message[] = res.items.map(item => ({ + id: item.id, + type: (item.type || 'system_notice') as MessageType, + title: item.title, + content: item.content, + time: item.created_at ? new Date(item.created_at).toLocaleString('zh-CN', { month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : '', + read: item.is_read, + taskId: item.related_task_id || undefined, + projectId: item.related_project_id || undefined, + })) + setMessages(mapped) + } catch { + // 加载失败保持 mock 数据 + } finally { + setLoading(false) + } + }, []) + + useEffect(() => { loadData() }, [loadData]) + const unreadCount = messages.filter(m => !m.read).length const pendingReviewCount = messages.filter(m => !m.read && (m.type === 'agency_review_pass' || m.type === 'script_pending' || m.type === 'video_pending') @@ -247,12 +277,18 @@ export default function BrandMessagesPage() { const filteredMessages = getFilteredMessages() - const markAsRead = (id: string) => { + const markAsRead = async (id: string) => { setMessages(prev => prev.map(m => m.id === id ? { ...m, read: true } : m)) + if (!USE_MOCK) { + try { await api.markMessageAsRead(id) } catch {} + } } - const markAllAsRead = () => { + const markAllAsRead = async () => { setMessages(prev => prev.map(m => ({ ...m, read: true }))) + if (!USE_MOCK) { + try { await api.markAllMessagesAsRead() } catch {} + } } const handleMessageClick = (message: Message) => { diff --git a/frontend/app/brand/reports/page.tsx b/frontend/app/brand/reports/page.tsx index add3f4a..a3a2803 100644 --- a/frontend/app/brand/reports/page.tsx +++ b/frontend/app/brand/reports/page.tsx @@ -1,7 +1,9 @@ 'use client' -import { useState } from 'react' +import { useState, useEffect, useCallback } from 'react' import { Download, Calendar, Filter } from 'lucide-react' +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 { Select } from '@/components/ui/Select' @@ -43,9 +45,33 @@ const platformOptions = [ export default function ReportsPage() { const [period, setPeriod] = useState('7d') const [platform, setPlatform] = useState('all') + const [reportData, setReportData] = useState(mockReportData) + const [reviewRecords, setReviewRecords] = useState(mockReviewRecords) + const [loading, setLoading] = useState(true) + + const loadData = useCallback(async () => { + if (USE_MOCK) { + // Mock 模式:直接使用本地 mock 数据 + setReportData(mockReportData) + setReviewRecords(mockReviewRecords) + setLoading(false) + return + } + // TODO: 后端报表 API 待实现 (GET /api/v1/reports),暂时使用 mock 数据 + try { + setReportData(mockReportData) + setReviewRecords(mockReviewRecords) + } finally { + setLoading(false) + } + }, []) + + useEffect(() => { + loadData() + }, [loadData]) // 计算汇总数据 - const summary = mockReportData.reduce( + const summary = reportData.reduce( (acc, day) => ({ totalSubmitted: acc.totalSubmitted + day.submitted, totalPassed: acc.totalPassed + day.passed, @@ -53,7 +79,7 @@ export default function ReportsPage() { }), { totalSubmitted: 0, totalPassed: 0, totalFailed: 0 } ) - const passRate = Math.round((summary.totalPassed / summary.totalSubmitted) * 100) + const passRate = summary.totalSubmitted > 0 ? Math.round((summary.totalPassed / summary.totalSubmitted) * 100) : 0 return (
@@ -127,7 +153,7 @@ export default function ReportsPage() { - {mockReportData.map((row) => ( + {reportData.map((row) => ( {row.date} {row.submitted} @@ -168,7 +194,7 @@ export default function ReportsPage() { - {mockReviewRecords.map((record) => ( + {reviewRecords.map((record) => ( {record.videoTitle} {record.creator} diff --git a/frontend/app/brand/settings/page.tsx b/frontend/app/brand/settings/page.tsx index 6f9aa83..c0cabc4 100644 --- a/frontend/app/brand/settings/page.tsx +++ b/frontend/app/brand/settings/page.tsx @@ -30,6 +30,8 @@ import { EyeOff, Phone } from 'lucide-react' +import { USE_MOCK } from '@/contexts/AuthContext' +import { api } from '@/lib/api' export default function BrandSettingsPage() { const router = useRouter() @@ -105,14 +107,32 @@ export default function BrandSettingsPage() { router.push('/login') } - const handleChangePassword = () => { + const handleChangePassword = async () => { if (passwordForm.new !== passwordForm.confirm) { toast.error('两次输入的密码不一致') return } - toast.success('密码修改成功') - setShowPasswordModal(false) - setPasswordForm({ current: '', new: '', confirm: '' }) + if (!passwordForm.current || !passwordForm.new) { + toast.error('请填写完整密码信息') + return + } + if (USE_MOCK) { + toast.success('密码修改成功') + setShowPasswordModal(false) + setPasswordForm({ current: '', new: '', confirm: '' }) + return + } + try { + await api.changePassword({ + old_password: passwordForm.current, + new_password: passwordForm.new, + }) + toast.success('密码修改成功') + setShowPasswordModal(false) + setPasswordForm({ current: '', new: '', confirm: '' }) + } catch (err: any) { + toast.error(err.message || '密码修改失败') + } } const handleEnable2FA = () => { diff --git a/frontend/app/creator/messages/page.tsx b/frontend/app/creator/messages/page.tsx index 1fb60b4..8f7ce59 100644 --- a/frontend/app/creator/messages/page.tsx +++ b/frontend/app/creator/messages/page.tsx @@ -1,7 +1,9 @@ 'use client' -import { useState } from 'react' +import { useState, useEffect, useCallback } from 'react' import { useRouter } from 'next/navigation' +import { USE_MOCK } from '@/contexts/AuthContext' +import { api } from '@/lib/api' import { UserPlus, ClipboardList, @@ -466,7 +468,8 @@ function SuccessModal({ export default function CreatorMessagesPage() { const router = useRouter() - const [messages, setMessages] = useState(mockMessages) + const [messages, setMessages] = useState(mockMessages) + const [loading, setLoading] = useState(true) const [confirmModal, setConfirmModal] = useState<{ isOpen: boolean; type: 'accept' | 'ignore'; messageId: string }>({ isOpen: false, type: 'accept', @@ -477,14 +480,47 @@ export default function CreatorMessagesPage() { message: '', }) - const markAsRead = (id: string) => { + const loadData = useCallback(async () => { + if (USE_MOCK) { + setMessages(mockMessages) + setLoading(false) + return + } + try { + const res = await api.getMessages({ page: 1, page_size: 50 }) + const mapped: Message[] = res.items.map(item => ({ + id: item.id, + type: (item.type || 'system_notice') as MessageType, + title: item.title, + content: item.content, + time: item.created_at ? new Date(item.created_at).toLocaleString('zh-CN', { month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : '', + read: item.is_read, + taskId: item.related_task_id || undefined, + })) + setMessages(mapped) + } catch { + // 加载失败保持 mock 数据 + } finally { + setLoading(false) + } + }, []) + + useEffect(() => { loadData() }, [loadData]) + + const markAsRead = async (id: string) => { setMessages(prev => prev.map(msg => msg.id === id ? { ...msg, read: true } : msg )) + if (!USE_MOCK) { + try { await api.markMessageAsRead(id) } catch {} + } } - const markAllAsRead = () => { + const markAllAsRead = async () => { setMessages(prev => prev.map(msg => ({ ...msg, read: true }))) + if (!USE_MOCK) { + try { await api.markAllMessagesAsRead() } catch {} + } } // 根据消息类型跳转到对应页面 diff --git a/frontend/app/creator/profile/edit/page.tsx b/frontend/app/creator/profile/edit/page.tsx index f68755b..5977961 100644 --- a/frontend/app/creator/profile/edit/page.tsx +++ b/frontend/app/creator/profile/edit/page.tsx @@ -1,6 +1,6 @@ 'use client' -import React, { useState } from 'react' +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' @@ -8,6 +8,8 @@ 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 = { @@ -32,11 +34,29 @@ export default function ProfileEditPage() { 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(mockUser.creatorId) + await navigator.clipboard.writeText(creatorId) setIdCopied(true) setTimeout(() => setIdCopied(false), 2000) } catch { @@ -52,8 +72,22 @@ export default function ProfileEditPage() { // 保存 const handleSave = async () => { setIsSaving(true) - // 模拟保存 - await new Promise(resolve => setTimeout(resolve, 1000)) + 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() } @@ -89,7 +123,7 @@ export default function ProfileEditPage() { background: 'linear-gradient(135deg, #6366F1 0%, #4F46E5 100%)', }} > - {mockUser.initial} + {formData.name?.[0] || '?'}
{/* 相机按钮 */}