Optimize user detail page for asset assessment and tag info. #VERCEL_SKIP Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
257 lines
7.2 KiB
TypeScript
257 lines
7.2 KiB
TypeScript
import { NextResponse } from "next/server"
|
||
import type { TrafficUser } from "@/types/traffic"
|
||
import { addUser, getUsersStore, queryUsers, type User } from "@/lib/mock-users"
|
||
|
||
// 中文名字生成器数据
|
||
const familyNames = [
|
||
"张",
|
||
"王",
|
||
"李",
|
||
"赵",
|
||
"陈",
|
||
"刘",
|
||
"杨",
|
||
"黄",
|
||
"周",
|
||
"吴",
|
||
"朱",
|
||
"孙",
|
||
"马",
|
||
"胡",
|
||
"郭",
|
||
"林",
|
||
"何",
|
||
"高",
|
||
"梁",
|
||
"郑",
|
||
"罗",
|
||
"宋",
|
||
"谢",
|
||
"唐",
|
||
"韩",
|
||
"曹",
|
||
"许",
|
||
"邓",
|
||
"萧",
|
||
"冯",
|
||
]
|
||
const givenNames1 = [
|
||
"志",
|
||
"建",
|
||
"文",
|
||
"明",
|
||
"永",
|
||
"春",
|
||
"秀",
|
||
"金",
|
||
"水",
|
||
"玉",
|
||
"国",
|
||
"立",
|
||
"德",
|
||
"海",
|
||
"和",
|
||
"荣",
|
||
"伟",
|
||
"新",
|
||
"英",
|
||
"佳",
|
||
]
|
||
const givenNames2 = [
|
||
"华",
|
||
"平",
|
||
"军",
|
||
"强",
|
||
"辉",
|
||
"敏",
|
||
"峰",
|
||
"磊",
|
||
"超",
|
||
"艳",
|
||
"娜",
|
||
"霞",
|
||
"燕",
|
||
"娟",
|
||
"静",
|
||
"丽",
|
||
"涛",
|
||
"洋",
|
||
"勇",
|
||
"龙",
|
||
]
|
||
|
||
// 生成固定的用户数据池
|
||
const userPool: TrafficUser[] = Array.from({ length: 1610 }, (_, i) => {
|
||
const familyName = familyNames[Math.floor(Math.random() * familyNames.length)]
|
||
const givenName1 = givenNames1[Math.floor(Math.random() * givenNames1.length)]
|
||
const givenName2 = givenNames2[Math.floor(Math.random() * givenNames2.length)]
|
||
const fullName = Math.random() > 0.5 ? familyName + givenName1 + givenName2 : familyName + givenName1
|
||
|
||
// 生成随机时间(在过去7天内)
|
||
const date = new Date()
|
||
date.setDate(date.getDate() - Math.floor(Math.random() * 7))
|
||
|
||
return {
|
||
id: `${Date.now()}-${i}`,
|
||
avatar: `/placeholder.svg?height=40&width=40&text=${fullName[0]}`,
|
||
nickname: fullName,
|
||
wechatId: `wxid_${Math.random().toString(36).substr(2, 8)}`,
|
||
phone: `1${["3", "5", "7", "8", "9"][Math.floor(Math.random() * 5)]}${Array.from({ length: 9 }, () => Math.floor(Math.random() * 10)).join("")}`,
|
||
region: [
|
||
"广东深圳",
|
||
"浙江杭州",
|
||
"江苏苏州",
|
||
"北京",
|
||
"上海",
|
||
"四川成都",
|
||
"湖北武汉",
|
||
"福建厦门",
|
||
"山东青岛",
|
||
"河南郑州",
|
||
][Math.floor(Math.random() * 10)],
|
||
note: [
|
||
"咨询产品价格",
|
||
"对产品很感兴趣",
|
||
"准备购买",
|
||
"需要更多信息",
|
||
"想了解优惠活动",
|
||
"询问产品规格",
|
||
"要求产品demo",
|
||
"索要产品目录",
|
||
"询问售后服务",
|
||
"要求上门演示",
|
||
][Math.floor(Math.random() * 10)],
|
||
status: ["pending", "added", "failed"][Math.floor(Math.random() * 3)] as TrafficUser["status"],
|
||
addTime: date.toISOString(),
|
||
source: ["抖音直播", "小红书", "微信朋友圈", "视频号", "公众号", "个人主页"][Math.floor(Math.random() * 6)],
|
||
assignedTo: "",
|
||
category: ["potential", "customer", "lost"][Math.floor(Math.random() * 3)] as TrafficUser["category"],
|
||
tags: [],
|
||
city: "",
|
||
persona: "",
|
||
rfmScore: Math.floor(Math.random() * 101),
|
||
}
|
||
})
|
||
|
||
// 计算今日新增数量
|
||
const todayStart = new Date()
|
||
todayStart.setHours(0, 0, 0, 0)
|
||
const todayUsers = userPool.filter((user) => new Date(user.addTime) >= todayStart)
|
||
|
||
// 生成微信好友数据池
|
||
const generateWechatFriends = (wechatId: string, count: number) => {
|
||
return Array.from({ length: count }, (_, i) => {
|
||
const familyName = familyNames[Math.floor(Math.random() * familyNames.length)]
|
||
const givenName1 = givenNames1[Math.floor(Math.random() * givenNames1.length)]
|
||
const givenName2 = givenNames2[Math.floor(Math.random() * givenNames2.length)]
|
||
const fullName = Math.random() > 0.5 ? familyName + givenName1 + givenName2 : familyName + givenName1
|
||
|
||
// 生成随机时间(在过去30天内)
|
||
const date = new Date()
|
||
date.setDate(date.getDate() - Math.floor(Math.random() * 30))
|
||
|
||
return {
|
||
id: `wechat-${wechatId}-${i}`,
|
||
avatar: `/placeholder.svg?height=40&width=40&text=${fullName[0]}`,
|
||
nickname: fullName,
|
||
wechatId: `wxid_${Math.random().toString(36).substr(2, 8)}`,
|
||
phone: `1${["3", "5", "7", "8", "9"][Math.floor(Math.random() * 5)]}${Array.from({ length: 9 }, () => Math.floor(Math.random() * 10)).join("")}`,
|
||
region: [
|
||
"广东深圳",
|
||
"浙江杭州",
|
||
"江苏苏州",
|
||
"北京",
|
||
"上海",
|
||
"四川成都",
|
||
"湖北武汉",
|
||
"福建厦门",
|
||
"山东青岛",
|
||
"河南郑州",
|
||
][Math.floor(Math.random() * 10)],
|
||
note: [
|
||
"咨询产品价格",
|
||
"对产品很感兴趣",
|
||
"准备购买",
|
||
"需要更多信息",
|
||
"想了解优惠活动",
|
||
"询问产品规格",
|
||
"要求产品demo",
|
||
"索要产品目录",
|
||
"询问售后服务",
|
||
"要求上门演示",
|
||
][Math.floor(Math.random() * 10)],
|
||
status: ["pending", "added", "failed"][Math.floor(Math.random() * 3)] as TrafficUser["status"],
|
||
addTime: date.toISOString(),
|
||
source: ["抖音直播", "小红书", "微信朋友圈", "视频号", "公众号", "个人主页", "微信好友"][
|
||
Math.floor(Math.random() * 7)
|
||
],
|
||
assignedTo: "",
|
||
category: ["potential", "customer", "lost"][Math.floor(Math.random() * 3)] as TrafficUser["category"],
|
||
tags: [],
|
||
city: "",
|
||
persona: "",
|
||
rfmScore: Math.floor(Math.random() * 101),
|
||
}
|
||
})
|
||
}
|
||
|
||
// 微信好友数据缓存
|
||
const wechatFriendsCache = new Map<string, TrafficUser[]>()
|
||
|
||
function parseArrayParam(v: string | null) {
|
||
if (!v) return []
|
||
return v
|
||
.split(",")
|
||
.map((s) => s.trim())
|
||
.filter(Boolean)
|
||
}
|
||
|
||
export const dynamic = "force-dynamic"
|
||
|
||
export async function GET(req: Request) {
|
||
const url = new URL(req.url)
|
||
const meta = url.searchParams.get("meta")
|
||
if (meta === "tags") {
|
||
const tags = Array.from(new Set(getUsersStore().flatMap((u) => u.tags))).sort()
|
||
const cities = Array.from(new Set(getUsersStore().map((u) => u.city))).sort()
|
||
const personas = Array.from(new Set(getUsersStore().flatMap((u) => u.persona))).sort()
|
||
const sources = Array.from(new Set(getUsersStore().map((u) => u.source))).sort()
|
||
return NextResponse.json({ success: true, data: { tags, cities, personas, sources } })
|
||
}
|
||
|
||
const q = url.searchParams.get("q") ?? undefined
|
||
const tags = url.searchParams.get("tags")?.split(",").filter(Boolean)
|
||
const status = url.searchParams.get("status")?.split(",").filter(Boolean) as any
|
||
const city = url.searchParams.get("city")?.split(",").filter(Boolean)
|
||
const persona = url.searchParams.get("persona")?.split(",").filter(Boolean)
|
||
const source = url.searchParams.get("source")?.split(",").filter(Boolean)
|
||
const rfmMin = Number(url.searchParams.get("rfmMin") ?? 0)
|
||
const rfmMax = Number(url.searchParams.get("rfmMax") ?? 100)
|
||
const page = Number(url.searchParams.get("page") ?? 1)
|
||
const pageSize = Number(url.searchParams.get("pageSize") ?? 20)
|
||
|
||
const { data, pagination } = queryUsers({
|
||
q,
|
||
tags,
|
||
status,
|
||
city,
|
||
persona,
|
||
source,
|
||
rfmMin,
|
||
rfmMax,
|
||
page,
|
||
pageSize,
|
||
})
|
||
|
||
// 用户估值:简单以 rfmScore * 100 作为估值
|
||
const totalValue = data.reduce((sum, u) => sum + u.rfmScore * 100, 0)
|
||
|
||
return NextResponse.json({ success: true, data: { items: data, pagination, totalValue } })
|
||
}
|
||
|
||
export async function POST(req: Request) {
|
||
const body = (await req.json()) as Partial<User>
|
||
const u = addUser(body)
|
||
return NextResponse.json({ success: true, data: u })
|
||
}
|