feat: improve report generation workflow

This commit is contained in:
2026-06-26 00:11:29 +08:00
parent 1c5841833f
commit 0b2a8b0190
27 changed files with 2662 additions and 717 deletions
+44 -36
View File
@@ -9,7 +9,6 @@ import type { Repository } from 'typeorm'
import { TemplateEntitySchema, type TemplateEntity } from '../entities/TemplateEntity'
import type {
TemplateFieldMapping,
TemplateInput,
TemplateItem,
TemplatePlaceholder,
@@ -33,8 +32,15 @@ const BUILT_IN_PPT_PLACEHOLDERS = new Set([
'hobby',
'game',
'food',
'class_image'
'class_image',
'meImage',
'me_image',
'workImage1',
'work_image_1',
'workImage2',
'work_image_2'
])
const BUILT_IN_PPT_IMAGE_PLACEHOLDERS = ['class_image', 'meImage', 'workImage1', 'workImage2']
function getTemplateStoragePath(): string {
return join(app.getPath('userData'), TEMPLATE_FOLDER_NAME)
@@ -82,23 +88,6 @@ function parseStoredPlaceholders(value: string): TemplatePlaceholder[] {
}
}
function parseStoredFieldMappings(value: string): TemplateFieldMapping {
try {
const parsed = JSON.parse(value)
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
? Object.fromEntries(
Object.entries(parsed).filter(
(entry): entry is [string, string] =>
typeof entry[0] === 'string' && typeof entry[1] === 'string'
)
)
: {}
} catch {
return {}
}
}
function mapTemplateEntity(entity: TemplateEntity): TemplateItem {
return {
id: entity.id,
@@ -110,7 +99,6 @@ function mapTemplateEntity(entity: TemplateEntity): TemplateItem {
filePath: resolveTemplateFilePath(entity.filePath),
fileExtension: entity.fileExtension,
placeholders: parseStoredPlaceholders(entity.placeholders),
fieldMappings: parseStoredFieldMappings(entity.fieldMappings),
createdAt: entity.createdAt,
updatedAt: entity.updatedAt
}
@@ -161,6 +149,25 @@ function isPictureShape(slideXml: string, shapeNameIndex: number): boolean {
)
}
function getXmlAttributeValue(xml: string, attributeName: string): string | null {
const match = xml.match(new RegExp(`${attributeName}="([^"]*)"`))
return match ? decodeXml(match[1]) : null
}
function normalizePlaceholderName(name: string): string {
return decodeXml(name).trim().replace(/^field[:_-]/i, '')
}
function getKnownPlaceholderName(name: string): string | null {
const placeholderName = normalizePlaceholderName(name)
if (BUILT_IN_PPT_PLACEHOLDERS.has(name) || BUILT_IN_PPT_PLACEHOLDERS.has(placeholderName)) {
return placeholderName
}
return null
}
function upsertPlaceholder(
placeholdersByName: Map<string, TemplatePlaceholder>,
name: string,
@@ -195,17 +202,24 @@ async function parsePptxPlaceholders(filePath: string): Promise<TemplatePlacehol
const slideXml = await slideFile.async('text')
for (const shapeMatch of slideXml.matchAll(/<p:cNvPr[^>]*name="([^"]+)"/g)) {
const rawName = shapeMatch[1]
const name = decodeXml(rawName)
for (const shapeMatch of slideXml.matchAll(/<p:cNvPr[^>]*>/g)) {
const shapeXml = shapeMatch[0]
const candidateNames = [
getXmlAttributeValue(shapeXml, 'name'),
getXmlAttributeValue(shapeXml, 'descr'),
getXmlAttributeValue(shapeXml, 'title')
].filter((value): value is string => Boolean(value))
const placeholderName = candidateNames
.map(getKnownPlaceholderName)
.find((value): value is string => Boolean(value))
if (!BUILT_IN_PPT_PLACEHOLDERS.has(name) && !/^field[:_-]/i.test(name)) {
if (!placeholderName) {
continue
}
upsertPlaceholder(
placeholdersByName,
name.replace(/^field[:_-]/i, ''),
placeholderName,
isPictureShape(slideXml, shapeMatch.index ?? 0) ? 'image' : 'text'
)
}
@@ -215,6 +229,10 @@ async function parsePptxPlaceholders(filePath: string): Promise<TemplatePlacehol
}
}
for (const placeholderName of BUILT_IN_PPT_IMAGE_PLACEHOLDERS) {
upsertPlaceholder(placeholdersByName, placeholderName, 'image')
}
return [...placeholdersByName.values()].sort((first, second) =>
first.name.localeCompare(second.name, 'zh-CN')
)
@@ -309,16 +327,6 @@ export async function saveTemplate(input: TemplateInput): Promise<TemplateItem>
: existingTemplate
? parseStoredPlaceholders(existingTemplate.placeholders)
: []
const placeholderNames = new Set(placeholders.map((placeholder) => placeholder.name))
const previousMappings = existingTemplate
? parseStoredFieldMappings(existingTemplate.fieldMappings)
: {}
const inputMappings = input.fieldMappings ?? previousMappings
const fieldMappings = Object.fromEntries(
Object.entries(inputMappings)
.map(([placeholder, field]) => [placeholder.trim(), field.trim()])
.filter(([placeholder, field]) => placeholderNames.has(placeholder) && field.length > 0)
)
const entity: TemplateEntity = {
id,
@@ -330,7 +338,7 @@ export async function saveTemplate(input: TemplateInput): Promise<TemplateItem>
filePath: nextStoredFilePath,
fileExtension: extension,
placeholders: JSON.stringify(placeholders),
fieldMappings: JSON.stringify(fieldMappings),
fieldMappings: '{}',
createdAt: existingTemplate?.createdAt ?? now,
updatedAt: now
}