feat(traffic-pool): 重构流量池详情页数据结构和样式

- 更新API接口路径和数据结构以匹配新后端实现
- 重构用户详情类型定义和数据处理逻辑
- 优化页面样式和布局
- 实现基于RMM评分的动态标签显示
- 完善统计数据的展示逻辑
This commit is contained in:
超级老白兔
2025-09-13 15:47:57 +08:00
parent fb362a56af
commit 268afb7209
5 changed files with 203 additions and 91 deletions

View File

@@ -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");
}
// 获取用户旅程记录

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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>

View File

@@ -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}`,
)
}
>