Files
cunkebao_v3/SuperAdmin/lib/traffic-pool-api.ts
Manus AI 2753265edb sync(本地→GitHub): 2026-06-03 21:17 全量单向同步
## GitHub 同步说明

| 项 | 内容 |
|:---|:---|
| 仓库 | https://github.com/fnvtk/cunkebao_v3 |
| 分支 | develop |
| 方向 | 本地 → GitHub(单向 push) |
| 是否拉取远程 | 否(未 fetch / pull / merge) |
| 是否改本地源文件 | 否(仅 git add/commit,未编辑业务/文档正文) |
| 远程基准 | origin/develop @ b5b40e8b |
| 本批工作区变更 | 999 files, +47622 / -4007 lines |

## 一并推送的本地已有 commit(此前未 push)

1. 3aa7ecf8 deploy: 官网 ckb.quwanzhi.com 宝塔上线 DNS+SSL+验收文档
2. 16a70045 fix(官网): 桌面导航仅保留一个留资 CTA 并重新上线
3. dbe7b7aa fix(官网): Hero 改为主 CTA 按钮并去掉重复 1200+ 数据条
4. ac82726e chore(部署): 五端上线验收46项并统一留资与缓存版本

## 本 commit 目录统计

| 目录 | 文件数 | 说明 |
|:---|---:|:---|
| 开发文档/ | 639 | 10、项目管理、未完成项跨仓汇总、五端需求/部署/接口 |
| Server/ | 129 | 后端 API、迁移、部署相关 |
| Touchkebao/ | 80 | PC 工作台、获客、算力 |
| 官网/ | 61 | ckb.quwanzhi.com 静态站与 CTA 迭代 |
| Cunkebao/ | 49 | 移动端场景/设置/流量池 |
| SuperAdmin/ | 26 | 超管后台 |
| .cursor/ | 11 | 规则与 Skill |
| 其他 | 14 | docker-compose、.obsidian、.codegraph 等 |

## 重点文件(本地为准)

- 开发文档/1、需求/修改/未完成项与跨仓验收汇总.md
- 开发文档/10、项目管理/账号密码与登录环境一览.md
- 开发文档/10、项目管理/(新建项目管理目录与截图)
- 官网/ 全站 + 宝塔上线文档
- 五端上线验收 46 项(见 ac82726e)

## 刻意未上传

- node_modules/、.DS_Store、.smart-env/
- .开发文档_nested_git_backup/
- **/__pycache__/

## 验收

- 提交页(含本说明): https://github.com/fnvtk/cunkebao_v3/commit/HEAD
- 分支树: https://github.com/fnvtk/cunkebao_v3/tree/develop

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 21:17:41 +08:00

193 lines
4.6 KiB
TypeScript

import { apiRequest, ApiResponse } from './api-utils';
/**
* 客户数据接口
*/
export interface Customer {
id: number;
companyId: number;
avatar: string;
nickname: string;
wechatId: string;
gender: string;
region: string;
tags: string[];
source: string;
companyName: string;
ownerWechatId: string;
ownerWechatNickname: string;
ownerWechatAlias: string;
ownerWechatDisplay: string;
createTime: string;
mobile: number;
}
export interface WechatFilterOption {
companyId: number;
companyName: string;
ownerWechatId: string;
ownerWechatNickname: string;
ownerWechatAlias: string;
ownerWechatDisplay: string;
customerCount: number;
}
/**
* 客户详情接口
*/
export interface CustomerDetail {
source: string;
sources?: Array<{ sourceName: string; createTime: string }>;
journey?: Array<{ type: string; label: string; time: string }>;
journeySlicedByOwner?: boolean;
addTime: string | null;
projectName: string;
avatar: string;
nickname: string;
region: string;
gender: string;
tags: string[];
wechatId?: string;
ownerWechatId?: string;
ownerWechatDisplay?: string;
ownerWechat?: { wechatId: string; nickname: string; alias: string; avatar: string };
rfmScore?: number;
rfmType?: number;
rfmTypeLabel?: string;
totalOrderAmount?: number;
totalOrderCount?: number;
phone?: string;
realName?: string;
}
/**
* 分页响应数据类型
*/
export interface PaginatedResponse<T> {
list: T[];
total: number;
page: number;
limit: number;
}
export interface TrafficPoolListParams {
page?: number;
limit?: number;
keyword?: string;
companyId?: number;
ownerWechatId?: string;
gender?: string;
}
/**
* 获取客户池列表
*/
export async function getTrafficPoolList(
params: TrafficPoolListParams = {}
): Promise<ApiResponse<PaginatedResponse<Customer>>> {
const {
page = 1,
limit = 50,
keyword = '',
companyId = 0,
ownerWechatId = '',
gender = '',
} = params;
const urlParams = new URLSearchParams();
urlParams.append('page', page.toString());
urlParams.append('limit', limit.toString());
if (keyword) urlParams.append('keyword', keyword);
if (companyId > 0) urlParams.append('companyId', companyId.toString());
if (ownerWechatId) urlParams.append('ownerWechatId', ownerWechatId);
if (gender && gender !== 'all') urlParams.append('gender', gender);
urlParams.append('lite', '1');
return apiRequest(`/trafficPool/list?${urlParams.toString()}`);
}
export interface TrafficPoolSummary {
scope: "global" | "company";
companyId?: number;
totalCustomers: number;
boundCustomers: number;
unboundCustomers: number;
pageSize: number;
totalPages: number;
topNormalProjects?: Array<{
companyId: number;
companyName: string;
customerCount: number;
}>;
}
export async function getTrafficPoolSummary(
companyId: number = 0,
limit: number = 50
): Promise<ApiResponse<TrafficPoolSummary>> {
const q = new URLSearchParams();
if (companyId > 0) q.append("companyId", String(companyId));
q.append("limit", String(limit));
return apiRequest(`/trafficPool/summary?${q.toString()}`);
}
export interface SyncDetectResult {
companyId: number;
wechatAccountBackfill: {
companyId: number;
deviceWechats: number;
backfilled: number;
columnPresent: boolean;
};
ownerWechatRepair: {
companyId: number;
scanned: number;
matched: number;
updated: number;
pending: number;
ownerFilledTotal: number;
deviceWechatCount: number;
ownerMatchedToDevice: number;
pendingExamples: Array<{ id: number; identifier: string; phone: string }>;
};
reportFile: string;
reportCsv: string;
}
export async function runWechatSyncDetect(
companyId: number,
limit: number = 5000
): Promise<ApiResponse<SyncDetectResult>> {
return apiRequest("/wechat/sync-detect", "POST", { companyId, limit });
}
/**
* 归属微信号筛选项(按项目 + 微信号 + 客资数)
*/
export async function getTrafficPoolWechatFilters(
companyId: number = 0
): Promise<ApiResponse<{ list: WechatFilterOption[] }>> {
const q = companyId > 0 ? `?companyId=${companyId}` : '';
return apiRequest(`/trafficPool/wechat-filters${q}`);
}
/**
* 批量迁移客资到目标项目
*/
export async function migrateTrafficPoolCustomers(
poolCompanyIds: number[],
targetProjectId: number
): Promise<ApiResponse<{ migrated: number }>> {
return apiRequest('/trafficPool/migrate', 'POST', {
poolCompanyIds,
targetProjectId,
});
}
/**
* 获取客户详情
*/
export async function getCustomerDetail(id: string): Promise<ApiResponse<CustomerDetail>> {
return apiRequest(`/trafficPool/detail?id=${id}`);
}