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:
@@ -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 })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user