From 49a8e2e534489002bd3b5b729c55c1416216a245 Mon Sep 17 00:00:00 2001 From: yoona <1486343814@qq.com> Date: Wed, 17 Jul 2024 22:56:41 +0800 Subject: [PATCH] feat: printTemplate (#1338) Co-authored-by: root Co-authored-by: sealday Reviewed-on: https://git.daoyoucloud.com/daoyoucloud/tachybase/pulls/1338 Reviewed-by: sealday Co-authored-by: yoona <1486343814@qq.com> Co-committed-by: yoona <1486343814@qq.com> --- .../plugin-print-template/.npmignore | 2 + .../plugin-print-template/README.md | 1 + .../plugin-print-template/client.d.ts | 2 + .../plugin-print-template/client.js | 1 + .../plugin-print-template/package.json | 15 +++ .../plugin-print-template/server.d.ts | 2 + .../plugin-print-template/server.js | 1 + .../src/client/index.tsx | 21 +++++ .../plugin-print-template/src/index.ts | 2 + .../src/server/actions/printTemplates.ts | 93 +++++++++++++++++++ .../src/server/actions/template-params.ts | 54 +++++++++++ .../src/server/collections/.gitkeep | 0 .../plugin-print-template/src/server/index.ts | 1 + .../src/server/plugin.ts | 29 ++++++ pnpm-lock.yaml | 53 ++++++++--- 15 files changed, 266 insertions(+), 11 deletions(-) create mode 100644 packages/plugins/@tachybase/plugin-print-template/.npmignore create mode 100644 packages/plugins/@tachybase/plugin-print-template/README.md create mode 100644 packages/plugins/@tachybase/plugin-print-template/client.d.ts create mode 100644 packages/plugins/@tachybase/plugin-print-template/client.js create mode 100644 packages/plugins/@tachybase/plugin-print-template/package.json create mode 100644 packages/plugins/@tachybase/plugin-print-template/server.d.ts create mode 100644 packages/plugins/@tachybase/plugin-print-template/server.js create mode 100644 packages/plugins/@tachybase/plugin-print-template/src/client/index.tsx create mode 100644 packages/plugins/@tachybase/plugin-print-template/src/index.ts create mode 100644 packages/plugins/@tachybase/plugin-print-template/src/server/actions/printTemplates.ts create mode 100644 packages/plugins/@tachybase/plugin-print-template/src/server/actions/template-params.ts create mode 100644 packages/plugins/@tachybase/plugin-print-template/src/server/collections/.gitkeep create mode 100644 packages/plugins/@tachybase/plugin-print-template/src/server/index.ts create mode 100644 packages/plugins/@tachybase/plugin-print-template/src/server/plugin.ts diff --git a/packages/plugins/@tachybase/plugin-print-template/.npmignore b/packages/plugins/@tachybase/plugin-print-template/.npmignore new file mode 100644 index 000000000..65f5e8779 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-print-template/.npmignore @@ -0,0 +1,2 @@ +/node_modules +/src diff --git a/packages/plugins/@tachybase/plugin-print-template/README.md b/packages/plugins/@tachybase/plugin-print-template/README.md new file mode 100644 index 000000000..ef79607e2 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-print-template/README.md @@ -0,0 +1 @@ +# @tachybase/plugin-print-template diff --git a/packages/plugins/@tachybase/plugin-print-template/client.d.ts b/packages/plugins/@tachybase/plugin-print-template/client.d.ts new file mode 100644 index 000000000..6c459cbac --- /dev/null +++ b/packages/plugins/@tachybase/plugin-print-template/client.d.ts @@ -0,0 +1,2 @@ +export * from './dist/client'; +export { default } from './dist/client'; diff --git a/packages/plugins/@tachybase/plugin-print-template/client.js b/packages/plugins/@tachybase/plugin-print-template/client.js new file mode 100644 index 000000000..b6e3be70e --- /dev/null +++ b/packages/plugins/@tachybase/plugin-print-template/client.js @@ -0,0 +1 @@ +module.exports = require('./dist/client/index.js'); diff --git a/packages/plugins/@tachybase/plugin-print-template/package.json b/packages/plugins/@tachybase/plugin-print-template/package.json new file mode 100644 index 000000000..1c79f8687 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-print-template/package.json @@ -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:*" + } +} diff --git a/packages/plugins/@tachybase/plugin-print-template/server.d.ts b/packages/plugins/@tachybase/plugin-print-template/server.d.ts new file mode 100644 index 000000000..c41081ddc --- /dev/null +++ b/packages/plugins/@tachybase/plugin-print-template/server.d.ts @@ -0,0 +1,2 @@ +export * from './dist/server'; +export { default } from './dist/server'; diff --git a/packages/plugins/@tachybase/plugin-print-template/server.js b/packages/plugins/@tachybase/plugin-print-template/server.js new file mode 100644 index 000000000..972842039 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-print-template/server.js @@ -0,0 +1 @@ +module.exports = require('./dist/server/index.js'); diff --git a/packages/plugins/@tachybase/plugin-print-template/src/client/index.tsx b/packages/plugins/@tachybase/plugin-print-template/src/client/index.tsx new file mode 100644 index 000000000..5ebd1cebf --- /dev/null +++ b/packages/plugins/@tachybase/plugin-print-template/src/client/index.tsx @@ -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; diff --git a/packages/plugins/@tachybase/plugin-print-template/src/index.ts b/packages/plugins/@tachybase/plugin-print-template/src/index.ts new file mode 100644 index 000000000..7e74612df --- /dev/null +++ b/packages/plugins/@tachybase/plugin-print-template/src/index.ts @@ -0,0 +1,2 @@ +export * from './server'; +export { default } from './server'; diff --git a/packages/plugins/@tachybase/plugin-print-template/src/server/actions/printTemplates.ts b/packages/plugins/@tachybase/plugin-print-template/src/server/actions/printTemplates.ts new file mode 100644 index 000000000..35dffec90 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-print-template/src/server/actions/printTemplates.ts @@ -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 + } +} diff --git a/packages/plugins/@tachybase/plugin-print-template/src/server/actions/template-params.ts b/packages/plugins/@tachybase/plugin-print-template/src/server/actions/template-params.ts new file mode 100644 index 000000000..f0fddd1a3 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-print-template/src/server/actions/template-params.ts @@ -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; + } +} diff --git a/packages/plugins/@tachybase/plugin-print-template/src/server/collections/.gitkeep b/packages/plugins/@tachybase/plugin-print-template/src/server/collections/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/plugins/@tachybase/plugin-print-template/src/server/index.ts b/packages/plugins/@tachybase/plugin-print-template/src/server/index.ts new file mode 100644 index 000000000..b68aea57f --- /dev/null +++ b/packages/plugins/@tachybase/plugin-print-template/src/server/index.ts @@ -0,0 +1 @@ +export { default } from './plugin'; diff --git a/packages/plugins/@tachybase/plugin-print-template/src/server/plugin.ts b/packages/plugins/@tachybase/plugin-print-template/src/server/plugin.ts new file mode 100644 index 000000000..aae04ceb2 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-print-template/src/server/plugin.ts @@ -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; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e638fdb80..1a56e905f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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==}