export type UserStatus = "活跃" | "沉睡" | "流失风险" export interface User { id: string name: string email: string phone: string avatar: string tags: string[] status: UserStatus rfmScore: number createdAt: string lastActiveAt: string } const familyNames = [ "张", "李", "王", "赵", "刘", "陈", "杨", "黄", "周", "吴", "徐", "孙", "胡", "朱", "高", "林", "何", "郭", "马", "罗", ] const givenNames = [ "伟", "芳", "娜", "敏", "静", "秀英", "丽", "强", "磊", "军", "洋", "艳", "勇", "杰", "娟", "涛", "明", "超", "霞", "平", "俊", "凯", "佳", "鑫", "鹏", "晨", "倩", "颖", "梅", "慧", "雪", "宇", "涵", "宁", "璐", "龙", "震", "航", "璟", "钰", ] const tagPool = [ "高价值", "近7日活跃", "新客", "回流", "社群达人", "潜在复购", "高互动", "低客单", "私域粉", "公众号粉", ] const statusPool: UserStatus[] = ["活跃", "沉睡", "流失风险"] const avatars = [ "/user-avatar-zhangsan.png", "/user-avatar-lisi.png", "/avatar-wanglei.png", "/generic-user-avatar.png", "/wechat-avatar-1.png", "/wechat-avatar-2.png", "/wechat-avatar-3.png", ] const rand = (min: number, max: number) => Math.floor(Math.random() * (max - min + 1)) + min const pick = (arr: T[]) => arr[rand(0, arr.length - 1)] function toPinyinLike(name: string) { const map: Record = { 张: "zhang", 李: "li", 王: "wang", 赵: "zhao", 刘: "liu", 陈: "chen", 杨: "yang", 黄: "huang", 周: "zhou", 吴: "wu", 徐: "xu", 孙: "sun", 胡: "hu", 朱: "zhu", 高: "gao", 林: "lin", 何: "he", 郭: "guo", 马: "ma", 罗: "luo", 伟: "wei", 芳: "fang", 娜: "na", 敏: "min", 静: "jing", 秀英: "xiuying", 丽: "li", 强: "qiang", 磊: "lei", 军: "jun", 洋: "yang", 艳: "yan", 勇: "yong", 杰: "jie", 娟: "juan", 涛: "tao", 明: "ming", 超: "chao", 霞: "xia", 平: "ping", 俊: "jun", 凯: "kai", 佳: "jia", 鑫: "xin", 鹏: "peng", 晨: "chen", 倩: "qian", 颖: "ying", 梅: "mei", 慧: "hui", 雪: "xue", 宇: "yu", 涵: "han", 宁: "ning", 璐: "lu", 龙: "long", 震: "zhen", 航: "hang", 璟: "jing", 钰: "yu", } return name .split("") .map((c) => map[c] ?? "u") .join("") } function randomPhone() { const prefixes = [ "139", "138", "137", "136", "135", "188", "187", "186", "185", "184", "183", "182", "159", "158", "157", "156", "155", ] return `${pick(prefixes)}${rand(1000, 9999)}${rand(1000, 9999)}` } function randomTags() { const count = rand(2, 4) const s = new Set() while (s.size < count) s.add(pick(tagPool)) return Array.from(s) } function timeNearNow(daysSpan = 90) { const now = Date.now() const offset = rand(0, daysSpan * 86400000) return new Date(now - offset).toISOString() } let cache: User[] | null = null function seed(n = 120) { const list: User[] = [] for (let i = 0; i < n; i++) { const name = `${pick(familyNames)}${pick(givenNames)}${Math.random() < 0.2 ? pick(givenNames) : ""}` const email = `${toPinyinLike(name)}${rand(1, 99)}@example.com` const phone = randomPhone() list.push({ id: `user_${Date.now()}_${i}`, name, email, phone, avatar: avatars[i % avatars.length], tags: randomTags(), status: pick(statusPool), rfmScore: rand(15, 95), createdAt: timeNearNow(180), lastActiveAt: timeNearNow(15), }) } return list } export function getUsersStore() { if (!cache) cache = seed() return cache } export interface QueryParams { q?: string tags?: string[] status?: UserStatus[] rfmMin?: number rfmMax?: number page?: number pageSize?: number } export function queryUsers(params: QueryParams) { const { q, tags, status, rfmMin = 0, rfmMax = 100, page = 1, pageSize = 20 } = params let list = getUsersStore() if (q && q.trim()) { const s = q.trim().toLowerCase() list = list.filter( (u) => u.name.toLowerCase().includes(s) || u.email.toLowerCase().includes(s) || u.phone.includes(s) || u.tags.some((t) => t.toLowerCase().includes(s)), ) } if (tags?.length) { list = list.filter((u) => tags.every((t) => u.tags.includes(t))) } if (status?.length) { const st = new Set(status) list = list.filter((u) => st.has(u.status)) } list = list.filter((u) => u.rfmScore >= rfmMin && u.rfmScore <= rfmMax) const total = list.length const start = (page - 1) * pageSize const end = start + pageSize const data = list.slice(start, end) // 列表行仅返回必要字段 const thin = data.map((u) => ({ id: u.id, name: u.name, email: u.email, phone: u.phone, rfmScore: u.rfmScore, lastActiveAt: u.lastActiveAt, tags: u.tags, })) return { data: thin, pagination: { page, pageSize, total, totalPages: Math.max(1, Math.ceil(total / pageSize)) } } } export function addUser(input: Partial) { const list = getUsersStore() const now = new Date().toISOString() const name = input.name ?? `${pick(familyNames)}${pick(givenNames)}` const email = input.email ?? `${toPinyinLike(name)}@example.com` const phone = input.phone ?? randomPhone() const u: User = { id: `user_${Date.now()}_${rand(1000, 9999)}`, name, email, phone, avatar: input.avatar ?? avatars[rand(0, avatars.length - 1)], tags: input.tags ?? randomTags(), status: input.status ?? pick(statusPool), rfmScore: input.rfmScore ?? rand(20, 80), createdAt: now, lastActiveAt: now, } list.unshift(u) return u } export function getUserById(id: string) { return getUsersStore().find((u) => u.id === id) ?? null }