"use client"; import { ChangeEvent, FormEvent, useCallback, useEffect, useMemo, useState, } from "react"; import { formatShanghaiDate as formatDate } from "../lib/date-utils"; type Partner = { id: string; name: string; wecom_name: string; owner: string; claimed_total: number; completed_total: number; }; type Task = { id: string; name: string; brand: string; quantity: number; claimed_quantity: number; due_at: string; status: string; source_url: string; source_sheet_id: string; source_sheet_name: string; source_synced_at: string | null; share_token: string; collection_start_date: string | null; collection_days: string; collection_schedule_updated_at: string | null; }; type Account = { id: string; platform: string; platform_uid: string; public_account_id: string; nickname: string; profile_url: string; ip_location: string; followers: number; post_count: number; avg_views: number; first_seen_at: string; last_seen_at: string; }; type Distribution = { id: string; task_id: string; content_id: string; partner_id: string; delegation_bundle_id: string | null; account_id: string | null; publish_url: string | null; publish_time: string | null; status: string; claimed_at: string; screenshot_key: string | null; ocr_status: string; exposure: number | null; views: 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; collection_status: string; collection_status_description: string | null; collection_updated_at: string | null; last_collection_day: number | null; updated_at: string; content_title: string; partner_name: string; account_nickname: string | null; account_platform: string | null; task_name: string; task_brand: string; due_at: string; }; type DashboardData = { partners: Partner[]; tasks: Task[]; accounts: Account[]; distributions: Distribution[]; portal_url: string; }; type FeishuPreview = { sheetId: string; sheetName: string; syncedAt: string; rowCount: number; columns: string[]; preview: Array<{ sourceRow: number; title: string; body: string; }>; }; type NavKey = | "overview" | "tasks" | "distributions" | "resources" | "recovery"; const NAV_ITEMS: Array<{ key: NavKey; label: string; mark: string }> = [ { key: "overview", label: "工作台", mark: "⌂" }, { key: "tasks", label: "内容任务", mark: "▤" }, { key: "distributions", label: "内容分发", mark: "↗" }, { key: "resources", label: "KOC资源", mark: "◎" }, { key: "recovery", label: "数据回收", mark: "◫" }, ]; const EMPTY_DATA: DashboardData = { partners: [], tasks: [], accounts: [], distributions: [], portal_url: "", }; async function readApiResponse(response: Response, fallback: string) { const text = await response.text(); const contentType = response.headers.get("content-type") ?? ""; if (!contentType.includes("json")) { if (response.status === 403) { throw new Error("请求被安全网关拦截,请刷新页面后重试"); } throw new Error(`${fallback}(服务返回异常,HTTP ${response.status})`); } try { return JSON.parse(text) as T & { error?: string }; } catch { throw new Error(`${fallback}(服务返回的数据无法解析)`); } } function formatNumber(value: number | null | undefined) { if (value === null || value === undefined) return "—"; if (value >= 10000) return `${(value / 10000).toFixed(value >= 100000 ? 0 : 1)}万`; return new Intl.NumberFormat("zh-CN").format(value); } function statusLabel(status: string) { const labels: Record = { claimed: "待发布", published: "已发布", collecting: "采集中", complete: "已完成", active: "分发中", closed: "已结束", }; return labels[status] || status; } function latestPublicMetrics(distribution: Distribution) { const legacyDay = distribution.d7_likes !== null ? 7 : distribution.d5_likes !== null ? 5 : distribution.d2_likes !== null ? 2 : null; const likes = distribution.latest_likes ?? (legacyDay ? distribution[`d${legacyDay}_likes`] : null); const comments = distribution.latest_comments ?? (legacyDay ? distribution[`d${legacyDay}_comments`] : null); const collects = distribution.latest_collects ?? (legacyDay ? distribution[`d${legacyDay}_collects`] : null); return { likes, comments, collects, total: likes === null ? null : likes + (comments ?? 0) + (collects ?? 0), }; } function hasCreatorMetrics(distribution: Distribution) { return distribution.exposure !== null && distribution.views !== null; } function parseCollectionDays(value: string | null | undefined) { try { const parsed = JSON.parse(value || "[]") as unknown; if (!Array.isArray(parsed)) return []; return [...new Set(parsed.map(Number))] .filter((day) => Number.isInteger(day) && day >= 1 && day <= 7) .sort((a, b) => a - b); } catch { return []; } } function todayInputValue() { const parts = new Intl.DateTimeFormat("zh-CN", { timeZone: "Asia/Shanghai", year: "numeric", month: "2-digit", day: "2-digit", }).formatToParts(new Date()); const value = Object.fromEntries( parts.map((part) => [part.type, part.value]), ); return `${value.year}-${value.month}-${value.day}`; } function addCalendarDays(value: string, days: number) { const match = value.match(/^(\d{4})-(\d{2})-(\d{2})$/); if (!match) return ""; const date = new Date( Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]) + days), ); return date.toISOString().slice(0, 10); } function xhsPublishUrl(value: string | null) { if (!value) return null; try { const url = new URL(value); const hostname = url.hostname.toLowerCase(); const isXhsHost = hostname === "xiaohongshu.com" || hostname.endsWith(".xiaohongshu.com") || hostname === "xhslink.com" || hostname.endsWith(".xhslink.com"); return ["http:", "https:"].includes(url.protocol) && isXhsHost ? url.toString() : null; } catch { return null; } } function collectionStatusLabel(status: string, hasMetrics: boolean) { if (hasMetrics && (!status || status === "pending")) return "采集成功"; const labels: Record = { pending: "待设置", scheduled: "已计划", collecting: "采集中", success: "采集成功", failed: "采集失败", }; return labels[status] || status || "待设置"; } function avatarColor(value: string) { const colors = ["coral", "cyan", "violet", "amber", "mint"]; const index = [...value].reduce((sum, char) => sum + char.charCodeAt(0), 0); return colors[index % colors.length]; } async function compressScreenshot(file: File) { if (file.size < 850_000 || !file.type.startsWith("image/")) return file; const bitmap = await createImageBitmap(file); const maxEdge = 1600; const scale = Math.min(1, maxEdge / Math.max(bitmap.width, bitmap.height)); const canvas = document.createElement("canvas"); canvas.width = Math.max(1, Math.round(bitmap.width * scale)); canvas.height = Math.max(1, Math.round(bitmap.height * scale)); const context = canvas.getContext("2d"); if (!context) { bitmap.close(); return file; } context.drawImage(bitmap, 0, 0, canvas.width, canvas.height); bitmap.close(); const blob = await new Promise((resolve) => canvas.toBlob(resolve, "image/jpeg", 0.82), ); if (!blob) return file; return new File([blob], `${file.name.replace(/\.[^.]+$/, "")}.jpg`, { type: "image/jpeg", }); } export default function Home() { const [data, setData] = useState(EMPTY_DATA); const [activeNav, setActiveNav] = useState("overview"); const [loading, setLoading] = useState(true); const [working, setWorking] = useState(false); const [error, setError] = useState(""); const [toast, setToast] = useState(""); const [menuOpen, setMenuOpen] = useState(false); const [taskModalOpen, setTaskModalOpen] = useState(false); const [metricModal, setMetricModal] = useState(null); const [taskForm, setTaskForm] = useState({ name: "", brand: "", dueAt: "2026-08-12", feishuUrl: "", }); const [sourcePreview, setSourcePreview] = useState(null); const [sourceWorking, setSourceWorking] = useState(false); const [metricForm, setMetricForm] = useState({ exposure: "", views: "" }); const loadData = useCallback(async () => { try { setLoading(true); const response = await fetch("/api/bootstrap", { cache: "no-store" }); const result = await readApiResponse(response, "加载失败"); if (!response.ok) throw new Error(result.error || "加载失败"); setData(result); } catch (reason) { setError(reason instanceof Error ? reason.message : "加载失败"); } finally { setLoading(false); } }, []); useEffect(() => { const timer = window.setTimeout(() => { void loadData(); }, 0); return () => window.clearTimeout(timer); }, [loadData]); useEffect(() => { if (!toast) return; const timer = window.setTimeout(() => setToast(""), 2600); return () => window.clearTimeout(timer); }, [toast]); const runAction = async ( payload: Record, successMessage: string, ) => { try { setWorking(true); const response = await fetch("/api/action", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); const result = await readApiResponse(response, "操作失败"); if (!response.ok) throw new Error(result.error || "操作失败"); setData(result); setToast(successMessage); return true; } catch (reason) { setToast(reason instanceof Error ? reason.message : "操作失败"); return false; } finally { setWorking(false); } }; const stats = useMemo(() => { const published = data.distributions.filter((item) => ["published", "collecting", "complete"].includes(item.status), ).length; const completed = data.distributions.filter( (item) => item.status === "complete", ).length; const pendingRecovery = data.distributions.filter( (item) => item.publish_url && (latestPublicMetrics(item).likes === null || !hasCreatorMetrics(item)), ).length; return { resources: data.accounts.length, activeTasks: data.tasks.filter((task) => task.status === "active").length, published, completed, pendingRecovery, }; }, [data]); const recentDistributions = data.distributions.slice(0, 6); const navigate = (key: NavKey) => { setActiveNav(key); setMenuOpen(false); }; const submitTask = async (event: FormEvent) => { event.preventDefault(); if (!sourcePreview) { setToast("请先读取飞书表格"); return; } const success = await runAction( { action: "create_task", ...taskForm }, `任务已创建,${sourcePreview.rowCount} 篇内容进入可领取池`, ); if (success) { setTaskModalOpen(false); setSourcePreview(null); setTaskForm({ name: "", brand: "", dueAt: "2026-08-12", feishuUrl: "", }); } }; const inspectFeishu = async () => { if (!taskForm.feishuUrl.trim()) { setToast("请先粘贴飞书表格链接"); return; } try { setSourceWorking(true); const response = await fetch("/api/action", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action: "inspect_feishu", feishuUrl: taskForm.feishuUrl, }), }); const result = await readApiResponse( response, "读取飞书表格失败", ); if (!response.ok) throw new Error(result.error || "读取飞书表格失败"); setSourcePreview(result); setTaskForm((current) => ({ ...current, name: current.name || result.sheetName.replace(/500篇$/, "分发任务"), })); setToast(`已读取 ${result.rowCount} 篇有效内容`); } catch (reason) { setSourcePreview(null); setToast(reason instanceof Error ? reason.message : "读取飞书表格失败"); } finally { setSourceWorking(false); } }; const copyTaskShareLink = async (task: Task) => { if (!data.portal_url || !task.share_token) { setToast("KOC外部站点尚未发布"); return; } const url = `${data.portal_url.replace(/\/$/, "")}/?task=${task.share_token}`; try { await navigator.clipboard.writeText(url); setToast("KOC领取链接已复制"); } catch { window.prompt("复制KOC领取链接", url); } }; const collectMetrics = async (distribution: Distribution) => { await runAction( { action: "collect_now", distributionId: distribution.id }, "公开数据已更新", ); }; const retryFailedMetrics = async (taskId: string, failedCount: number) => { if (failedCount === 0) { setToast("当前没有采集异常的笔记"); return; } await runAction( { action: "retry_failed_collections", taskId }, `已完成 ${failedCount} 篇异常笔记的一键补采`, ); }; const saveCollectionSchedule = async ( taskId: string, startDate: string, days: number[], ) => { return runAction( { action: "save_collection_schedule", taskId, startDate, days, }, `采集任务已创建,将在所选日期10:00自动执行`, ); }; const uploadScreenshot = async ( distribution: Distribution, event: ChangeEvent, ) => { const file = event.target.files?.[0]; if (!file) return; try { setWorking(true); const uploadFile = await compressScreenshot(file); const form = new FormData(); form.append("distributionId", distribution.id); form.append("file", uploadFile); const response = await fetch("/api/upload", { method: "POST", body: form }); const result = await readApiResponse(response, "上传失败"); if (!response.ok) throw new Error(result.error || "上传失败"); setData(result); setToast("创作者中心截图已收回"); } catch (reason) { setToast(reason instanceof Error ? reason.message : "上传失败"); } finally { setWorking(false); event.target.value = ""; } }; const submitManualMetrics = async (event: FormEvent) => { event.preventDefault(); if (!metricModal) return; const success = await runAction( { action: "manual_metrics", distributionId: metricModal.id, exposure: metricForm.exposure, views: metricForm.views, }, "创作者中心数据已确认", ); if (success) { setMetricModal(null); setMetricForm({ exposure: "", views: "" }); } }; const openMetricFallback = (distribution: Distribution) => { setMetricModal(distribution); setMetricForm({ exposure: distribution.exposure?.toString() || "", views: distribution.views?.toString() || "", }); }; const pageTitle: Record = { overview: { title: "分发工作台", subtitle: "用最少动作,跑完内容领取、发布与数据回收。", }, tasks: { title: "内容任务", subtitle: "从飞书同步内容,按数量开放给合作方领取。", }, distributions: { title: "内容分发", subtitle: "先按任务查看进度,再进入单个任务处理每篇笔记。", }, resources: { title: "KOC资源", subtitle: "只沉淀真实发布过的账号,不要求提前登记。", }, recovery: { title: "数据回收", subtitle: "按任务设置自动采集日,集中查看最新公开数据与创作者截图。", }, }; return (
{menuOpen &&
KOC LOOP / {pageTitle[activeNav].title}
飞书内容表已接入

MVP · 核心闭环

{pageTitle[activeNav].title}

{pageTitle[activeNav].subtitle}

{activeNav === "overview" && (
今天 7月27日
)}
{loading ? ( ) : error ? (
!

暂时无法打开工作台

{error}

) : ( <> {activeNav === "overview" && ( )} {activeNav === "tasks" && ( setTaskModalOpen(true)} onCopyShare={copyTaskShareLink} /> )} {activeNav === "distributions" && ( )} {activeNav === "resources" && ( )} {activeNav === "recovery" && ( item.publish_url)} working={working} onCollect={collectMetrics} onSaveSchedule={saveCollectionSchedule} onUpload={uploadScreenshot} onFallback={openMetricFallback} onRetryFailed={retryFailedMetrics} /> )} )}
{taskModalOpen && (
setTaskModalOpen(false)}>
event.stopPropagation()}>

