273 lines
6.6 KiB
TypeScript
273 lines
6.6 KiB
TypeScript
|
|
import {
|
|||
|
|
Document,
|
|||
|
|
HeadingLevel,
|
|||
|
|
ImageRun,
|
|||
|
|
Packer,
|
|||
|
|
Paragraph,
|
|||
|
|
TableOfContents,
|
|||
|
|
TextRun,
|
|||
|
|
AlignmentType,
|
|||
|
|
PageBreak,
|
|||
|
|
BorderStyle,
|
|||
|
|
} from "docx"
|
|||
|
|
import type { DocumentationPage } from "@/lib/documentation/catalog"
|
|||
|
|
|
|||
|
|
export type DocumentationRenderItem = {
|
|||
|
|
page: DocumentationPage
|
|||
|
|
screenshotPng?: Buffer
|
|||
|
|
error?: string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function groupBy<T>(items: T[], getKey: (item: T) => string): Record<string, T[]> {
|
|||
|
|
const map: Record<string, T[]> = {}
|
|||
|
|
for (const item of items) {
|
|||
|
|
const key = getKey(item)
|
|||
|
|
if (!map[key]) map[key] = []
|
|||
|
|
map[key].push(item)
|
|||
|
|
}
|
|||
|
|
return map
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export async function renderDocumentationDocx(items: DocumentationRenderItem[]) {
|
|||
|
|
const now = new Date()
|
|||
|
|
const title = "Soul派对 - 应用功能文档"
|
|||
|
|
const subtitle = `生成时间:${now.toLocaleString("zh-CN", { hour12: false })}`
|
|||
|
|
const version = `文档版本:v1.0`
|
|||
|
|
|
|||
|
|
const children: Paragraph[] = []
|
|||
|
|
|
|||
|
|
children.push(new Paragraph({ text: "" }))
|
|||
|
|
children.push(new Paragraph({ text: "" }))
|
|||
|
|
children.push(
|
|||
|
|
new Paragraph({
|
|||
|
|
text: title,
|
|||
|
|
heading: HeadingLevel.TITLE,
|
|||
|
|
alignment: AlignmentType.CENTER,
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
children.push(
|
|||
|
|
new Paragraph({
|
|||
|
|
children: [new TextRun({ text: subtitle, size: 24 })],
|
|||
|
|
alignment: AlignmentType.CENTER,
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
children.push(
|
|||
|
|
new Paragraph({
|
|||
|
|
children: [new TextRun({ text: version, size: 20, color: "666666" })],
|
|||
|
|
alignment: AlignmentType.CENTER,
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
children.push(new Paragraph({ text: "" }))
|
|||
|
|
children.push(new Paragraph({ text: "" }))
|
|||
|
|
|
|||
|
|
children.push(
|
|||
|
|
new Paragraph({
|
|||
|
|
text: "文档概述",
|
|||
|
|
heading: HeadingLevel.HEADING_1,
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
children.push(
|
|||
|
|
new Paragraph({
|
|||
|
|
children: [
|
|||
|
|
new TextRun({
|
|||
|
|
text: "本文档自动生成,包含应用程序所有核心页面的功能说明与界面截图。文档按功能模块分组,便于快速查阅和理解应用结构。",
|
|||
|
|
}),
|
|||
|
|
],
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
children.push(
|
|||
|
|
new Paragraph({
|
|||
|
|
children: [
|
|||
|
|
new TextRun({
|
|||
|
|
text: `共包含 ${items.length} 个页面,${Object.keys(groupBy(items, (i) => i.page.group)).length} 个功能模块。`,
|
|||
|
|
}),
|
|||
|
|
],
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
children.push(new Paragraph({ text: "" }))
|
|||
|
|
|
|||
|
|
children.push(
|
|||
|
|
new Paragraph({
|
|||
|
|
text: "目录",
|
|||
|
|
heading: HeadingLevel.HEADING_1,
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
children.push(
|
|||
|
|
new TableOfContents("目录", {
|
|||
|
|
hyperlink: true,
|
|||
|
|
headingStyleRange: "1-3",
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
children.push(
|
|||
|
|
new Paragraph({
|
|||
|
|
children: [new PageBreak()],
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
const grouped = groupBy(items, (i) => i.page.group)
|
|||
|
|
const groupNames = Object.keys(grouped)
|
|||
|
|
|
|||
|
|
let pageNumber = 1
|
|||
|
|
for (const groupName of groupNames) {
|
|||
|
|
children.push(
|
|||
|
|
new Paragraph({
|
|||
|
|
text: groupName,
|
|||
|
|
heading: HeadingLevel.HEADING_1,
|
|||
|
|
border: {
|
|||
|
|
bottom: { color: "2DD4BF", size: 6, style: BorderStyle.SINGLE },
|
|||
|
|
},
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
children.push(new Paragraph({ text: "" }))
|
|||
|
|
|
|||
|
|
for (const item of grouped[groupName]) {
|
|||
|
|
const { page } = item
|
|||
|
|
|
|||
|
|
children.push(
|
|||
|
|
new Paragraph({
|
|||
|
|
text: `${pageNumber}. ${page.title}`,
|
|||
|
|
heading: HeadingLevel.HEADING_2,
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
if (page.subtitle) {
|
|||
|
|
children.push(
|
|||
|
|
new Paragraph({
|
|||
|
|
children: [new TextRun({ text: page.subtitle, italics: true, color: "666666" })],
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
children.push(
|
|||
|
|
new Paragraph({
|
|||
|
|
children: [
|
|||
|
|
new TextRun({ text: "页面路径:", bold: true }),
|
|||
|
|
new TextRun({ text: page.path, color: "2563EB" }),
|
|||
|
|
],
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
if (page.caption) {
|
|||
|
|
children.push(new Paragraph({ text: "" }))
|
|||
|
|
children.push(
|
|||
|
|
new Paragraph({
|
|||
|
|
children: [new TextRun({ text: "功能说明:", bold: true })],
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
children.push(
|
|||
|
|
new Paragraph({
|
|||
|
|
children: [new TextRun({ text: page.caption })],
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
children.push(new Paragraph({ text: "" }))
|
|||
|
|
|
|||
|
|
if (item.error) {
|
|||
|
|
children.push(
|
|||
|
|
new Paragraph({
|
|||
|
|
children: [
|
|||
|
|
new TextRun({ text: "截图状态:", bold: true }),
|
|||
|
|
new TextRun({ text: `失败 - ${item.error}`, color: "DC2626" }),
|
|||
|
|
],
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
} else if (item.screenshotPng) {
|
|||
|
|
children.push(
|
|||
|
|
new Paragraph({
|
|||
|
|
children: [new TextRun({ text: "界面截图:", bold: true })],
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
children.push(new Paragraph({ text: "" }))
|
|||
|
|
children.push(
|
|||
|
|
new Paragraph({
|
|||
|
|
children: [
|
|||
|
|
new ImageRun({
|
|||
|
|
data: item.screenshotPng,
|
|||
|
|
transformation: { width: 320, height: 693 },
|
|||
|
|
}),
|
|||
|
|
],
|
|||
|
|
alignment: AlignmentType.CENTER,
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
children.push(
|
|||
|
|
new Paragraph({
|
|||
|
|
children: [new TextRun({ text: `图${pageNumber}: ${page.title}界面`, size: 20, color: "666666" })],
|
|||
|
|
alignment: AlignmentType.CENTER,
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
children.push(new Paragraph({ text: "" }))
|
|||
|
|
children.push(new Paragraph({ text: "" }))
|
|||
|
|
pageNumber++
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
children.push(
|
|||
|
|
new Paragraph({
|
|||
|
|
children: [new PageBreak()],
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
children.push(
|
|||
|
|
new Paragraph({
|
|||
|
|
text: "附录",
|
|||
|
|
heading: HeadingLevel.HEADING_1,
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
children.push(
|
|||
|
|
new Paragraph({
|
|||
|
|
children: [new TextRun({ text: "技术说明", bold: true })],
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
children.push(
|
|||
|
|
new Paragraph({
|
|||
|
|
children: [
|
|||
|
|
new TextRun({
|
|||
|
|
text: "• 截图尺寸:430×932像素 (iPhone 14 Pro Max)",
|
|||
|
|
}),
|
|||
|
|
],
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
children.push(
|
|||
|
|
new Paragraph({
|
|||
|
|
children: [
|
|||
|
|
new TextRun({
|
|||
|
|
text: "• 截图方式:Playwright自动化浏览器截图",
|
|||
|
|
}),
|
|||
|
|
],
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
children.push(
|
|||
|
|
new Paragraph({
|
|||
|
|
children: [
|
|||
|
|
new TextRun({
|
|||
|
|
text: "• 文档格式:Microsoft Word (.docx)",
|
|||
|
|
}),
|
|||
|
|
],
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
children.push(new Paragraph({ text: "" }))
|
|||
|
|
children.push(
|
|||
|
|
new Paragraph({
|
|||
|
|
children: [new TextRun({ text: "本文档由系统自动生成,如有问题请联系技术支持。", color: "666666", size: 20 })],
|
|||
|
|
alignment: AlignmentType.CENTER,
|
|||
|
|
}),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
const doc = new Document({
|
|||
|
|
title: "Soul派对 - 应用功能文档",
|
|||
|
|
description: "自动生成的应用功能文档",
|
|||
|
|
creator: "Soul派对文档生成器",
|
|||
|
|
sections: [
|
|||
|
|
{
|
|||
|
|
properties: {},
|
|||
|
|
children,
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return await Packer.toBuffer(doc)
|
|||
|
|
}
|