feat: KOC详情弹窗 + 任务中心单入口改版 + 资源池默认合作方/采集兜底

KOC 详情弹窗(合作方/数据回收/工作台三处入口):
- 新增 KocDetailModal:基本信息 / 绑定KOC账号画像 / 参与任务记录 / 履约漏斗
- 工作台最近分发、回收队列账号名、截图回收合作方名、合作方页名称、分发详情头部合作方均可点开
- 分发表行点击仍打开分发详情(stopPropagation 隔离)

任务中心单入口改版(对齐"单任务双阶段"模型):
- 任务卡发布/回收面板整块可点,全卡仅保留一个「查看明细」,回收面板重复按钮移除
- 详情页自述身份:动态 label(任务详情·分发与回收一体/仅分发)+ 阶段徽章(含回收·截止/未开通回收)
- 内容分发表新增阶段筛选 chips(全部/待发布/已发布待数据/待收截图,未开通回收不显示截图项),筛选落空给专用空态文案
- 数据回收页入口文案改为「选择一个任务进入回收队列」,全站不再区分发布/回收两种任务类型

资源池 KOC 默认加入合作方 + 采集兜底:
- partners 新增 released_account_id(mysql/0023):显式解绑/被顶替的账号不再被默认合作方同步回填
- 任务未设置采集计划时,默认按发布时间起连续采集 7 天,等待文案显示「等待D1 · 默认7天」
This commit is contained in:
ABAPPLO
2026-10-12 11:11:31 +08:00
parent c11afd5f99
commit 87f4d4066d
20 changed files with 1472 additions and 195 deletions

90
tests/koc-detail.test.mjs Normal file
View File

@@ -0,0 +1,90 @@
import { strict as assert } from "node:assert";
import { readFileSync } from "node:fs";
import test from "node:test";
const source = readFileSync(new URL("../app/admin-app.tsx", import.meta.url), "utf8");
test("KOC 详情弹窗组件:三段式(基本信息 / 绑定账号 / 参与记录)", () => {
const start = source.indexOf("function KocDetailModal");
assert.ok(start > 0, "应存在 KocDetailModal 组件");
const body = source.slice(start, source.indexOf("function RecoveryPage"));
assert.match(body, /KOC详情:\$\{partner\.name\}/);
assert.match(body, /① 基本信息/);
assert.match(body, /② 绑定 KOC 账号(资源)/);
assert.match(body, /③ 参与任务记录/);
assert.match(body, /暂无领取记录/);
// 概览漏斗:领取 → 发布 → 回收完成
assert.match(body, /领取 <b>\{history\.length\}<\/b>/);
assert.match(body, /发布 <b>\{publishedCount\}<\/b>/);
assert.match(body, /回收完成 <b>\{completedCount\}<\/b>/);
// 绑定账号缺失时的兜底文案区分群主与外部KOC
assert.match(body, /群主合作方无需绑定 KOC 资源账号/);
});
test("工作台:按 id 挂载 KOC 详情,操作刷新后内容自动更新", () => {
assert.match(source, /const \[detailPartnerId, setDetailPartnerId\] = useState<string \| null>\(null\);/);
assert.match(
source,
/data\.partners\.find\(\(partner\) => partner\.id === detailPartnerId\)/,
);
assert.match(source, /item\.partner_id === partnerRow\.id/);
assert.match(source, /<KocDetailModal/);
});
test("工作台最近分发:行可点开分发详情,合作方可点开 KOC 详情", () => {
const start = source.indexOf("function OverviewPage");
const body = source.slice(start, source.indexOf("function OwnerLinkRow"));
assert.match(body, /onOpenDetail=\{onOpenDetail\}/);
assert.match(body, /onOpenPartner=\{onOpenPartner\}/);
});
test("分发表/截图回收表:合作方名为 KOC 详情入口且不触发行点击", () => {
const tableStart = source.indexOf("function DistributionTable");
const tableBody = source.slice(tableStart, source.indexOf("type AdminPreviewImage"));
assert.match(tableBody, /onOpenPartner\?: \(partnerId: string\) => void;/);
assert.match(tableBody, /className="koc-link"/);
assert.match(tableBody, /event\.stopPropagation\(\);\s*onOpenPartner\(item\.partner_id\)/);
const shotStart = source.indexOf("function ScreenshotTaskTable");
const shotBody = source.slice(shotStart, source.indexOf("const RESOURCE_PAGE_SIZE"));
assert.match(shotBody, /onOpenPartner\?: \(partnerId: string\) => void;/);
assert.match(shotBody, /onOpenPartner\(item\.partner_id\)/);
});
test("数据回收页:回收队列账号名与截图回收合作方均可点开 KOC 详情", () => {
const start = source.indexOf("function RecoveryPage");
const body = source.slice(start);
assert.match(body, /onOpenPartner: \(partnerId: string\) => void;/);
// 回收队列:账号名(待识别账号兜底)为入口,且阻断行点击冒泡
assert.match(body, /\{item\.account_nickname \|\| "待识别账号"\}\s*<\/button>/);
assert.match(
body,
/event\.stopPropagation\(\);\s*onOpenPartner\(item\.partner_id\)/,
);
// 截图回收表透传
assert.match(body, /<ScreenshotTaskTable\s+distributions=\{taskDistributions\}\s+onOpenPartner=\{onOpenPartner\}/);
});
test("合作方页:合作方名称单元格可点开 KOC 详情", () => {
const start = source.indexOf("function PartnersPage");
const body = source.slice(start, source.indexOf("function ResourcesPage"));
assert.match(body, /onOpenPartner: \(partner: Partner\) => void;/);
assert.match(body, /className="partner-name-cell"/);
assert.match(body, /onClick=\{\(\) => onOpenPartner\(partner\)\}/);
});
test("分发详情弹窗:头部合作方可跳转 KOC 详情", () => {
const start = source.indexOf("function DistributionDetailModal");
const body = source.slice(start, source.indexOf("function KocDetailModal"));
assert.match(body, /onOpenPartner\?: \(partnerId: string\) => void;/);
assert.match(body, /detail-meta-koc/);
assert.match(body, /onOpenPartner\(item\.partner_id\)/);
});
test("KOC 详情样式:入口链接与历史记录列表", () => {
const css = readFileSync(new URL("../app/globals.css", import.meta.url), "utf8");
assert.match(css, /^\.koc-link \{/m);
assert.match(css, /^\.partner-name-cell \{/m);
assert.match(css, /^\.koc-history-list \{/m);
assert.match(css, /^\.koc-summary-strip \{/m);
});