飞书链接导入

新建分发任务

{sourcePreview && (
{sourcePreview.sheetName} 工作表 {sourcePreview.sheetId} · 已匹配 {sourcePreview.columns.length} 个字段
{sourcePreview.rowCount} 篇
{sourcePreview.columns.map((column) => {column})}
{sourcePreview.preview.map((row) => (
{row.sourceRow} {row.title}
))}
)}
)} {metricModal && (
setMetricModal(null)}>
event.stopPropagation()}>

OCR异常兜底

确认创作者数据

{metricModal.account_nickname || "待识别账号"} {metricModal.content_title}
)} {toast &&
{toast}
} {working &&
}
); } function LoadingState() { return (
); } function OverviewPage({ stats, distributions, tasks, onNavigate, }: { stats: { resources: number; activeTasks: number; published: number; completed: number; pendingRecovery: number; }; distributions: Distribution[]; tasks: Task[]; onNavigate: (key: NavKey) => void; }) { const cards = [ { label: "已沉淀账号资源", value: stats.resources, hint: "均来自真实发布", tone: "ink", }, { label: "正在分发任务", value: stats.activeTasks, hint: `${tasks.reduce((sum, task) => sum + task.claimed_quantity, 0)} 篇已被领取`, tone: "green", }, { label: "已回填发布", value: stats.published, hint: "链接自动识别账号", tone: "blue", }, { label: "待完成数据回收", value: stats.pendingRecovery, hint: stats.pendingRecovery > 0 ? "需要跟进" : "全部按时完成", tone: "orange", }, ]; return ( <>
{cards.map((card) => (

{card.label}

{card.value}
{card.hint}
))}

当前闭环

每篇内容只沿着一条记录向前走

01
内容已同步{tasks.reduce((sum, task) => sum + task.quantity, 0)} 篇
02
KOC领取{tasks.reduce((sum, task) => sum + task.claimed_quantity, 0)} 篇
03
发布回填{stats.published} 篇
04
数据回收{stats.completed} 篇完成
{stats.pendingRecovery}待回收

今天最需要推进

把已发布笔记的数据收回来

按任务选择采集日期,系统在当天10:00更新点赞、收藏、评论和总互动。

进行中的任务

按截止时间排序

{tasks.map((task) => { const progress = Math.round((task.claimed_quantity / task.quantity) * 100); return (
{task.brand.slice(0, 1)}
{task.name}{task.brand}
{progress}%
{task.claimed_quantity}/{task.quantity} 篇已领取 {formatDate(task.due_at)}截止
); })}

最近分发

领取、发布账号与回收状态一眼看清

); } function TasksPage({ tasks, portalUrl, onCreate, onCopyShare, }: { tasks: Task[]; portalUrl: string; onCreate: () => void; onCopyShare: (task: Task) => void; }) { return (

内容从飞书来

一行内容,一份可领取笔记

不在这里重复编辑正文。这里只控制任务数量、领取进度和截止时间。

任务列表

{tasks.length} 个任务

{tasks.map((task) => { const progress = Math.round((task.claimed_quantity / task.quantity) * 100); return (
{task.brand.slice(0, 1)}

{task.name}

{task.brand}
{task.source_sheet_name ? `飞书 · ${task.source_sheet_name}` : "历史示例内容表"}
{task.claimed_quantity}/ {task.quantity} 已领取
截止日期{formatDate(task.due_at)}
{statusLabel(task.status)}
{portalUrl && task.share_token && ( )}
); })}
); } function TaskScopeList({ tasks, distributions, mode, onOpen, }: { tasks: Task[]; distributions: Distribution[]; mode: "distribution" | "recovery"; onOpen: (taskId: string) => void; }) { return (
{tasks.map((task) => { const taskDistributions = distributions.filter( (item) => item.task_id === task.id, ); const published = taskDistributions.filter((item) => item.publish_url); const publicDataComplete = published.filter( (item) => latestPublicMetrics(item).likes !== null, ).length; const creatorComplete = published.filter( (item) => hasCreatorMetrics(item), ).length; const progress = task.quantity ? Math.round((task.claimed_quantity / task.quantity) * 100) : 0; return (
{task.brand.slice(0, 1)}

{task.name}

{task.brand} · {formatDate(task.due_at)} 截止
{statusLabel(task.status)}
飞书来源 {task.source_url ? ( {task.source_sheet_name || `工作表 ${task.source_sheet_id}`} ↗ ) : ( 历史示例内容表 )}
{mode === "distribution" ? ( <>
内容总量{task.quantity}
已领取{task.claimed_quantity}
已发布{published.length}
) : (
已发布{published.length}
公开数据{publicDataComplete}
创作者数据{creatorComplete}
)}
); })}
); } function TaskDetailHeader({ task, label, onBack, }: { task: Task; label: string; onBack: () => void; }) { return (
{task.brand.slice(0, 1)}

{label}

{task.name}

{task.brand} · {task.quantity} 篇 · {formatDate(task.due_at)} 截止
{task.source_url && ( 打开飞书原表 ↗ )}
); } function DistributionPage({ tasks, distributions, }: { tasks: Task[]; distributions: Distribution[]; }) { const [selectedTaskId, setSelectedTaskId] = useState(null); const selectedTask = tasks.find((task) => task.id === selectedTaskId); if (!selectedTask) { return (

按任务管理

选择一个分发任务

每个任务独立查看领取、发布和账号识别进度,不混排不同项目的笔记。

); } const taskDistributions = distributions.filter( (item) => item.task_id === selectedTask.id, ); const published = taskDistributions.filter((item) => item.publish_url).length; return (
setSelectedTaskId(null)} />
已领取{taskDistributions.length}
已回填链接{published}
待发布{taskDistributions.length - published}
当前仅展示“{selectedTask.name}”

内容分发表

实际发布账号在链接回填后自动补齐

); } function DistributionTable({ distributions, compact = false, }: { distributions: Distribution[]; compact?: boolean; }) { return (
{distributions.map((item) => ( ))}
内容 领取合作方 实际发布账号 发布时间 数据状态 状态
{item.content_id.split("-").at(-1)?.toUpperCase()}
{item.content_title}{item.task_name} · {item.task_brand}
{item.partner_name} {item.account_nickname ? (
{item.account_nickname.slice(0, 1)}
{item.account_nickname}{item.account_platform}
) : ( 回填链接后识别 )}
{formatDate(item.publish_time, true)} {item.d7_likes !== null ? ( D7 已回收 ) : item.d2_likes !== null ? ( 采集中 ) : ( {item.publish_url ? "等待D2" : "尚未发布"} )} {statusLabel(item.status)}
{!compact && distributions.length === 0 &&
暂无分发记录
}
); } function ResourcesPage({ accounts, distributions, }: { accounts: Account[]; distributions: Distribution[]; }) { const sources = (accountId: string) => [...new Set(distributions.filter((item) => item.account_id === accountId).map((item) => item.partner_name))]; const isPartnerManagedOnly = (accountId: string) => { const deliveries = distributions.filter( (item) => item.account_id === accountId, ); return ( deliveries.some((item) => item.delegation_bundle_id) && deliveries.every((item) => item.delegation_bundle_id) ); }; return (
{accounts.length} 个真实发布账号

资源从合作中自然长出来

不向KOC索要名单。发布链接第一次出现时建档,再次出现时更新合作次数与效果。

唯一去重规则 平台 + 账号主页

账号资源库

只展示真实交付过的账号

{accounts.map((account) => (
{account.nickname.slice(0, 1)}

{account.nickname}

{account.platform} · {account.ip_location}
{account.platform === "小红书" ? "小红书号" : account.platform === "抖音" ? "抖音号" : "账号"} {account.public_account_id || "待识别"}
粉丝{formatNumber(account.followers)}
合作发布{account.post_count}
历史合作来源
{sources(account.id).map((source) => {source})} {isPartnerManagedOnly(account.id) && ( 合作社资源 · 不可直联 )}
最近合作 {formatDate(account.last_seen_at)} {account.profile_url && 查看主页 ↗}
))}
); } function RecoveryPage({ tasks, distributions, working, onCollect, onSaveSchedule, onUpload, onFallback, onRetryFailed, }: { tasks: Task[]; distributions: Distribution[]; working: boolean; onCollect: (distribution: Distribution) => void; onSaveSchedule: ( taskId: string, startDate: string, days: number[], ) => Promise; onUpload: (distribution: Distribution, event: ChangeEvent) => void; onFallback: (distribution: Distribution) => void; onRetryFailed: (taskId: string, failedCount: number) => void; }) { const [selectedTaskId, setSelectedTaskId] = useState(null); const selectedTask = tasks.find((task) => task.id === selectedTaskId); if (!selectedTask) { return (

按任务回收

选择一个数据回收任务

进入任务后设置自动采集日期,统一查看每篇笔记的最新互动数据。

); } const taskDistributions = distributions.filter( (item) => item.task_id === selectedTask.id, ); const failedCollectionCount = taskDistributions.filter( (item) => item.collection_status === "failed", ).length; return (
setSelectedTaskId(null)} />

数据回收队列

{taskDistributions.length} 篇已发布笔记 · 展示最近一次采集结果

{taskDistributions.map((item) => { const metrics = latestPublicMetrics(item); const noteUrl = xhsPublishUrl(item.publish_url); const hasMetrics = metrics.likes !== null; const collectionStatus = item.collection_status || (hasMetrics ? "success" : "pending"); return ( ); })}
内容 / 发布账号 发布时间 点赞 收藏 评论 总互动 数据更新时间 采集状态 创作者截图 操作
{Array.from(item.content_title)[0] || "笔"}
{noteUrl ? ( {item.content_title} ) : ( {item.content_title} )} {item.account_nickname || "待识别账号"} {item.account_platform ? ` · ${item.account_platform}` : ""}
{formatDate(item.publish_time, true)}
{formatNumber(metrics.likes)}
{formatNumber(metrics.collects)}
{formatNumber(metrics.comments)}
{formatNumber(metrics.total)} 赞 + 藏 + 评
{formatDate(item.collection_updated_at || (hasMetrics ? item.updated_at : null), true)} {item.last_collection_day && 第{item.last_collection_day}天采集}
{collectionStatusLabel(collectionStatus, hasMetrics)} {item.collection_status_description || (hasMetrics ? "成功" : "等待采集计划")}
{item.screenshot_key ? ( 查看截图 ↗ ) : ( 待KOC上传 )} {hasCreatorMetrics(item) ? ( 曝光 {formatNumber(item.exposure)} 阅读 {formatNumber(item.views)} ) : item.screenshot_key ? ( 待KOC填写数据 ) : null} {item.screenshot_key && !hasCreatorMetrics(item) && ( )}
{item.screenshot_key && !hasCreatorMetrics(item) ? ( ) : ( )}
{taskDistributions.length === 0 && (
当前任务还没有已发布笔记
)}
); } function CollectionScheduleCard({ task, working, onSave, }: { task: Task; working: boolean; onSave: ( taskId: string, startDate: string, days: number[], ) => Promise; }) { const [startDate, setStartDate] = useState( task.collection_start_date || todayInputValue(), ); const [days, setDays] = useState( parseCollectionDays(task.collection_days), ); const toggleDay = (day: number) => { setDays((current) => current.includes(day) ? current.filter((item) => item !== day) : [...current, day].sort((a, b) => a - b), ); }; return (

自动采集计划

选择开始日期与采集日

勾选第1天到第7天,所选日期均在北京时间10:00自动采集。
已选择 {days.length} 天
{Array.from({ length: 7 }, (_, index) => index + 1).map((day) => { const selected = days.includes(day); const date = addCalendarDays(startDate, day - 1); return ( ); })}
左右滑动查看第1—7天
{task.collection_schedule_updated_at && (

当前计划更新于 {formatDate(task.collection_schedule_updated_at, true)}

)}
); }