feat: sync Sidebar and BottomNav, standardize user profile API

Align Sidebar & BottomNav menus, remove "Search", add user profile mock data, implement /api/users, add FilterDrawer, complete Section, ProfileHeader, MetricsRFM components

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
This commit is contained in:
v0
2025-08-08 07:00:12 +00:00
parent 4eed69520c
commit f0a6a364f2
85 changed files with 3318 additions and 6786 deletions

View File

@@ -1,5 +1,6 @@
import { NextResponse } from "next/server"
import { NextResponse, NextRequest } from "next/server"
import type { TrafficUser } from "@/types/traffic"
import { addUser, filterUsers, getDistinctTags, getUserDetail } from "@/lib/mock-users"
// 中文名字生成器数据
const familyNames = [
@@ -191,82 +192,51 @@ const generateWechatFriends = (wechatId: string, count: number) => {
// 微信好友数据缓存
const wechatFriendsCache = new Map<string, TrafficUser[]>()
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const page = Number.parseInt(searchParams.get("page") || "1")
const pageSize = Number.parseInt(searchParams.get("pageSize") || "10")
const search = searchParams.get("search") || ""
const category = searchParams.get("category") || "all"
const source = searchParams.get("source") || "all"
const status = searchParams.get("status") || "all"
const startDate = searchParams.get("startDate")
const endDate = searchParams.get("endDate")
const wechatSource = searchParams.get("wechatSource") || ""
let filteredUsers = [...userPool]
// 如果有微信来源参数,生成或获取微信好友数据
if (wechatSource) {
if (!wechatFriendsCache.has(wechatSource)) {
// 生成150-300个随机好友
const friendCount = Math.floor(Math.random() * (300 - 150)) + 150
wechatFriendsCache.set(wechatSource, generateWechatFriends(wechatSource, friendCount))
}
filteredUsers = wechatFriendsCache.get(wechatSource) || []
}
// 应用过滤条件
filteredUsers = filteredUsers.filter((user) => {
const matchesSearch = search
? user.nickname.toLowerCase().includes(search.toLowerCase()) ||
user.wechatId.toLowerCase().includes(search.toLowerCase()) ||
user.phone.includes(search)
: true
const matchesCategory = category === "all" ? true : user.category === category
const matchesSource = source === "all" ? true : user.source === source
const matchesStatus = status === "all" ? true : user.status === status
const matchesDate =
startDate && endDate
? new Date(user.addTime) >= new Date(startDate) && new Date(user.addTime) <= new Date(endDate)
: true
return matchesSearch && matchesCategory && matchesSource && matchesStatus && matchesDate
})
// 按添加时间倒序排序
filteredUsers.sort((a, b) => new Date(b.addTime).getTime() - new Date(a.addTime).getTime())
// 计算分页
const total = filteredUsers.length
const totalPages = Math.ceil(total / pageSize)
const start = (page - 1) * pageSize
const end = start + pageSize
const users = filteredUsers.slice(start, end)
// 计算分类统计
const categoryStats = {
potential: userPool.filter((user) => user.category === "potential").length,
customer: userPool.filter((user) => user.category === "customer").length,
lost: userPool.filter((user) => user.category === "lost").length,
}
// 模拟网络延迟
await new Promise((resolve) => setTimeout(resolve, 500))
return NextResponse.json({
users,
pagination: {
total,
totalPages,
currentPage: page,
pageSize,
},
stats: {
total: wechatSource ? filteredUsers.length : userPool.length,
todayNew: wechatSource ? Math.floor(filteredUsers.length * 0.1) : todayUsers.length,
categoryStats,
},
})
function parseArrayParam(v: string | null) {
if (!v) return []
return v.split(",").map((s) => s.trim()).filter(Boolean)
}
export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url)
const meta = searchParams.get("meta")
const id = searchParams.get("id")
if (meta === "tags") {
const tags = getDistinctTags()
return NextResponse.json({ success: true, data: { tags } })
}
if (id) {
const user = getUserDetail(id)
if (!user) return NextResponse.json({ success: false, error: "NOT_FOUND" }, { status: 404 })
return NextResponse.json({ success: true, data: user })
}
const q = searchParams.get("q") || undefined
const tagsParam = searchParams.get("tags") || ""
const tags = tagsParam ? tagsParam.split(",").filter(Boolean) : []
const statusParam = searchParams.get("status") || ""
const status = statusParam ? (statusParam.split(",") as any) : []
const rfmMin = Number(searchParams.get("rfmMin") ?? "0")
const rfmMax = Number(searchParams.get("rfmMax") ?? "100")
const page = Number(searchParams.get("page") ?? "1")
const pageSize = Number(searchParams.get("pageSize") ?? "20")
const res = filterUsers({ q, tags, status, rfmMin, rfmMax, page, pageSize })
return NextResponse.json({ success: true, data: res })
}
export async function POST(req: NextRequest) {
const body = await req.json().catch(() => null)
if (!body || !body.name || !body.phone || !body.email) {
return NextResponse.json({ success: false, error: "INVALID_PAYLOAD" }, { status: 400 })
}
const user = addUser({
name: body.name,
phone: body.phone,
email: body.email,
tags: Array.isArray(body.tags) ? body.tags.slice(0, 20) : [],
})
return NextResponse.json({ success: true, data: user })
}