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>
293 lines
7.5 KiB
TypeScript
293 lines
7.5 KiB
TypeScript
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)
|
|
}
|