feat: 采集失败KOC补交截图闭环 + 任务中心分发详情弹窗

公开数据补交闭环(F1):
- distributions 新增 public_metrics_screenshot_key(mysql/0022 迁移)
- KOC 门户:链接已回填但采集失败时展示补交数据截图入口(kind=public-metrics)
- 运营端:回收队列"核对补录"支持公开互动数据(赞/藏/评),按补交截图补录后采集状态置成功
- 企微催办:采集失败纳入待办(公开数据待补交)
- task-result-image 支持 kind=publish 输出发布截图

门户文案(F2):
- 自动采集数据明确"由系统每天自动采集,无需你填写",仅识别失败时才手动填写曝光/阅读

任务中心分发详情:
- 任务中心/数据回收的分发表行可点击,弹窗查看详情
- 详情按任务类型适配:发布阶段/数据回收(自动采集)/创作者数据(第7天)/截图回收四区块三段式布局
This commit is contained in:
ABAPPLO
2026-10-09 17:02:46 +08:00
parent c686003cfb
commit c11afd5f99
15 changed files with 1250 additions and 67 deletions

View File

@@ -27,21 +27,27 @@ type DueSoonRow = {
wecom_external_user_id: string | null;
task_id: string;
task_name: string;
pending_phase: "publish" | "recovery";
pending_phase: "publish" | "recovery" | "metrics";
pending_due_at: string;
content_title: string;
};
function pendingActionLabel(pendingPhase: string) {
return pendingPhase === "recovery" ? "截图待提交" : "待发布";
if (pendingPhase === "recovery") return "截图待提交";
if (pendingPhase === "metrics") return "公开数据待补交";
return "待发布";
}
function pendingSummary(rows: DueSoonRow[]) {
const shot = rows.filter((row) => row.pending_phase === "recovery").length;
const publish = rows.length - shot;
if (shot === 0) return `${rows.length} 条笔记待发布`;
if (publish === 0) return `${rows.length} 份截图待提交`;
return `${publish} 条笔记待发布、${shot} 份截图待提交`;
const metrics = rows.filter((row) => row.pending_phase === "metrics").length;
const publish = rows.length - shot - metrics;
const parts: string[] = [];
if (publish > 0) parts.push(`${publish} 条笔记待发布`);
if (shot > 0) parts.push(`${shot} 份截图待提交`);
if (metrics > 0) parts.push(`${metrics} 条公开数据待补交`);
if (parts.length === 0) return `${rows.length} 条待跟进`;
return parts.join("、");
}
export function computeDueCutoff(today: string, dueDays: number) {
@@ -66,7 +72,8 @@ export async function runDueSoonWecomNotifications(
const portalUrl = bindingValue(getRuntimeEnv().KOC_PORTAL_URL);
const taskFilter = String(taskId ?? "").trim();
// 双阶段催办:发布阶段(未回填链接)看 due_at;回收阶段(已发布未收截图)看 recovery_due_at
// 双阶段催办:发布阶段(未回填链接)看 due_at;回收阶段(已发布未收截图)看 recovery_due_at;
// 采集失败兜底:已发布但公开数据采集失败时催 KOC 补交数据截图(不限日期,尽快补)
const dueSoonBase = `SELECT
d.id AS distribution_id,
d.partner_id,
@@ -74,8 +81,17 @@ export async function runDueSoonWecomNotifications(
p.wecom_external_user_id,
t.id AS task_id,
t.name AS task_name,
CASE WHEN d.publish_url IS NULL OR d.publish_url = '' THEN 'publish' ELSE 'recovery' END AS pending_phase,
CASE WHEN d.publish_url IS NULL OR d.publish_url = '' THEN t.due_at ELSE t.recovery_due_at END AS pending_due_at,
CASE
WHEN d.publish_url IS NULL OR d.publish_url = '' THEN 'publish'
WHEN d.collection_status = 'failed' THEN 'metrics'
ELSE 'recovery'
END AS pending_phase,
CASE
WHEN d.publish_url IS NULL OR d.publish_url = '' THEN t.due_at
WHEN d.collection_status = 'failed'
THEN COALESCE(NULLIF(t.recovery_due_at, ''), t.due_at)
ELSE t.recovery_due_at
END AS pending_due_at,
c.title AS content_title
FROM distributions d
JOIN tasks t ON t.id = d.task_id
@@ -89,6 +105,8 @@ export async function runDueSoonWecomNotifications(
AND d.result_submitted_at IS NULL
AND t.recovery_due_at IS NOT NULL AND t.recovery_due_at != ''
AND t.recovery_due_at <= ?)
OR (d.publish_url IS NOT NULL AND d.publish_url != ''
AND d.collection_status = 'failed')
)`;
const statement = db
.prepare(