Files
Mycontent/lib/documentation/docx.ts
2025-12-29 14:01:37 +08:00

273 lines
6.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}