diff --git a/web-admin/src/utils/crm/contract-html-to-docx.test.ts b/web-admin/src/utils/crm/contract-html-to-docx.test.ts new file mode 100644 index 000000000..03ee9091a --- /dev/null +++ b/web-admin/src/utils/crm/contract-html-to-docx.test.ts @@ -0,0 +1,88 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' +import { + buildContractDocxFileName, + injectSxWatermarkHtml, + parseContractSupplierCode, + prepareContractHtmlForDocx, + stripContractLogoHtml, + toAbsoluteAssetUrls +} from './contract-html-to-docx' + +describe('parseContractSupplierCode', () => { + it('parses supplier code from contractNo', () => { + assert.equal(parseContractSupplierCode('SX/X-JT20250926002'), 'SX') + assert.equal(parseContractSupplierCode('YD/X-JT20250926001'), 'YD') + }) + + it('defaults to SX when missing or unmatched', () => { + assert.equal(parseContractSupplierCode(undefined), 'SX') + assert.equal(parseContractSupplierCode(''), 'SX') + assert.equal(parseContractSupplierCode('NO-MATCH'), 'SX') + }) +}) + +describe('stripContractLogoHtml', () => { + it('removes logo img and logo-only wrapper div', () => { + const html = + '

正文
'
+ const out = stripContractLogoHtml(html)
+ assert.equal(out.includes('logo.gif'), false)
+ assert.equal(out.includes('正文'), true)
+ assert.equal(out.includes('watermask.jpg'), true)
+ })
+})
+
+describe('toAbsoluteAssetUrls', () => {
+ it('prefixes root-relative src with origin', () => {
+ const out = toAbsoluteAssetUrls(
+ '
',
+ 'http://localhost:3006'
+ )
+ assert.equal(out.includes('src="http://localhost:3006/seals/a.png"'), true)
+ assert.equal(out.includes('src="https://cdn.example/x.png"'), true)
+ })
+})
+
+describe('injectSxWatermarkHtml', () => {
+ it('prepends watermark image with absolute url', () => {
+ const out = injectSxWatermarkHtml('合同
', 'http://localhost:3006/watermask.jpg') + assert.equal(out.includes('http://localhost:3006/watermask.jpg'), true) + assert.equal(out.includes('alt="水印"'), true) + assert.equal(out.includes('合同
'), true) + }) +}) + +describe('prepareContractHtmlForDocx', () => { + it('adds SX watermark and keeps logo', () => { + const out = prepareContractHtmlForDocx({ + html: '
正文
', + contractNo: 'SX/X-JT20250926002', + origin: 'http://localhost:3006' + }) + assert.equal(out.includes('watermask.jpg'), true) + assert.equal(out.includes('logo.gif'), true) + assert.equal(out.includes('http://localhost:3006/logo.gif'), true) + }) + + it('strips logo and skips watermark for non-SX', () => { + const out = prepareContractHtmlForDocx({ + html: '
正文
', + contractNo: 'YD/X-JT20250926001', + origin: 'http://localhost:3006' + }) + assert.equal(out.includes('watermask'), false) + assert.equal(out.includes('logo.gif'), false) + assert.equal(out.includes('正文'), true) + }) +}) + +describe('buildContractDocxFileName', () => { + it('uses contract name and sanitizes illegal chars', () => { + assert.equal(buildContractDocxFileName('甲/乙合同', 'SX/X-JT1'), '甲-乙合同合同.docx') + }) + + it('falls back to contractNo', () => { + assert.equal(buildContractDocxFileName('', 'SX/X-JT1'), 'SX-X-JT1合同.docx') + }) +}) diff --git a/web-admin/src/utils/crm/contract-html-to-docx.ts b/web-admin/src/utils/crm/contract-html-to-docx.ts new file mode 100644 index 000000000..61337b082 --- /dev/null +++ b/web-admin/src/utils/crm/contract-html-to-docx.ts @@ -0,0 +1,64 @@ +/** 合同打印弹窗:HTML → DOCX 预处理与生成 */ + +export function parseContractSupplierCode(contractNo?: string | null): string { + if (!contractNo) return 'SX' + const match = String(contractNo).match(/^([A-Z]+)\/X-JT/) + return match?.[1] || 'SX' +} + +export function stripContractLogoHtml(html: string): string { + let result = html.replace(/