aegon-gen 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +12 -0
- package/src/App.vue +3 -0
- package/src/api/index.ts +19 -0
- package/src/api/modules/gen-ai/gen-entry/index.ts +30 -0
- package/src/api/modules/gen-ai/model-manager/index.ts +42 -0
- package/src/api/modules/gen-ai/model-manager/mockApi.ts +33 -0
- package/src/api/modules/index.ts +98 -0
- package/src/api/modules/user/index.ts +4 -0
- package/src/api/request.ts +102 -0
- package/src/assets/sample-access-icon.png +0 -0
- package/src/assets/sample-pie-chart.png +0 -0
- package/src/assets/vue.svg +1 -0
- package/src/components/CapsuleScrollbar.vue +93 -0
- package/src/components/Export/ExcelExport.vue +592 -0
- package/src/components/Export/ExcelExport2.vue +494 -0
- package/src/components/Export/ExcelExport3.vue +342 -0
- package/src/components/Export/ExcelExport4.vue +665 -0
- package/src/components/Export/excelExport.js +547 -0
- package/src/components/Export/excelExport.ts +551 -0
- package/src/components/GEN-AI/index.vue +142 -0
- package/src/components/GEN-AI/index1.vue +456 -0
- package/src/components/GEN-AI/index10.vue +5 -0
- package/src/components/GEN-AI/index2.vue +568 -0
- package/src/components/GEN-AI/index3.vue +623 -0
- package/src/components/GEN-AI/index4.vue +629 -0
- package/src/components/GEN-AI/index5.vue +578 -0
- package/src/components/GEN-AI/index6.vue +656 -0
- package/src/components/GEN-AI/index7.vue +717 -0
- package/src/components/GEN-AI/index8.vue +405 -0
- package/src/components/GEN-AI/index9.vue +1065 -0
- package/src/components/GEN-AI/types.ts +12 -0
- package/src/components/GEN-AI/utils.ts +42 -0
- package/src/components/HelloWorld.vue +41 -0
- package/src/components/PageCard.vue +7 -0
- package/src/components/PageHeader.vue +32 -0
- package/src/components/backup/index5 copy.vue +556 -0
- package/src/components/backup/index5.vue +620 -0
- package/src/components/backup/index9 copy.vue +1029 -0
- package/src/components/backup/index9-pro.vue +1065 -0
- package/src/components/backup/index9.vue +1057 -0
- package/src/components/el-date-picker.vue +64 -0
- package/src/directives/btnLoading.ts +427 -0
- package/src/directives/debounce copy.ts +670 -0
- package/src/directives/debounce.ts +98 -0
- package/src/directives/index.ts +25 -0
- package/src/layouts/MainLayout.vue +101 -0
- package/src/main.ts +85 -0
- package/src/router/index.ts +76 -0
- package/src/router/menus.ts +28 -0
- package/src/style.css +79 -0
- package/src/styles/_variables.scss +24 -0
- package/src/styles/app-button.css +26 -0
- package/src/styles/element-overrides.css +23 -0
- package/src/styles/global.css +44 -0
- package/src/styles/index.scss +1 -0
- package/src/styles/page-card.css +21 -0
- package/src/styles/variables.css +26 -0
- package/src/test/mock.ts +101 -0
- package/src/test/test1.vue +402 -0
- package/src/test/test2.vue +1689 -0
- package/src/types/gen-ai/gen-entry/index.ts +17 -0
- package/src/types/gen-ai/model-manager/index.ts +19 -0
- package/src/utils/docxExport.ts +1610 -0
- package/src/utils/gen-ai-navigation.ts +37 -0
- package/src/utils/gen-ai-scroll.ts +455 -0
- package/src/utils/openDataLoaderWordExport.ts +33 -0
- package/src/utils/pageScrollbar.ts +115 -0
- package/src/utils/randomTranscode.ts +87 -0
- package/src/utils/reportPdfExport.ts +44 -0
- package/src/views/AdminCenter/index.vue +817 -0
- package/src/views/Blank.vue +68 -0
- package/src/views/Home.vue +29 -0
- package/src/views/ReportCenter/index.vue +1380 -0
- package/src/views/TemplateCenter/Knowledge.ts +83 -0
- package/src/views/TemplateCenter/data.d.ts +10 -0
- package/src/views/TemplateCenter/index.vue +1205 -0
- package/src/views/TemplateCenter/service.ts +69 -0
- package/src/views/gen-ai/components/RecentReportsTable.vue +193 -0
- package/src/views/gen-ai/gen-entry/index.vue +309 -0
- package/src/views/gen-ai/gen-entry/mockData.ts +160 -0
- package/src/views/gen-ai/management-center/index.vue +53 -0
- package/src/views/gen-ai/model-manager/ChapterTitleScroll.vue +275 -0
- package/src/views/gen-ai/model-manager/index.vue +1205 -0
- package/src/views/gen-ai/model-manager/mockData.ts +122 -0
- package/src/views/gen-ai/report-center/index.vue +158 -0
- package/src/vite-env.d.ts +38 -0
|
@@ -0,0 +1,1610 @@
|
|
|
1
|
+
import JSZip from 'jszip'
|
|
2
|
+
import html2canvas from 'html2canvas'
|
|
3
|
+
import { relsXml } from 'html-docx-js-typescript/dist/assets'
|
|
4
|
+
import { defaultMargins, mhtPartTemplate } from 'html-docx-js-typescript/dist/templates'
|
|
5
|
+
|
|
6
|
+
export interface DocxHeaderFooterOptions {
|
|
7
|
+
/** 页眉右上角文字,默认「商业机密」 */
|
|
8
|
+
headerText?: string
|
|
9
|
+
/** 页脚是否显示「当前页 / 总页数」,默认 true */
|
|
10
|
+
showPageNumbers?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface DocxExportOptions {
|
|
14
|
+
orientation?: 'portrait' | 'landscape'
|
|
15
|
+
margins?: Partial<typeof defaultMargins>
|
|
16
|
+
/** 页面宽度(twips),默认 A4 竖向 */
|
|
17
|
+
pageWidth?: number
|
|
18
|
+
/** 页面高度(twips),默认 A4 竖向 */
|
|
19
|
+
pageHeight?: number
|
|
20
|
+
headerFooter?: DocxHeaderFooterOptions
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** A4 竖向:210mm × 297mm(Word twips,1 inch = 1440 twips) */
|
|
24
|
+
export const A4_PORTRAIT = {
|
|
25
|
+
width: 11906,
|
|
26
|
+
height: 16838,
|
|
27
|
+
} as const
|
|
28
|
+
|
|
29
|
+
/** A4 常用 2cm 页边距 ≈ 1134 twips */
|
|
30
|
+
export const A4_MARGINS_2CM = {
|
|
31
|
+
top: 1134,
|
|
32
|
+
right: 1134,
|
|
33
|
+
bottom: 1134,
|
|
34
|
+
left: 1134,
|
|
35
|
+
header: 720,
|
|
36
|
+
footer: 720,
|
|
37
|
+
gutter: 0,
|
|
38
|
+
} as const
|
|
39
|
+
|
|
40
|
+
/** 可打印区域宽度(px@96dpi):210mm - 左右各 2cm ≈ 642px */
|
|
41
|
+
export const A4_CONTENT_WIDTH_PX = Math.round(((210 - 4) / 25.4) * 96)
|
|
42
|
+
|
|
43
|
+
/** Word 可打印区宽度(pt):210mm - 左右各 2cm ≈ 482pt */
|
|
44
|
+
export const A4_CONTENT_WIDTH_PT = Math.round(((210 - 40) / 25.4) * 72)
|
|
45
|
+
|
|
46
|
+
/** 图表导出高度(与页面 400px 比例协调,且可落在单页 A4 内) */
|
|
47
|
+
export const A4_CHART_HEIGHT_PX = 380
|
|
48
|
+
|
|
49
|
+
/** 单张图表在 A4 可打印区域内的最大高度 */
|
|
50
|
+
export const A4_CHART_MAX_HEIGHT_PX = Math.round(((297 - 4) / 25.4) * 96 * 0.42)
|
|
51
|
+
|
|
52
|
+
/** 左浮动无障碍图标(固定尺寸,不超出页边距) */
|
|
53
|
+
export const DOCX_FLOAT_ICON_SIZE_PX = 72
|
|
54
|
+
|
|
55
|
+
/** 饼图左栏最大宽度:A4 可打印区 42% 减去间距 */
|
|
56
|
+
export const DOCX_PIE_CHART_MAX_WIDTH_PX = Math.min(
|
|
57
|
+
220,
|
|
58
|
+
Math.floor(A4_CONTENT_WIDTH_PX * 0.42) - 16
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
/** 饼图最大高度(避免 Word 单页溢出) */
|
|
62
|
+
export const DOCX_PIE_CHART_MAX_HEIGHT_PX = Math.min(
|
|
63
|
+
120,
|
|
64
|
+
Math.floor(A4_CHART_MAX_HEIGHT_PX * 0.32)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
export const DEFAULT_DOCX_EXPORT_OPTIONS: DocxExportOptions = {
|
|
68
|
+
orientation: 'portrait',
|
|
69
|
+
pageWidth: A4_PORTRAIT.width,
|
|
70
|
+
pageHeight: A4_PORTRAIT.height,
|
|
71
|
+
margins: { ...A4_MARGINS_2CM },
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function mergeMargins(margins?: Partial<typeof defaultMargins>) {
|
|
75
|
+
return { ...defaultMargins, ...margins }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function resolvePageSize(options: DocxExportOptions) {
|
|
79
|
+
const orientation = options.orientation ?? 'portrait'
|
|
80
|
+
if (options.pageWidth && options.pageHeight) {
|
|
81
|
+
return { width: options.pageWidth, height: options.pageHeight, orientation }
|
|
82
|
+
}
|
|
83
|
+
if (orientation === 'landscape') {
|
|
84
|
+
return { width: A4_PORTRAIT.height, height: A4_PORTRAIT.width, orientation }
|
|
85
|
+
}
|
|
86
|
+
return { width: A4_PORTRAIT.width, height: A4_PORTRAIT.height, orientation }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** UTF-8 正确 quoted-printable 编码(修复中文乱码,且保留 MHT 图片关联) */
|
|
90
|
+
function encodeQuotedPrintableUtf8(input: string): string {
|
|
91
|
+
const bytes = new TextEncoder().encode(input)
|
|
92
|
+
let out = ''
|
|
93
|
+
let lineLen = 0
|
|
94
|
+
|
|
95
|
+
const softBreak = () => {
|
|
96
|
+
out += '=\r\n'
|
|
97
|
+
lineLen = 0
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const appendChar = (ch: string, encodedLen: number) => {
|
|
101
|
+
if (lineLen + encodedLen > 75) softBreak()
|
|
102
|
+
out += ch
|
|
103
|
+
lineLen += encodedLen
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
107
|
+
const b = bytes[i]!
|
|
108
|
+
const isPrintable =
|
|
109
|
+
(b >= 33 && b <= 60) || (b >= 62 && b <= 126) || b === 9 || b === 32
|
|
110
|
+
|
|
111
|
+
if (isPrintable) {
|
|
112
|
+
appendChar(String.fromCharCode(b), 1)
|
|
113
|
+
} else {
|
|
114
|
+
const enc = `=${b.toString(16).toUpperCase().padStart(2, '0')}`
|
|
115
|
+
appendChar(enc, 3)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return out
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function prepareImageParts(htmlSource: string) {
|
|
123
|
+
const imageContentParts: string[] = []
|
|
124
|
+
const html = htmlSource.replace(
|
|
125
|
+
/src=(["'])data:([^"']+)\1/gi,
|
|
126
|
+
(_match, _quote: string, dataUrl: string) => {
|
|
127
|
+
const comma = dataUrl.indexOf(',')
|
|
128
|
+
const meta = dataUrl.slice(0, comma)
|
|
129
|
+
const encodedContent = dataUrl.slice(comma + 1)
|
|
130
|
+
const semi = meta.indexOf(';')
|
|
131
|
+
const contentType = meta.slice(0, semi)
|
|
132
|
+
const contentEncoding = meta.slice(semi + 1)
|
|
133
|
+
if (!contentType || !encodedContent) return _match
|
|
134
|
+
|
|
135
|
+
const ext = contentType.split('/')[1]?.replace(/[^a-z0-9]/gi, '') || 'png'
|
|
136
|
+
const index = imageContentParts.length
|
|
137
|
+
const contentLocation = `file:///C:/fake/image${index}.${ext}`
|
|
138
|
+
imageContentParts.push(
|
|
139
|
+
mhtPartTemplate(contentType, contentEncoding, contentLocation, encodedContent)
|
|
140
|
+
)
|
|
141
|
+
return `src="${contentLocation}"`
|
|
142
|
+
}
|
|
143
|
+
)
|
|
144
|
+
return { htmlSource: html, imageContentParts }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function buildMhtDocument(htmlSource: string): string {
|
|
148
|
+
const { htmlSource: htmlWithRefs, imageContentParts } = prepareImageParts(htmlSource)
|
|
149
|
+
const encodedBody = encodeQuotedPrintableUtf8(htmlWithRefs)
|
|
150
|
+
const parts = imageContentParts.join('\n')
|
|
151
|
+
|
|
152
|
+
return `MIME-Version: 1.0
|
|
153
|
+
Content-Type: multipart/related;
|
|
154
|
+
type="text/html";
|
|
155
|
+
boundary="----=mhtDocumentPart"
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
------=mhtDocumentPart
|
|
159
|
+
Content-Type: text/html;
|
|
160
|
+
charset="utf-8"
|
|
161
|
+
Content-Transfer-Encoding: quoted-printable
|
|
162
|
+
Content-Location: file:///C:/fake/document.html
|
|
163
|
+
|
|
164
|
+
${encodedBody}
|
|
165
|
+
|
|
166
|
+
${parts}
|
|
167
|
+
|
|
168
|
+
------=mhtDocumentPart--
|
|
169
|
+
`
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function loadImageNaturalSize(
|
|
173
|
+
dataUrl: string
|
|
174
|
+
): Promise<{ width: number; height: number }> {
|
|
175
|
+
return new Promise((resolve, reject) => {
|
|
176
|
+
const probe = new Image()
|
|
177
|
+
probe.onload = () =>
|
|
178
|
+
resolve({ width: probe.naturalWidth, height: probe.naturalHeight })
|
|
179
|
+
probe.onerror = () => reject(new Error('chart image load failed'))
|
|
180
|
+
probe.src = dataUrl
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** 将图表缩放到 A4 页边距内,避免 Word 裁切或撑出页面 */
|
|
185
|
+
function fitChartToA4Content(naturalWidth: number, naturalHeight: number) {
|
|
186
|
+
const maxW = A4_CONTENT_WIDTH_PX
|
|
187
|
+
const maxH = Math.min(A4_CHART_HEIGHT_PX, A4_CHART_MAX_HEIGHT_PX)
|
|
188
|
+
|
|
189
|
+
let width = maxW
|
|
190
|
+
let height = Math.round((naturalHeight / naturalWidth) * width)
|
|
191
|
+
|
|
192
|
+
if (height > maxH) {
|
|
193
|
+
height = maxH
|
|
194
|
+
width = Math.round((naturalWidth / naturalHeight) * height)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return { width, height }
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** 图表容器:Word 需使用数值 width/height(勿用 100% 属性,否则会只显示一半) */
|
|
201
|
+
async function createChartBlock(dataUrl: string): Promise<HTMLDivElement> {
|
|
202
|
+
const natural = await loadImageNaturalSize(dataUrl)
|
|
203
|
+
const { width, height } = fitChartToA4Content(natural.width, natural.height)
|
|
204
|
+
|
|
205
|
+
const wrap = document.createElement('div')
|
|
206
|
+
wrap.className = 'docx-chart-wrap'
|
|
207
|
+
wrap.style.cssText = [
|
|
208
|
+
`width:${width}px`,
|
|
209
|
+
`max-width:${A4_CONTENT_WIDTH_PX}px`,
|
|
210
|
+
'margin:12pt auto',
|
|
211
|
+
'text-align:center',
|
|
212
|
+
'page-break-inside:avoid',
|
|
213
|
+
'box-sizing:border-box',
|
|
214
|
+
'overflow:visible',
|
|
215
|
+
].join(';')
|
|
216
|
+
|
|
217
|
+
const img = document.createElement('img')
|
|
218
|
+
img.className = 'docx-chart-img'
|
|
219
|
+
img.src = dataUrl
|
|
220
|
+
img.setAttribute('width', String(width))
|
|
221
|
+
img.setAttribute('height', String(height))
|
|
222
|
+
img.style.cssText = [
|
|
223
|
+
`width:${width}px`,
|
|
224
|
+
`height:${height}px`,
|
|
225
|
+
`max-width:${A4_CONTENT_WIDTH_PX}px`,
|
|
226
|
+
'display:block',
|
|
227
|
+
'margin:0 auto',
|
|
228
|
+
'border:0',
|
|
229
|
+
].join(';')
|
|
230
|
+
|
|
231
|
+
wrap.appendChild(img)
|
|
232
|
+
return wrap
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** ECharts:按 A4 可打印区导出完整图表,避免裁切与溢出 */
|
|
236
|
+
export async function rasterizeEchartsForDocx(
|
|
237
|
+
sourceRoot: HTMLElement,
|
|
238
|
+
targetRoot: HTMLElement,
|
|
239
|
+
echartsApi: {
|
|
240
|
+
getInstanceByDom: (el: HTMLElement) => {
|
|
241
|
+
resize: (opts?: { width?: number; height?: number }) => void
|
|
242
|
+
getDataURL: (opts: object) => string
|
|
243
|
+
} | undefined
|
|
244
|
+
}
|
|
245
|
+
) {
|
|
246
|
+
const sourceCharts = sourceRoot.querySelectorAll('.echarts-container')
|
|
247
|
+
const targetCharts = targetRoot.querySelectorAll('.echarts-container')
|
|
248
|
+
const exportW = A4_CONTENT_WIDTH_PX
|
|
249
|
+
const exportH = A4_CHART_HEIGHT_PX
|
|
250
|
+
|
|
251
|
+
for (let i = 0; i < sourceCharts.length; i++) {
|
|
252
|
+
const sourceEl = sourceCharts[i] as HTMLElement
|
|
253
|
+
const targetEl = targetCharts[i] as HTMLElement
|
|
254
|
+
if (!targetEl) continue
|
|
255
|
+
|
|
256
|
+
const prevWidth = sourceEl.style.width
|
|
257
|
+
const prevHeight = sourceEl.style.height
|
|
258
|
+
const prevMaxWidth = sourceEl.style.maxWidth
|
|
259
|
+
|
|
260
|
+
let dataUrl = ''
|
|
261
|
+
const inst = echartsApi.getInstanceByDom(sourceEl)
|
|
262
|
+
if (inst) {
|
|
263
|
+
try {
|
|
264
|
+
sourceEl.style.width = `${exportW}px`
|
|
265
|
+
sourceEl.style.height = `${exportH}px`
|
|
266
|
+
sourceEl.style.maxWidth = `${exportW}px`
|
|
267
|
+
inst.resize({ width: exportW, height: exportH })
|
|
268
|
+
await new Promise((r) => setTimeout(r, 320))
|
|
269
|
+
dataUrl = inst.getDataURL({
|
|
270
|
+
type: 'png',
|
|
271
|
+
pixelRatio: 1,
|
|
272
|
+
backgroundColor: '#ffffff',
|
|
273
|
+
excludeComponents: ['toolbox', 'dataZoom'],
|
|
274
|
+
})
|
|
275
|
+
} finally {
|
|
276
|
+
sourceEl.style.width = prevWidth
|
|
277
|
+
sourceEl.style.height = prevHeight
|
|
278
|
+
sourceEl.style.maxWidth = prevMaxWidth
|
|
279
|
+
inst.resize()
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (!dataUrl) {
|
|
284
|
+
const canvas = sourceEl.querySelector('canvas')
|
|
285
|
+
if (canvas instanceof HTMLCanvasElement) {
|
|
286
|
+
dataUrl = canvas.toDataURL('image/png')
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (!dataUrl) continue
|
|
291
|
+
|
|
292
|
+
const chartBlock = await createChartBlock(dataUrl)
|
|
293
|
+
chartBlock.classList.add('echarts-container')
|
|
294
|
+
targetEl.replaceWith(chartBlock)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function svgToPngImg(svg: SVGSVGElement): Promise<HTMLImageElement> {
|
|
299
|
+
const clone = svg.cloneNode(true) as SVGSVGElement
|
|
300
|
+
if (!clone.getAttribute('xmlns')) {
|
|
301
|
+
clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg')
|
|
302
|
+
}
|
|
303
|
+
const rect = svg.getBoundingClientRect()
|
|
304
|
+
const w = Math.max(rect.width, 16)
|
|
305
|
+
const h = Math.max(rect.height, 16)
|
|
306
|
+
clone.setAttribute('width', String(w))
|
|
307
|
+
clone.setAttribute('height', String(h))
|
|
308
|
+
|
|
309
|
+
const svgData = new XMLSerializer().serializeToString(clone)
|
|
310
|
+
const url = URL.createObjectURL(
|
|
311
|
+
new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' })
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
return await new Promise((resolve, reject) => {
|
|
316
|
+
const img = new Image()
|
|
317
|
+
img.onload = () => {
|
|
318
|
+
const canvas = document.createElement('canvas')
|
|
319
|
+
canvas.width = w * 2
|
|
320
|
+
canvas.height = h * 2
|
|
321
|
+
const ctx = canvas.getContext('2d')
|
|
322
|
+
ctx?.drawImage(img, 0, 0, canvas.width, canvas.height)
|
|
323
|
+
const wordImg = document.createElement('img')
|
|
324
|
+
wordImg.src = canvas.toDataURL('image/png')
|
|
325
|
+
wordImg.style.width = `${w}px`
|
|
326
|
+
wordImg.style.height = `${h}px`
|
|
327
|
+
wordImg.style.verticalAlign = 'middle'
|
|
328
|
+
wordImg.style.display = 'inline-block'
|
|
329
|
+
resolve(wordImg)
|
|
330
|
+
}
|
|
331
|
+
img.onerror = () => reject(new Error('SVG load failed'))
|
|
332
|
+
img.src = url
|
|
333
|
+
})
|
|
334
|
+
} finally {
|
|
335
|
+
URL.revokeObjectURL(url)
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** Element Plus 图标等:html2canvas 栅格化,避免 <use> 外链丢失 */
|
|
340
|
+
export async function rasterizeIconsForDocx(
|
|
341
|
+
sourceRoot: HTMLElement,
|
|
342
|
+
targetRoot: HTMLElement
|
|
343
|
+
) {
|
|
344
|
+
const sourceIcons = sourceRoot.querySelectorAll('.el-icon')
|
|
345
|
+
const targetIcons = targetRoot.querySelectorAll('.el-icon')
|
|
346
|
+
|
|
347
|
+
for (let i = 0; i < sourceIcons.length; i++) {
|
|
348
|
+
const src = sourceIcons[i] as HTMLElement
|
|
349
|
+
const tgt = targetIcons[i] as HTMLElement
|
|
350
|
+
if (!tgt) continue
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
const canvas = await html2canvas(src, {
|
|
354
|
+
backgroundColor: null,
|
|
355
|
+
scale: 2,
|
|
356
|
+
logging: false,
|
|
357
|
+
})
|
|
358
|
+
const img = document.createElement('img')
|
|
359
|
+
img.src = canvas.toDataURL('image/png')
|
|
360
|
+
img.setAttribute('width', '24')
|
|
361
|
+
img.setAttribute('height', '24')
|
|
362
|
+
img.style.width = '24px'
|
|
363
|
+
img.style.height = '24px'
|
|
364
|
+
img.style.verticalAlign = 'middle'
|
|
365
|
+
img.style.display = 'inline-block'
|
|
366
|
+
tgt.replaceWith(img)
|
|
367
|
+
} catch {
|
|
368
|
+
const svg = src.querySelector('svg')
|
|
369
|
+
if (svg) {
|
|
370
|
+
try {
|
|
371
|
+
const img = await svgToPngImg(svg)
|
|
372
|
+
tgt.replaceWith(img)
|
|
373
|
+
} catch {
|
|
374
|
+
/* keep original */
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** 图标替换后剩余的独立 SVG(如 Lucide)转 PNG */
|
|
382
|
+
export async function rasterizeStandaloneSvgsForDocx(targetRoot: HTMLElement) {
|
|
383
|
+
const svgs = [...targetRoot.querySelectorAll('svg')]
|
|
384
|
+
for (const svg of svgs) {
|
|
385
|
+
try {
|
|
386
|
+
const img = await svgToPngImg(svg)
|
|
387
|
+
svg.replaceWith(img)
|
|
388
|
+
} catch {
|
|
389
|
+
/* keep */
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async function fetchImageAsDataUrl(url: string): Promise<string> {
|
|
395
|
+
const res = await fetch(url)
|
|
396
|
+
if (!res.ok) throw new Error(`fetch image failed: ${res.status}`)
|
|
397
|
+
const blob = await res.blob()
|
|
398
|
+
return new Promise((resolve, reject) => {
|
|
399
|
+
const reader = new FileReader()
|
|
400
|
+
reader.onload = () => resolve(reader.result as string)
|
|
401
|
+
reader.onerror = () => reject(new Error('read image blob failed'))
|
|
402
|
+
reader.readAsDataURL(blob)
|
|
403
|
+
})
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export function getDocxImageTargetSize(img: HTMLImageElement): {
|
|
407
|
+
width: number
|
|
408
|
+
height: number
|
|
409
|
+
} {
|
|
410
|
+
const naturalW = img.naturalWidth || Number.parseInt(img.getAttribute('width') || '0', 10) || 100
|
|
411
|
+
const naturalH = img.naturalHeight || Number.parseInt(img.getAttribute('height') || '0', 10) || 100
|
|
412
|
+
|
|
413
|
+
if (img.classList.contains('doc-float-icon')) {
|
|
414
|
+
return {
|
|
415
|
+
width: DOCX_FLOAT_ICON_SIZE_PX,
|
|
416
|
+
height: DOCX_FLOAT_ICON_SIZE_PX,
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (img.classList.contains('doc-pie-chart-img')) {
|
|
421
|
+
let width = Math.min(
|
|
422
|
+
DOCX_PIE_CHART_MAX_WIDTH_PX,
|
|
423
|
+
Number.parseInt(img.getAttribute('width') || '0', 10) || DOCX_PIE_CHART_MAX_WIDTH_PX,
|
|
424
|
+
A4_CONTENT_WIDTH_PX
|
|
425
|
+
)
|
|
426
|
+
let height = Math.round((naturalH / naturalW) * width)
|
|
427
|
+
if (height > DOCX_PIE_CHART_MAX_HEIGHT_PX) {
|
|
428
|
+
height = DOCX_PIE_CHART_MAX_HEIGHT_PX
|
|
429
|
+
width = Math.round((naturalW / naturalH) * height)
|
|
430
|
+
}
|
|
431
|
+
return { width, height }
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (naturalW > A4_CONTENT_WIDTH_PX) {
|
|
435
|
+
return {
|
|
436
|
+
width: A4_CONTENT_WIDTH_PX,
|
|
437
|
+
height: Math.round((naturalH / naturalW) * A4_CONTENT_WIDTH_PX),
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return { width: naturalW, height: naturalH }
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function imgElementToDataUrl(
|
|
445
|
+
img: HTMLImageElement,
|
|
446
|
+
targetW: number,
|
|
447
|
+
targetH: number
|
|
448
|
+
): Promise<string> {
|
|
449
|
+
return new Promise((resolve, reject) => {
|
|
450
|
+
const draw = () => {
|
|
451
|
+
if (!targetW || !targetH) {
|
|
452
|
+
reject(new Error('invalid image dimensions'))
|
|
453
|
+
return
|
|
454
|
+
}
|
|
455
|
+
const canvas = document.createElement('canvas')
|
|
456
|
+
canvas.width = targetW
|
|
457
|
+
canvas.height = targetH
|
|
458
|
+
const ctx = canvas.getContext('2d')
|
|
459
|
+
if (!ctx) {
|
|
460
|
+
reject(new Error('no canvas context'))
|
|
461
|
+
return
|
|
462
|
+
}
|
|
463
|
+
ctx.drawImage(img, 0, 0, targetW, targetH)
|
|
464
|
+
resolve(canvas.toDataURL('image/png'))
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (img.complete && img.naturalWidth > 0) {
|
|
468
|
+
draw()
|
|
469
|
+
return
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const prevOnload = img.onload
|
|
473
|
+
const prevOnerror = img.onerror
|
|
474
|
+
img.onload = () => {
|
|
475
|
+
img.onload = prevOnload
|
|
476
|
+
img.onerror = prevOnerror
|
|
477
|
+
draw()
|
|
478
|
+
}
|
|
479
|
+
img.onerror = () => {
|
|
480
|
+
img.onload = prevOnload
|
|
481
|
+
img.onerror = prevOnerror
|
|
482
|
+
reject(new Error('image load failed'))
|
|
483
|
+
}
|
|
484
|
+
if (!img.complete) {
|
|
485
|
+
const src = img.getAttribute('src')
|
|
486
|
+
if (src) img.src = src
|
|
487
|
+
}
|
|
488
|
+
})
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/** 将页面中的 <img>(Vite 资源 URL 等)转为 data URL,避免 Word MHT 丢失图片 */
|
|
492
|
+
export async function rasterizeImagesForDocx(
|
|
493
|
+
root: HTMLElement,
|
|
494
|
+
sourceRoot?: HTMLElement
|
|
495
|
+
) {
|
|
496
|
+
const images = [...root.querySelectorAll('img')] as HTMLImageElement[]
|
|
497
|
+
const sourceImages = sourceRoot
|
|
498
|
+
? ([...sourceRoot.querySelectorAll('img')] as HTMLImageElement[])
|
|
499
|
+
: images
|
|
500
|
+
|
|
501
|
+
await Promise.all(
|
|
502
|
+
images.map(async (img, index) => {
|
|
503
|
+
const src = img.getAttribute('src') || img.src
|
|
504
|
+
if (!src) return
|
|
505
|
+
|
|
506
|
+
const liveImg = sourceImages[index] ?? img
|
|
507
|
+
const { width, height } = getDocxImageTargetSize(liveImg)
|
|
508
|
+
|
|
509
|
+
if (src.startsWith('data:')) {
|
|
510
|
+
img.setAttribute('width', String(width))
|
|
511
|
+
img.setAttribute('height', String(height))
|
|
512
|
+
img.style.width = `${width}px`
|
|
513
|
+
img.style.height = `${height}px`
|
|
514
|
+
img.style.maxWidth = `${width}px`
|
|
515
|
+
img.style.maxHeight = `${height}px`
|
|
516
|
+
return
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const applyRasterized = (dataUrl: string) => {
|
|
520
|
+
img.setAttribute('src', dataUrl)
|
|
521
|
+
img.src = dataUrl
|
|
522
|
+
img.setAttribute('width', String(width))
|
|
523
|
+
img.setAttribute('height', String(height))
|
|
524
|
+
img.style.width = `${width}px`
|
|
525
|
+
img.style.height = `${height}px`
|
|
526
|
+
img.style.maxWidth = `${width}px`
|
|
527
|
+
img.style.maxHeight = `${height}px`
|
|
528
|
+
img.style.display = 'block'
|
|
529
|
+
img.style.border = '0'
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
try {
|
|
533
|
+
applyRasterized(await imgElementToDataUrl(liveImg, width, height))
|
|
534
|
+
return
|
|
535
|
+
} catch {
|
|
536
|
+
/* fall through to fetch */
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
try {
|
|
540
|
+
const fetchUrl = liveImg.currentSrc || liveImg.src || src
|
|
541
|
+
const probe = new Image()
|
|
542
|
+
probe.src = await fetchImageAsDataUrl(fetchUrl)
|
|
543
|
+
await new Promise<void>((resolve, reject) => {
|
|
544
|
+
probe.onload = () => resolve()
|
|
545
|
+
probe.onerror = () => reject(new Error('probe load failed'))
|
|
546
|
+
})
|
|
547
|
+
applyRasterized(await imgElementToDataUrl(probe, width, height))
|
|
548
|
+
} catch (e) {
|
|
549
|
+
console.warn('Failed to rasterize image for docx:', src, e)
|
|
550
|
+
}
|
|
551
|
+
})
|
|
552
|
+
)
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
export async function rasterizeMediaForDocx(
|
|
556
|
+
sourceRoot: HTMLElement,
|
|
557
|
+
targetRoot: HTMLElement,
|
|
558
|
+
echartsApi?: {
|
|
559
|
+
getInstanceByDom: (el: HTMLElement) => {
|
|
560
|
+
resize: () => void
|
|
561
|
+
getDataURL: (opts: object) => string
|
|
562
|
+
} | undefined
|
|
563
|
+
}
|
|
564
|
+
) {
|
|
565
|
+
if (echartsApi) {
|
|
566
|
+
await rasterizeEchartsForDocx(sourceRoot, targetRoot, echartsApi)
|
|
567
|
+
}
|
|
568
|
+
await rasterizeIconsForDocx(sourceRoot, targetRoot)
|
|
569
|
+
await rasterizeStandaloneSvgsForDocx(targetRoot)
|
|
570
|
+
await rasterizeImagesForDocx(targetRoot)
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const DOCX_XML_NS =
|
|
574
|
+
'xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" ' +
|
|
575
|
+
'xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"'
|
|
576
|
+
|
|
577
|
+
function escapeXmlText(text: string): string {
|
|
578
|
+
return text
|
|
579
|
+
.replace(/&/g, '&')
|
|
580
|
+
.replace(/</g, '<')
|
|
581
|
+
.replace(/>/g, '>')
|
|
582
|
+
.replace(/"/g, '"')
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function pageFieldRun(instr: string, placeholder = '1'): string {
|
|
586
|
+
return `<w:r>
|
|
587
|
+
<w:rPr><w:sz w:val="18"/><w:color w:val="666666"/></w:rPr>
|
|
588
|
+
<w:fldChar w:fldCharType="begin"/>
|
|
589
|
+
</w:r>
|
|
590
|
+
<w:r>
|
|
591
|
+
<w:rPr><w:sz w:val="18"/><w:color w:val="666666"/></w:rPr>
|
|
592
|
+
<w:instrText xml:space="preserve"> ${instr} </w:instrText>
|
|
593
|
+
</w:r>
|
|
594
|
+
<w:r>
|
|
595
|
+
<w:rPr><w:sz w:val="18"/><w:color w:val="666666"/></w:rPr>
|
|
596
|
+
<w:fldChar w:fldCharType="separate"/>
|
|
597
|
+
</w:r>
|
|
598
|
+
<w:r>
|
|
599
|
+
<w:rPr><w:sz w:val="18"/><w:color w:val="666666"/></w:rPr>
|
|
600
|
+
<w:t>${placeholder}</w:t>
|
|
601
|
+
</w:r>
|
|
602
|
+
<w:r>
|
|
603
|
+
<w:rPr><w:sz w:val="18"/><w:color w:val="666666"/></w:rPr>
|
|
604
|
+
<w:fldChar w:fldCharType="end"/>
|
|
605
|
+
</w:r>`
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function buildHeaderXml(headerText: string): string {
|
|
609
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
610
|
+
<w:hdr ${DOCX_XML_NS}>
|
|
611
|
+
<w:p>
|
|
612
|
+
<w:pPr><w:jc w:val="right"/></w:pPr>
|
|
613
|
+
<w:r>
|
|
614
|
+
<w:rPr>
|
|
615
|
+
<w:rFonts w:ascii="Microsoft YaHei" w:eastAsia="微软雅黑" w:hAnsi="Microsoft YaHei"/>
|
|
616
|
+
<w:sz w:val="18"/>
|
|
617
|
+
<w:color w:val="C00000"/>
|
|
618
|
+
</w:rPr>
|
|
619
|
+
<w:t>${escapeXmlText(headerText)}</w:t>
|
|
620
|
+
</w:r>
|
|
621
|
+
</w:p>
|
|
622
|
+
</w:hdr>`
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function buildFooterXml(): string {
|
|
626
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
627
|
+
<w:ftr ${DOCX_XML_NS}>
|
|
628
|
+
<w:p>
|
|
629
|
+
<w:pPr><w:jc w:val="center"/></w:pPr>
|
|
630
|
+
${pageFieldRun('PAGE')}
|
|
631
|
+
<w:r>
|
|
632
|
+
<w:rPr><w:sz w:val="18"/><w:color w:val="666666"/></w:rPr>
|
|
633
|
+
<w:t xml:space="preserve"> / </w:t>
|
|
634
|
+
</w:r>
|
|
635
|
+
${pageFieldRun('NUMPAGES')}
|
|
636
|
+
</w:p>
|
|
637
|
+
</w:ftr>`
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function buildDocumentXmlWithHeaderFooter(
|
|
641
|
+
width: number,
|
|
642
|
+
height: number,
|
|
643
|
+
orient: string,
|
|
644
|
+
margins: ReturnType<typeof mergeMargins>
|
|
645
|
+
): string {
|
|
646
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
647
|
+
<w:document
|
|
648
|
+
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
|
649
|
+
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
|
|
650
|
+
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
|
651
|
+
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
|
|
652
|
+
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
|
|
653
|
+
xmlns:ns6="http://schemas.openxmlformats.org/schemaLibrary/2006/main"
|
|
654
|
+
xmlns:c="http://schemas.openxmlformats.org/drawingml/2006/chart"
|
|
655
|
+
xmlns:ns8="http://schemas.openxmlformats.org/drawingml/2006/chartDrawing"
|
|
656
|
+
xmlns:dgm="http://schemas.openxmlformats.org/drawingml/2006/diagram"
|
|
657
|
+
xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture"
|
|
658
|
+
xmlns:ns11="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"
|
|
659
|
+
xmlns:dsp="http://schemas.microsoft.com/office/drawing/2008/diagram"
|
|
660
|
+
xmlns:ns13="urn:schemas-microsoft-com:office:excel"
|
|
661
|
+
xmlns:o="urn:schemas-microsoft-com:office:office"
|
|
662
|
+
xmlns:v="urn:schemas-microsoft-com:vml"
|
|
663
|
+
xmlns:w10="urn:schemas-microsoft-com:office:word"
|
|
664
|
+
xmlns:ns17="urn:schemas-microsoft-com:office:powerpoint"
|
|
665
|
+
xmlns:odx="http://opendope.org/xpaths"
|
|
666
|
+
xmlns:odc="http://opendope.org/conditions"
|
|
667
|
+
xmlns:odq="http://opendope.org/questions"
|
|
668
|
+
xmlns:odi="http://opendope.org/components"
|
|
669
|
+
xmlns:odgm="http://opendope.org/SmartArt/DataHierarchy"
|
|
670
|
+
xmlns:ns24="http://schemas.openxmlformats.org/officeDocument/2006/bibliography"
|
|
671
|
+
xmlns:ns25="http://schemas.openxmlformats.org/drawingml/2006/compatibility"
|
|
672
|
+
xmlns:ns26="http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas">
|
|
673
|
+
<w:body>
|
|
674
|
+
<w:altChunk r:id="htmlChunk" />
|
|
675
|
+
<w:sectPr>
|
|
676
|
+
<w:headerReference w:type="default" r:id="rIdHeader"/>
|
|
677
|
+
<w:footerReference w:type="default" r:id="rIdFooter"/>
|
|
678
|
+
<w:pgSz w:w="${width}" w:h="${height}" w:orient="${orient}" />
|
|
679
|
+
<w:pgMar w:top="${margins.top}"
|
|
680
|
+
w:right="${margins.right}"
|
|
681
|
+
w:bottom="${margins.bottom}"
|
|
682
|
+
w:left="${margins.left}"
|
|
683
|
+
w:header="${margins.header}"
|
|
684
|
+
w:footer="${margins.footer}"
|
|
685
|
+
w:gutter="${margins.gutter}"/>
|
|
686
|
+
</w:sectPr>
|
|
687
|
+
</w:body>
|
|
688
|
+
</w:document>`
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const CONTENT_TYPES_WITH_HEADER_FOOTER = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
692
|
+
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
|
693
|
+
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml" />
|
|
694
|
+
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
|
|
695
|
+
<Override PartName="/word/afchunk.mht" ContentType="message/rfc822"/>
|
|
696
|
+
<Override PartName="/word/header1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml"/>
|
|
697
|
+
<Override PartName="/word/footer1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"/>
|
|
698
|
+
</Types>
|
|
699
|
+
`
|
|
700
|
+
|
|
701
|
+
const DOCUMENT_RELS_WITH_HEADER_FOOTER = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
702
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
703
|
+
<Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/aFChunk"
|
|
704
|
+
Target="/word/afchunk.mht" Id="htmlChunk" />
|
|
705
|
+
<Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/header"
|
|
706
|
+
Target="header1.xml" Id="rIdHeader" />
|
|
707
|
+
<Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer"
|
|
708
|
+
Target="footer1.xml" Id="rIdFooter" />
|
|
709
|
+
</Relationships>
|
|
710
|
+
`
|
|
711
|
+
|
|
712
|
+
async function buildDocxZip(
|
|
713
|
+
html: string,
|
|
714
|
+
options: DocxExportOptions = DEFAULT_DOCX_EXPORT_OPTIONS
|
|
715
|
+
): Promise<Blob> {
|
|
716
|
+
const { width, height, orientation } = resolvePageSize(options)
|
|
717
|
+
const margins = mergeMargins({ ...A4_MARGINS_2CM, ...options.margins })
|
|
718
|
+
const headerFooter = {
|
|
719
|
+
headerText: options.headerFooter?.headerText ?? '商业机密',
|
|
720
|
+
showPageNumbers: options.headerFooter?.showPageNumbers ?? true,
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const zip = new JSZip()
|
|
724
|
+
zip.file('[Content_Types].xml', CONTENT_TYPES_WITH_HEADER_FOOTER)
|
|
725
|
+
zip.folder('_rels')!.file('.rels', relsXml)
|
|
726
|
+
|
|
727
|
+
const wordFolder = zip.folder('word')!
|
|
728
|
+
wordFolder
|
|
729
|
+
.file(
|
|
730
|
+
'document.xml',
|
|
731
|
+
buildDocumentXmlWithHeaderFooter(width, height, orientation, margins)
|
|
732
|
+
)
|
|
733
|
+
.file('afchunk.mht', buildMhtDocument(html))
|
|
734
|
+
.file('header1.xml', buildHeaderXml(headerFooter.headerText))
|
|
735
|
+
|
|
736
|
+
if (headerFooter.showPageNumbers) {
|
|
737
|
+
wordFolder.file('footer1.xml', buildFooterXml())
|
|
738
|
+
} else {
|
|
739
|
+
wordFolder.file(
|
|
740
|
+
'footer1.xml',
|
|
741
|
+
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:ftr ${DOCX_XML_NS}><w:p/></w:ftr>`
|
|
742
|
+
)
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
wordFolder.folder('_rels')!.file('document.xml.rels', DOCUMENT_RELS_WITH_HEADER_FOOTER)
|
|
746
|
+
|
|
747
|
+
const buffer = await zip.generateAsync({
|
|
748
|
+
type: 'arraybuffer',
|
|
749
|
+
compression: 'DEFLATE',
|
|
750
|
+
})
|
|
751
|
+
|
|
752
|
+
return new Blob([buffer], {
|
|
753
|
+
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
754
|
+
})
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
export function buildWordDocumentHtml(bodyHtml: string): string {
|
|
758
|
+
return `<!DOCTYPE html>
|
|
759
|
+
<html xmlns:o="urn:schemas-microsoft-com:office:office"
|
|
760
|
+
xmlns:w="urn:schemas-microsoft-com:office:word"
|
|
761
|
+
xmlns="http://www.w3.org/TR/REC-html40"
|
|
762
|
+
lang="zh-Hant">
|
|
763
|
+
<head>
|
|
764
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
765
|
+
<meta charset="UTF-8">
|
|
766
|
+
<!--[if gte mso 9]>
|
|
767
|
+
<xml>
|
|
768
|
+
<w:WordDocument>
|
|
769
|
+
<w:View>Print</w:View>
|
|
770
|
+
<w:Zoom>100</w:Zoom>
|
|
771
|
+
<w:DoNotOptimizeForBrowser/>
|
|
772
|
+
</w:WordDocument>
|
|
773
|
+
</xml>
|
|
774
|
+
<![endif]-->
|
|
775
|
+
<style>
|
|
776
|
+
@page {
|
|
777
|
+
size: A4 portrait;
|
|
778
|
+
margin: 2cm;
|
|
779
|
+
mso-page-orientation: portrait;
|
|
780
|
+
}
|
|
781
|
+
body {
|
|
782
|
+
font-family: Calibri, "Segoe UI", Arial, Helvetica, sans-serif;
|
|
783
|
+
font-size: 11pt;
|
|
784
|
+
line-height: 1.15;
|
|
785
|
+
color: #000000;
|
|
786
|
+
margin: 0;
|
|
787
|
+
padding: 0;
|
|
788
|
+
width: 100%;
|
|
789
|
+
max-width: 100%;
|
|
790
|
+
box-sizing: border-box;
|
|
791
|
+
}
|
|
792
|
+
.docx-page-root,
|
|
793
|
+
.doc-sample-root,
|
|
794
|
+
.doc-sample-body {
|
|
795
|
+
width: ${A4_CONTENT_WIDTH_PT}pt !important;
|
|
796
|
+
max-width: ${A4_CONTENT_WIDTH_PT}pt !important;
|
|
797
|
+
margin: 0;
|
|
798
|
+
padding: 0;
|
|
799
|
+
box-sizing: border-box;
|
|
800
|
+
font-family: Calibri, "Segoe UI", Arial, Helvetica, sans-serif;
|
|
801
|
+
font-size: 11pt;
|
|
802
|
+
line-height: 1.15;
|
|
803
|
+
color: #000000;
|
|
804
|
+
text-align: left;
|
|
805
|
+
}
|
|
806
|
+
.docx-table-wrap {
|
|
807
|
+
width: ${A4_CONTENT_WIDTH_PT}pt !important;
|
|
808
|
+
max-width: ${A4_CONTENT_WIDTH_PT}pt !important;
|
|
809
|
+
margin: 6pt 0 12pt;
|
|
810
|
+
padding: 0;
|
|
811
|
+
overflow: hidden;
|
|
812
|
+
}
|
|
813
|
+
table, .doc-table, .doc-table-word {
|
|
814
|
+
border-collapse: collapse !important;
|
|
815
|
+
width: ${A4_CONTENT_WIDTH_PT}pt !important;
|
|
816
|
+
max-width: ${A4_CONTENT_WIDTH_PT}pt !important;
|
|
817
|
+
table-layout: fixed !important;
|
|
818
|
+
mso-table-layout-alt: fixed !important;
|
|
819
|
+
word-wrap: break-word !important;
|
|
820
|
+
overflow-wrap: break-word !important;
|
|
821
|
+
margin: 0 !important;
|
|
822
|
+
border: 1px solid #000000;
|
|
823
|
+
box-sizing: border-box;
|
|
824
|
+
page-break-inside: auto !important;
|
|
825
|
+
}
|
|
826
|
+
tr, td, th {
|
|
827
|
+
page-break-inside: auto !important;
|
|
828
|
+
}
|
|
829
|
+
.doc-table-complex th,
|
|
830
|
+
.doc-table-complex td,
|
|
831
|
+
.doc-table-word th,
|
|
832
|
+
.doc-table-word td {
|
|
833
|
+
font-size: 9pt !important;
|
|
834
|
+
padding: 2pt 3pt !important;
|
|
835
|
+
}
|
|
836
|
+
.doc-h1, h1.doc-h1 {
|
|
837
|
+
font-size: 20pt !important;
|
|
838
|
+
font-weight: bold !important;
|
|
839
|
+
color: #c00000 !important;
|
|
840
|
+
text-align: left !important;
|
|
841
|
+
margin: 0 0 12pt !important;
|
|
842
|
+
padding: 0 !important;
|
|
843
|
+
border: none !important;
|
|
844
|
+
}
|
|
845
|
+
.doc-h2, h2.doc-h2 {
|
|
846
|
+
font-size: 14pt !important;
|
|
847
|
+
font-weight: bold !important;
|
|
848
|
+
color: #000000 !important;
|
|
849
|
+
margin: 12pt 0 6pt !important;
|
|
850
|
+
padding: 0 !important;
|
|
851
|
+
border: none !important;
|
|
852
|
+
}
|
|
853
|
+
.doc-h3, h3.doc-h3 {
|
|
854
|
+
font-size: 12pt !important;
|
|
855
|
+
font-weight: bold !important;
|
|
856
|
+
color: #000000 !important;
|
|
857
|
+
margin: 10pt 0 4pt !important;
|
|
858
|
+
}
|
|
859
|
+
.doc-p, p.doc-p {
|
|
860
|
+
font-size: 11pt !important;
|
|
861
|
+
line-height: 1.15 !important;
|
|
862
|
+
color: #000000 !important;
|
|
863
|
+
margin: 0 0 10pt !important;
|
|
864
|
+
text-align: left !important;
|
|
865
|
+
}
|
|
866
|
+
.doc-link-line, p.doc-link-line {
|
|
867
|
+
font-size: 11pt !important;
|
|
868
|
+
margin: 0 0 2pt !important;
|
|
869
|
+
padding: 0 !important;
|
|
870
|
+
}
|
|
871
|
+
.doc-link-anchor, a.doc-link-anchor {
|
|
872
|
+
color: #0563c1 !important;
|
|
873
|
+
text-decoration: underline !important;
|
|
874
|
+
}
|
|
875
|
+
.typewriter-paragraph {
|
|
876
|
+
white-space: pre-wrap;
|
|
877
|
+
word-break: break-word;
|
|
878
|
+
font-size: inherit !important;
|
|
879
|
+
line-height: inherit !important;
|
|
880
|
+
color: inherit !important;
|
|
881
|
+
}
|
|
882
|
+
.doc-ol-export { margin: 6pt 0 10pt; padding: 0; font-size: 11pt; color: #000000; }
|
|
883
|
+
.doc-ol-export-item {
|
|
884
|
+
margin: 0 0 2pt 0.5in !important;
|
|
885
|
+
text-indent: -0.25in !important;
|
|
886
|
+
font-size: 11pt !important;
|
|
887
|
+
line-height: 1.15 !important;
|
|
888
|
+
font-weight: normal !important;
|
|
889
|
+
color: #000000 !important;
|
|
890
|
+
}
|
|
891
|
+
.doc-ul-export-item {
|
|
892
|
+
margin: 0 0 2pt 0.75in !important;
|
|
893
|
+
text-indent: -0.25in !important;
|
|
894
|
+
font-size: 11pt !important;
|
|
895
|
+
line-height: 1.15 !important;
|
|
896
|
+
font-weight: normal !important;
|
|
897
|
+
color: #000000 !important;
|
|
898
|
+
}
|
|
899
|
+
th, td {
|
|
900
|
+
border: 1px solid #000000;
|
|
901
|
+
padding: 3pt 4pt !important;
|
|
902
|
+
font-size: 10pt !important;
|
|
903
|
+
color: #000000;
|
|
904
|
+
word-wrap: break-word !important;
|
|
905
|
+
overflow-wrap: break-word !important;
|
|
906
|
+
box-sizing: border-box;
|
|
907
|
+
}
|
|
908
|
+
th { font-weight: bold; background-color: #ffffff; }
|
|
909
|
+
.doc-th-green, th.doc-th-green { background-color: #c6efce !important; color: #000000 !important; }
|
|
910
|
+
.doc-th-red, th.doc-th-red { background-color: #ffc7ce !important; color: #000000 !important; }
|
|
911
|
+
.doc-chart-intro {
|
|
912
|
+
display: block;
|
|
913
|
+
width: ${A4_CONTENT_WIDTH_PT}pt !important;
|
|
914
|
+
max-width: ${A4_CONTENT_WIDTH_PT}pt !important;
|
|
915
|
+
margin: 0 0 10pt;
|
|
916
|
+
overflow: hidden;
|
|
917
|
+
box-sizing: border-box;
|
|
918
|
+
}
|
|
919
|
+
.doc-chart-intro__left,
|
|
920
|
+
.doc-chart-intro__right {
|
|
921
|
+
display: block;
|
|
922
|
+
float: none !important;
|
|
923
|
+
width: 100% !important;
|
|
924
|
+
max-width: ${A4_CONTENT_WIDTH_PT}pt !important;
|
|
925
|
+
margin: 0 0 8pt 0 !important;
|
|
926
|
+
overflow: visible;
|
|
927
|
+
box-sizing: border-box;
|
|
928
|
+
}
|
|
929
|
+
.doc-pie-chart-img { display: block; width: ${DOCX_PIE_CHART_MAX_WIDTH_PX}px; height: auto; max-width: ${DOCX_PIE_CHART_MAX_WIDTH_PX}px; max-height: ${DOCX_PIE_CHART_MAX_HEIGHT_PX}px; box-sizing: border-box; }
|
|
930
|
+
.doc-chart-intro-text { margin: 0; text-align: left; font-size: 11pt !important; }
|
|
931
|
+
.doc-images-block { overflow: hidden; margin: 0 0 10pt; max-width: ${A4_CONTENT_WIDTH_PX}px; width: 100%; box-sizing: border-box; }
|
|
932
|
+
.doc-float-icon { float: left; width: ${DOCX_FLOAT_ICON_SIZE_PX}px; height: ${DOCX_FLOAT_ICON_SIZE_PX}px; max-width: ${DOCX_FLOAT_ICON_SIZE_PX}px; max-height: ${DOCX_FLOAT_ICON_SIZE_PX}px; margin: 0 12pt 8pt 0; display: block; box-sizing: border-box; }
|
|
933
|
+
.doc-images-p { margin: 0 0 10pt; text-align: left; font-size: 11pt; line-height: 1.15; }
|
|
934
|
+
.success-row td { background-color: #f0f9eb; }
|
|
935
|
+
.info-row td { background-color: #e6f7ff; }
|
|
936
|
+
.warning-row td { background-color: #fdf6ec; }
|
|
937
|
+
.danger-row td { background-color: #fef0f0; }
|
|
938
|
+
img { max-width: 100%; height: auto; }
|
|
939
|
+
.docx-chart-wrap,
|
|
940
|
+
.echarts-container.docx-chart-wrap {
|
|
941
|
+
max-width: ${A4_CONTENT_WIDTH_PX}px;
|
|
942
|
+
margin: 12pt auto !important;
|
|
943
|
+
text-align: center !important;
|
|
944
|
+
page-break-inside: avoid;
|
|
945
|
+
page-break-before: auto;
|
|
946
|
+
box-sizing: border-box;
|
|
947
|
+
overflow: visible;
|
|
948
|
+
}
|
|
949
|
+
.docx-chart-wrap .docx-chart-img,
|
|
950
|
+
.docx-chart-wrap img {
|
|
951
|
+
display: block !important;
|
|
952
|
+
margin: 0 auto !important;
|
|
953
|
+
border: 0;
|
|
954
|
+
max-width: ${A4_CONTENT_WIDTH_PX}px;
|
|
955
|
+
}
|
|
956
|
+
button, .el-button {
|
|
957
|
+
display: inline-block !important;
|
|
958
|
+
padding: 4px 12px;
|
|
959
|
+
margin: 0 6px 6px 0;
|
|
960
|
+
border: 1px solid #dcdfe6;
|
|
961
|
+
border-radius: 4px;
|
|
962
|
+
font-size: 12px;
|
|
963
|
+
background: #fff;
|
|
964
|
+
color: #606266;
|
|
965
|
+
}
|
|
966
|
+
.el-button--danger { background: #fef0f0 !important; color: #f56c6c !important; border-color: #fbc4c4 !important; }
|
|
967
|
+
.el-button--success { background: #f0f9eb !important; color: #67c23a !important; }
|
|
968
|
+
.el-button--primary { background: #ecf5ff !important; color: #409eff !important; }
|
|
969
|
+
.section-title {
|
|
970
|
+
font-size: 18px;
|
|
971
|
+
font-weight: bold;
|
|
972
|
+
color: #1a1a1a;
|
|
973
|
+
border-top: 1px solid #e5e7eb;
|
|
974
|
+
padding-top: 24px;
|
|
975
|
+
margin-bottom: 20px;
|
|
976
|
+
}
|
|
977
|
+
</style>
|
|
978
|
+
</head>
|
|
979
|
+
<body><div class="docx-page-root">${bodyHtml}</div></body>
|
|
980
|
+
</html>`
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
const STYLE_PROPS = [
|
|
984
|
+
'color',
|
|
985
|
+
'fontSize',
|
|
986
|
+
'fontFamily',
|
|
987
|
+
'fontWeight',
|
|
988
|
+
'fontStyle',
|
|
989
|
+
'backgroundColor',
|
|
990
|
+
'textAlign',
|
|
991
|
+
'lineHeight',
|
|
992
|
+
'marginTop',
|
|
993
|
+
'marginBottom',
|
|
994
|
+
'marginLeft',
|
|
995
|
+
'marginRight',
|
|
996
|
+
'paddingTop',
|
|
997
|
+
'paddingBottom',
|
|
998
|
+
'paddingLeft',
|
|
999
|
+
'paddingRight',
|
|
1000
|
+
'borderTop',
|
|
1001
|
+
'borderBottom',
|
|
1002
|
+
'borderLeft',
|
|
1003
|
+
'borderRight',
|
|
1004
|
+
'display',
|
|
1005
|
+
'width',
|
|
1006
|
+
'height',
|
|
1007
|
+
'maxWidth',
|
|
1008
|
+
'verticalAlign',
|
|
1009
|
+
'whiteSpace',
|
|
1010
|
+
'letterSpacing',
|
|
1011
|
+
] as const
|
|
1012
|
+
|
|
1013
|
+
type InlineStyleMap = Record<string, string>
|
|
1014
|
+
|
|
1015
|
+
const DOC_CLASS_INLINE_STYLES: Record<string, InlineStyleMap> = {
|
|
1016
|
+
'doc-h1': {
|
|
1017
|
+
fontSize: '20pt',
|
|
1018
|
+
fontWeight: 'bold',
|
|
1019
|
+
color: '#c00000',
|
|
1020
|
+
textAlign: 'left',
|
|
1021
|
+
margin: '0 0 12pt',
|
|
1022
|
+
padding: '0',
|
|
1023
|
+
border: 'none',
|
|
1024
|
+
},
|
|
1025
|
+
'doc-h2': {
|
|
1026
|
+
fontSize: '14pt',
|
|
1027
|
+
fontWeight: 'bold',
|
|
1028
|
+
color: '#000000',
|
|
1029
|
+
margin: '12pt 0 6pt',
|
|
1030
|
+
padding: '0',
|
|
1031
|
+
border: 'none',
|
|
1032
|
+
},
|
|
1033
|
+
'doc-h3': {
|
|
1034
|
+
fontSize: '12pt',
|
|
1035
|
+
fontWeight: 'bold',
|
|
1036
|
+
color: '#000000',
|
|
1037
|
+
margin: '10pt 0 4pt',
|
|
1038
|
+
},
|
|
1039
|
+
'doc-p': {
|
|
1040
|
+
fontSize: '11pt',
|
|
1041
|
+
lineHeight: '1.15',
|
|
1042
|
+
color: '#000000',
|
|
1043
|
+
margin: '0 0 10pt',
|
|
1044
|
+
textAlign: 'left',
|
|
1045
|
+
},
|
|
1046
|
+
'doc-link-line': {
|
|
1047
|
+
fontSize: '11pt',
|
|
1048
|
+
margin: '0 0 2pt',
|
|
1049
|
+
padding: '0',
|
|
1050
|
+
},
|
|
1051
|
+
'doc-link-anchor': {
|
|
1052
|
+
color: '#0563c1',
|
|
1053
|
+
textDecoration: 'underline',
|
|
1054
|
+
},
|
|
1055
|
+
'doc-th-green': {
|
|
1056
|
+
backgroundColor: '#c6efce',
|
|
1057
|
+
color: '#000000',
|
|
1058
|
+
fontWeight: 'bold',
|
|
1059
|
+
},
|
|
1060
|
+
'doc-th-red': {
|
|
1061
|
+
backgroundColor: '#ffc7ce',
|
|
1062
|
+
color: '#000000',
|
|
1063
|
+
fontWeight: 'bold',
|
|
1064
|
+
},
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
function applyInlineStyles(el: HTMLElement, styles: InlineStyleMap) {
|
|
1068
|
+
for (const [prop, val] of Object.entries(styles)) {
|
|
1069
|
+
;(el.style as unknown as Record<string, string>)[prop] = val
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
function applyDocClassInlineStyles(el: HTMLElement) {
|
|
1074
|
+
for (const cls of el.classList) {
|
|
1075
|
+
const styles = DOC_CLASS_INLINE_STYLES[cls]
|
|
1076
|
+
if (styles) applyInlineStyles(el, styles)
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
function mountCloneForStyleRead(clone: HTMLElement): () => void {
|
|
1081
|
+
const host = document.createElement('div')
|
|
1082
|
+
host.setAttribute('data-docx-export-host', 'true')
|
|
1083
|
+
host.style.cssText = `position:fixed;left:-100000px;top:0;width:${A4_CONTENT_WIDTH_PX}px;max-width:100%;visibility:hidden;pointer-events:none;box-sizing:border-box;`
|
|
1084
|
+
host.appendChild(clone)
|
|
1085
|
+
document.body.appendChild(host)
|
|
1086
|
+
return () => {
|
|
1087
|
+
host.remove()
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
function clampAllImagesForDocx(root: HTMLElement) {
|
|
1092
|
+
root.querySelectorAll('img').forEach((node) => {
|
|
1093
|
+
if (!(node instanceof HTMLImageElement)) return
|
|
1094
|
+
const { width, height } = getDocxImageTargetSize(node)
|
|
1095
|
+
node.setAttribute('width', String(width))
|
|
1096
|
+
node.setAttribute('height', String(height))
|
|
1097
|
+
node.style.maxWidth = `${width}px`
|
|
1098
|
+
node.style.maxHeight = `${height}px`
|
|
1099
|
+
node.style.width = `${width}px`
|
|
1100
|
+
node.style.height = `${height}px`
|
|
1101
|
+
node.style.boxSizing = 'border-box'
|
|
1102
|
+
})
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
function flattenChartIntroForDocx(root: HTMLElement) {
|
|
1106
|
+
root.querySelectorAll('.doc-chart-intro').forEach((intro) => {
|
|
1107
|
+
if (!(intro instanceof HTMLElement)) return
|
|
1108
|
+
intro.style.cssText = [
|
|
1109
|
+
'display:block',
|
|
1110
|
+
`width:${A4_CONTENT_WIDTH_PT}pt`,
|
|
1111
|
+
`max-width:${A4_CONTENT_WIDTH_PT}pt`,
|
|
1112
|
+
'margin:0 0 10pt',
|
|
1113
|
+
'overflow:visible',
|
|
1114
|
+
'box-sizing:border-box',
|
|
1115
|
+
].join(';')
|
|
1116
|
+
|
|
1117
|
+
const left = intro.querySelector('.doc-chart-intro__left')
|
|
1118
|
+
const right = intro.querySelector('.doc-chart-intro__right')
|
|
1119
|
+
const img = intro.querySelector('.doc-pie-chart-img')
|
|
1120
|
+
|
|
1121
|
+
if (left instanceof HTMLElement) {
|
|
1122
|
+
left.style.cssText = [
|
|
1123
|
+
'display:block',
|
|
1124
|
+
'float:none',
|
|
1125
|
+
'width:100%',
|
|
1126
|
+
`max-width:${A4_CONTENT_WIDTH_PT}pt`,
|
|
1127
|
+
'margin:0 0 8pt 0',
|
|
1128
|
+
'box-sizing:border-box',
|
|
1129
|
+
].join(';')
|
|
1130
|
+
}
|
|
1131
|
+
if (img instanceof HTMLImageElement) {
|
|
1132
|
+
const { width, height } = getDocxImageTargetSize(img)
|
|
1133
|
+
const maxW = Math.min(width, Math.floor(A4_CONTENT_WIDTH_PT * 0.55))
|
|
1134
|
+
const maxH = DOCX_PIE_CHART_MAX_HEIGHT_PX
|
|
1135
|
+
img.style.cssText = [
|
|
1136
|
+
'display:block',
|
|
1137
|
+
`width:${maxW}px`,
|
|
1138
|
+
`height:${height}px`,
|
|
1139
|
+
`max-width:${maxW}px`,
|
|
1140
|
+
`max-height:${maxH}px`,
|
|
1141
|
+
'border:0',
|
|
1142
|
+
'box-sizing:border-box',
|
|
1143
|
+
].join(';')
|
|
1144
|
+
img.setAttribute('width', String(maxW))
|
|
1145
|
+
img.setAttribute('height', String(height))
|
|
1146
|
+
}
|
|
1147
|
+
if (right instanceof HTMLElement) {
|
|
1148
|
+
right.style.cssText = [
|
|
1149
|
+
'display:block',
|
|
1150
|
+
'float:none',
|
|
1151
|
+
'width:100%',
|
|
1152
|
+
`max-width:${A4_CONTENT_WIDTH_PT}pt`,
|
|
1153
|
+
'margin:0',
|
|
1154
|
+
'overflow:visible',
|
|
1155
|
+
'box-sizing:border-box',
|
|
1156
|
+
].join(';')
|
|
1157
|
+
}
|
|
1158
|
+
})
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
function flattenImagesBlockForDocx(root: HTMLElement) {
|
|
1162
|
+
root.querySelectorAll('.doc-images-block').forEach((block) => {
|
|
1163
|
+
if (!(block instanceof HTMLElement)) return
|
|
1164
|
+
block.style.overflow = 'hidden'
|
|
1165
|
+
block.style.maxWidth = `${A4_CONTENT_WIDTH_PX}px`
|
|
1166
|
+
block.style.width = '100%'
|
|
1167
|
+
block.style.boxSizing = 'border-box'
|
|
1168
|
+
})
|
|
1169
|
+
|
|
1170
|
+
root.querySelectorAll('.doc-float-icon').forEach((img) => {
|
|
1171
|
+
if (!(img instanceof HTMLImageElement)) return
|
|
1172
|
+
const size = DOCX_FLOAT_ICON_SIZE_PX
|
|
1173
|
+
img.style.cssText = `float:left;width:${size}px;height:${size}px;max-width:${size}px;max-height:${size}px;margin:0 12pt 8pt 0;display:block;border:0;box-sizing:border-box;`
|
|
1174
|
+
img.setAttribute('width', String(size))
|
|
1175
|
+
img.setAttribute('height', String(size))
|
|
1176
|
+
})
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
const DOC_LIST_ITEM_STYLE =
|
|
1180
|
+
'margin:0 0 2pt 0.5in;text-indent:-0.25in;font-size:11pt;line-height:1.15;font-weight:normal;color:#000000;'
|
|
1181
|
+
const DOC_BULLET_ITEM_STYLE =
|
|
1182
|
+
'margin:0 0 2pt 0.75in;text-indent:-0.25in;font-size:11pt;line-height:1.15;font-weight:normal;color:#000000;'
|
|
1183
|
+
|
|
1184
|
+
function getListItemLabel(li: HTMLLIElement): string {
|
|
1185
|
+
const label = li.querySelector(':scope > .doc-ol-label')
|
|
1186
|
+
if (label?.textContent?.trim()) return label.textContent.trim()
|
|
1187
|
+
|
|
1188
|
+
const nestedUl = li.querySelector(':scope > ul.doc-ul')
|
|
1189
|
+
if (nestedUl) {
|
|
1190
|
+
const parts: string[] = []
|
|
1191
|
+
li.childNodes.forEach((node) => {
|
|
1192
|
+
if (node === nestedUl) return
|
|
1193
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
1194
|
+
const text = node.textContent?.trim()
|
|
1195
|
+
if (text) parts.push(text)
|
|
1196
|
+
} else if (node instanceof HTMLElement && node.tagName !== 'UL') {
|
|
1197
|
+
const text = node.textContent?.trim()
|
|
1198
|
+
if (text) parts.push(text)
|
|
1199
|
+
}
|
|
1200
|
+
})
|
|
1201
|
+
if (parts.length) return parts.join(' ')
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
return li.textContent?.trim() ?? ''
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
/** Word MHT 无法正确处理嵌套 ol/ul,整表展平为手动编号/圆点段落 */
|
|
1208
|
+
function normalizeListsForDocx(root: HTMLElement) {
|
|
1209
|
+
root.querySelectorAll('ol.doc-ol').forEach((ol) => {
|
|
1210
|
+
if (!(ol instanceof HTMLOListElement)) return
|
|
1211
|
+
|
|
1212
|
+
const container = document.createElement('div')
|
|
1213
|
+
container.className = 'doc-ol-export'
|
|
1214
|
+
container.style.cssText = 'margin:6pt 0 10pt;padding:0;font-size:11pt;color:#000000;'
|
|
1215
|
+
|
|
1216
|
+
let index = 0
|
|
1217
|
+
ol.querySelectorAll(':scope > li').forEach((li) => {
|
|
1218
|
+
if (!(li instanceof HTMLLIElement)) return
|
|
1219
|
+
index++
|
|
1220
|
+
|
|
1221
|
+
const nestedUl = li.querySelector(':scope > ul.doc-ul')
|
|
1222
|
+
const mainText = getListItemLabel(li)
|
|
1223
|
+
|
|
1224
|
+
const numP = document.createElement('p')
|
|
1225
|
+
numP.className = 'doc-ol-export-item'
|
|
1226
|
+
numP.style.cssText = DOC_LIST_ITEM_STYLE
|
|
1227
|
+
numP.textContent = `${index}. ${mainText}`
|
|
1228
|
+
container.appendChild(numP)
|
|
1229
|
+
|
|
1230
|
+
if (nestedUl) {
|
|
1231
|
+
[...nestedUl.querySelectorAll(':scope > li')].forEach((item) => {
|
|
1232
|
+
const bulletP = document.createElement('p')
|
|
1233
|
+
bulletP.className = 'doc-ul-export-item'
|
|
1234
|
+
bulletP.style.cssText = DOC_BULLET_ITEM_STYLE
|
|
1235
|
+
bulletP.textContent = `• ${item.textContent?.trim() ?? ''}`
|
|
1236
|
+
container.appendChild(bulletP)
|
|
1237
|
+
})
|
|
1238
|
+
}
|
|
1239
|
+
})
|
|
1240
|
+
|
|
1241
|
+
ol.replaceWith(container)
|
|
1242
|
+
})
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/** 统计表格逻辑列数(含 colspan) */
|
|
1246
|
+
function getTableColumnCount(table: HTMLTableElement): number {
|
|
1247
|
+
let maxCols = 0
|
|
1248
|
+
Array.from(table.rows).forEach((row) => {
|
|
1249
|
+
let cols = 0
|
|
1250
|
+
Array.from(row.cells).forEach((cell) => {
|
|
1251
|
+
cols += cell.colSpan || 1
|
|
1252
|
+
})
|
|
1253
|
+
maxCols = Math.max(maxCols, cols)
|
|
1254
|
+
})
|
|
1255
|
+
return maxCols
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
function getColumnWidthsPt(colCount: number): number[] {
|
|
1259
|
+
const total = A4_CONTENT_WIDTH_PT
|
|
1260
|
+
if (colCount === 3) {
|
|
1261
|
+
const c0 = Math.round(total * 0.42)
|
|
1262
|
+
const c1 = Math.round(total * 0.29)
|
|
1263
|
+
return [c0, c1, total - c0 - c1]
|
|
1264
|
+
}
|
|
1265
|
+
if (colCount === 5) {
|
|
1266
|
+
const c0 = Math.round(total * 0.26)
|
|
1267
|
+
const rest = Math.floor((total - c0) / 4)
|
|
1268
|
+
return [c0, rest, rest, rest, total - c0 - rest * 3]
|
|
1269
|
+
}
|
|
1270
|
+
const each = Math.floor(total / colCount)
|
|
1271
|
+
return Array.from({ length: colCount }, (_, index) =>
|
|
1272
|
+
index === colCount - 1 ? total - each * (colCount - 1) : each
|
|
1273
|
+
)
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
function applyTableColgroupPt(table: HTMLTableElement, widthsPt: number[]) {
|
|
1277
|
+
table.querySelector('colgroup')?.remove()
|
|
1278
|
+
const colgroup = document.createElement('colgroup')
|
|
1279
|
+
widthsPt.forEach((width) => {
|
|
1280
|
+
const col = document.createElement('col')
|
|
1281
|
+
col.setAttribute('width', String(width))
|
|
1282
|
+
col.setAttribute(
|
|
1283
|
+
'style',
|
|
1284
|
+
`width:${width}pt;mso-width-source:userset;mso-width-alt:${width * 20}`
|
|
1285
|
+
)
|
|
1286
|
+
colgroup.appendChild(col)
|
|
1287
|
+
})
|
|
1288
|
+
table.insertBefore(colgroup, table.firstChild)
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
type DocxCellInfo = {
|
|
1292
|
+
text: string
|
|
1293
|
+
tag: 'th' | 'td'
|
|
1294
|
+
className: string
|
|
1295
|
+
textAlign: string
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
/** 将含 colspan/rowspan 的表格展开为逻辑网格 */
|
|
1299
|
+
function expandTableToGrid(table: HTMLTableElement): DocxCellInfo[][] {
|
|
1300
|
+
const colCount = getTableColumnCount(table)
|
|
1301
|
+
const grid: (DocxCellInfo | null)[][] = []
|
|
1302
|
+
|
|
1303
|
+
Array.from(table.rows).forEach((row, rowIndex) => {
|
|
1304
|
+
if (!grid[rowIndex]) grid[rowIndex] = Array(colCount).fill(null)
|
|
1305
|
+
let colIndex = 0
|
|
1306
|
+
|
|
1307
|
+
Array.from(row.cells).forEach((cell) => {
|
|
1308
|
+
while (colIndex < colCount && grid[rowIndex]![colIndex] !== null) colIndex++
|
|
1309
|
+
|
|
1310
|
+
const info: DocxCellInfo = {
|
|
1311
|
+
text: cell.textContent?.trim() ?? '',
|
|
1312
|
+
tag: cell.tagName === 'TH' ? 'th' : 'td',
|
|
1313
|
+
className: cell.className,
|
|
1314
|
+
textAlign: (cell as HTMLElement).style.textAlign || '',
|
|
1315
|
+
}
|
|
1316
|
+
const colspan = cell.colSpan || 1
|
|
1317
|
+
const rowspan = cell.rowSpan || 1
|
|
1318
|
+
|
|
1319
|
+
for (let r = 0; r < rowspan; r++) {
|
|
1320
|
+
for (let c = 0; c < colspan; c++) {
|
|
1321
|
+
const targetRow = rowIndex + r
|
|
1322
|
+
if (!grid[targetRow]) grid[targetRow] = Array(colCount).fill(null)
|
|
1323
|
+
grid[targetRow]![colIndex + c] =
|
|
1324
|
+
r === 0 && c === 0 ? info : { ...info, text: '' }
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
colIndex += colspan
|
|
1328
|
+
})
|
|
1329
|
+
})
|
|
1330
|
+
|
|
1331
|
+
return grid.filter((row) => row.some((cell) => cell !== null)) as DocxCellInfo[][]
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
function applyWordTableStyles(table: HTMLTableElement, colCount: number) {
|
|
1335
|
+
const isComplex = table.classList.contains('doc-table-complex') || colCount >= 5
|
|
1336
|
+
const cellFontSize = isComplex ? '9pt' : '10pt'
|
|
1337
|
+
const cellPadding = isComplex ? '2pt 3pt' : '3pt 4pt'
|
|
1338
|
+
const colWidthsPt = getColumnWidthsPt(colCount)
|
|
1339
|
+
|
|
1340
|
+
applyTableColgroupPt(table, colWidthsPt)
|
|
1341
|
+
|
|
1342
|
+
table.setAttribute('width', String(A4_CONTENT_WIDTH_PT))
|
|
1343
|
+
table.style.cssText = [
|
|
1344
|
+
'border-collapse:collapse',
|
|
1345
|
+
`width:${A4_CONTENT_WIDTH_PT}pt`,
|
|
1346
|
+
`max-width:${A4_CONTENT_WIDTH_PT}pt`,
|
|
1347
|
+
'table-layout:fixed',
|
|
1348
|
+
'mso-table-layout-alt:fixed',
|
|
1349
|
+
'word-wrap:break-word',
|
|
1350
|
+
'overflow-wrap:break-word',
|
|
1351
|
+
'margin:0',
|
|
1352
|
+
'border:1px solid #000000',
|
|
1353
|
+
'box-sizing:border-box',
|
|
1354
|
+
'page-break-inside:auto',
|
|
1355
|
+
].join(';')
|
|
1356
|
+
|
|
1357
|
+
table.querySelectorAll('tr').forEach((row) => {
|
|
1358
|
+
;(row as HTMLElement).style.pageBreakInside = 'auto'
|
|
1359
|
+
Array.from(row.cells).forEach((cell) => {
|
|
1360
|
+
cell.removeAttribute('colspan')
|
|
1361
|
+
cell.removeAttribute('rowspan')
|
|
1362
|
+
cell.style.border = '1px solid #000000'
|
|
1363
|
+
cell.style.padding = cellPadding
|
|
1364
|
+
cell.style.fontSize = cellFontSize
|
|
1365
|
+
cell.style.wordWrap = 'break-word'
|
|
1366
|
+
cell.style.overflowWrap = 'break-word'
|
|
1367
|
+
cell.style.boxSizing = 'border-box'
|
|
1368
|
+
cell.style.verticalAlign = 'top'
|
|
1369
|
+
cell.style.pageBreakInside = 'auto'
|
|
1370
|
+
|
|
1371
|
+
if (!cell.classList.contains('doc-th-green') && !cell.classList.contains('doc-th-red')) {
|
|
1372
|
+
cell.style.color = '#000000'
|
|
1373
|
+
}
|
|
1374
|
+
})
|
|
1375
|
+
})
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
function createWordTableCell(info: DocxCellInfo, cellFontSize: string, cellPadding: string) {
|
|
1379
|
+
const cell = document.createElement(info.tag)
|
|
1380
|
+
cell.className = info.className
|
|
1381
|
+
cell.textContent = info.text
|
|
1382
|
+
cell.style.border = '1px solid #000000'
|
|
1383
|
+
cell.style.padding = cellPadding
|
|
1384
|
+
cell.style.fontSize = cellFontSize
|
|
1385
|
+
cell.style.wordWrap = 'break-word'
|
|
1386
|
+
cell.style.overflowWrap = 'break-word'
|
|
1387
|
+
cell.style.boxSizing = 'border-box'
|
|
1388
|
+
cell.style.verticalAlign = 'top'
|
|
1389
|
+
cell.style.pageBreakInside = 'auto'
|
|
1390
|
+
if (info.textAlign) cell.style.textAlign = info.textAlign
|
|
1391
|
+
if (!cell.classList.contains('doc-th-green') && !cell.classList.contains('doc-th-red')) {
|
|
1392
|
+
cell.style.color = '#000000'
|
|
1393
|
+
}
|
|
1394
|
+
return cell
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
function rebuildFlatWordTable(
|
|
1398
|
+
grid: DocxCellInfo[][],
|
|
1399
|
+
tableClasses: string
|
|
1400
|
+
): HTMLDivElement {
|
|
1401
|
+
const colCount = grid[0]?.length ?? 0
|
|
1402
|
+
const isComplex = tableClasses.includes('doc-table-complex') || colCount >= 5
|
|
1403
|
+
const cellFontSize = isComplex ? '9pt' : '10pt'
|
|
1404
|
+
const cellPadding = isComplex ? '2pt 3pt' : '3pt 4pt'
|
|
1405
|
+
|
|
1406
|
+
const wrap = document.createElement('div')
|
|
1407
|
+
wrap.className = 'docx-table-wrap'
|
|
1408
|
+
wrap.style.cssText = [
|
|
1409
|
+
`width:${A4_CONTENT_WIDTH_PT}pt`,
|
|
1410
|
+
`max-width:${A4_CONTENT_WIDTH_PT}pt`,
|
|
1411
|
+
'margin:6pt 0 12pt',
|
|
1412
|
+
'padding:0',
|
|
1413
|
+
'overflow:hidden',
|
|
1414
|
+
'box-sizing:border-box',
|
|
1415
|
+
].join(';')
|
|
1416
|
+
|
|
1417
|
+
const table = document.createElement('table')
|
|
1418
|
+
table.className = `${tableClasses} doc-table-word`.trim()
|
|
1419
|
+
applyWordTableStyles(table, colCount)
|
|
1420
|
+
|
|
1421
|
+
grid.forEach((row) => {
|
|
1422
|
+
const tr = document.createElement('tr')
|
|
1423
|
+
tr.style.pageBreakInside = 'auto'
|
|
1424
|
+
row.forEach((info) => {
|
|
1425
|
+
tr.appendChild(createWordTableCell(info, cellFontSize, cellPadding))
|
|
1426
|
+
})
|
|
1427
|
+
table.appendChild(tr)
|
|
1428
|
+
})
|
|
1429
|
+
|
|
1430
|
+
wrap.appendChild(table)
|
|
1431
|
+
return wrap
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
function tableHasSpanningCells(table: HTMLTableElement): boolean {
|
|
1435
|
+
return Array.from(table.querySelectorAll('th, td')).some((cell) => {
|
|
1436
|
+
const el = cell as HTMLTableCellElement
|
|
1437
|
+
return (el.colSpan || 1) > 1 || (el.rowSpan || 1) > 1
|
|
1438
|
+
})
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
/** 重建 Word 安全表格:pt 宽度 + 无 colspan/rowspan + 外层 wrap 防溢出 */
|
|
1442
|
+
function rebuildTablesForWord(root: HTMLElement) {
|
|
1443
|
+
root.querySelectorAll('table.doc-table').forEach((node) => {
|
|
1444
|
+
if (!(node instanceof HTMLTableElement)) return
|
|
1445
|
+
|
|
1446
|
+
const tableClasses = [...node.classList]
|
|
1447
|
+
.filter((c) => c !== 'doc-table-word')
|
|
1448
|
+
.join(' ')
|
|
1449
|
+
const colCount = getTableColumnCount(node)
|
|
1450
|
+
|
|
1451
|
+
if (tableHasSpanningCells(node)) {
|
|
1452
|
+
const grid = expandTableToGrid(node)
|
|
1453
|
+
node.replaceWith(rebuildFlatWordTable(grid, tableClasses))
|
|
1454
|
+
return
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
const wrap = document.createElement('div')
|
|
1458
|
+
wrap.className = 'docx-table-wrap'
|
|
1459
|
+
wrap.style.cssText = [
|
|
1460
|
+
`width:${A4_CONTENT_WIDTH_PT}pt`,
|
|
1461
|
+
`max-width:${A4_CONTENT_WIDTH_PT}pt`,
|
|
1462
|
+
'margin:6pt 0 12pt',
|
|
1463
|
+
'padding:0',
|
|
1464
|
+
'overflow:hidden',
|
|
1465
|
+
'box-sizing:border-box',
|
|
1466
|
+
].join(';')
|
|
1467
|
+
|
|
1468
|
+
node.classList.add('doc-table-word')
|
|
1469
|
+
applyWordTableStyles(node, colCount)
|
|
1470
|
+
node.parentNode?.insertBefore(wrap, node)
|
|
1471
|
+
wrap.appendChild(node)
|
|
1472
|
+
})
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
/** Word 导出移除预览分页标记,避免 MHT 中产生整页空白 */
|
|
1476
|
+
export function stripWordPageBreaks(root: HTMLElement) {
|
|
1477
|
+
root.querySelectorAll('[data-export-page-break]').forEach((el) => el.remove())
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
/** @deprecated 使用 stripWordPageBreaks,注入分页符会在 Word 中产生大量空白 */
|
|
1481
|
+
export function injectWordPageBreaks(root: HTMLElement) {
|
|
1482
|
+
stripWordPageBreaks(root)
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
/** 剥离 Vue / 无用属性,内联计算样式,尽量贴近页面 1:1 */
|
|
1486
|
+
export function prepareDomForDocx(root: HTMLElement, inPlace = false): HTMLElement {
|
|
1487
|
+
const clone = inPlace ? root : (root.cloneNode(true) as HTMLElement)
|
|
1488
|
+
|
|
1489
|
+
clone.querySelectorAll('.no-print, .animate-pulse, .typewriter-cursor').forEach((el) =>
|
|
1490
|
+
el.remove()
|
|
1491
|
+
)
|
|
1492
|
+
|
|
1493
|
+
const unmount = mountCloneForStyleRead(clone)
|
|
1494
|
+
|
|
1495
|
+
try {
|
|
1496
|
+
clone.querySelectorAll('*').forEach((el) => {
|
|
1497
|
+
if (!(el instanceof HTMLElement)) return
|
|
1498
|
+
|
|
1499
|
+
applyDocClassInlineStyles(el)
|
|
1500
|
+
|
|
1501
|
+
const computed = window.getComputedStyle(el)
|
|
1502
|
+
if (computed.display === 'flex' || computed.display === 'inline-flex') {
|
|
1503
|
+
el.style.display = 'block'
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
for (const prop of STYLE_PROPS) {
|
|
1507
|
+
const val = computed[prop]
|
|
1508
|
+
if (val && val !== 'initial' && val !== 'normal' && val !== 'auto' && val !== 'none') {
|
|
1509
|
+
const styleRecord = el.style as unknown as Record<string, string>
|
|
1510
|
+
if (!styleRecord[prop]) {
|
|
1511
|
+
styleRecord[prop] = val
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
if (el.tagName === 'TABLE') {
|
|
1517
|
+
el.style.borderCollapse = 'collapse'
|
|
1518
|
+
el.style.tableLayout = 'fixed'
|
|
1519
|
+
el.style.width = `${A4_CONTENT_WIDTH_PT}pt`
|
|
1520
|
+
el.style.maxWidth = `${A4_CONTENT_WIDTH_PT}pt`
|
|
1521
|
+
el.style.pageBreakInside = 'auto'
|
|
1522
|
+
}
|
|
1523
|
+
if (el.tagName === 'TD' || el.tagName === 'TH') {
|
|
1524
|
+
el.style.border = '1px solid #000000'
|
|
1525
|
+
el.style.padding = '4pt 6pt'
|
|
1526
|
+
el.style.fontSize = '11pt'
|
|
1527
|
+
if (!el.classList.contains('doc-th-green') && !el.classList.contains('doc-th-red')) {
|
|
1528
|
+
el.style.color = '#000000'
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
if (el.tagName === 'IMG') {
|
|
1532
|
+
const img = el as HTMLImageElement
|
|
1533
|
+
el.style.display = el.style.display || 'block'
|
|
1534
|
+
el.style.maxWidth = el.style.maxWidth || '100%'
|
|
1535
|
+
if (!img.getAttribute('width') && img.naturalWidth > 0) {
|
|
1536
|
+
img.setAttribute('width', String(img.naturalWidth))
|
|
1537
|
+
}
|
|
1538
|
+
if (!img.getAttribute('height') && img.naturalHeight > 0) {
|
|
1539
|
+
img.setAttribute('height', String(img.naturalHeight))
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
})
|
|
1543
|
+
} finally {
|
|
1544
|
+
unmount()
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
clone.querySelectorAll('*').forEach((el) => {
|
|
1548
|
+
if (!(el instanceof HTMLElement)) return
|
|
1549
|
+
;[...el.attributes].forEach((attr) => {
|
|
1550
|
+
if (
|
|
1551
|
+
attr.name.startsWith('data-v-') ||
|
|
1552
|
+
attr.name === 'data-v-app' ||
|
|
1553
|
+
attr.name.startsWith('data-cy') ||
|
|
1554
|
+
attr.name === 'data-docx-export-host'
|
|
1555
|
+
) {
|
|
1556
|
+
el.removeAttribute(attr.name)
|
|
1557
|
+
}
|
|
1558
|
+
})
|
|
1559
|
+
})
|
|
1560
|
+
|
|
1561
|
+
clampAllImagesForDocx(clone)
|
|
1562
|
+
flattenChartIntroForDocx(clone)
|
|
1563
|
+
flattenImagesBlockForDocx(clone)
|
|
1564
|
+
normalizeListsForDocx(clone)
|
|
1565
|
+
rebuildTablesForWord(clone)
|
|
1566
|
+
|
|
1567
|
+
clone.querySelectorAll('.typewriter-card__body, .content-wrapper, .doc-sample-body').forEach((el) => {
|
|
1568
|
+
if (!(el instanceof HTMLElement)) return
|
|
1569
|
+
el.style.minHeight = '0'
|
|
1570
|
+
el.style.height = 'auto'
|
|
1571
|
+
el.style.padding = '0'
|
|
1572
|
+
el.style.maxWidth = `${A4_CONTENT_WIDTH_PT}pt`
|
|
1573
|
+
el.style.width = `${A4_CONTENT_WIDTH_PT}pt`
|
|
1574
|
+
el.style.boxSizing = 'border-box'
|
|
1575
|
+
})
|
|
1576
|
+
|
|
1577
|
+
clone.classList.add('docx-page-root')
|
|
1578
|
+
clone.style.maxHeight = 'none'
|
|
1579
|
+
clone.style.overflow = 'visible'
|
|
1580
|
+
clone.style.border = 'none'
|
|
1581
|
+
clone.style.padding = '0'
|
|
1582
|
+
clone.style.minHeight = '0'
|
|
1583
|
+
clone.style.backgroundColor = '#ffffff'
|
|
1584
|
+
clone.style.boxSizing = 'border-box'
|
|
1585
|
+
clone.style.width = `${A4_CONTENT_WIDTH_PT}pt`
|
|
1586
|
+
clone.style.maxWidth = `${A4_CONTENT_WIDTH_PT}pt`
|
|
1587
|
+
clone.style.margin = '0'
|
|
1588
|
+
clone.style.fontFamily = 'Calibri, "Segoe UI", Arial, Helvetica, sans-serif'
|
|
1589
|
+
clone.style.fontSize = '11pt'
|
|
1590
|
+
clone.style.lineHeight = '1.15'
|
|
1591
|
+
clone.style.color = '#000000'
|
|
1592
|
+
|
|
1593
|
+
clone.querySelectorAll('.echarts-container').forEach((el) => {
|
|
1594
|
+
if (!(el instanceof HTMLElement)) return
|
|
1595
|
+
if (el.querySelector('.docx-chart-wrap')) return
|
|
1596
|
+
el.style.width = '100%'
|
|
1597
|
+
el.style.maxWidth = '100%'
|
|
1598
|
+
el.style.textAlign = 'center'
|
|
1599
|
+
el.style.margin = '12pt auto'
|
|
1600
|
+
})
|
|
1601
|
+
|
|
1602
|
+
return clone
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
export async function exportHtmlToDocx(
|
|
1606
|
+
fullHtml: string,
|
|
1607
|
+
options: DocxExportOptions = DEFAULT_DOCX_EXPORT_OPTIONS
|
|
1608
|
+
): Promise<Blob> {
|
|
1609
|
+
return buildDocxZip(fullHtml, { ...DEFAULT_DOCX_EXPORT_OPTIONS, ...options })
|
|
1610
|
+
}
|