refactor: overhaul UI for streamlined user experience
Redesign navigation, home overview, user portrait, and valuation pages with improved functionality and responsive design. Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
This commit is contained in:
292
services/data-analysis-service.ts
Normal file
292
services/data-analysis-service.ts
Normal file
@@ -0,0 +1,292 @@
|
||||
import type {
|
||||
UserRFMData,
|
||||
RFMAnalysisResult,
|
||||
UserFilterOptions,
|
||||
DataComparisonResult,
|
||||
UserSegment,
|
||||
} from "@/types/data-analysis"
|
||||
|
||||
// 模拟用户RFM数据
|
||||
export const getUserRFMData = (): UserRFMData[] => {
|
||||
return [
|
||||
{
|
||||
userId: "1",
|
||||
userName: "张三",
|
||||
phone: "13812345678",
|
||||
lastPurchaseDate: "2023-07-15",
|
||||
purchaseFrequency: 12,
|
||||
totalSpent: 15800,
|
||||
rfmScore: {
|
||||
recency: 5,
|
||||
frequency: 5,
|
||||
monetary: 5,
|
||||
totalScore: 15,
|
||||
},
|
||||
segment: "高价值活跃用户",
|
||||
valueEstimation: 3200,
|
||||
},
|
||||
{
|
||||
userId: "2",
|
||||
userName: "李四",
|
||||
phone: "13987654321",
|
||||
lastPurchaseDate: "2023-06-20",
|
||||
purchaseFrequency: 8,
|
||||
totalSpent: 9500,
|
||||
rfmScore: {
|
||||
recency: 4,
|
||||
frequency: 4,
|
||||
monetary: 4,
|
||||
totalScore: 12,
|
||||
},
|
||||
segment: "中价值活跃用户",
|
||||
valueEstimation: 1800,
|
||||
},
|
||||
{
|
||||
userId: "3",
|
||||
userName: "王五",
|
||||
phone: "13765432198",
|
||||
lastPurchaseDate: "2023-04-10",
|
||||
purchaseFrequency: 3,
|
||||
totalSpent: 3200,
|
||||
rfmScore: {
|
||||
recency: 2,
|
||||
frequency: 2,
|
||||
monetary: 3,
|
||||
totalScore: 7,
|
||||
},
|
||||
segment: "低价值流失风险用户",
|
||||
valueEstimation: 650,
|
||||
},
|
||||
{
|
||||
userId: "4",
|
||||
userName: "赵六",
|
||||
phone: "13654321987",
|
||||
lastPurchaseDate: "2023-07-18",
|
||||
purchaseFrequency: 15,
|
||||
totalSpent: 25000,
|
||||
rfmScore: {
|
||||
recency: 5,
|
||||
frequency: 5,
|
||||
monetary: 5,
|
||||
totalScore: 15,
|
||||
},
|
||||
segment: "高价值活跃用户",
|
||||
valueEstimation: 4500,
|
||||
},
|
||||
{
|
||||
userId: "5",
|
||||
userName: "钱七",
|
||||
phone: "13543219876",
|
||||
lastPurchaseDate: "2023-03-15",
|
||||
purchaseFrequency: 6,
|
||||
totalSpent: 12000,
|
||||
rfmScore: {
|
||||
recency: 1,
|
||||
frequency: 3,
|
||||
monetary: 4,
|
||||
totalScore: 8,
|
||||
},
|
||||
segment: "中价值沉睡用户",
|
||||
valueEstimation: 1200,
|
||||
},
|
||||
{
|
||||
userId: "6",
|
||||
userName: "孙八",
|
||||
phone: "13432198765",
|
||||
lastPurchaseDate: "2023-07-10",
|
||||
purchaseFrequency: 4,
|
||||
totalSpent: 5800,
|
||||
rfmScore: {
|
||||
recency: 4,
|
||||
frequency: 3,
|
||||
monetary: 3,
|
||||
totalScore: 10,
|
||||
},
|
||||
segment: "中价值活跃用户",
|
||||
valueEstimation: 950,
|
||||
},
|
||||
{
|
||||
userId: "7",
|
||||
userName: "周九",
|
||||
phone: "13321987654",
|
||||
lastPurchaseDate: "2023-05-25",
|
||||
purchaseFrequency: 2,
|
||||
totalSpent: 2500,
|
||||
rfmScore: {
|
||||
recency: 3,
|
||||
frequency: 2,
|
||||
monetary: 2,
|
||||
totalScore: 7,
|
||||
},
|
||||
segment: "低价值流失风险用户",
|
||||
valueEstimation: 480,
|
||||
},
|
||||
{
|
||||
userId: "8",
|
||||
userName: "吴十",
|
||||
phone: "13219876543",
|
||||
lastPurchaseDate: "2023-07-20",
|
||||
purchaseFrequency: 10,
|
||||
totalSpent: 18000,
|
||||
rfmScore: {
|
||||
recency: 5,
|
||||
frequency: 4,
|
||||
monetary: 5,
|
||||
totalScore: 14,
|
||||
},
|
||||
segment: "高价值活跃用户",
|
||||
valueEstimation: 3600,
|
||||
},
|
||||
{
|
||||
userId: "9",
|
||||
userName: "郑十一",
|
||||
phone: "13198765432",
|
||||
lastPurchaseDate: "2023-02-10",
|
||||
purchaseFrequency: 5,
|
||||
totalSpent: 8000,
|
||||
rfmScore: {
|
||||
recency: 1,
|
||||
frequency: 3,
|
||||
monetary: 3,
|
||||
totalScore: 7,
|
||||
},
|
||||
segment: "中价值沉睡用户",
|
||||
valueEstimation: 850,
|
||||
},
|
||||
{
|
||||
userId: "10",
|
||||
userName: "王十二",
|
||||
phone: "13098765432",
|
||||
lastPurchaseDate: "2023-07-05",
|
||||
purchaseFrequency: 7,
|
||||
totalSpent: 13500,
|
||||
rfmScore: {
|
||||
recency: 4,
|
||||
frequency: 4,
|
||||
monetary: 4,
|
||||
totalScore: 12,
|
||||
},
|
||||
segment: "中价值活跃用户",
|
||||
valueEstimation: 2200,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// 定义分群分布的类型
|
||||
interface SegmentDistribution {
|
||||
segment: UserSegment
|
||||
count: number
|
||||
percentage: number
|
||||
}
|
||||
|
||||
// 分析RFM数据
|
||||
export const analyzeRFMData = (data: UserRFMData[]): RFMAnalysisResult => {
|
||||
const userCount = data.length
|
||||
|
||||
// 计算平均值
|
||||
const averageRecency = data.reduce((sum, user) => sum + user.rfmScore.recency, 0) / userCount
|
||||
const averageFrequency = data.reduce((sum, user) => sum + user.rfmScore.frequency, 0) / userCount
|
||||
const averageMonetary = data.reduce((sum, user) => sum + user.rfmScore.monetary, 0) / userCount
|
||||
|
||||
// 计算总估值和平均估值
|
||||
const totalValueEstimation = data.reduce((sum, user) => sum + user.valueEstimation, 0)
|
||||
const averageValueEstimation = totalValueEstimation / userCount
|
||||
|
||||
// 计算分群分布
|
||||
const segmentCounts: Record<UserSegment, number> = {} as Record<UserSegment, number>
|
||||
data.forEach((user) => {
|
||||
segmentCounts[user.segment] = (segmentCounts[user.segment] || 0) + 1
|
||||
})
|
||||
|
||||
const segmentDistribution: SegmentDistribution[] = Object.entries(segmentCounts)
|
||||
.map(([segment, count]) => ({
|
||||
segment: segment as UserSegment,
|
||||
count,
|
||||
percentage: (count / userCount) * 100,
|
||||
}))
|
||||
.sort((a, b) => b.count - a.count)
|
||||
|
||||
return {
|
||||
userCount,
|
||||
averageRecency,
|
||||
averageFrequency,
|
||||
averageMonetary,
|
||||
segmentDistribution,
|
||||
totalValueEstimation,
|
||||
averageValueEstimation,
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤用户数据
|
||||
export const filterUserData = (data: UserRFMData[], options: UserFilterOptions): UserRFMData[] => {
|
||||
return data.filter((user) => {
|
||||
// 分群过滤
|
||||
if (options.segments.length > 0 && !options.segments.includes(user.segment)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 日期范围过滤
|
||||
if (options.dateRange.start && options.dateRange.end) {
|
||||
const userDate = new Date(user.lastPurchaseDate)
|
||||
const startDate = new Date(options.dateRange.start)
|
||||
const endDate = new Date(options.dateRange.end)
|
||||
if (userDate < startDate || userDate > endDate) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 价值范围过滤
|
||||
if (options.valueRange.min !== undefined && options.valueRange.max !== undefined) {
|
||||
if (user.valueEstimation < options.valueRange.min || user.valueEstimation > options.valueRange.max) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// 比较两个时间段的数据
|
||||
export const compareDataPeriods = (beforeData: UserRFMData[], afterData: UserRFMData[]): DataComparisonResult[] => {
|
||||
const segments: UserSegment[] = [
|
||||
"高价值活跃用户",
|
||||
"高价值流失风险用户",
|
||||
"高价值沉睡用户",
|
||||
"中价值活跃用户",
|
||||
"中价值流失风险用户",
|
||||
"中价值沉睡用户",
|
||||
"低价值活跃用户",
|
||||
"低价值流失风险用户",
|
||||
"低价值沉睡用户",
|
||||
"新用户",
|
||||
]
|
||||
|
||||
return segments
|
||||
.map((segment) => {
|
||||
const beforeSegment = beforeData.filter((user) => user.segment === segment)
|
||||
const afterSegment = afterData.filter((user) => user.segment === segment)
|
||||
|
||||
const beforeCount = beforeSegment.length
|
||||
const afterCount = afterSegment.length
|
||||
|
||||
const beforeValue = beforeSegment.reduce((sum, user) => sum + user.valueEstimation, 0)
|
||||
const afterValue = afterSegment.reduce((sum, user) => sum + user.valueEstimation, 0)
|
||||
|
||||
const changePercentage =
|
||||
beforeCount === 0 ? (afterCount === 0 ? 0 : 100) : ((afterCount - beforeCount) / beforeCount) * 100
|
||||
|
||||
const valueChangePercentage =
|
||||
beforeValue === 0 ? (afterValue === 0 ? 0 : 100) : ((afterValue - beforeValue) / beforeValue) * 100
|
||||
|
||||
return {
|
||||
segment,
|
||||
beforeCount,
|
||||
afterCount,
|
||||
changePercentage,
|
||||
beforeValue,
|
||||
afterValue,
|
||||
valueChangePercentage,
|
||||
}
|
||||
})
|
||||
.filter((result) => result.beforeCount > 0 || result.afterCount > 0)
|
||||
}
|
||||
240
services/user-portrait-service.ts
Normal file
240
services/user-portrait-service.ts
Normal file
@@ -0,0 +1,240 @@
|
||||
import type { UserPortrait, UserCategory, UserTag, UserSegment } from "@/types/user-portrait"
|
||||
|
||||
// 模拟用户数据
|
||||
export const getUsers = (): UserPortrait[] => {
|
||||
return [
|
||||
{
|
||||
id: "1",
|
||||
name: "张三",
|
||||
nickname: "小张",
|
||||
phone: "13812345678",
|
||||
imei: "123456789012345",
|
||||
idNumber: "310************123",
|
||||
source: "微信",
|
||||
sourceDetail: "朋友圈广告",
|
||||
registerDate: "2023-05-15",
|
||||
lastActive: "2023-07-20",
|
||||
value: "high",
|
||||
spendingCapacity: "高",
|
||||
spendingLevel: "A",
|
||||
spendingAmount: "¥15,000",
|
||||
address: "上海市浦东新区张江高科技园区",
|
||||
tags: ["潜在客户", "对产品感兴趣", "高消费", "科技爱好者"],
|
||||
status: "active",
|
||||
entryPoint: "首页Banner",
|
||||
behavior: ["浏览产品", "加入购物车", "咨询客服", "分享商品"],
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "李四",
|
||||
nickname: "阿四",
|
||||
phone: "13987654321",
|
||||
imei: "987654321098765",
|
||||
idNumber: "440************456",
|
||||
source: "抖音",
|
||||
sourceDetail: "短视频推广",
|
||||
registerDate: "2023-04-10",
|
||||
lastActive: "2023-07-18",
|
||||
value: "medium",
|
||||
spendingCapacity: "中",
|
||||
spendingLevel: "B",
|
||||
spendingAmount: "¥8,500",
|
||||
address: "广州市天河区珠江新城",
|
||||
tags: ["新用户", "已咨询", "中等消费", "时尚爱好者"],
|
||||
status: "active",
|
||||
entryPoint: "推荐页",
|
||||
behavior: ["浏览产品", "收藏商品", "比较价格"],
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "王五",
|
||||
nickname: "老王",
|
||||
phone: "13765432198",
|
||||
imei: "456789012345678",
|
||||
idNumber: "110************789",
|
||||
source: "小红书",
|
||||
sourceDetail: "博主推荐",
|
||||
registerDate: "2023-03-05",
|
||||
lastActive: "2023-05-25",
|
||||
value: "low",
|
||||
spendingCapacity: "低",
|
||||
spendingLevel: "C",
|
||||
spendingAmount: "¥3,200",
|
||||
address: "北京市朝阳区三里屯",
|
||||
tags: ["已购买", "需要跟进", "低消费", "美妆爱好者"],
|
||||
status: "inactive",
|
||||
entryPoint: "搜索结果",
|
||||
behavior: ["浏览产品", "查看评价", "放弃购买"],
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "赵六",
|
||||
nickname: "六子",
|
||||
phone: "13654321987",
|
||||
imei: "789012345678901",
|
||||
idNumber: "510************012",
|
||||
source: "官网",
|
||||
sourceDetail: "搜索引擎",
|
||||
registerDate: "2023-02-20",
|
||||
lastActive: "2023-07-19",
|
||||
value: "high",
|
||||
spendingCapacity: "高",
|
||||
spendingLevel: "A+",
|
||||
spendingAmount: "¥25,000",
|
||||
address: "成都市高新区天府大道",
|
||||
tags: ["VIP客户", "高频购买", "高消费", "数码爱好者"],
|
||||
status: "active",
|
||||
entryPoint: "直接访问",
|
||||
behavior: ["浏览产品", "购买商品", "评价商品", "推荐给朋友"],
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
name: "钱七",
|
||||
nickname: "钱多多",
|
||||
phone: "13543219876",
|
||||
imei: "890123456789012",
|
||||
idNumber: "330************345",
|
||||
source: "微信",
|
||||
sourceDetail: "公众号推文",
|
||||
registerDate: "2023-01-15",
|
||||
lastActive: "2023-04-10",
|
||||
value: "medium",
|
||||
spendingCapacity: "中",
|
||||
spendingLevel: "B+",
|
||||
spendingAmount: "¥12,000",
|
||||
address: "杭州市西湖区文三路",
|
||||
tags: ["已流失", "需要挽回", "中等消费", "旅游爱好者"],
|
||||
status: "lost",
|
||||
entryPoint: "活动页面",
|
||||
behavior: ["浏览产品", "加入购物车", "放弃购买"],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// 获取用户分类
|
||||
export const getUserCategories = (): UserCategory[] => {
|
||||
return [
|
||||
{
|
||||
id: "1",
|
||||
name: "高价值用户",
|
||||
description: "消费能力强,购买频率高的用户",
|
||||
count: 32456,
|
||||
criteria: [{ field: "value", operator: "equals", value: "high" }],
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "活跃用户",
|
||||
description: "近30天内有活动的用户",
|
||||
count: 78452,
|
||||
criteria: [{ field: "status", operator: "equals", value: "active" }],
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "流失风险用户",
|
||||
description: "30-90天未活动的用户",
|
||||
count: 15689,
|
||||
criteria: [{ field: "status", operator: "equals", value: "inactive" }],
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "已流失用户",
|
||||
description: "90天以上未活动的用户",
|
||||
count: 8765,
|
||||
criteria: [{ field: "status", operator: "equals", value: "lost" }],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// 获取用户标签
|
||||
export const getUserTags = (): UserTag[] => {
|
||||
return [
|
||||
{
|
||||
id: "1",
|
||||
name: "高消费",
|
||||
category: "消费能力",
|
||||
count: 32456,
|
||||
description: "月均消费金额超过10000元的用户",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "科技爱好者",
|
||||
category: "兴趣爱好",
|
||||
count: 45678,
|
||||
description: "对科技产品有浓厚兴趣的用户",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "时尚爱好者",
|
||||
category: "兴趣爱好",
|
||||
count: 38765,
|
||||
description: "对时尚产品有浓厚兴趣的用户",
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "美妆爱好者",
|
||||
category: "兴趣爱好",
|
||||
count: 42345,
|
||||
description: "对美妆产品有浓厚兴趣的用户",
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
name: "数码爱好者",
|
||||
category: "兴趣爱好",
|
||||
count: 36789,
|
||||
description: "对数码产品有浓厚兴趣的用户",
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
name: "旅游爱好者",
|
||||
category: "兴趣爱好",
|
||||
count: 28976,
|
||||
description: "对旅游产品有浓厚兴趣的用户",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// 获取用户分群
|
||||
export const getUserSegments = (): UserSegment[] => {
|
||||
return [
|
||||
{
|
||||
id: "1",
|
||||
name: "高价值潜在客户",
|
||||
description: "高消费能力但尚未购买的用户",
|
||||
count: 12456,
|
||||
criteria: [
|
||||
{ field: "value", operator: "equals", value: "high" },
|
||||
{ field: "tags", operator: "contains", value: "潜在客户" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "高频购买VIP",
|
||||
description: "高频次购买的VIP用户",
|
||||
count: 8765,
|
||||
criteria: [
|
||||
{ field: "value", operator: "equals", value: "high" },
|
||||
{ field: "tags", operator: "contains", value: "VIP客户" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "流失风险高价值",
|
||||
description: "有流失风险的高价值用户",
|
||||
count: 3456,
|
||||
criteria: [
|
||||
{ field: "value", operator: "equals", value: "high" },
|
||||
{ field: "status", operator: "equals", value: "inactive" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "待挽回用户",
|
||||
description: "已流失但有挽回价值的用户",
|
||||
count: 5678,
|
||||
criteria: [
|
||||
{ field: "status", operator: "equals", value: "lost" },
|
||||
{ field: "tags", operator: "contains", value: "需要挽回" },
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user