feat(traffic-pool): 重构流量池详情页数据结构和样式
- 更新API接口路径和数据结构以匹配新后端实现 - 重构用户详情类型定义和数据处理逻辑 - 优化页面样式和布局 - 实现基于RMM评分的动态标签显示 - 完善统计数据的展示逻辑
This commit is contained in:
@@ -2,7 +2,7 @@ import request from "@/api/request";
|
||||
import type { UserTagsResponse } from "./data";
|
||||
|
||||
export function getTrafficPoolDetail(wechatId: string) {
|
||||
return request("/v1/wechats/getWechatInfo", { wechatId }, "GET");
|
||||
return request("/v1/traffic/pool/getUserInfo", { wechatId }, "GET");
|
||||
}
|
||||
|
||||
// 获取用户旅程记录
|
||||
|
||||
@@ -1,39 +1,64 @@
|
||||
// 设备信息类型
|
||||
export interface DeviceInfo {
|
||||
id: number;
|
||||
memo: string;
|
||||
imei: string;
|
||||
brand: string;
|
||||
alive: number;
|
||||
address: string;
|
||||
}
|
||||
|
||||
// 来源信息类型
|
||||
export interface SourceInfo {
|
||||
nickname: string;
|
||||
avatar: string;
|
||||
gender: number;
|
||||
phone: string;
|
||||
wechatId: string;
|
||||
alias: string;
|
||||
createTime: string;
|
||||
friendId: number;
|
||||
wechatAccountId: number;
|
||||
lastMsgTime: string;
|
||||
device: DeviceInfo;
|
||||
}
|
||||
|
||||
// 统计总计类型
|
||||
export interface TotalStats {
|
||||
msg: number;
|
||||
money: number;
|
||||
isFriend: boolean;
|
||||
percentage: string;
|
||||
}
|
||||
|
||||
// RMM评分类型
|
||||
export interface RmmScore {
|
||||
r: number;
|
||||
f: number;
|
||||
m: number;
|
||||
}
|
||||
|
||||
// 用户详情类型
|
||||
export interface TrafficPoolUserDetail {
|
||||
id: number;
|
||||
identifier: string;
|
||||
wechatId: string;
|
||||
nickname: string;
|
||||
avatar: string;
|
||||
wechatId: string;
|
||||
status: number | string;
|
||||
addTime: string;
|
||||
lastInteraction: string;
|
||||
deviceName?: string;
|
||||
wechatAccountName?: string;
|
||||
customerServiceName?: string;
|
||||
poolNames?: string[];
|
||||
rfmScore?: {
|
||||
recency: number;
|
||||
frequency: number;
|
||||
monetary: number;
|
||||
segment?: string;
|
||||
};
|
||||
totalSpent?: number;
|
||||
interactionCount?: number;
|
||||
conversionRate?: number;
|
||||
tags?: string[];
|
||||
packages?: string[];
|
||||
interactions?: Array<{
|
||||
id: string;
|
||||
type: string;
|
||||
content: string;
|
||||
timestamp: string;
|
||||
value?: number;
|
||||
}>;
|
||||
gender: number;
|
||||
phone: string;
|
||||
alias: string;
|
||||
lastMsgTime: string;
|
||||
source: SourceInfo[];
|
||||
packages: any[];
|
||||
total: TotalStats;
|
||||
rmm: RmmScore;
|
||||
}
|
||||
|
||||
// 扩展的用户详情类型
|
||||
export interface ExtendedUserDetail extends TrafficPoolUserDetail {
|
||||
userInfo: {
|
||||
// 保留原有的扩展字段用于向后兼容
|
||||
userInfo?: {
|
||||
nickname: string;
|
||||
avatar: string;
|
||||
wechatId: string;
|
||||
@@ -44,23 +69,23 @@ export interface ExtendedUserDetail extends TrafficPoolUserDetail {
|
||||
unknowFriend: number;
|
||||
};
|
||||
};
|
||||
rfmScore: {
|
||||
rfmScore?: {
|
||||
recency: number;
|
||||
frequency: number;
|
||||
monetary: number;
|
||||
totalScore: number;
|
||||
};
|
||||
trafficPools: {
|
||||
trafficPools?: {
|
||||
currentPool: string;
|
||||
availablePools: string[];
|
||||
};
|
||||
userTags: Array<{
|
||||
userTags?: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
color: string;
|
||||
type: string;
|
||||
}>;
|
||||
valueTags: Array<{
|
||||
valueTags?: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
color: string;
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
.container {
|
||||
padding: 0;
|
||||
background: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
// 头部样式
|
||||
.header {
|
||||
display: flex;
|
||||
@@ -123,7 +117,7 @@
|
||||
|
||||
// 内容区域
|
||||
.content {
|
||||
padding: 16px;
|
||||
padding: 10px 10px 10px 16px;
|
||||
}
|
||||
|
||||
.tabContent {
|
||||
|
||||
@@ -22,6 +22,21 @@ import type {
|
||||
} from "./data";
|
||||
import styles from "./index.module.scss";
|
||||
|
||||
// RMM评分辅助函数
|
||||
const getRmmValueLevel = (totalScore: number): string => {
|
||||
if (totalScore >= 12) return "高价值客户";
|
||||
if (totalScore >= 8) return "中等价值客户";
|
||||
if (totalScore >= 4) return "低价值客户";
|
||||
return "潜在客户";
|
||||
};
|
||||
|
||||
const getRmmColor = (totalScore: number): string => {
|
||||
if (totalScore >= 12) return "danger";
|
||||
if (totalScore >= 8) return "warning";
|
||||
if (totalScore >= 4) return "primary";
|
||||
return "default";
|
||||
};
|
||||
|
||||
const TrafficPoolDetail: React.FC = () => {
|
||||
const { wxid, userId } = useParams();
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -45,43 +60,89 @@ const TrafficPoolDetail: React.FC = () => {
|
||||
setLoading(true);
|
||||
getTrafficPoolDetail(wxid as string)
|
||||
.then(res => {
|
||||
// 将API数据转换为扩展的用户详情数据
|
||||
// 直接使用API返回的数据结构
|
||||
const extendedUser: ExtendedUserDetail = {
|
||||
...res,
|
||||
// 添加userInfo属性
|
||||
userInfo: res.userInfo,
|
||||
// 模拟RFM评分数据
|
||||
// 根据新数据结构构建userInfo
|
||||
userInfo: {
|
||||
nickname: res.nickname,
|
||||
avatar: res.avatar,
|
||||
wechatId: res.wechatId,
|
||||
friendShip: {
|
||||
totalFriend: res.source?.length || 0,
|
||||
maleFriend: res.source?.filter(s => s.gender === 1).length || 0,
|
||||
femaleFriend: res.source?.filter(s => s.gender === 2).length || 0,
|
||||
unknowFriend: res.source?.filter(s => s.gender === 0).length || 0,
|
||||
},
|
||||
},
|
||||
// 使用API返回的RMM数据
|
||||
rfmScore: {
|
||||
recency: 5,
|
||||
frequency: 5,
|
||||
monetary: 5,
|
||||
totalScore: 15,
|
||||
recency: res.rmm.r,
|
||||
frequency: res.rmm.f,
|
||||
monetary: res.rmm.m,
|
||||
totalScore: res.rmm.r + res.rmm.f + res.rmm.m,
|
||||
},
|
||||
// 模拟流量池数据
|
||||
// 根据数据推断流量池信息
|
||||
trafficPools: {
|
||||
currentPool: "新用户池",
|
||||
availablePools: ["高价值客户池", "活跃用户池"],
|
||||
currentPool: res.total.isFriend ? "已添加好友池" : "待添加池",
|
||||
availablePools: ["高价值客户池", "活跃用户池", "新用户池"],
|
||||
},
|
||||
// 模拟用户标签数据
|
||||
// 基于数据生成用户标签
|
||||
userTags: [
|
||||
{ id: "1", name: "近期活跃", color: "success", type: "user" },
|
||||
{ id: "2", name: "高频互动", color: "primary", type: "user" },
|
||||
{ id: "3", name: "高消费", color: "warning", type: "user" },
|
||||
{ id: "4", name: "老客户", color: "danger", type: "user" },
|
||||
...(res.total.isFriend
|
||||
? [
|
||||
{
|
||||
id: "friend",
|
||||
name: "已添加好友",
|
||||
color: "success",
|
||||
type: "status",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(res.total.money > 0
|
||||
? [
|
||||
{
|
||||
id: "paid",
|
||||
name: "付费用户",
|
||||
color: "warning",
|
||||
type: "value",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(res.total.msg > 10
|
||||
? [
|
||||
{
|
||||
id: "active",
|
||||
name: "高频互动",
|
||||
color: "primary",
|
||||
type: "behavior",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(res.source?.length > 1
|
||||
? [
|
||||
{
|
||||
id: "multi",
|
||||
name: "多设备用户",
|
||||
color: "danger",
|
||||
type: "device",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
// 模拟价值标签数据
|
||||
// 基于RMM评分生成价值标签
|
||||
valueTags: [
|
||||
{
|
||||
id: "1",
|
||||
name: "重要保持客户",
|
||||
color: "primary",
|
||||
id: "rmm",
|
||||
name: getRmmValueLevel(res.rmm.r + res.rmm.f + res.rmm.m),
|
||||
color: getRmmColor(res.rmm.r + res.rmm.f + res.rmm.m),
|
||||
icon: "crown",
|
||||
rfmScore: 14,
|
||||
valueLevel: "高价值",
|
||||
rfmScore: res.rmm.r + res.rmm.f + res.rmm.m,
|
||||
valueLevel: getRmmValueLevel(res.rmm.r + res.rmm.f + res.rmm.m),
|
||||
},
|
||||
],
|
||||
};
|
||||
console.log(extendedUser);
|
||||
console.log("用户详情数据:", extendedUser);
|
||||
|
||||
setUser(extendedUser);
|
||||
})
|
||||
@@ -257,7 +318,7 @@ const TrafficPoolDetail: React.FC = () => {
|
||||
<Card className={styles.userCard}>
|
||||
<div className={styles.userInfo}>
|
||||
<Avatar
|
||||
src={user.userInfo.avatar}
|
||||
src={user.avatar}
|
||||
className={styles.avatar}
|
||||
fallback={
|
||||
<div className={styles.avatarFallback}>
|
||||
@@ -266,20 +327,29 @@ const TrafficPoolDetail: React.FC = () => {
|
||||
}
|
||||
/>
|
||||
<div className={styles.userDetails}>
|
||||
<div className={styles.nickname}>{user.userInfo.nickname}</div>
|
||||
<div className={styles.wechatId}>{user.userInfo.wechatId}</div>
|
||||
<div className={styles.nickname}>{user.nickname}</div>
|
||||
<div className={styles.wechatId}>{user.wechatId}</div>
|
||||
<div className={styles.tags}>
|
||||
<Tag
|
||||
color="warning"
|
||||
fill="outline"
|
||||
className={styles.userTag}
|
||||
>
|
||||
<CrownOutlined />
|
||||
重要价值客户
|
||||
</Tag>
|
||||
<Tag color="danger" fill="outline" className={styles.userTag}>
|
||||
优先添加
|
||||
</Tag>
|
||||
{user.valueTags?.map(tag => (
|
||||
<Tag
|
||||
key={tag.id}
|
||||
color={tag.color}
|
||||
fill="outline"
|
||||
className={styles.userTag}
|
||||
>
|
||||
<CrownOutlined />
|
||||
{tag.name}
|
||||
</Tag>
|
||||
))}
|
||||
{user.total.isFriend && (
|
||||
<Tag
|
||||
color="success"
|
||||
fill="outline"
|
||||
className={styles.userTag}
|
||||
>
|
||||
已添加好友
|
||||
</Tag>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -322,11 +392,29 @@ const TrafficPoolDetail: React.FC = () => {
|
||||
{/* 关联信息 */}
|
||||
<Card title="关联信息" className={styles.infoCard}>
|
||||
<List>
|
||||
<List.Item extra="设备4">设备</List.Item>
|
||||
<List.Item extra="微信4-1">微信号</List.Item>
|
||||
<List.Item extra="客服1">客服</List.Item>
|
||||
<List.Item extra="2025/07/21">添加时间</List.Item>
|
||||
<List.Item extra="2025/07/25">最近互动</List.Item>
|
||||
<List.Item
|
||||
extra={
|
||||
user.source?.length
|
||||
? `${user.source.length}个设备`
|
||||
: "无设备"
|
||||
}
|
||||
>
|
||||
设备
|
||||
</List.Item>
|
||||
<List.Item extra={user.wechatId || "--"}>微信号</List.Item>
|
||||
<List.Item extra={user.alias || "--"}>别名</List.Item>
|
||||
<List.Item
|
||||
extra={
|
||||
user.source?.[0]?.createTime
|
||||
? formatDateTime(user.source[0].createTime)
|
||||
: "--"
|
||||
}
|
||||
>
|
||||
添加时间
|
||||
</List.Item>
|
||||
<List.Item extra={user.lastMsgTime || "--"}>
|
||||
最近互动
|
||||
</List.Item>
|
||||
</List>
|
||||
</Card>
|
||||
|
||||
@@ -404,7 +492,7 @@ const TrafficPoolDetail: React.FC = () => {
|
||||
className={styles.statValue}
|
||||
style={{ color: "#52c41a" }}
|
||||
>
|
||||
¥9561
|
||||
¥{user.total.money || 0}
|
||||
</div>
|
||||
<div className={styles.statLabel}>总消费</div>
|
||||
</div>
|
||||
@@ -413,7 +501,7 @@ const TrafficPoolDetail: React.FC = () => {
|
||||
className={styles.statValue}
|
||||
style={{ color: "#1677ff" }}
|
||||
>
|
||||
6
|
||||
{user.total.msg || 0}
|
||||
</div>
|
||||
<div className={styles.statLabel}>互动次数</div>
|
||||
</div>
|
||||
@@ -422,13 +510,18 @@ const TrafficPoolDetail: React.FC = () => {
|
||||
className={styles.statValue}
|
||||
style={{ color: "#722ed1" }}
|
||||
>
|
||||
3%
|
||||
{user.total.percentage || "0"}%
|
||||
</div>
|
||||
<div className={styles.statLabel}>转化率</div>
|
||||
</div>
|
||||
<div className={styles.statItem}>
|
||||
<div className={styles.statValue} style={{ color: "#999" }}>
|
||||
未添加
|
||||
<div
|
||||
className={styles.statValue}
|
||||
style={{
|
||||
color: user.total.isFriend ? "#52c41a" : "#999",
|
||||
}}
|
||||
>
|
||||
{user.total.isFriend ? "已添加" : "未添加"}
|
||||
</div>
|
||||
<div className={styles.statLabel}>添加状态</div>
|
||||
</div>
|
||||
@@ -443,7 +536,7 @@ const TrafficPoolDetail: React.FC = () => {
|
||||
className={styles.statValue}
|
||||
style={{ color: "#1677ff" }}
|
||||
>
|
||||
{user.userInfo.friendShip.totalFriend}
|
||||
{user.userInfo?.friendShip.totalFriend || 0}
|
||||
</div>
|
||||
<div className={styles.statLabel}>总好友</div>
|
||||
</div>
|
||||
@@ -452,7 +545,7 @@ const TrafficPoolDetail: React.FC = () => {
|
||||
className={styles.statValue}
|
||||
style={{ color: "#1677ff" }}
|
||||
>
|
||||
{user.userInfo.friendShip.maleFriend}
|
||||
{user.userInfo?.friendShip.maleFriend || 0}
|
||||
</div>
|
||||
<div className={styles.statLabel}>男性好友</div>
|
||||
</div>
|
||||
@@ -461,13 +554,13 @@ const TrafficPoolDetail: React.FC = () => {
|
||||
className={styles.statValue}
|
||||
style={{ color: "#eb2f96" }}
|
||||
>
|
||||
{user.userInfo.friendShip.femaleFriend}
|
||||
{user.userInfo?.friendShip.femaleFriend || 0}
|
||||
</div>
|
||||
<div className={styles.statLabel}>女性好友</div>
|
||||
</div>
|
||||
<div className={styles.statItem}>
|
||||
<div className={styles.statValue} style={{ color: "#999" }}>
|
||||
{user.userInfo.friendShip.unknowFriend}
|
||||
{user.userInfo?.friendShip.unknowFriend || 0}
|
||||
</div>
|
||||
<div className={styles.statLabel}>未知性别</div>
|
||||
</div>
|
||||
|
||||
@@ -209,7 +209,7 @@ const TrafficPoolList: React.FC = () => {
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() =>
|
||||
navigate(
|
||||
`/mine/traffic-pool/detail/${item.sourceId}/${item.id}`,
|
||||
`/mine/traffic-pool/detail/${item.wechatId}/${item.id}`,
|
||||
)
|
||||
}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user