feat: 项目层级树+合作方绑定KOC资源+客户群全量可选+精简编辑表单,移除复盘报表
- 项目支持父子层级:树形折叠展示、详情面板内联管理总群成员、推送聚合子项目任务、环检测与子项目删除守卫 - 外部KOC合作方绑定 KOC 资源账号(唯一占用、换绑/解绑、资源页一键设为合作方),列表直显联系方式 - 绑定客户群改为全量外部群缓存(含各群主),同步按钮强刷;member_count 缺失以 member_list 兜底 - 任务/项目编辑模式隐藏不可变字段(平台/内容类型/品牌/总群成员),详情面板维护成员时保留 parent_id - 删除复盘报表整栏(导航/页面/task_report_list/导出路由/CSV lib 及测试)
This commit is contained in:
108
lib/mvp-db.ts
108
lib/mvp-db.ts
@@ -375,6 +375,11 @@ export async function ensureSchema(database?: DatabaseClient) {
|
||||
"wecom_external_user_id",
|
||||
"wecom_external_user_id TEXT",
|
||||
);
|
||||
await ensureColumn(
|
||||
"partners",
|
||||
"account_id",
|
||||
"account_id TEXT NOT NULL DEFAULT ''",
|
||||
);
|
||||
await ensureColumn(
|
||||
"users",
|
||||
"wecom_userid",
|
||||
@@ -902,7 +907,17 @@ export async function getDashboardData(ownerUserid?: string) {
|
||||
const [partnersResult, tasksResult, accountsResult, distributionsResult] =
|
||||
await Promise.all([
|
||||
ownerFilter === null
|
||||
? db.prepare("SELECT * FROM partners ORDER BY created_at DESC").all()
|
||||
? db
|
||||
.prepare(
|
||||
`SELECT p.*, a.nickname AS account_nickname,
|
||||
a.public_account_id AS account_public_id,
|
||||
a.current_contact AS account_contact,
|
||||
a.profile_url AS account_profile_url
|
||||
FROM partners p
|
||||
LEFT JOIN accounts a ON a.id = p.account_id
|
||||
ORDER BY p.created_at DESC`,
|
||||
)
|
||||
.all()
|
||||
: emptyResult(),
|
||||
ownerFilter
|
||||
? db
|
||||
@@ -960,97 +975,6 @@ export async function getDashboardData(ownerUserid?: string) {
|
||||
};
|
||||
}
|
||||
|
||||
export type TaskReportRow = {
|
||||
id: string;
|
||||
name: string;
|
||||
task_type: string;
|
||||
brand: string;
|
||||
platform: string;
|
||||
status: string;
|
||||
due_at: string;
|
||||
created_at: string;
|
||||
quantity: number;
|
||||
project_name: string | null;
|
||||
claimed_count: number;
|
||||
published_count: number;
|
||||
completed_count: number;
|
||||
exposure_total: number | null;
|
||||
views_total: number | null;
|
||||
d2_likes: number | null;
|
||||
d2_comments: number | null;
|
||||
d2_collects: number | null;
|
||||
d5_likes: number | null;
|
||||
d5_comments: number | null;
|
||||
d5_collects: number | null;
|
||||
d7_likes: number | null;
|
||||
d7_comments: number | null;
|
||||
d7_collects: number | null;
|
||||
latest_likes: number | null;
|
||||
latest_comments: number | null;
|
||||
latest_collects: number | null;
|
||||
latest_shares: number | null;
|
||||
related_task_id: string | null;
|
||||
related_task_name: string | null;
|
||||
reverse_related_task_id: string | null;
|
||||
reverse_related_task_name: string | null;
|
||||
submitted_count: number;
|
||||
};
|
||||
|
||||
export async function getTaskReportData(db: DatabaseClient, ownerUserid?: string) {
|
||||
// undefined=全量;""=未绑定企微的空范围(不能绑定 '',否则会匹配 owner_userid='' 的任务)
|
||||
if (ownerUserid !== undefined && String(ownerUserid).trim() === "") {
|
||||
return [] as TaskReportRow[];
|
||||
}
|
||||
const ownerFilter = String(ownerUserid ?? "").trim();
|
||||
const base = `SELECT
|
||||
t.id,
|
||||
t.name,
|
||||
t.task_type,
|
||||
t.brand,
|
||||
t.platform,
|
||||
t.status,
|
||||
t.due_at,
|
||||
t.created_at,
|
||||
t.quantity,
|
||||
t.related_task_id,
|
||||
rt.name AS related_task_name,
|
||||
(SELECT rrt.id FROM tasks rrt WHERE rrt.related_task_id = t.id LIMIT 1) AS reverse_related_task_id,
|
||||
(SELECT rrt.name FROM tasks rrt WHERE rrt.related_task_id = t.id LIMIT 1) AS reverse_related_task_name,
|
||||
p.name AS project_name,
|
||||
COUNT(d.id) AS claimed_count,
|
||||
SUM(CASE WHEN TRIM(COALESCE(d.publish_url, '')) != '' THEN 1 ELSE 0 END) AS published_count,
|
||||
SUM(CASE WHEN d.status = 'complete' THEN 1 ELSE 0 END) AS completed_count,
|
||||
SUM(CASE WHEN d.result_submitted_at IS NOT NULL THEN 1 ELSE 0 END) AS submitted_count,
|
||||
SUM(d.exposure) AS exposure_total,
|
||||
SUM(d.views) AS views_total,
|
||||
SUM(d.d2_likes) AS d2_likes,
|
||||
SUM(d.d2_comments) AS d2_comments,
|
||||
SUM(d.d2_collects) AS d2_collects,
|
||||
SUM(d.d5_likes) AS d5_likes,
|
||||
SUM(d.d5_comments) AS d5_comments,
|
||||
SUM(d.d5_collects) AS d5_collects,
|
||||
SUM(d.d7_likes) AS d7_likes,
|
||||
SUM(d.d7_comments) AS d7_comments,
|
||||
SUM(d.d7_collects) AS d7_collects,
|
||||
SUM(d.latest_likes) AS latest_likes,
|
||||
SUM(d.latest_comments) AS latest_comments,
|
||||
SUM(d.latest_collects) AS latest_collects,
|
||||
SUM(d.latest_shares) AS latest_shares
|
||||
FROM tasks t
|
||||
LEFT JOIN distributions d ON d.task_id = t.id
|
||||
LEFT JOIN projects p ON p.id = t.project_id
|
||||
LEFT JOIN tasks rt ON rt.id = t.related_task_id`;
|
||||
const statement = ownerFilter
|
||||
? db
|
||||
.prepare(
|
||||
`${base} WHERE (t.owner_userid = ? OR p.owner_userid = ?) GROUP BY t.id ORDER BY t.created_at DESC`,
|
||||
)
|
||||
.bind(ownerFilter, ownerFilter)
|
||||
: db.prepare(`${base} GROUP BY t.id ORDER BY t.created_at DESC`);
|
||||
const result = await statement.all<TaskReportRow>();
|
||||
return result.results;
|
||||
}
|
||||
|
||||
export function uid(prefix: string) {
|
||||
return `${prefix}-${crypto.randomUUID().slice(0, 8)}`;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ export type ProjectRow = {
|
||||
main_group_member_ids: string;
|
||||
owner_userid: string;
|
||||
main_chat_id: string;
|
||||
parent_id: string;
|
||||
status: string;
|
||||
note: string;
|
||||
created_at: string;
|
||||
@@ -32,6 +33,7 @@ export type ProjectSummary = {
|
||||
mainGroupMemberIds: string[];
|
||||
ownerUserid: string;
|
||||
mainChatId: string;
|
||||
parentProjectId: string;
|
||||
taskCount: number;
|
||||
};
|
||||
|
||||
@@ -49,6 +51,16 @@ export type ProjectTaskRow = {
|
||||
share_token: string | null;
|
||||
owner_userid: string;
|
||||
task_chat_id: string;
|
||||
project_name?: string;
|
||||
};
|
||||
|
||||
export type ProjectSubproject = {
|
||||
id: string;
|
||||
name: string;
|
||||
brand: string;
|
||||
ownerUserid: string;
|
||||
mainChatId: string;
|
||||
taskCount: number;
|
||||
};
|
||||
|
||||
export type ProjectMainChat = {
|
||||
@@ -60,6 +72,8 @@ export type ProjectDetail = {
|
||||
project: ProjectSummary;
|
||||
tasks: ProjectTaskRow[];
|
||||
mainChat: ProjectMainChat;
|
||||
subprojects: ProjectSubproject[];
|
||||
pushTasks: ProjectTaskRow[];
|
||||
};
|
||||
|
||||
export type MainChatEnsureReason =
|
||||
@@ -224,6 +238,59 @@ export async function ensureProjectMainChat(
|
||||
}
|
||||
}
|
||||
|
||||
async function loadProjectParents(
|
||||
db: DatabaseClient,
|
||||
): Promise<Map<string, string>> {
|
||||
const result = await db
|
||||
.prepare("SELECT id, parent_id FROM projects")
|
||||
.all<{ id: string; parent_id: string }>();
|
||||
return new Map(result.results.map((row) => [row.id, text(row.parent_id)]));
|
||||
}
|
||||
|
||||
// candidate 是否位于 ancestor 的子孙链上(沿 candidate 的祖先链向上找 ancestor)
|
||||
function isDescendantProject(
|
||||
parents: Map<string, string>,
|
||||
ancestorId: string,
|
||||
candidateId: string,
|
||||
): boolean {
|
||||
let current = candidateId;
|
||||
const seen = new Set<string>();
|
||||
while (current) {
|
||||
if (current === ancestorId) return true;
|
||||
if (seen.has(current)) return false;
|
||||
seen.add(current);
|
||||
current = parents.get(current) ?? "";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function collectDescendantIds(
|
||||
db: DatabaseClient,
|
||||
projectId: string,
|
||||
): Promise<string[]> {
|
||||
const parents = await loadProjectParents(db);
|
||||
const childrenOf = new Map<string, string[]>();
|
||||
for (const [id, parent] of parents) {
|
||||
if (!parent) continue;
|
||||
const list = childrenOf.get(parent) ?? [];
|
||||
list.push(id);
|
||||
childrenOf.set(parent, list);
|
||||
}
|
||||
const descendants: string[] = [];
|
||||
const queue = [text(projectId)];
|
||||
const visited = new Set(queue);
|
||||
while (queue.length > 0) {
|
||||
const current = queue.shift() as string;
|
||||
for (const child of childrenOf.get(current) ?? []) {
|
||||
if (visited.has(child)) continue;
|
||||
visited.add(child);
|
||||
descendants.push(child);
|
||||
queue.push(child);
|
||||
}
|
||||
}
|
||||
return descendants;
|
||||
}
|
||||
|
||||
export async function listProjects(
|
||||
db: DatabaseClient,
|
||||
ownerUserid?: string,
|
||||
@@ -236,7 +303,7 @@ export async function listProjects(
|
||||
const result = await (ownerFilter
|
||||
? db
|
||||
.prepare(
|
||||
`SELECT p.id, p.name, p.brand, p.main_group_member_ids, p.owner_userid, p.main_chat_id, p.status, p.note, p.created_at,
|
||||
`SELECT p.id, p.name, p.brand, p.main_group_member_ids, p.owner_userid, p.main_chat_id, p.parent_id, p.status, p.note, p.created_at,
|
||||
(SELECT COUNT(*) FROM tasks t WHERE t.project_id = p.id) AS task_count
|
||||
FROM projects p
|
||||
WHERE p.owner_userid = ?
|
||||
@@ -245,7 +312,7 @@ export async function listProjects(
|
||||
.bind(ownerFilter)
|
||||
: db
|
||||
.prepare(
|
||||
`SELECT p.id, p.name, p.brand, p.main_group_member_ids, p.owner_userid, p.main_chat_id, p.status, p.note, p.created_at,
|
||||
`SELECT p.id, p.name, p.brand, p.main_group_member_ids, p.owner_userid, p.main_chat_id, p.parent_id, p.status, p.note, p.created_at,
|
||||
(SELECT COUNT(*) FROM tasks t WHERE t.project_id = p.id) AS task_count
|
||||
FROM projects p
|
||||
ORDER BY p.created_at DESC`,
|
||||
@@ -261,6 +328,7 @@ export async function listProjects(
|
||||
mainGroupMemberIds: parseJsonArray(row.main_group_member_ids),
|
||||
ownerUserid: row.owner_userid ?? "",
|
||||
mainChatId: row.main_chat_id ?? "",
|
||||
parentProjectId: row.parent_id ?? "",
|
||||
taskCount: Number(row.task_count),
|
||||
}));
|
||||
}
|
||||
@@ -272,7 +340,7 @@ export async function getProjectDetail(
|
||||
const id = text(projectId);
|
||||
const project = await db
|
||||
.prepare(
|
||||
`SELECT id, name, brand, main_group_member_ids, owner_userid, main_chat_id, status, note, created_at
|
||||
`SELECT id, name, brand, main_group_member_ids, owner_userid, main_chat_id, parent_id, status, note, created_at
|
||||
FROM projects WHERE id = ?`,
|
||||
)
|
||||
.bind(id)
|
||||
@@ -289,6 +357,33 @@ export async function getProjectDetail(
|
||||
.bind(id)
|
||||
.all<ProjectTaskRow>();
|
||||
|
||||
const subprojects = await db
|
||||
.prepare(
|
||||
`SELECT p.id, p.name, p.brand, p.owner_userid, p.main_chat_id,
|
||||
(SELECT COUNT(*) FROM tasks t WHERE t.project_id = p.id) AS task_count
|
||||
FROM projects p
|
||||
WHERE p.parent_id = ?
|
||||
ORDER BY p.created_at DESC`,
|
||||
)
|
||||
.bind(id)
|
||||
.all<ProjectRow & { task_count: number }>();
|
||||
|
||||
const descendantIds = await collectDescendantIds(db, id);
|
||||
const pushIds = [id, ...descendantIds];
|
||||
const placeholders = pushIds.map(() => "?").join(", ");
|
||||
const pushTasks = await db
|
||||
.prepare(
|
||||
`SELECT t.id, t.name, t.brand, t.quantity, t.claimed_quantity, t.due_at, t.status,
|
||||
t.task_type, t.platform, t.content_format, t.share_token, t.owner_userid, t.task_chat_id,
|
||||
pj.name AS project_name
|
||||
FROM tasks t
|
||||
LEFT JOIN projects pj ON pj.id = t.project_id
|
||||
WHERE t.project_id IN (${placeholders})
|
||||
ORDER BY t.created_at DESC`,
|
||||
)
|
||||
.bind(...pushIds)
|
||||
.all<ProjectTaskRow>();
|
||||
|
||||
return {
|
||||
project: {
|
||||
id: project.id,
|
||||
@@ -300,6 +395,7 @@ export async function getProjectDetail(
|
||||
mainGroupMemberIds: parseJsonArray(project.main_group_member_ids),
|
||||
ownerUserid: project.owner_userid ?? "",
|
||||
mainChatId: project.main_chat_id ?? "",
|
||||
parentProjectId: project.parent_id ?? "",
|
||||
taskCount: tasks.results.length,
|
||||
},
|
||||
tasks: tasks.results,
|
||||
@@ -310,6 +406,15 @@ export async function getProjectDetail(
|
||||
project.owner_userid,
|
||||
),
|
||||
},
|
||||
subprojects: subprojects.results.map((row) => ({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
brand: row.brand,
|
||||
ownerUserid: row.owner_userid ?? "",
|
||||
mainChatId: row.main_chat_id ?? "",
|
||||
taskCount: Number(row.task_count),
|
||||
})),
|
||||
pushTasks: pushTasks.results,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -320,6 +425,7 @@ export type SaveProjectInput = {
|
||||
mainGroupMemberIds?: unknown;
|
||||
ownerUserid?: unknown;
|
||||
note?: unknown;
|
||||
parentProjectId?: unknown;
|
||||
};
|
||||
|
||||
export async function saveProject(
|
||||
@@ -333,19 +439,33 @@ export async function saveProject(
|
||||
const brand = text(input.brand);
|
||||
const note = text(input.note ?? "");
|
||||
const ownerUserid = text(input.ownerUserid).slice(0, 64);
|
||||
const parentProjectId = text(input.parentProjectId);
|
||||
const memberIds = effectiveProjectMembers(input.mainGroupMemberIds, ownerUserid);
|
||||
if (!name) throw new ProjectError("请填写项目名称");
|
||||
|
||||
if (parentProjectId) {
|
||||
if (parentProjectId === id) {
|
||||
throw new ProjectError("上级项目不能是项目自身");
|
||||
}
|
||||
const parents = await loadProjectParents(db);
|
||||
if (!parents.has(parentProjectId)) {
|
||||
throw new ProjectError("上级项目不存在", 404);
|
||||
}
|
||||
if (id && isDescendantProject(parents, id, parentProjectId)) {
|
||||
throw new ProjectError("上级项目不能是当前项目的子项目");
|
||||
}
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
let projectId = id;
|
||||
if (id) {
|
||||
const result = await db
|
||||
.prepare(
|
||||
`UPDATE projects
|
||||
SET name = ?, brand = ?, main_group_member_ids = ?, owner_userid = ?, note = ?
|
||||
SET name = ?, brand = ?, main_group_member_ids = ?, owner_userid = ?, note = ?, parent_id = ?
|
||||
WHERE id = ?`,
|
||||
)
|
||||
.bind(name, brand, JSON.stringify(memberIds), ownerUserid, note, id)
|
||||
.bind(name, brand, JSON.stringify(memberIds), ownerUserid, note, parentProjectId, id)
|
||||
.run();
|
||||
if (result.meta.changes === 0) throw new ProjectError("项目不存在", 404);
|
||||
} else {
|
||||
@@ -353,8 +473,8 @@ export async function saveProject(
|
||||
await db
|
||||
.prepare(
|
||||
`INSERT INTO projects
|
||||
(id, name, brand, main_group_member_ids, owner_userid, status, note, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, 'active', ?, ?)`,
|
||||
(id, name, brand, main_group_member_ids, owner_userid, parent_id, status, note, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 'active', ?, ?)`,
|
||||
)
|
||||
.bind(
|
||||
projectId,
|
||||
@@ -362,6 +482,7 @@ export async function saveProject(
|
||||
brand,
|
||||
JSON.stringify(memberIds),
|
||||
ownerUserid,
|
||||
parentProjectId,
|
||||
note,
|
||||
now,
|
||||
)
|
||||
@@ -384,6 +505,13 @@ export async function deleteProject(
|
||||
): Promise<void> {
|
||||
const id = text(projectId);
|
||||
if (!id) throw new ProjectError("缺少项目 ID");
|
||||
const childCount = await db
|
||||
.prepare("SELECT COUNT(*) AS count FROM projects WHERE parent_id = ?")
|
||||
.bind(id)
|
||||
.first<{ count: number }>();
|
||||
if (Number(childCount?.count ?? 0) > 0) {
|
||||
throw new ProjectError("该项目下还有子项目,请先删除子项目或把它们移到其他项目下");
|
||||
}
|
||||
const result = await db
|
||||
.prepare("DELETE FROM projects WHERE id = ?")
|
||||
.bind(id)
|
||||
|
||||
@@ -3,11 +3,8 @@ import {
|
||||
addTaskJoinWay,
|
||||
deleteJoinWay,
|
||||
getJoinWayQrCode,
|
||||
hasAppCredentials,
|
||||
listCustomerGroupChats,
|
||||
updateTaskJoinWay,
|
||||
type WecomConfig,
|
||||
type WecomGroupChat,
|
||||
} from "./wecom-client";
|
||||
|
||||
type FetchLike = typeof fetch;
|
||||
@@ -16,19 +13,6 @@ function text(value: unknown) {
|
||||
return String(value ?? "").trim();
|
||||
}
|
||||
|
||||
export async function listOwnerCustomerChats(
|
||||
config: WecomConfig,
|
||||
ownerUserid: string,
|
||||
fetchImpl: FetchLike = fetch,
|
||||
): Promise<WecomGroupChat[]> {
|
||||
if (!hasAppCredentials(config)) {
|
||||
throw new Error("企业微信自建应用未配置(WECOM_CORP_ID / WECOM_AGENT_ID / WECOM_SECRET)");
|
||||
}
|
||||
const owner = text(ownerUserid);
|
||||
if (!owner) return [];
|
||||
return listCustomerGroupChats(config, fetchImpl, [owner]);
|
||||
}
|
||||
|
||||
export type BindTaskCustomerChatResult = {
|
||||
customerChatId: string;
|
||||
customerChatName: string;
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
import type { TaskReportRow } from "./mvp-db";
|
||||
|
||||
const CSV_HEADERS = [
|
||||
"任务ID",
|
||||
"任务名称",
|
||||
"任务类型",
|
||||
"品牌",
|
||||
"平台",
|
||||
"所属项目",
|
||||
"任务状态",
|
||||
"计划数量",
|
||||
"领取数",
|
||||
"发布数",
|
||||
"完成数",
|
||||
"完成率",
|
||||
"截止日期",
|
||||
"创建时间",
|
||||
"曝光量合计",
|
||||
"阅读量合计",
|
||||
"D2点赞",
|
||||
"D2评论",
|
||||
"D2收藏",
|
||||
"D5点赞",
|
||||
"D5评论",
|
||||
"D5收藏",
|
||||
"D7点赞",
|
||||
"D7评论",
|
||||
"D7收藏",
|
||||
"最新点赞",
|
||||
"最新评论",
|
||||
"最新收藏",
|
||||
"最新转发",
|
||||
"关联任务",
|
||||
"截图提交数",
|
||||
];
|
||||
|
||||
function taskTypeLabel(value: string) {
|
||||
return value === "screenshot_collect" ? "截图采集" : "内容发布";
|
||||
}
|
||||
|
||||
function taskStatusLabel(value: string) {
|
||||
return value === "active" ? "进行中" : value === "completed" ? "已完结" : value;
|
||||
}
|
||||
|
||||
function completionRate(row: TaskReportRow) {
|
||||
if (!row.claimed_count) return "";
|
||||
return `${Math.round((row.completed_count / row.claimed_count) * 100)}%`;
|
||||
}
|
||||
|
||||
function csvCell(value: string | number | null | undefined) {
|
||||
const text = value === null || value === undefined ? "" : String(value);
|
||||
if (/[",\r\n]/.test(text)) return `"${text.replaceAll('"', '""')}"`;
|
||||
return text;
|
||||
}
|
||||
|
||||
export function buildTaskReportCsv(rows: TaskReportRow[]) {
|
||||
const lines = [CSV_HEADERS.join(",")];
|
||||
for (const row of rows) {
|
||||
lines.push(
|
||||
[
|
||||
row.id,
|
||||
row.name,
|
||||
taskTypeLabel(row.task_type),
|
||||
row.brand,
|
||||
row.platform,
|
||||
row.project_name ?? "",
|
||||
taskStatusLabel(row.status),
|
||||
row.quantity,
|
||||
row.claimed_count,
|
||||
row.published_count,
|
||||
row.completed_count,
|
||||
completionRate(row),
|
||||
row.due_at,
|
||||
row.created_at,
|
||||
row.exposure_total,
|
||||
row.views_total,
|
||||
row.d2_likes,
|
||||
row.d2_comments,
|
||||
row.d2_collects,
|
||||
row.d5_likes,
|
||||
row.d5_comments,
|
||||
row.d5_collects,
|
||||
row.d7_likes,
|
||||
row.d7_comments,
|
||||
row.d7_collects,
|
||||
row.latest_likes,
|
||||
row.latest_comments,
|
||||
row.latest_collects,
|
||||
row.latest_shares,
|
||||
row.related_task_name ?? row.reverse_related_task_name ?? "",
|
||||
row.task_type === "screenshot_collect" ? row.submitted_count : "",
|
||||
]
|
||||
.map(csvCell)
|
||||
.join(","),
|
||||
);
|
||||
}
|
||||
return `\uFEFF${lines.join("\r\n")}`;
|
||||
}
|
||||
@@ -394,12 +394,23 @@ export async function listCustomerGroupChats(
|
||||
const getPayload = await readEnvelope(getResp, "获取企业微信客户群详情失败");
|
||||
if (!getResp.ok) continue;
|
||||
if (Number(getPayload.errcode ?? 0) !== 0) continue;
|
||||
const detail = getPayload.group_chat;
|
||||
const detail = getPayload.group_chat as
|
||||
| {
|
||||
name?: unknown;
|
||||
owner?: unknown;
|
||||
member_count?: unknown;
|
||||
member_list?: unknown[];
|
||||
}
|
||||
| undefined;
|
||||
// 企微实际响应不带 member_count(只有 member_list),以成员列表长度为准
|
||||
const memberCount = Array.isArray(detail?.member_list)
|
||||
? detail.member_list.length
|
||||
: Number(detail?.member_count ?? 0);
|
||||
groups.push({
|
||||
chatId,
|
||||
name: bindingValue(detail?.name),
|
||||
ownerUserId: bindingValue(detail?.owner),
|
||||
memberCount: Number(detail?.member_count ?? 0),
|
||||
memberCount,
|
||||
status,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user