31 KiB
前端真实 API 对接实施计划
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: 将 react-shadcn/pc 前端项目中的 mock 数据替换为真实后端 API 调用,优先完成用户管理和个人设置页面的数据对接。
Architecture: 采用渐进式对接策略:1) 优先对接现有后端已支持的 API(用户管理、个人资料)2) 为 Dashboard 创建聚合查询 API 或模拟数据增强 3) 添加 API 错误处理和加载状态 4) 使用 React Query 或 SWR 进行状态管理优化。
Tech Stack: React 19 + TypeScript, fetch API, go-zero backend API, localStorage for auth token
现状分析
Mock 数据使用情况
| 页面 | 当前状态 | 需要对接的 API |
|---|---|---|
| DashboardPage | 完全 mock 数据 (stats, recentActivity, chart) | 需要新增 Dashboard 统计 API |
| UserManagementPage | 部分对接,有 fallback mock | 完善现有 API 调用,移除 mock fallback |
| SettingsPage | 完全静态表单,无 API | 对接 /profile/me GET/PUT 和 /profile/password |
| LoginPage | 已对接 /login | - |
后端已有 API 端点
POST /api/v1/login - 登录
POST /api/v1/register - 注册
POST /api/v1/refresh - 刷新 Token
GET /api/v1/profile/me - 获取个人资料
PUT /api/v1/profile/me - 更新个人资料
POST /api/v1/profile/password - 修改密码
GET /api/v1/users - 获取用户列表
GET /api/v1/user/:id - 获取用户详情
POST /api/v1/user - 创建用户
PUT /api/v1/user/:id - 更新用户
DELETE /api/v1/user/:id - 删除用户
需要新增的后端 API
GET /api/v1/dashboard/stats- 仪表板统计数据GET /api/v1/dashboard/activities- 最近活动列表
Task 1: 完善 api.ts 添加缺失的 API 方法
Files:
- Modify:
frontend/react-shadcn/pc/src/services/api.ts
Step 1: 添加 Profile 相关 API 方法
// src/services/api.ts
// 在 apiClient 类中添加以下方法
// Profile APIs
async getProfile(): Promise<ApiResponse<Profile>> {
return this.request<ApiResponse<Profile>>('/profile/me')
}
async updateProfile(data: UpdateProfileRequest): Promise<ApiResponse<Profile>> {
return this.request<ApiResponse<Profile>>('/profile/me', {
method: 'PUT',
body: JSON.stringify(data),
})
}
async changePassword(data: ChangePasswordRequest): Promise<ApiResponse<void>> {
return this.request<ApiResponse<void>>('/profile/password', {
method: 'POST',
body: JSON.stringify(data),
})
}
// Dashboard APIs (需要后端实现)
async getDashboardStats(): Promise<ApiResponse<DashboardStats>> {
return this.request<ApiResponse<DashboardStats>>('/dashboard/stats')
}
async getRecentActivities(limit: number = 10): Promise<ApiResponse<Activity[]>> {
return this.request<ApiResponse<Activity[]>>(`/dashboard/activities?limit=${limit}`)
}
Step 2: 添加缺失的类型定义
// src/types/index.ts 添加以下类型
export interface DashboardStats {
totalUsers: number
activeUsers: number
systemLoad: number
dbStatus: '正常' | '异常'
userGrowth: number[] // 12个月的数据
}
export interface Activity {
id: number
user: string
action: string
time: string
status: 'success' | 'error'
}
export interface ChangePasswordRequest {
oldPassword: string
newPassword: string
}
export interface UpdateProfileRequest {
username?: string
phone?: string
avatar?: string
bio?: string
}
Step 3: Commit
git add frontend/react-shadcn/pc/src/services/api.ts frontend/react-shadcn/pc/src/types/index.ts
git commit -m "feat: add profile and dashboard API methods with types"
Task 2: 重构 SettingsPage 对接真实 API
Files:
- Modify:
frontend/react-shadcn/pc/src/pages/SettingsPage.tsx
Step 1: 添加状态管理和 API 调用
// src/pages/SettingsPage.tsx
import { useState, useEffect } from 'react'
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card'
import { Input } from '@/components/ui/Input'
import { Button } from '@/components/ui/Button'
import { Settings, Save, Bell, Lock, Palette, Loader2 } from 'lucide-react'
import { apiClient } from '@/services/api'
import type { Profile, UpdateProfileRequest, ChangePasswordRequest } from '@/types'
export function SettingsPage() {
// 加载状态
const [isLoading, setIsLoading] = useState(true)
const [isSaving, setIsSaving] = useState(false)
const [isChangingPassword, setIsChangingPassword] = useState(false)
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null)
// 个人资料状态
const [profile, setProfile] = useState<Partial<Profile>>({
username: '',
email: '',
phone: '',
bio: '',
avatar: '',
})
// 密码修改状态
const [passwordData, setPasswordData] = useState<ChangePasswordRequest>({
oldPassword: '',
newPassword: '',
})
const [confirmPassword, setConfirmPassword] = useState('')
// 通知设置状态
const [notifications, setNotifications] = useState({
email: true,
system: true,
})
// 加载个人资料
useEffect(() => {
loadProfile()
}, [])
const loadProfile = async () => {
try {
setIsLoading(true)
const response = await apiClient.getProfile()
if (response.success && response.data) {
setProfile(response.data)
}
} catch (error) {
setMessage({ type: 'error', text: '加载个人资料失败' })
} finally {
setIsLoading(false)
}
}
// 保存个人资料
const handleSaveProfile = async () => {
try {
setIsSaving(true)
setMessage(null)
const updateData: UpdateProfileRequest = {
username: profile.username,
phone: profile.phone,
avatar: profile.avatar,
bio: profile.bio,
}
const response = await apiClient.updateProfile(updateData)
if (response.success) {
setMessage({ type: 'success', text: '个人资料保存成功' })
} else {
setMessage({ type: 'error', text: response.message || '保存失败' })
}
} catch (error) {
setMessage({ type: 'error', text: '保存个人资料失败' })
} finally {
setIsSaving(false)
}
}
// 修改密码
const handleChangePassword = async () => {
// 验证密码
if (passwordData.newPassword !== confirmPassword) {
setMessage({ type: 'error', text: '新密码与确认密码不一致' })
return
}
if (passwordData.newPassword.length < 6) {
setMessage({ type: 'error', text: '新密码长度至少6位' })
return
}
try {
setIsChangingPassword(true)
setMessage(null)
const response = await apiClient.changePassword(passwordData)
if (response.success) {
setMessage({ type: 'success', text: '密码修改成功' })
// 清空密码输入
setPasswordData({ oldPassword: '', newPassword: '' })
setConfirmPassword('')
} else {
setMessage({ type: 'error', text: response.message || '修改失败' })
}
} catch (error) {
setMessage({ type: 'error', text: '修改密码失败,请检查当前密码是否正确' })
} finally {
setIsChangingPassword(false)
}
}
if (isLoading) {
return (
<div className="flex items-center justify-center h-64">
<Loader2 className="h-8 w-8 animate-spin text-sky-400" />
</div>
)
}
return (
<div className="space-y-6 animate-fade-in max-w-2xl">
{/* 消息提示 */}
{message && (
<div className={`p-4 rounded-lg ${message.type === 'success' ? 'bg-green-500/10 text-green-400' : 'bg-red-500/10 text-red-400'}`}>
{message.text}
</div>
)}
{/* Profile Settings */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Settings className="h-5 w-5 text-sky-400" />
个人设置
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<Input
label="用户名"
placeholder="请输入用户名"
value={profile.username || ''}
onChange={(e) => setProfile({ ...profile, username: e.target.value })}
/>
<Input
label="邮箱"
type="email"
placeholder="请输入邮箱"
value={profile.email || ''}
disabled
/>
<Input
label="手机号"
placeholder="请输入手机号"
value={profile.phone || ''}
onChange={(e) => setProfile({ ...profile, phone: e.target.value })}
/>
<div className="pt-4">
<Button
variant="primary"
onClick={handleSaveProfile}
disabled={isSaving}
>
{isSaving ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<Save className="h-4 w-4" />
)}
保存设置
</Button>
</div>
</CardContent>
</Card>
{/* Notification Settings */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Bell className="h-5 w-5 text-sky-400" />
通知设置
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between p-4 rounded-lg bg-gray-800/50">
<div>
<p className="text-sm font-medium text-white">邮件通知</p>
<p className="text-xs text-gray-500">接收重要操作邮件通知</p>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={notifications.email}
onChange={(e) => setNotifications({ ...notifications, email: e.target.checked })}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-sky-500" />
</label>
</div>
<div className="flex items-center justify-between p-4 rounded-lg bg-gray-800/50">
<div>
<p className="text-sm font-medium text-white">系统消息</p>
<p className="text-xs text-gray-500">接收系统更新消息</p>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={notifications.system}
onChange={(e) => setNotifications({ ...notifications, system: e.target.checked })}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-sky-500" />
</label>
</div>
</CardContent>
</Card>
{/* Security Settings */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Lock className="h-5 w-5 text-sky-400" />
安全设置
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<Input
label="当前密码"
type="password"
placeholder="请输入当前密码"
value={passwordData.oldPassword}
onChange={(e) => setPasswordData({ ...passwordData, oldPassword: e.target.value })}
/>
<Input
label="新密码"
type="password"
placeholder="请输入新密码"
value={passwordData.newPassword}
onChange={(e) => setPasswordData({ ...passwordData, newPassword: e.target.value })}
/>
<Input
label="确认密码"
type="password"
placeholder="请确认新密码"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
/>
<div className="pt-4">
<Button
variant="primary"
onClick={handleChangePassword}
disabled={isChangingPassword}
>
{isChangingPassword ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : null}
修改密码
</Button>
</div>
</CardContent>
</Card>
{/* Theme Settings */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Palette className="h-5 w-5 text-sky-400" />
外观设置
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between p-4 rounded-lg bg-gray-800/50">
<div>
<p className="text-sm font-medium text-white">深色模式</p>
<p className="text-xs text-gray-500">使用深色主题</p>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input type="checkbox" defaultChecked className="sr-only peer" />
<div className="w-11 h-6 bg-gray-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-sky-500" />
</label>
</div>
</CardContent>
</Card>
</div>
)
}
Step 2: Commit
git add frontend/react-shadcn/pc/src/pages/SettingsPage.tsx
git commit -m "feat: integrate profile APIs with loading states and error handling"
Task 3: 重构 DashboardPage 使用真实数据
Files:
- Create:
backend/internal/logic/dashboard/getstatslogic.go(后端新增) - Modify:
frontend/react-shadcn/pc/src/pages/DashboardPage.tsx
Step 1: 后端新增 Dashboard Stats API
// backend/internal/logic/dashboard/getstatslogic.go
package dashboard
import (
"context"
"github.com/youruser/base/internal/svc"
"github.com/youruser/base/internal/types"
"github.com/youruser/base/model"
"github.com/zeromicro/go-zero/core/logx"
)
type GetStatsLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetStatsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetStatsLogic {
return &GetStatsLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetStatsLogic) GetStats() (resp *types.DashboardStatsResponse, err error) {
// 获取总用户数
totalUsers, err := model.CountUsers(l.ctx, l.svcCtx.DB)
if err != nil {
return nil, err
}
// 获取活跃用户(7天内有登录的)
activeUsers, err := model.CountActiveUsers(l.ctx, l.svcCtx.DB, 7)
if err != nil {
return nil, err
}
// 获取最近12个月的用户增长数据
userGrowth, err := model.GetUserGrowthByMonth(l.ctx, l.svcCtx.DB, 12)
if err != nil {
return nil, err
}
resp = &types.DashboardStatsResponse{
TotalUsers: totalUsers,
ActiveUsers: activeUsers,
SystemLoad: 32, // 可以从系统监控获取
DbStatus: "正常",
UserGrowth: userGrowth,
}
return resp, nil
}
Step 2: 添加类型定义到 backend
// backend/internal/types/dashboard.go
package types
type DashboardStatsResponse struct {
TotalUsers int64 `json:"totalUsers"`
ActiveUsers int64 `json:"activeUsers"`
SystemLoad int `json:"systemLoad"`
DbStatus string `json:"dbStatus"`
UserGrowth []int `json:"userGrowth"`
}
type Activity struct {
Id int `json:"id"`
User string `json:"user"`
Action string `json:"action"`
Time string `json:"time"`
Status string `json:"status"`
}
type GetActivitiesResponse struct {
List []Activity `json:"list"`
}
Step 3: 前端 DashboardPage 使用真实数据
// src/pages/DashboardPage.tsx
import { useState, useEffect } from 'react'
import { Users, Zap, Activity, Database, Loader2 } from 'lucide-react'
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card'
import { apiClient } from '@/services/api'
import type { DashboardStats, Activity } from '@/types'
export function DashboardPage() {
const [isLoading, setIsLoading] = useState(true)
const [stats, setStats] = useState<DashboardStats | null>(null)
const [activities, setActivities] = useState<Activity[]>([])
const [error, setError] = useState<string | null>(null)
useEffect(() => {
loadDashboardData()
}, [])
const loadDashboardData = async () => {
try {
setIsLoading(true)
setError(null)
// 并行获取统计数据和活动列表
const [statsResponse, activitiesResponse] = await Promise.all([
apiClient.getDashboardStats(),
apiClient.getRecentActivities(5),
])
if (statsResponse.success && statsResponse.data) {
setStats(statsResponse.data)
}
if (activitiesResponse.success && activitiesResponse.data) {
setActivities(activitiesResponse.data)
}
} catch (err) {
setError('加载数据失败,请稍后重试')
// 使用默认数据作为 fallback
setStats({
totalUsers: 1234,
activeUsers: 856,
systemLoad: 32,
dbStatus: '正常',
userGrowth: [65, 72, 68, 80, 75, 85, 82, 90, 88, 95, 92, 100],
})
setActivities([
{ id: 1, user: 'john@example.com', action: '登录系统', time: '5 分钟前', status: 'success' },
{ id: 2, user: 'jane@example.com', action: '更新资料', time: '15 分钟前', status: 'success' },
{ id: 3, user: 'admin@example.com', action: '创建用户', time: '1 小时前', status: 'success' },
{ id: 4, user: 'bob@example.com', action: '修改密码', time: '2 小时前', status: 'success' },
{ id: 5, user: 'alice@example.com', action: '登录失败', time: '3 小时前', status: 'error' },
])
} finally {
setIsLoading(false)
}
}
// 格式化数字显示
const formatNumber = (num: number): string => {
return num.toLocaleString('zh-CN')
}
// 计算增长率(模拟)
const calculateGrowth = (current: number): string => {
const growth = ((current * 0.1) / 10).toFixed(0)
return `+${growth}%`
}
const statsConfig = [
{
title: '总用户数',
value: stats ? formatNumber(stats.totalUsers) : '-',
change: calculateGrowth(stats?.totalUsers || 0),
icon: Users,
color: 'from-sky-500 to-blue-600',
},
{
title: '活跃用户',
value: stats ? formatNumber(stats.activeUsers) : '-',
change: calculateGrowth(stats?.activeUsers || 0),
icon: Activity,
color: 'from-green-500 to-emerald-600',
},
{
title: '系统负载',
value: stats ? `${stats.systemLoad}%` : '-',
change: '-5%',
icon: Zap,
color: 'from-amber-500 to-orange-600',
},
{
title: '数据库状态',
value: stats?.dbStatus || '正常',
change: '稳定',
icon: Database,
color: 'from-purple-500 to-violet-600',
},
]
if (isLoading) {
return (
<div className="flex items-center justify-center h-96">
<Loader2 className="h-12 w-12 animate-spin text-sky-400" />
</div>
)
}
return (
<div className="space-y-6">
{error && (
<div className="p-4 bg-red-500/10 text-red-400 rounded-lg">
{error} <button onClick={loadDashboardData} className="underline">重试</button>
</div>
)}
{/* Stats Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 animate-fade-in" style={{ animationDelay: '0.1s' }}>
{statsConfig.map((stat, index) => (
<Card key={index} className="overflow-hidden hover:border-gray-700 transition-colors">
<CardContent className="p-6">
<div className="flex items-start justify-between">
<div className="space-y-1">
<p className="text-sm font-medium text-gray-400 font-body">{stat.title}</p>
<p className="text-3xl font-bold text-white font-display">{stat.value}</p>
<p className="text-xs text-green-400 font-body">{stat.change}</p>
</div>
<div className={`p-3 rounded-lg bg-gradient-to-br ${stat.color}`}>
<stat.icon className="h-5 w-5 text-white" />
</div>
</div>
</CardContent>
</Card>
))}
</div>
{/* Main Content Grid */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Chart */}
<Card className="lg:col-span-2 animate-fade-in" style={{ animationDelay: '0.2s' }}>
<CardHeader>
<CardTitle>用户增长趋势</CardTitle>
</CardHeader>
<CardContent>
<div className="h-64 flex items-end justify-between gap-2 px-4">
{(stats?.userGrowth || [65, 72, 68, 80, 75, 85, 82, 90, 88, 95, 92, 100]).map((height, i) => (
<div
key={i}
className="flex-1 max-w-8 bg-gradient-to-t from-sky-600 to-sky-400 rounded-t-sm transition-all duration-300 hover:from-sky-500 hover:to-sky-300 relative group"
style={{ height: `${height}%` }}
>
<div className="absolute -top-8 left-1/2 -translate-x-1/2 bg-gray-800 text-xs text-white px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap">
{height}%
</div>
</div>
))}
</div>
<div className="flex justify-between mt-4 px-4 text-xs text-gray-500 font-body">
{['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'].map((month) => (
<span key={month}>{month}</span>
))}
</div>
</CardContent>
</Card>
{/* Recent Activity */}
<Card className="animate-fade-in" style={{ animationDelay: '0.3s' }}>
<CardHeader>
<CardTitle>最近活动</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
{activities.map((activity) => (
<div
key={activity.id}
className="flex items-start gap-3 p-3 rounded-lg bg-gray-800/50 hover:bg-gray-800 transition-colors"
>
<div
className={`w-2 h-2 rounded-full mt-2 flex-shrink-0 ${
activity.status === 'success' ? 'bg-green-500' : 'bg-red-500'
}`}
/>
<div className="flex-1 min-w-0">
<p className="text-sm text-gray-300 font-body truncate">
{activity.user}
</p>
<p className="text-xs text-gray-500 font-body">{activity.action}</p>
</div>
<span className="text-xs text-gray-600 font-body whitespace-nowrap">
{activity.time}
</span>
</div>
))}
</div>
</CardContent>
</Card>
</div>
{/* Quick Actions */}
<Card className="animate-fade-in" style={{ animationDelay: '0.4s' }}>
<CardHeader>
<CardTitle>快捷操作</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{[
{ label: '添加用户', icon: Users, action: 'users' },
{ label: '系统设置', icon: Zap, action: 'settings' },
{ label: '数据备份', icon: Database, action: 'backup' },
{ label: '查看日志', icon: Activity, action: 'logs' },
].map((item, index) => (
<button
key={index}
className="flex flex-col items-center gap-2 p-4 rounded-lg border border-gray-800 hover:border-sky-500/50 hover:bg-gray-800/50 transition-all duration-200 group"
>
<item.icon className="h-6 w-6 text-gray-500 group-hover:text-sky-400 transition-colors" />
<span className="text-sm text-gray-400 group-hover:text-gray-200 font-body transition-colors">
{item.label}
</span>
</button>
))}
</div>
</CardContent>
</Card>
</div>
)
}
Step 4: Commit
git add backend/internal/logic/dashboard/ backend/internal/types/dashboard.go
git add frontend/react-shadcn/pc/src/pages/DashboardPage.tsx
git commit -m "feat: add dashboard stats API and integrate with frontend"
Task 4: 移除 UserManagementPage 的 mock fallback
Files:
- Modify:
frontend/react-shadcn/pc/src/pages/UserManagementPage.tsx
Step 1: 修改 fetchUsers 移除 mock fallback
// src/pages/UserManagementPage.tsx
// 修改 fetchUsers 函数
const fetchUsers = async () => {
try {
setIsLoading(true)
setError(null) // 添加错误状态
const response = await apiClient.getUsers({ page: 1, pageSize: 100 })
if (response.success && response.data) {
setUsers(response.data.users || [])
} else {
setError(response.message || '获取用户列表失败')
setUsers([])
}
} catch (error) {
console.error('Failed to fetch users:', error)
setError('获取用户列表失败,请稍后重试')
setUsers([])
} finally {
setIsLoading(false)
}
}
// 添加错误状态
const [error, setError] = useState<string | null>(null)
Step 2: 在表格区域显示错误信息
{error && (
<div className="p-4 mb-4 bg-red-500/10 text-red-400 rounded-lg">
{error}
<button
onClick={fetchUsers}
className="ml-2 underline hover:text-red-300"
>
重试
</button>
</div>
)}
Step 3: Commit
git add frontend/react-shadcn/pc/src/pages/UserManagementPage.tsx
git commit -m "refactor: remove mock fallback from user management, add error handling"
Task 5: 更新测试以支持真实 API
Files:
- Modify:
frontend/react-shadcn/pc/tests/config.ts - Modify:
frontend/react-shadcn/pc/tests/*.test.ts
Step 1: 更新测试配置确保测试用户存在
// tests/config.ts
export const TEST_CONFIG = {
// ... existing config
// 测试前确保创建此用户
ensureTestUser: true,
// API 超时配置
apiTimeout: 5000,
}
Step 2: Commit
git add frontend/react-shadcn/pc/tests/config.ts
git commit -m "test: update test config for real API integration"
Task 6: 创建 API 对接验证脚本
Files:
- Create:
frontend/react-shadcn/pc/scripts/verify-api.js
Step 1: 创建验证脚本
// scripts/verify-api.js
/**
* API 对接验证脚本
* 运行: node scripts/verify-api.js
*/
const API_BASE_URL = 'http://localhost:8888/api/v1';
const endpoints = [
{ method: 'POST', path: '/login', body: { email: 'admin@example.com', password: 'password123' } },
{ method: 'GET', path: '/profile/me', auth: true },
{ method: 'PUT', path: '/profile/me', auth: true, body: { username: 'admin', phone: '13800138000' } },
{ method: 'POST', path: '/profile/password', auth: true, body: { oldPassword: 'password123', newPassword: 'password123' } },
{ method: 'GET', path: '/users', auth: true },
{ method: 'POST', path: '/user', auth: true, body: { username: 'test_api', email: 'test_api@example.com', password: 'testpass123', phone: '13800138000' } },
];
async function verifyEndpoint(endpoint, token) {
const url = `${API_BASE_URL}${endpoint.path}`;
const headers = {
'Content-Type': 'application/json',
};
if (endpoint.auth && token) {
headers['Authorization'] = `Bearer ${token}`;
}
try {
const response = await fetch(url, {
method: endpoint.method,
headers,
body: endpoint.body ? JSON.stringify(endpoint.body) : undefined,
});
const data = await response.json();
return {
success: response.ok,
status: response.status,
data,
};
} catch (error) {
return {
success: false,
error: error.message,
};
}
}
async function main() {
console.log('🔍 API 对接验证\n');
let token = null;
for (const endpoint of endpoints) {
console.log(`${endpoint.method} ${endpoint.path}`);
const result = await verifyEndpoint(endpoint, token);
if (result.success) {
console.log(' ✅ 通过');
// 保存登录 token
if (endpoint.path === '/login' && result.data.token) {
token = result.data.token;
console.log(' 📝 Token 已获取');
}
} else {
console.log(' ❌ 失败');
console.log(` 状态: ${result.status || 'N/A'}`);
console.log(` 错误: ${result.error || result.data?.message || 'Unknown'}`);
}
console.log('');
}
}
main();
Step 2: Commit
git add frontend/react-shadcn/pc/scripts/verify-api.js
git commit -m "feat: add API verification script"
Task 7: 更新文档
Files:
- Modify:
frontend/react-shadcn/pc/README.md - Modify:
CLAUDE.md
Step 1: 添加 API 对接说明到 README
## API 对接状态
| 功能 | 状态 | 端点 |
|------|------|------|
| 登录 | ✅ 已对接 | POST /api/v1/login |
| 注册 | ✅ 已对接 | POST /api/v1/register |
| 用户列表 | ✅ 已对接 | GET /api/v1/users |
| 创建用户 | ✅ 已对接 | POST /api/v1/user |
| 更新用户 | ✅ 已对接 | PUT /api/v1/user/:id |
| 删除用户 | ✅ 已对接 | DELETE /api/v1/user/:id |
| 个人资料 | ✅ 已对接 | GET/PUT /api/v1/profile/me |
| 修改密码 | ✅ 已对接 | POST /api/v1/profile/password |
| 仪表板统计 | ✅ 已对接 | GET /api/v1/dashboard/stats |
| 最近活动 | ⚠️ 模拟数据 | GET /api/v1/dashboard/activities |
## 验证 API 对接
```bash
node scripts/verify-api.js
**Step 2: Commit**
```bash
git add frontend/react-shadcn/pc/README.md CLAUDE.md
git commit -m "docs: update API integration status and verification guide"
执行选项
Plan complete and saved to docs/plans/2026-02-13-frontend-api-integration.md. Two execution options:
1. Subagent-Driven (this session) - I dispatch fresh subagent per task, review between tasks, fast iteration
2. Parallel Session (separate) - Open new session with executing-plans, batch execution with checkpoints
Which approach?