feat/printTemplate (#1345)

Co-authored-by: root <root@huan.daoyoucloud.com>
Reviewed-on: daoyoucloud/tachybase#1345
Reviewed-by: sealday <zhanglin@daoyoucloud.com>
Co-authored-by: yoona <1486343814@qq.com>
Co-committed-by: yoona <1486343814@qq.com>
This commit is contained in:
yoona 2024-07-18 16:25:07 +08:00 committed by sealday
parent eaa9bb0bd2
commit 73fdc790f4
3 changed files with 396 additions and 335 deletions

View File

@ -1,14 +1,14 @@
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { PassThrough, Readable } from 'stream'; import { Readable } from 'stream';
import { Context } from '@tachybase/actions'; import { Context } from '@tachybase/actions';
import { Model } from '@tachybase/database'; import { Model } from '@tachybase/database';
import Docxtemplater from 'docxtemplater'; import Docxtemplater from 'docxtemplater';
// 导入 Docxtemplater 的 InspectModule 类
import InspectModule from 'docxtemplater/js/inspect-module';
import PizZip from 'pizzip'; import PizZip from 'pizzip';
import { getTemplateParams } from './template-params'; // 导入 getTemplateParams
export const generate = async (ctx: Context) => { export const generate = async (ctx: Context) => {
const id = ctx.action.params.id; const id = ctx.action.params.id;
const repo = ctx.db.getRepository('templateManage'); const repo = ctx.db.getRepository('templateManage');
@ -22,47 +22,23 @@ export const generate = async (ctx: Context) => {
const rawTemplate = template.get(); const rawTemplate = template.get();
const templatePath = path.join(process.env.PWD, rawTemplate.template[0].get().url); const templatePath = path.join(process.env.PWD, rawTemplate.template[0].get().url);
const data = rawTemplate.testData; const data = rawTemplate.testData;
const outputPath = path.join(__dirname, './output.docx');
try { try {
// 获取模板中的占位符
const tags = (await getTemplateParams(templatePath)) as any;
console.log('Template tags:', tags);
// 验证数据是否包含所有占位符
const missingTags = tags.filter((tag) => !data.prototype.hasOwnProperty.call(tag.replace(/[{}]/g, '')));
if (missingTags.length > 0) {
// throw new Error(`Missing data for tags: ${missingTags.join(',')}`);
console.log('Missing data for tags');
}
// 生成定制的 docx 文件 // 生成定制的 docx 文件
const buffer = generateDocxFromTemplate(templatePath, data); const buffer = generateDocxFromTemplate(templatePath, data);
// 创建一个可读流 // 创建一个可读流
const readableStream = new Readable({ const readableStream = new Readable({
read(size) { read(size) {
// 将Buffer数据push到可读流中
this.push(buffer); this.push(buffer);
// 标记流的末尾
this.push(null); this.push(null);
}, },
}); });
ctx.withoutDataWrapping = true; ctx.withoutDataWrapping = true;
// 设置响应类型为二进制流
ctx.set('Content-Type', 'application/octet-stream'); ctx.set('Content-Type', 'application/octet-stream');
// 将可读流设置为响应体
ctx.body = readableStream; ctx.body = readableStream;
// // 确保生成的 docx 文件存在
// if (!fs.existsSync(outputPath)) {
// throw new Error('Generated DOCX file not found');
// }
// ctx.body = 'Word and PDF files generated';
} catch (error) { } catch (error) {
console.error('Error:', error); console.error('Error:', error);
ctx.status = 500; ctx.status = 500;
@ -70,6 +46,50 @@ export const generate = async (ctx: Context) => {
} }
}; };
export const getTags = async (ctx: Context) => {
const id = ctx.action.params.id;
const repo = ctx.db.getRepository('templateManage');
const template: Model = await repo.findOne({
filter: {
id,
},
appends: ['template'],
});
const rawTemplate = template.get();
const templatePath = path.join(process.env.PWD, rawTemplate.template[0].get().url);
try {
const content = fs.readFileSync(templatePath, 'binary');
const zip = new PizZip(content);
const iModule = new InspectModule();
const doc = new Docxtemplater(zip, { modules: [iModule] });
// 获取所有占位符
const allTags = iModule.getAllTags();
console.log('All tags in the document:', allTags);
// 将 tags 转换为 JSON 格式的字符串
const tagsJson = JSON.stringify(allTags, null, 2);
// 创建一个可读流
const readableStream = new Readable({
read(size) {
this.push(tagsJson); // 将 tagsJson 写入流中
this.push(null); // 结束流
},
});
// 设置响应头和响应内容
ctx.withoutDataWrapping = true;
ctx.set('Content-Type', 'application/json');
ctx.body = readableStream;
} catch (error) {
console.error('Error:', error);
ctx.status = 500;
ctx.body = 'Error generating tags: ' + error.message;
}
};
function generateDocxFromTemplate(templatePath, data): Buffer { function generateDocxFromTemplate(templatePath, data): Buffer {
try { try {
const content = fs.readFileSync(templatePath, 'binary'); const content = fs.readFileSync(templatePath, 'binary');
@ -77,17 +97,11 @@ function generateDocxFromTemplate(templatePath, data): Buffer {
const doc = new Docxtemplater(zip); const doc = new Docxtemplater(zip);
doc.setData(data); doc.setData(data);
try {
doc.render(); doc.render();
} catch (error) {
console.error('Error rendering template:', error);
throw error;
}
return doc.getZip().generate({ type: 'nodebuffer' }); return doc.getZip().generate({ type: 'nodebuffer' });
} catch (error) { } catch (error) {
console.error('Error generating DOCX from template:', error); console.error('Error generating DOCX from template:', error);
throw error; // Ensure errors are propagated correctly throw error;
} }
} }

View File

@ -1,6 +1,6 @@
import { Plugin } from '@tachybase/server'; import { Plugin } from '@tachybase/server';
import { generate } from './actions/printTemplates'; import { generate, getTags } from './actions/printTemplates';
export class PluginPrintTemplateServer extends Plugin { export class PluginPrintTemplateServer extends Plugin {
async afterAdd() {} async afterAdd() {}
@ -13,6 +13,7 @@ export class PluginPrintTemplateServer extends Plugin {
name: 'printTemplates', name: 'printTemplates',
actions: { actions: {
generate, generate,
getTags,
}, },
}); });
} }

File diff suppressed because it is too large Load Diff