关于 Report Builder
Report Builder 是用于根据数据制作业务报告的 Skill,适合编写定期报告或决策文档的团队。它可整合摘要、指标、图表和格式化内容,并导出为 PDF、Word 或 HTML。
主要功能
- 01业务与财务报告
- 02执行摘要与建议
- 03图表、表格和趋势分析
- 04项目状态与里程碑报告
- 05导出为 PDF、Word 和 HTML
- 06定期报告调度
适用场景
- 01编制月度绩效回顾
- 02总结项目进度与风险
- 03比较不同时期的财务结果
- 04安排定期利益相关方报告
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ eddiebe147/ claude-settings --skill report-builder安装后在 Claude Code 中输入/report-builder确认已加载。
详细介绍
报告构建器
报告构建器技能可自动创建专业的业务报告,包含数据分析、可视化内容、执行摘要和格式化章节。它支持多种报告类型,包括财务报告、项目状态更新、市场分析、绩效评估和分析报告。该技能可整合来自多个来源的数据,并以清晰、可执行的形式呈现。
生成包含图表、表格、趋势分析和关键见解的 PDF、Word 或 HTML 格式报告。非常适用于定期报告、自动化仪表板和数据驱动的决策文档。
核心工作流
工作流 1:生成执行报告
目的: 创建包含摘要、关键指标和建议的高层级报告
步骤:
- 从各种来源收集数据
- 计算关键绩效指标(KPI)
- 生成突出关键要点的执行摘要
- 创建数据可视化内容(图表、示意图)
- 添加包含支持数据的详细章节
- 纳入建议和行动事项
- 采用专业版式进行排版
- 添加品牌标识并导出为 PDF
实现:
const PDFDocument = require('pdfkit');
const ChartJS = require('chartjs-node-canvas');
const fs = require('fs');
async function generateExecutiveReport(reportData, outputPath) {
const doc = new PDFDocument({ margin: 50 });
const stream = fs.createWriteStream(outputPath);
doc.pipe(stream);
// Cover page
doc.fontSize(32)
.fillColor('#2C3E50')
.text(reportData.title, { align: 'center' })
.moveDown()
.fontSize(18)
.fillColor('#7F8C8D')
.text(reportData.subtitle, { align: 'center' })
.moveDown(2)
.fontSize(12)
.text(`Reporting Period: ${reportData.period}`, { align: 'center' })
.text(`Generated: ${new Date().toLocaleDateString()}`, { align: 'center' });
// Company logo if provided
if (reportData.logo) {
doc.image(reportData.logo, doc.page.width / 2 - 50, 150, { width: 100 });
}
// Executive Summary
doc.addPage();
doc.fontSize(24)
.fillColor('#2C3E50')
.text('Executive Summary', { underline: true })
.moveDown();
doc.fontSize(11)
.fillColor('#34495E')
.text(reportData.executiveSummary, { align: 'justify', lineGap: 4 });
// Key Metrics Section
doc.moveDown(2);
doc.fontSize(18)
.fillColor('#2C3E50')
.text('Key Performance Indicators');
doc.moveDown();
// KPI Cards (2x2 grid)
const kpiWidth = 220;
const kpiHeight = 100;
const spacing = 20;
reportData.kpis.forEach((kpi, index) => {
const row = Math.floor(index / 2);
const col = index % 2;
const x = 50 + col * (kpiWidth + spacing);
const y = doc.y + row * (kpiHeight + spacing);
// KPI Card background
doc.fillColor(kpi.trend === 'up' ? '#27AE60' : kpi.trend === 'down' ? '#E74C3C' : '#3498DB')
.opacity(0.1)
.rect(x, y, kpiWidth, kpiHeight)
.fill();
doc.opacity(1);
// KPI Label
doc.fontSize(10)
.fillColor('#7F8C8D')
.text(kpi.label, x + 10, y + 10, { width: kpiWidth - 20 });
// KPI Value
doc.fontSize(28)
.fillColor('#2C3E50')
.text(kpi.value, x + 10, y + 30);
// KPI Change
const changeColor = kpi.trend === 'up' ? '#27AE60' : kpi.trend === 'down' ? '#E74C3C' : '#7F8C8D';
doc.fontSize(12)
.fillColor(changeColor)
.text(kpi.change, x + 10, y + 70);
});
// Adjust Y position after KPI grid
doc.y += Math.ceil(reportData.kpis.length / 2) * (kpiHeight + spacing) + spacing;
// Detailed Sections
reportData.sections.forEach(async (section) => {
doc.addPage();
// Section Title
doc.fontSize(20)
.fillColor('#2C3E50')
.text(section.title, { underline: true })
.moveDown();
// Section Content
doc.fontSize(11)
.fillColor('#34495E')
.text(section.content, { align: 'justify', lineGap: 4 });
// Add chart if section has data
if (section.chartData) {
doc.moveDown();
const chartImage = await generateChart(section.chartData);
doc.image(chartImage, 100, doc.y, { width: 400 });
doc.moveDown(15); // Space for chart
}
// Add table if section has table data
if (section.tableData) {
doc.moveDown();
addTable(doc, section.tableData);
}
});
// Recommendations
doc.addPage();
doc.fontSize(20)
.fillColor('#2C3E50')
.text('Recommendations', { underline: true })
.moveDown();
reportData.recommendations.forEach((rec, index) => {
doc.fontSize(11)
.fillColor('#34495E')
.text(`${index + 1}. ${rec}`, { lineGap: 6 });
doc.moveDown(0.5);
});
// Footer on all pages
const pages = doc.bufferedPageRange();
for (let i = 0; i < pages.count; i++) {
doc.switchToPage(i);
doc.fontSize(8)
.fillColor('#95A5A6')
.text(
`${reportData.company} | Confidential | Page ${i + 1} of ${pages.count}`,
50,
doc.page.height - 50,
{ align: 'center', width: doc.page.width - 100 }
);
}
doc.end();
return new Promise((resolve, reject) => {
stream.on('finish', () => resolve(outputPath));
stream.on('error', reject);
});
}
async function generateChart(chartData) {
const chartCanvas = new ChartJS.ChartJSNodeCanvas({ width: 800, height: 400 });
const configuration = {
type: chartData.type || 'bar',
data: {
labels: chartData.labels,
datasets: [{
label: chartData.label,
data: chartData.values,
backgroundColor: chartData.colors || '#3498DB',
borderColor: '#2C3E50',
borderWidth: 1
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true
}
}
}
};
return await chartCanvas.renderToBuffer(configuration);
}
function addTable(doc, tableData) {
const tableTop = doc.y;
const cellPadding = 8;
const rowHeight = 25;
const colWidth = (doc.page.width - 100) / tableData.headers.length;
// Header row
doc.fillColor('#34495E')
.rect(50, tableTop, doc.page.width - 100, rowHeight)
.fill();
doc.fillColor('#FFFFFF')
.fontSize(10);
tableData.headers.forEach((header, i) => {
doc.text(header, 50 + i * colWidth + cellPadding, tableTop + cellPadding, {
width: colWidth - cellPadding * 2
});
});
// Data rows
doc.fillColor('#34495E');
tableData.rows.forEach((row, rowIndex) => {
const y = tableTop + (rowIndex + 1) * rowHeight;
// Alternate row colors
if (rowIndex % 2 === 1) {
doc.fillColor('#F8F9FA')
.rect(50, y, doc.page.width - 100, rowHeight)
.fill();
}
doc.fillColor('#2C3E50');
row.forEach((cell, cellIndex) => {
doc.text(cell, 50 + cellIndex * colWidth + cellPadding, y + cellPadding, {
width: colWidth - cellPadding * 2
});
});
});
doc.y = tableTop + (tableData.rows.length + 1) * rowHeight + 10;
}
工作流 2:月度绩效报告
目的: 自动生成包含指标、趋势和比较的月度报告
步骤:
- 查询本月和之前各期的数据
- 计算环比和同比变化
- 生成趋势图
- 找出表现最佳者和需要关注的领域
- 创建比较表格
- 根据数据模式添加叙述性见解
- 排版并分发报告
实现:
async function generateMonthlyReport(month, year, dataSource) {
// Fetch data
const currentData = await dataSource.getMonthData(month, year);
const previousMonthData = await dataSource.getMonthData(month - 1, year);
const lastYearData = await dataSource.getMonthData(month, year - 1);
// Calculate metrics
const metrics = {
revenue: {
current: currentData.revenue,
momChange: calculatePercentChange(currentData.revenue, previousMonthData.revenue),
yoyChange: calculatePercentChange(currentData.revenue, lastYearData.revenue)
},
customers: {
current: currentData.customers,
momChange: calculatePercentChange(currentData.customers, previousMonthData.customers),
yoyChange: calculatePercentChange(currentData.customers, lastYearData.customers)
},
avgOrderValue: {
current: currentData.revenue / currentData.orders,
momChange: calculatePercentChange(
currentData.revenue / currentData.orders,
previousMonthData.revenue / previousMonthData.orders
)
}
};
// Generate report data
const reportData = {
title: 'Monthly Performance Report',
subtitle: `${getMonthName(month)} ${year}`,
period: `${getMonthName(month)} 1-${new Date(year, month, 0).getDate()}, ${year}`,
company: 'Your Company',
executiveSummary: generateExecutiveSummary(metrics, currentData),
kpis: [
{
label: 'Total Revenue',
value: formatCurrency(metrics.revenue.current),
change: `${metrics.revenue.momChange > 0 ? '+' : ''}${metrics.revenue.momChange.toFixed(1)}% MoM`,
trend: metrics.revenue.momChange > 0 ? 'up' : 'down'
},
{
label: 'New Customers',
value: metrics.customers.current.toLocaleString(),
change: `${metrics.customers.yoyChange > 0 ? '+' : ''}${metrics.customers.yoyChange.toFixed(1)}% YoY`,
trend: metrics.customers.yoyChange > 0 ? 'up' : 'down'
},
{
label: 'Avg Order Value',
value: formatCurrency(metrics.avgOrderValue.current),
change: `${metrics.avgOrderValue.momChange > 0 ? '+' : ''}${metrics.avgOrderValue.momChange.toFixed(1)}% MoM`,
trend: metrics.avgOrderValue.momChange > 0 ? 'up' : 'down'
},
{
label: 'Customer Satisfaction',
value: `${currentData.satisfaction}%`,
change: 'Stable',
trend: 'neutral'
}
],
sections: [
{
title: 'Revenue Analysis',
content: `Revenue for ${getMonthName(month)} totaled ${formatCurrency(metrics.revenue.current)}, representing a ${Math.abs(metrics.revenue.momChange).toFixed(1)}% ${metrics.revenue.momChange > 0 ? 'increase' : 'decrease'} compared to last month...`,
chartData: {
type: 'line',
labels: getLast6Months(month, year),
values: await dataSource.getRevenueHistory(6),
label: 'Revenue Trend'
}
},
{
title: 'Customer Acquisition',
content: `Customer acquisition efforts resulted in ${metrics.customers.current} new customers this month...`,
tableData: {
headers: ['Channel', 'Customers', 'Cost', 'CAC'],
rows: currentData.acquisitionChannels.map(channel => [
channel.name,
channel.customers.toString(),
formatCurrency(channel.cost),
formatCurrency(channel.cost / channel.customers)
])
}
},
{
title: 'Product Performance',
content: 'Top-performing products and categories for the period...',
chartData: {
type: 'bar',
labels: currentData.topProducts.map(p => p.name),
values: currentData.topProducts.map(p => p.revenue),
label: 'Revenue by Product'
}
}
],
recommendations: generateRecommendations(metrics, currentData)
};
return await generateExecutiveReport(reportData, `./reports/monthly-${year}-${month}.pdf`);
}
function calculatePercentChange(current, previous) {
if (previous === 0) return 0;
return ((current - previous) / previous) * 100;
}
function formatCurrency(amount) {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(amount);
}
function generateExecutiveSummary(metrics, data) {
const revenueDirection = metrics.revenue.momChange > 0 ? 'increased' : 'decreased';
const revenuePercent = Math.abs(metrics.revenue.momChange).toFixed(1);
return `
Revenue ${revenueDirection} by ${revenuePercent}% compared to the previous month,
driven primarily by ${data.primaryGrowthDriver}. Customer acquisition
${metrics.customers.momChange > 0 ? 'exceeded' : 'fell short of'} targets, with
${metrics.customers.current} new customers. The average order value ${metrics.avgOrderValue.momChange > 0 ? 'improved' : 'declined'}
by ${Math.abs(metrics.avgOrderValue.momChange).toFixed(1)}%, indicating
${metrics.avgOrderValue.momChange > 0 ? 'successful upselling efforts' : 'price pressure in the market'}.
`.trim();
}
function generateRecommendations(metrics, data) {
const recommendations = [];
if (metrics.revenue.momChange < -5) {
recommendations.push('Consider launching promotional campaigns to boost short-term revenue');
}
if (metrics.customers.momChange < 0) {
recommendations.push('Increase marketing spend in top-performing acquisition channels');
}
if (metrics.avgOrderValue.momChange < 0) {
recommendations.push('Implement cross-selling and bundling strategies to increase order values');
}
if (data.satisfaction < 80) {
recommendations.push('Focus on improving customer satisfaction through enhanced support and service quality');
}
return recommendations;
}
工作流 3:项目状态报告
目的: 生成包含里程碑、任务和风险的项目进度报告
步骤:
- 收集项目数据(任务、里程碑、团队成员)
- 计算完成百分比和时间进度状态
- 找出已完成、进行中和逾期的事项
- 列出风险和问题
- 创建甘特图或时间线可视化内容
- 添加团队成员的贡献
- 纳入后续步骤和行动事项
- 排版成适合利益相关者阅读的报告
实现:
async function generateProjectReport(projectData, outputPath) {
const completionRate = calculateCompletionRate(projectData.tasks);
const scheduleStatus = assessScheduleStatus(projectData);
const reportData = {
title: `Project Status Report: ${projectData.name}`,
subtitle: `Status as of ${new Date().toLocaleDateString()}`,
period: `${projectData.startDate} - ${projectData.endDate}`,
company: projectData.organization,
executiveSummary: `
The ${projectData.name} project is currently ${completionRate}% complete.
The project is ${scheduleStatus.status} schedule by ${scheduleStatus.variance} days.
${projectData.risks.length} risks have been identified and are being actively managed.
`,
kpis: [
{
label: 'Completion',
value: `${completionRate}%`,
change: `${projectData.tasksCompletedThisWeek} tasks completed this week`,
trend: completionRate > 50 ? 'up' : 'neutral'
},
{
label: 'Schedule Status',
value: scheduleStatus.status,
change: `${Math.abs(scheduleStatus.variance)} days ${scheduleStatus.variance > 0 ? 'ahead' : 'behind'}`,
trend: scheduleStatus.variance >= 0 ? 'up' : 'down'
},
{
label: 'Budget',
value: formatCurrency(projectData.spentBudget),
change: `${((projectData.spentBudget / projectData.totalBudget) * 100).toFixed(1)}% of total`,
trend: projectData.spentBudget <= projectData.totalBudget ? 'up' : 'down'
},
{
label: 'Team Health',
value: projectData.teamMorale,
change: 'Based on last survey',
trend: 'neutral'
}
],
sections: [
{
title: 'Milestones',
content: 'Current status of project milestones:',
tableData: {
headers: ['Milestone', 'Due Date', 'Status', 'Completion'],
rows: projectData.milestones.map(m => [
m.name,
m.dueDate,
m.status,
`${m.completion}%`
])
}
},
{
title: 'Completed This Period',
content: projectData.completedTasks.map(t => `• ${t.name}`).join('\n')
},
{
title: 'In Progress',
content: projectData.inProgressTasks.map(t => `• ${t.name} (${t.assignee})`).join('\n')
},
{
title: 'Risks and Issues',
content: projectData.risks.length > 0
? projectData.risks.map((r, i) => `${i + 1}. ${r.description} (Impact: ${r.impact}, Mitigation: ${r.mitigation})`).join('\n\n')
: 'No significant risks identified at this time.'
}
],
recommendations: [
...projectData.nextSteps.map(step => `Complete: ${step}`),
...projectData.risks.filter(r => r.impact === 'High').map(r => `Address high-impact risk: ${r.description}`)
]
};
return await generateExecutiveReport(reportData, outputPath);
}
function calculateCompletionRate(tasks) {
const total = tasks.length;
const completed = tasks.filter(t => t.status === 'completed').length;
return Math.round((completed / total) * 100);
}
function assessScheduleStatus(projectData) {
const today = new Date();
const expectedCompletion = new Date(projectData.expectedCompletionDate);
const originalEnd = new Date(projectData.endDate);
const daysVariance = Math.round((expectedCompletion - originalEnd) / (1000 * 60 * 60 * 24));
return {
status: daysVariance <= 0 ? 'On' : 'Behind',
variance: daysVariance
};
}
工作流 4:财务报告生成
目的: 创建财务报表和分析报告
步骤:
- 从会计系统提取财务数据
- 计算财务比率和指标
- 生成资产负债表、损益表和现金流量表
- 创建趋势分析和比较
- 添加收入、支出和利润率图表
- 对差异添加注释和说明
- 按照会计准则排版
- 规范排版后导出为 PDF
工作流 5:自动化报告排期
目的: 安排并自动生成定期报告
步骤:
- 定义报告排期(每日、每周、每月)
- 设置数据源连接
- 创建报告模板
- 安排报告生成任务
- 按指定间隔自动生成报告
- 通过电子邮件或文件存储进行分发
- 归档生成的报告
- 完成后发送通知
实现:
const cron = require('node-cron');
const nodemailer = require('nodemailer');
class ReportScheduler {
constructor() {
this.schedules = [];
}
scheduleReport(config) {
const job = cron.schedule(config.cronExpression, async () => {
try {
console.log(`Generating ${config.reportType} report...`);
// Generate report
const reportPath = await this.generateReport(config);
// Send report
if (config.emailRecipients) {
await this.emailReport(reportPath, config.emailRecipients, config);
}
// Archive
if (config.archivePath) {
await this.archiveReport(reportPath, config.archivePath);
}
console.log(`Report generated successfully: ${reportPath}`);
} catch (error) {
console.error(`Report generation failed: ${error.message}`);
this.notifyFailure(config, error);
}
});
this.schedules.push({ config, job });
}
async generateReport(config) {
switch (config.reportType) {
case 'monthly':
return await generateMonthlyReport(new Date().getMonth(), new Date().getFullYear(), config.dataSource);
case 'project':
return await generateProjectReport(await config.dataSource.getProjectData(), config.outputPath);
default:
throw new Error(`Unknown report type: ${config.reportType}`);
}
}
async emailReport(reportPath, recipients, config) {
const transporter = nodemailer.createTransporter(config.emailConfig);
await transporter.sendMail({
from: config.emailConfig.from,
to: recipients.join(', '),
subject: `${config.reportName} - ${new Date().toLocaleDateString()}`,
html: `
<p>Please find attached the automated ${config.reportName}.</p>
<p>This report was generated on ${new Date().toLocaleString()}.</p>
`,
attachments: [
{
filename: `${config.reportName}.pdf`,
path: reportPath
}
]
});
}
async archiveReport(reportPath, archivePath) {
const fs = require('fs').promises;
const path = require('path');
const timestamp = new Date().toISOString().replace(/:/g, '-');
const archiveFilename = `report-${timestamp}.pdf`;
await fs.copyFile(reportPath, path.join(archivePath, archiveFilename));
}
}
// Usage:
const scheduler = new ReportScheduler();
scheduler.scheduleReport({
reportType: 'monthly',
reportName: 'Monthly Performance Report',
cronExpression: '0 9 1 * *', // 9 AM on 1st of each month
dataSource: myDataSource,
outputPath: './reports/monthly-latest.pdf',
emailRecipients: ['[email protected]', '[email protected]'],
emailConfig: { /* nodemailer config */ },
archivePath: './reports/archive'
});
快速参考
| 操作 | 命令/触发语 |
|---|---|
| 生成报告 | "create [report type] for [period]" |
| 执行报告 | "generate executive summary" |
| 月度报告 | "create monthly performance report" |
| 项目状态 | "generate project status report" |
| 添加图表 | "include [chart type] in report" |
| 安排报告 | "automate [report] generation" |
| 自定义报告 | "build custom report with [sections]" |
最佳实践
- 数据准确性: 生成报告前验证所有数据源
- 格式一致: 为定期报告类型使用模板
- 执行摘要: 始终先呈现关键要点,方便繁忙的高管快速了解
- 可视化优先: 使用图表快速传达趋势
- 可执行的洞察: 提供具体建议,而不只是展示数据
- 版本控制: 跟踪报告版本和更改
- 分发: 了解受众并相应调整内容深度
- 及时性: 按固定周期生成报告
- 背景信息: 提供对比(环比、同比、与目标对比)
- 专业布局: 保持品牌形象和格式一致
- 无障碍性: 确保报告在所有设备上均易于阅读
- 归档: 保留历史报告以进行趋势分析
常见模式
季度业务回顾:
const qbrData = {
title: 'Q4 2025 Business Review',
sections: ['Financial Performance', 'Operational Metrics', 'Market Position', 'Strategic Initiatives'],
compareAgainst: ['Q3 2025', 'Q4 2024'],
includeForecast: true,
forecastPeriod: 'Q1 2026'
};
销售仪表板:
const salesReport = {
kpis: ['Revenue', 'Deals Closed', 'Pipeline Value', 'Conversion Rate'],
breakdowns: ['by Region', 'by Product', 'by Sales Rep'],
trends: ['Last 12 months', 'Year over Year'],
targets: true
};
依赖项
安装所需的软件包:
npm install pdfkit chartjs-node-canvas
npm install node-cron nodemailer # For scheduling and email
npm install exceljs # For Excel export option
npm install handlebars # For templating
错误处理
- 数据缺失: 妥善处理 null/undefined 值
- 数据源故障: 为 API/数据库查询设置重试逻辑
- 图表生成: 如果图表生成失败,则回退为表格
- 文件系统: 写入文件前检查权限
- 邮件发送失败: 记录错误并重试发送
- 计划冲突: 避免报告生成任务重叠
性能提示
- 缓存图表图像以便重复使用
- 对大型数据集使用流式处理
- 尽可能并行生成图表
- 嵌入前压缩图像
- 对超大型报告使用分页
- 预先聚合数据以提升性能
高级功能
交互式 HTML 报告:
const html = `
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<h1>${reportData.title}</h1>
<canvas id="chart"></canvas>
<script>
// Interactive Chart.js visualization
</script>
</body>
</html>
`;
多格式导出:
async function exportReport(reportData, formats = ['pdf', 'html', 'xlsx']) {
const outputs = {};
if (formats.includes('pdf')) outputs.pdf = await generatePDF(reportData);
if (formats.includes('html')) outputs.html = await generateHTML(reportData);
if (formats.includes('xlsx')) outputs.xlsx = await generateExcel(reportData);
return outputs;
}
评论
支持 Markdown