FEAT => 本次更新项目为:
This commit is contained in:
@@ -1,5 +1,9 @@
|
|||||||
import request from "@/api/request";
|
import request from "@/api/request";
|
||||||
import type { TrafficPoolUserDetail, UserJourneyResponse } from "./data";
|
import type {
|
||||||
|
TrafficPoolUserDetail,
|
||||||
|
UserJourneyResponse,
|
||||||
|
UserTagsResponse,
|
||||||
|
} from "./data";
|
||||||
|
|
||||||
export function getTrafficPoolDetail(
|
export function getTrafficPoolDetail(
|
||||||
wechatId: string
|
wechatId: string
|
||||||
@@ -17,8 +21,8 @@ export function getUserJourney(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户标签
|
// 获取用户标签
|
||||||
export function getUserTags(userId: string): Promise<any> {
|
export function getUserTags(userId: string): Promise<UserTagsResponse> {
|
||||||
return request("/v1/user/tags", { userId }, "GET");
|
return request("/v1/traffic/pool/getUserTags", { userId }, "GET");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加用户标签
|
// 添加用户标签
|
||||||
|
|||||||
@@ -62,6 +62,20 @@ export interface UserJourneyResponse {
|
|||||||
total: number;
|
total: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 用户标签类型
|
||||||
|
export interface UserTagItem {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
color?: string;
|
||||||
|
type?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户标签响应类型
|
||||||
|
export interface UserTagsResponse {
|
||||||
|
wechat: UserTagItem[];
|
||||||
|
siteLabels: UserTagItem[];
|
||||||
|
}
|
||||||
|
|
||||||
// 扩展的用户详情类型 - 用于前端展示
|
// 扩展的用户详情类型 - 用于前端展示
|
||||||
export interface ExtendedUserDetail extends TrafficPoolUserDetail {
|
export interface ExtendedUserDetail extends TrafficPoolUserDetail {
|
||||||
// 前端计算或模拟的数据
|
// 前端计算或模拟的数据
|
||||||
|
|||||||
@@ -24,12 +24,14 @@ import {
|
|||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import Layout from "@/components/Layout/Layout";
|
import Layout from "@/components/Layout/Layout";
|
||||||
import NavCommon from "@/components/NavCommon";
|
import NavCommon from "@/components/NavCommon";
|
||||||
import { getTrafficPoolDetail, getUserJourney } from "./api";
|
import { getTrafficPoolDetail, getUserJourney, getUserTags } from "./api";
|
||||||
import type {
|
import type {
|
||||||
TrafficPoolUserDetail,
|
TrafficPoolUserDetail,
|
||||||
ExtendedUserDetail,
|
ExtendedUserDetail,
|
||||||
InteractionRecord,
|
InteractionRecord,
|
||||||
UserJourneyRecord,
|
UserJourneyRecord,
|
||||||
|
UserTagsResponse,
|
||||||
|
UserTagItem,
|
||||||
} from "./data";
|
} from "./data";
|
||||||
import styles from "./index.module.scss";
|
import styles from "./index.module.scss";
|
||||||
|
|
||||||
@@ -47,6 +49,10 @@ const TrafficPoolDetail: React.FC = () => {
|
|||||||
const [journeyTotal, setJourneyTotal] = useState(0);
|
const [journeyTotal, setJourneyTotal] = useState(0);
|
||||||
const pageSize = 10;
|
const pageSize = 10;
|
||||||
|
|
||||||
|
// 用户标签相关状态
|
||||||
|
const [tagsLoading, setTagsLoading] = useState(false);
|
||||||
|
const [userTagsList, setUserTagsList] = useState<UserTagItem[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!wxid) return;
|
if (!wxid) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -117,12 +123,30 @@ const TrafficPoolDetail: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 获取用户标签数据
|
||||||
|
const fetchUserTags = async () => {
|
||||||
|
if (!userId) return;
|
||||||
|
|
||||||
|
setTagsLoading(true);
|
||||||
|
try {
|
||||||
|
const response: UserTagsResponse = await getUserTags(userId);
|
||||||
|
setUserTagsList(response.siteLabels || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取用户标签失败:", error);
|
||||||
|
} finally {
|
||||||
|
setTagsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 标签切换处理
|
// 标签切换处理
|
||||||
const handleTabChange = (tab: string) => {
|
const handleTabChange = (tab: string) => {
|
||||||
setActiveTab(tab);
|
setActiveTab(tab);
|
||||||
if (tab === "journey" && journeyList.length === 0) {
|
if (tab === "journey" && journeyList.length === 0) {
|
||||||
fetchUserJourney(1);
|
fetchUserJourney(1);
|
||||||
}
|
}
|
||||||
|
if (tab === "tags" && userTagsList.length === 0) {
|
||||||
|
fetchUserTags();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
@@ -245,6 +269,12 @@ const TrafficPoolDetail: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 获取标签颜色
|
||||||
|
const getTagColor = (index: number): string => {
|
||||||
|
const colors = ["primary", "success", "warning", "danger", "default"];
|
||||||
|
return colors[index % colors.length];
|
||||||
|
};
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return (
|
return (
|
||||||
<Layout header={<NavCommon title="用户详情" />} loading={loading}>
|
<Layout header={<NavCommon title="用户详情" />} loading={loading}>
|
||||||
@@ -256,73 +286,69 @@ const TrafficPoolDetail: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout loading={loading}>
|
<Layout
|
||||||
<div className={styles.container}>
|
loading={loading}
|
||||||
{/* 头部 */}
|
header={
|
||||||
<div className={styles.header}>
|
<>
|
||||||
<div className={styles.title}>用户详情</div>
|
<NavCommon title="用户详情" />
|
||||||
<Button
|
{/* 用户基本信息 */}
|
||||||
className={styles.closeBtn}
|
<Card className={styles.userCard}>
|
||||||
onClick={handleClose}
|
<div className={styles.userInfo}>
|
||||||
fill="none"
|
<Avatar
|
||||||
size="small"
|
src={user.userInfo.avatar}
|
||||||
>
|
className={styles.avatar}
|
||||||
<CloseOutlined />
|
fallback={<UserOutlined />}
|
||||||
</Button>
|
/>
|
||||||
</div>
|
<div className={styles.userDetails}>
|
||||||
|
<div className={styles.nickname}>{user.userInfo.nickname}</div>
|
||||||
{/* 用户基本信息 */}
|
<div className={styles.wechatId}>{user.userInfo.wechatId}</div>
|
||||||
<Card className={styles.userCard}>
|
<div className={styles.tags}>
|
||||||
<div className={styles.userInfo}>
|
<Tag
|
||||||
<Avatar
|
color="warning"
|
||||||
src={user.userInfo.avatar}
|
fill="outline"
|
||||||
className={styles.avatar}
|
className={styles.userTag}
|
||||||
fallback={<UserOutlined />}
|
>
|
||||||
/>
|
<CrownOutlined />
|
||||||
<div className={styles.userDetails}>
|
重要价值客户
|
||||||
<div className={styles.nickname}>{user.userInfo.nickname}</div>
|
</Tag>
|
||||||
<div className={styles.wechatId}>{user.userInfo.wechatId}</div>
|
<Tag color="danger" fill="outline" className={styles.userTag}>
|
||||||
<div className={styles.tags}>
|
优先添加
|
||||||
<Tag color="warning" fill="outline" className={styles.userTag}>
|
</Tag>
|
||||||
<CrownOutlined />
|
</div>
|
||||||
重要价值客户
|
|
||||||
</Tag>
|
|
||||||
<Tag color="danger" fill="outline" className={styles.userTag}>
|
|
||||||
优先添加
|
|
||||||
</Tag>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</Card>
|
||||||
|
{/* 导航标签 */}
|
||||||
|
<div className={styles.tabNav}>
|
||||||
|
<div
|
||||||
|
className={`${styles.tabItem} ${
|
||||||
|
activeTab === "basic" ? styles.active : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => handleTabChange("basic")}
|
||||||
|
>
|
||||||
|
基本信息
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`${styles.tabItem} ${
|
||||||
|
activeTab === "journey" ? styles.active : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => handleTabChange("journey")}
|
||||||
|
>
|
||||||
|
用户旅程
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`${styles.tabItem} ${
|
||||||
|
activeTab === "tags" ? styles.active : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => handleTabChange("tags")}
|
||||||
|
>
|
||||||
|
用户标签
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</>
|
||||||
|
}
|
||||||
{/* 导航标签 */}
|
>
|
||||||
<div className={styles.tabNav}>
|
<div className={styles.container}>
|
||||||
<div
|
|
||||||
className={`${styles.tabItem} ${
|
|
||||||
activeTab === "basic" ? styles.active : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => handleTabChange("basic")}
|
|
||||||
>
|
|
||||||
基本信息
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`${styles.tabItem} ${
|
|
||||||
activeTab === "journey" ? styles.active : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => handleTabChange("journey")}
|
|
||||||
>
|
|
||||||
用户旅程
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`${styles.tabItem} ${
|
|
||||||
activeTab === "tags" ? styles.active : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => handleTabChange("tags")}
|
|
||||||
>
|
|
||||||
用户标签
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 内容区域 */}
|
{/* 内容区域 */}
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
{activeTab === "basic" && (
|
{activeTab === "basic" && (
|
||||||
@@ -593,20 +619,12 @@ const TrafficPoolDetail: React.FC = () => {
|
|||||||
<div className={styles.tabContent}>
|
<div className={styles.tabContent}>
|
||||||
{/* 用户标签 */}
|
{/* 用户标签 */}
|
||||||
<Card title="用户标签" className={styles.infoCard}>
|
<Card title="用户标签" className={styles.infoCard}>
|
||||||
{user.userTags && user.userTags.length > 0 ? (
|
{tagsLoading && userTagsList.length === 0 ? (
|
||||||
<div className={styles.tagsSection}>
|
<div className={styles.loadingContainer}>
|
||||||
{user.userTags.map(tag => (
|
<SpinLoading color="primary" style={{ fontSize: 24 }} />
|
||||||
<Tag
|
<div className={styles.loadingText}>加载中...</div>
|
||||||
key={tag.id}
|
|
||||||
color={tag.color}
|
|
||||||
fill="outline"
|
|
||||||
className={styles.tagItem}
|
|
||||||
>
|
|
||||||
{tag.name}
|
|
||||||
</Tag>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : userTagsList.length === 0 ? (
|
||||||
<div className={styles.emptyState}>
|
<div className={styles.emptyState}>
|
||||||
<div className={styles.emptyIcon}>
|
<div className={styles.emptyIcon}>
|
||||||
<TagOutlined style={{ fontSize: 48, color: "#ccc" }} />
|
<TagOutlined style={{ fontSize: 48, color: "#ccc" }} />
|
||||||
@@ -614,6 +632,19 @@ const TrafficPoolDetail: React.FC = () => {
|
|||||||
<div className={styles.emptyText}>暂无用户标签</div>
|
<div className={styles.emptyText}>暂无用户标签</div>
|
||||||
<div className={styles.emptyDesc}>该用户还没有任何标签</div>
|
<div className={styles.emptyDesc}>该用户还没有任何标签</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className={styles.tagsSection}>
|
||||||
|
{userTagsList.map((tag, index) => (
|
||||||
|
<Tag
|
||||||
|
key={tag.id}
|
||||||
|
color={getTagColor(index)}
|
||||||
|
fill="outline"
|
||||||
|
className={styles.tagItem}
|
||||||
|
>
|
||||||
|
{tag.name}
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
@@ -661,14 +692,9 @@ const TrafficPoolDetail: React.FC = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* 添加新标签按钮 */}
|
{/* 添加新标签按钮 */}
|
||||||
<Button
|
<Button block color="primary" className={styles.addTagBtn}>
|
||||||
block
|
|
||||||
color="primary"
|
|
||||||
size="large"
|
|
||||||
className={styles.addTagBtn}
|
|
||||||
>
|
|
||||||
<TagOutlined />
|
<TagOutlined />
|
||||||
添加新标签
|
添加新标签
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user