feat: 实现动态 DOCX 模板与样式映射

This commit is contained in:
SkyJourney
2026-07-30 14:10:43 +08:00
parent fe5d67fb6d
commit fa159d916f
41 changed files with 3610 additions and 7 deletions
+121
View File
@@ -0,0 +1,121 @@
import { z } from "zod";
const colorSchema = z
.string()
.regex(/^#[0-9a-f]{6}$/iu, "DOCX 使");
const fontNameSchema = z
.string()
.trim()
.min(1)
.max(100)
.regex(
/^[\p{L}\p{N}\s._-]+$/u,
"DOCX 字体名称包含不支持的字符"
);
const pointSizeSchema = z.number().min(6).max(72);
const spacingSchema = z.number().min(0).max(144);
export const docxStylePresetSchema = z.enum([
"general",
"technical",
"official",
"formal",
"tender"
]);
export type DocxStylePreset = z.infer<
typeof docxStylePresetSchema
>;
export const docxFontFamilySchema = z.object({
latin: fontNameSchema,
eastAsia: fontNameSchema,
complexScript: fontNameSchema.optional()
});
export type DocxFontFamily = z.infer<
typeof docxFontFamilySchema
>;
const docxBodyStyleSchema = z.object({
fonts: docxFontFamilySchema.optional(),
sizePt: pointSizeSchema.optional(),
color: colorSchema.optional(),
lineSpacing: z.number().min(1).max(3).optional(),
firstLineIndentChars: z.number().min(0).max(10).optional(),
spacingBeforePt: spacingSchema.optional(),
spacingAfterPt: spacingSchema.optional()
});
const docxHeadingStyleSchema = z.object({
fonts: docxFontFamilySchema.optional(),
sizesPt: z
.tuple([
pointSizeSchema,
pointSizeSchema,
pointSizeSchema,
pointSizeSchema,
pointSizeSchema,
pointSizeSchema
])
.optional(),
color: colorSchema.optional(),
bold: z.boolean().optional(),
spacingBeforePt: spacingSchema.optional(),
spacingAfterPt: spacingSchema.optional()
});
const docxCodeStyleSchema = z.object({
fonts: docxFontFamilySchema.optional(),
sizePt: pointSizeSchema.optional(),
color: colorSchema.optional(),
backgroundColor: colorSchema.optional(),
borderColor: colorSchema.optional(),
lineSpacing: z.number().min(1).max(3).optional()
});
const docxBlockStyleSchema = z.object({
color: colorSchema.optional(),
backgroundColor: colorSchema.optional(),
borderColor: colorSchema.optional(),
leftIndentChars: z.number().min(0).max(20).optional(),
italic: z.boolean().optional()
});
const docxTableStyleSchema = z.object({
fonts: docxFontFamilySchema.optional(),
sizePt: pointSizeSchema.optional(),
color: colorSchema.optional(),
headerColor: colorSchema.optional(),
headerBackgroundColor: colorSchema.optional(),
borderColor: colorSchema.optional(),
cellMarginMm: z.number().min(0).max(20).optional()
});
const docxCaptionStyleSchema = z.object({
fonts: docxFontFamilySchema.optional(),
sizePt: pointSizeSchema.optional(),
color: colorSchema.optional(),
italic: z.boolean().optional(),
alignment: z.enum(["left", "center", "right"]).optional()
});
export const docxThemeStyleSchema = z.object({
preset: docxStylePresetSchema,
body: docxBodyStyleSchema.optional(),
headings: docxHeadingStyleSchema.optional(),
code: docxCodeStyleSchema.optional(),
blockQuote: docxBlockStyleSchema.optional(),
table: docxTableStyleSchema.optional(),
caption: docxCaptionStyleSchema.optional(),
hyperlink: z
.object({
color: colorSchema.optional(),
underline: z.boolean().optional()
})
.optional()
});
export type DocxThemeStyle = z.infer<
typeof docxThemeStyleSchema
>;
+1
View File
@@ -1,5 +1,6 @@
export * from "./document.js";
export * from "./docx.js";
export * from "./docx-style.js";
export * from "./document-profile.js";
export * from "./document-link.js";
export * from "./export-config.js";
+2
View File
@@ -1,5 +1,6 @@
import { z } from "zod";
import { documentProfileNameSchema } from "./document-profile.js";
import { docxThemeStyleSchema } from "./docx-style.js";
import {
footerSchema,
headerSchema,
@@ -50,6 +51,7 @@ export const themeManifestSchema = z.object({
footer: footerSchema.optional()
})
.optional(),
docxStyle: docxThemeStyleSchema.optional(),
bundled: z.boolean()
});