feat: 复盘报表视图与 CSV 导出(manager 专属)

This commit is contained in:
ABAPPLO
2026-09-01 17:46:11 +08:00
parent 3e4269729c
commit 4c1ff2d5b8
6 changed files with 639 additions and 0 deletions

View File

@@ -872,6 +872,79 @@ export async function getDashboardData() {
};
}
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;
};
export async function getTaskReportData(db: DatabaseClient) {
const result = await db
.prepare(
`SELECT
t.id,
t.name,
t.task_type,
t.brand,
t.platform,
t.status,
t.due_at,
t.created_at,
t.quantity,
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(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
GROUP BY t.id
ORDER BY t.created_at DESC`,
)
.all<TaskReportRow>();
return result.results;
}
export function uid(prefix: string) {
return `${prefix}-${crypto.randomUUID().slice(0, 8)}`;
}

94
lib/task-report-csv.ts Normal file
View File

@@ -0,0 +1,94 @@
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,
]
.map(csvCell)
.join(","),
);
}
return `\uFEFF${lines.join("\r\n")}`;
}