export type Status = "活跃" | "沉睡" | "已封禁" export type UserBase = { id: string name: string phone: string email: string tags: string[] rfmScore: number lastActivity: string status: Status } export type UserDetail = UserBase & { avatar?: string company?: string position?: string recency: number frequency: number monetary: number interactions: { id: string; type: string; time: string; note?: string }[] purchaseHistory: { id: string; amount: number; time: string; item: string }[] wechatAccounts: { id: string; nickname: string; avatar?: string }[] } /* helpers */ const NOW = Date.now() const rand = (min: number, max: number) => Math.floor(Math.random() * (max - min + 1)) + min const maskPhone = (p: string) => p.replace(/^(\d{3})\d{4}(\d{4})$/, "$1****$2") const pick = (arr: T[]) => arr[rand(0, arr.length - 1)] const TAGS = [ "高价值用户", "活跃用户", "潜在客户", "价格敏感", "科技爱好者", "内容创作者", "一线城市", "二线城市", "iPhone", "Android", "社群成员", "低活跃", "沉睡风险", "新用户", "忠诚用户", ] const COMPANIES = ["合星科技", "云杉数智", "万像互动", "星远数科", "数研云", "青瓦科技"] const POSITIONS = ["产品经理", "运营经理", "市场总监", "技术负责人", "销售", "数据分析师"] const AVATARS = [ "/user-avatar-zhangsan.png", "/user-avatar-lisi.png", "/wechat-avatar-1.png", "/wechat-avatar-2.png", "/wechat-avatar-3.png", ] /* seed users */ const baseNames = [ "王磊","刘婷","张三","李四","赵六","钱七","周敏","孙悦","吴迪","郑航", "冯晨","褚野","卫国","蒋楠","沈静","韩睿","唐奕","曹越","彭博","鲁洋", "韦东","昌华","顾诚","孟辉","尹雪","谭清","严杰","霍宇","龚一","程远", ] const USERS: UserDetail[] = baseNames.slice(0, 24).map((name, idx) => { const n = idx + 1 const rawPhone = `1${rand(3,9)}${rand(0,9)}${rand(0,9)}${rand(10000000, 99999999)}` const email = `${pinyinLike(name)}${n}@example.com`.toLowerCase() const tagCount = rand(2, 5) const tags = Array.from(new Set(Array.from({ length: tagCount }, () => pick(TAGS)))) const status: Status = ["活跃","活跃","活跃","沉睡","已封禁"][rand(0,4)] const rfm = rand(45, 95) const lastActivity = new Date(NOW - rand(0, 7) * 86400_000 - rand(0, 12) * 3600_000).toISOString() const interactions = Array.from({ length: rand(1, 4) }).map((_, i) => ({ id: `i_${n}_${i}`, type: pick(["咨询", "浏览", "下载白皮书", "提交表单", "聊天"]), time: new Date(NOW - rand(0, 14) * 86400_000 - rand(0, 20) * 3600_000).toISOString(), note: pick(["", "询价", "对比竞品", "需要发票", "待回访"]), })) const purchaseHistory = rand(0, 1) ? [{ id: `o_${n}_1`, amount: rand(299, 9999), time: new Date(NOW - rand(0, 30) * 86400_000).toISOString(), item: pick(["标准版SaaS","高级版SaaS","增值模块"]) }] : [] const wechatAccounts = Array.from({ length: rand(1, 2) }).map((_, i) => ({ id: `wx_${n}_${i}`, nickname: `${name}-微信${i+1}`, avatar: pick(AVATARS), })) return { id: `user_${1000 + n}`, name, phone: maskPhone(rawPhone), email, tags, rfmScore: rfm, lastActivity, status, avatar: pick(AVATARS), company: pick(COMPANIES), position: pick(POSITIONS), recency: rand(1, 10), frequency: rand(1, 30), monetary: rand(0, 20000), interactions, purchaseHistory, wechatAccounts, } }) function pinyinLike(name: string) { // super simple fake pinyin-ish const map: Record = { "王":"wang","张":"zhang","李":"li","刘":"liu","赵":"zhao","钱":"qian","孙":"sun","周":"zhou", "吴":"wu","郑":"zheng","冯":"feng","褚":"chu","卫":"wei","蒋":"jiang","沈":"shen","韩":"han", "唐":"tang","曹":"cao","彭":"peng","鲁":"lu","韦":"wei","昌":"chang","顾":"gu","孟":"meng", "尹":"yin","谭":"tan","严":"yan","霍":"huo","龚":"gong","程":"cheng", } const first = map[name[0]] || "user" const rest = "abcxyz" return `${first}${rest[Math.floor(Math.random()*rest.length)]}${rest[Math.floor(Math.random()*rest.length)]}` } /* public APIs */ export type FilterOptions = { q?: string tags?: string[] status?: Status[] rfmMin?: number rfmMax?: number page?: number pageSize?: number } export function getUsers(): UserBase[] { return USERS.map(({ interactions, purchaseHistory, wechatAccounts, recency, frequency, monetary, company, position, avatar, ...u }) => u) } export function getUserDetail(id: string): UserDetail | null { return USERS.find((u) => u.id === id) ?? null } export function getDistinctTags(): string[] { const s = new Set() USERS.forEach((u) => u.tags.forEach((t) => s.add(t))) return Array.from(s) } export function filterUsers(opts: FilterOptions) { const { q = "", tags = [], status = [], rfmMin = 0, rfmMax = 100, page = 1, pageSize = 20, } = opts let list = getUsers() if (q) { const ql = q.toLowerCase() list = list.filter( (u) => u.name.toLowerCase().includes(ql) || u.phone.includes(q) || u.email.toLowerCase().includes(ql) || u.tags.some((t) => t.toLowerCase().includes(ql)), ) } if (tags.length) { list = list.filter((u) => tags.some((t) => u.tags.includes(t))) } if (status.length) { list = list.filter((u) => status.includes(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 items = list.slice(start, end) return { items, total, page, pageSize } } export function addUser(payload: { name: string; phone: string; email: string; tags?: string[] }): UserDetail { const n = USERS.length + 1000 const u: UserDetail = { id: `user_${n}`, name: payload.name, phone: maskPhone(payload.phone), email: payload.email, tags: payload.tags ?? [], rfmScore: 60 + (n % 40), lastActivity: new Date().toISOString(), status: "活跃", avatar: pick(AVATARS), company: pick(COMPANIES), position: pick(POSITIONS), recency: rand(1, 5), frequency: rand(1, 10), monetary: rand(0, 5000), interactions: [], purchaseHistory: [], wechatAccounts: [], } USERS.unshift(u) return u }