feat: printTemplate (#1338)
Co-authored-by: root <root@huan.daoyoucloud.com> Co-authored-by: sealday <zhanglin@daoyoucloud.com> Reviewed-on: daoyoucloud/tachybase#1338 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:
parent
6a151a136c
commit
49a8e2e534
@ -0,0 +1,2 @@
|
||||
/node_modules
|
||||
/src
|
@ -0,0 +1 @@
|
||||
# @tachybase/plugin-print-template
|
2
packages/plugins/@tachybase/plugin-print-template/client.d.ts
vendored
Normal file
2
packages/plugins/@tachybase/plugin-print-template/client.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './dist/client';
|
||||
export { default } from './dist/client';
|
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/client/index.js');
|
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "@tachybase/plugin-print-template",
|
||||
"version": "0.21.73",
|
||||
"main": "dist/server/index.js",
|
||||
"dependencies": {
|
||||
"docxtemplater": "^3.48.0",
|
||||
"pizzip": "^3.1.7",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tachybase/client": "workspace:*",
|
||||
"@tachybase/server": "workspace:*",
|
||||
"@tachybase/test": "workspace:*"
|
||||
}
|
||||
}
|
2
packages/plugins/@tachybase/plugin-print-template/server.d.ts
vendored
Normal file
2
packages/plugins/@tachybase/plugin-print-template/server.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './dist/server';
|
||||
export { default } from './dist/server';
|
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/server/index.js');
|
@ -0,0 +1,21 @@
|
||||
import { Plugin } from '@tachybase/client';
|
||||
|
||||
export class PluginPrintTemplateClient extends Plugin {
|
||||
async afterAdd() {
|
||||
// await this.app.pm.add()
|
||||
}
|
||||
|
||||
async beforeLoad() {}
|
||||
|
||||
// You can get and modify the app instance here
|
||||
async load() {
|
||||
console.log(this.app);
|
||||
// this.app.addComponents({})
|
||||
// this.app.addScopes({})
|
||||
// this.app.addProvider()
|
||||
// this.app.addProviders()
|
||||
// this.app.router.add()
|
||||
}
|
||||
}
|
||||
|
||||
export default PluginPrintTemplateClient;
|
@ -0,0 +1,2 @@
|
||||
export * from './server';
|
||||
export { default } from './server';
|
@ -0,0 +1,93 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { PassThrough, Readable } from 'stream';
|
||||
import { Context } from '@tachybase/actions';
|
||||
import { Model } from '@tachybase/database';
|
||||
|
||||
import Docxtemplater from 'docxtemplater';
|
||||
import PizZip from 'pizzip';
|
||||
|
||||
import { getTemplateParams } from './template-params'; // 导入 getTemplateParams
|
||||
|
||||
export const generate = 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'],
|
||||
});
|
||||
console.log(template.get());
|
||||
|
||||
const rawTemplate = template.get();
|
||||
const templatePath = path.join(process.env.PWD, rawTemplate.template[0].get().url);
|
||||
|
||||
const data = rawTemplate.testData;
|
||||
|
||||
const outputPath = path.join(__dirname, './output.docx');
|
||||
|
||||
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 文件
|
||||
const buffer = generateDocxFromTemplate(templatePath, data);
|
||||
|
||||
// 创建一个可读流
|
||||
const readableStream = new Readable({
|
||||
read(size) {
|
||||
// 将Buffer数据push到可读流中
|
||||
this.push(buffer);
|
||||
// 标记流的末尾
|
||||
this.push(null);
|
||||
},
|
||||
});
|
||||
|
||||
ctx.withoutDataWrapping = true;
|
||||
// 设置响应类型为二进制流
|
||||
ctx.set('Content-Type', 'application/octet-stream');
|
||||
// 将可读流设置为响应体
|
||||
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) {
|
||||
console.error('Error:', error);
|
||||
ctx.status = 500;
|
||||
ctx.body = 'Error generating files: ' + error.message;
|
||||
}
|
||||
};
|
||||
|
||||
function generateDocxFromTemplate(templatePath, data): Buffer {
|
||||
try {
|
||||
const content = fs.readFileSync(templatePath, 'binary');
|
||||
const zip = new PizZip(content);
|
||||
const doc = new Docxtemplater(zip);
|
||||
|
||||
doc.setData(data);
|
||||
|
||||
try {
|
||||
doc.render();
|
||||
} catch (error) {
|
||||
console.error('Error rendering template:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return doc.getZip().generate({ type: 'nodebuffer' });
|
||||
} catch (error) {
|
||||
console.error('Error generating DOCX from template:', error);
|
||||
throw error; // Ensure errors are propagated correctly
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
import fs from 'fs';
|
||||
|
||||
import PizZip from 'pizzip';
|
||||
import { parseStringPromise } from 'xml2js';
|
||||
|
||||
export async function getTemplateParams(templatePath) {
|
||||
try {
|
||||
const content = fs.readFileSync(templatePath, 'binary');
|
||||
const zip = new PizZip(content);
|
||||
|
||||
const getXmlContent = (fileName) => {
|
||||
const file = zip.file(fileName);
|
||||
return file ? file.asText() : '';
|
||||
};
|
||||
|
||||
const docXml = getXmlContent('word/document.xml');
|
||||
|
||||
const parseXml = async (xml) => {
|
||||
try {
|
||||
const result = await parseStringPromise(xml, { explicitArray: false, ignoreAttrs: true });
|
||||
const body = result['w:document']['w:body'];
|
||||
return Array.isArray(body['w:p']) ? body['w:p'] : [body['w:p']];
|
||||
} catch (error) {
|
||||
console.error('Error parsing XML:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const xmlContent = await parseXml(docXml);
|
||||
|
||||
const extractTags = (nodes) => {
|
||||
const tags = new Set();
|
||||
nodes.forEach((node) => {
|
||||
if (node['w:r'] && Array.isArray(node['w:r'])) {
|
||||
node['w:r'].forEach((r) => {
|
||||
if (r['w:t'] && r['w:t']['#']) {
|
||||
const matches = r['w:t']['#'].match(/\{[\w-]+\.}/g);
|
||||
if (matches) {
|
||||
matches.forEach((tag) => tags.add(tag));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return tags;
|
||||
};
|
||||
|
||||
const tags = extractTags(xmlContent);
|
||||
return Array.from(tags); // Ensure the return value is an array
|
||||
} catch (error) {
|
||||
console.error('Error in getTemplateParams:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
export { default } from './plugin';
|
@ -0,0 +1,29 @@
|
||||
import { Plugin } from '@tachybase/server';
|
||||
|
||||
import { generate } from './actions/printTemplates';
|
||||
|
||||
export class PluginPrintTemplateServer extends Plugin {
|
||||
async afterAdd() {}
|
||||
|
||||
async beforeLoad() {}
|
||||
|
||||
async load() {
|
||||
// localhost:3000/api/printTemplates:generate
|
||||
this.app.resourcer.define({
|
||||
name: 'printTemplates',
|
||||
actions: {
|
||||
generate,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async install() {}
|
||||
|
||||
async afterEnable() {}
|
||||
|
||||
async afterDisable() {}
|
||||
|
||||
async remove() {}
|
||||
}
|
||||
|
||||
export default PluginPrintTemplateServer;
|
@ -3483,6 +3483,27 @@ importers:
|
||||
specifier: ^6.11.2
|
||||
version: 6.21.0(react-dom@18.3.1)(react@18.3.1)
|
||||
|
||||
packages/plugins/@tachybase/plugin-print-template:
|
||||
dependencies:
|
||||
'@tachybase/client':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/client
|
||||
'@tachybase/server':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/server
|
||||
'@tachybase/test':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/test
|
||||
docxtemplater:
|
||||
specifier: ^3.48.0
|
||||
version: 3.48.0
|
||||
pizzip:
|
||||
specifier: ^3.1.7
|
||||
version: 3.1.7
|
||||
xml2js:
|
||||
specifier: ^0.6.2
|
||||
version: 0.6.2
|
||||
|
||||
packages/plugins/@tachybase/plugin-saml:
|
||||
dependencies:
|
||||
'@tachybase/actions':
|
||||
@ -13928,7 +13949,6 @@ packages:
|
||||
/@xmldom/xmldom@0.8.10:
|
||||
resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
dev: true
|
||||
|
||||
/@xtuc/ieee754@1.2.0:
|
||||
resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
|
||||
@ -17432,6 +17452,13 @@ packages:
|
||||
dependencies:
|
||||
esutils: 2.0.3
|
||||
|
||||
/docxtemplater@3.48.0:
|
||||
resolution: {integrity: sha512-eM/sSUIVZND/GeY7FCC8kL2QV9oEQZ/RNxfY9cC07JutpVMFC9EEBOk4ajs3mcN72gqzOUqrsdwXPmKTD9TyHA==}
|
||||
engines: {node: '>=0.10'}
|
||||
dependencies:
|
||||
'@xmldom/xmldom': 0.8.10
|
||||
dev: false
|
||||
|
||||
/dom-accessibility-api@0.5.16:
|
||||
resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==}
|
||||
|
||||
@ -22634,7 +22661,7 @@ packages:
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
iconv-lite: 0.4.24
|
||||
sax: 1.3.0
|
||||
sax: 1.4.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
@ -23330,6 +23357,10 @@ packages:
|
||||
/pako@1.0.11:
|
||||
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
|
||||
|
||||
/pako@2.1.0:
|
||||
resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==}
|
||||
dev: false
|
||||
|
||||
/param-case@3.0.4:
|
||||
resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==}
|
||||
dependencies:
|
||||
@ -23710,6 +23741,12 @@ packages:
|
||||
optionalDependencies:
|
||||
nice-napi: 1.0.2
|
||||
|
||||
/pizzip@3.1.7:
|
||||
resolution: {integrity: sha512-VemVeAQtdIA74AN1Fsd5OmbMbEeS4YOwwlcudgzvmUrOIOPrk1idYC5Tw5FUFq/I0c26ziNOw9z//iPmGfp1jA==}
|
||||
dependencies:
|
||||
pako: 2.1.0
|
||||
dev: false
|
||||
|
||||
/pkg-dir@4.2.0:
|
||||
resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
|
||||
engines: {node: '>=8'}
|
||||
@ -26886,13 +26923,9 @@ packages:
|
||||
immutable: 4.3.5
|
||||
source-map-js: 1.2.0
|
||||
|
||||
/sax@1.3.0:
|
||||
resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==}
|
||||
|
||||
/sax@1.4.1:
|
||||
resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
|
||||
requiresBuild: true
|
||||
optional: true
|
||||
|
||||
/saxes@5.0.1:
|
||||
resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==}
|
||||
@ -30356,7 +30389,7 @@ packages:
|
||||
resolution: {integrity: sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
dependencies:
|
||||
sax: 1.3.0
|
||||
sax: 1.4.1
|
||||
xmlbuilder: 11.0.1
|
||||
dev: true
|
||||
|
||||
@ -30364,7 +30397,7 @@ packages:
|
||||
resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
dependencies:
|
||||
sax: 1.3.0
|
||||
sax: 1.4.1
|
||||
xmlbuilder: 11.0.1
|
||||
dev: true
|
||||
|
||||
@ -30372,14 +30405,12 @@ packages:
|
||||
resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
dependencies:
|
||||
sax: 1.3.0
|
||||
sax: 1.4.1
|
||||
xmlbuilder: 11.0.1
|
||||
dev: true
|
||||
|
||||
/xmlbuilder@11.0.1:
|
||||
resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==}
|
||||
engines: {node: '>=4.0'}
|
||||
dev: true
|
||||
|
||||
/xmlbuilder@15.1.1:
|
||||
resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==}
|
||||
|
Loading…
Reference in New Issue
Block a user