md2ui 1.0.10 → 1.0.11
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 +3 -2
- package/src/App.vue +6 -3
- package/src/components/ImageZoom.vue +119 -15
- package/src/composables/useDocManager.js +17 -8
- package/src/composables/useExportWord.js +154 -28
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "md2ui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "将本地 Markdown 文档转换为美观的 HTML 页面",
|
|
6
6
|
"author": "",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"scripts": {
|
|
23
23
|
"dev": "vite",
|
|
24
24
|
"build": "vite build",
|
|
25
|
-
"preview": "vite preview"
|
|
25
|
+
"preview": "vite preview",
|
|
26
|
+
"publish": "bash publish.sh"
|
|
26
27
|
},
|
|
27
28
|
"files": [
|
|
28
29
|
"bin",
|
package/src/App.vue
CHANGED
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
</button>
|
|
79
79
|
</transition>
|
|
80
80
|
<!-- 图片放大 -->
|
|
81
|
-
<ImageZoom :visible="zoomVisible" :
|
|
81
|
+
<ImageZoom :visible="zoomVisible" :images="zoomImages" :currentIndex="zoomIndex" @update:currentIndex="zoomIndex = $event" @close="zoomVisible = false" />
|
|
82
82
|
|
|
83
83
|
</div>
|
|
84
84
|
</template>
|
|
@@ -105,6 +105,8 @@ const sidebarCollapsed = ref(false)
|
|
|
105
105
|
const tocCollapsed = ref(false)
|
|
106
106
|
const zoomVisible = ref(false)
|
|
107
107
|
const zoomContent = ref('')
|
|
108
|
+
const zoomImages = ref([])
|
|
109
|
+
const zoomIndex = ref(0)
|
|
108
110
|
|
|
109
111
|
// composables
|
|
110
112
|
const {
|
|
@@ -126,8 +128,9 @@ const { isMobile, mobileDrawerOpen, mobileTocOpen } = useMobile()
|
|
|
126
128
|
// 内容区点击:委托给 docManager,图片放大回调在这里处理
|
|
127
129
|
function onContentClick(event) {
|
|
128
130
|
handleContentClick(event, {
|
|
129
|
-
onZoom(
|
|
130
|
-
|
|
131
|
+
onZoom({ images, index }) {
|
|
132
|
+
zoomImages.value = images
|
|
133
|
+
zoomIndex.value = index
|
|
131
134
|
zoomVisible.value = true
|
|
132
135
|
}
|
|
133
136
|
})
|
|
@@ -23,11 +23,25 @@
|
|
|
23
23
|
<RotateCcw :size="16" />
|
|
24
24
|
</button>
|
|
25
25
|
<span class="zoom-level">{{ Math.round(scale * 100) }}%</span>
|
|
26
|
+
<!-- 图片计数 -->
|
|
27
|
+
<span v-if="images.length > 1" class="image-counter">{{ currentIndex + 1 }} / {{ images.length }}</span>
|
|
26
28
|
<button class="zoom-btn close-btn" @click="close" title="关闭">
|
|
27
29
|
<X :size="16" />
|
|
28
30
|
</button>
|
|
29
31
|
</div>
|
|
30
32
|
|
|
33
|
+
<!-- 左切换按钮 -->
|
|
34
|
+
<button
|
|
35
|
+
v-if="images.length > 1"
|
|
36
|
+
class="nav-btn nav-btn-left"
|
|
37
|
+
:class="{ 'nav-btn-disabled': currentIndex <= 0 }"
|
|
38
|
+
:disabled="currentIndex <= 0"
|
|
39
|
+
@click="goPrev"
|
|
40
|
+
title="上一张"
|
|
41
|
+
>
|
|
42
|
+
<ChevronLeft :size="28" />
|
|
43
|
+
</button>
|
|
44
|
+
|
|
31
45
|
<!-- 图片容器 -->
|
|
32
46
|
<div
|
|
33
47
|
class="image-wrapper"
|
|
@@ -35,16 +49,28 @@
|
|
|
35
49
|
transform: `translate(${translateX}px, ${translateY}px) scale(${scale})`,
|
|
36
50
|
transformOrigin: 'center center'
|
|
37
51
|
}"
|
|
38
|
-
v-html="
|
|
52
|
+
v-html="currentContent"
|
|
39
53
|
></div>
|
|
54
|
+
|
|
55
|
+
<!-- 右切换按钮 -->
|
|
56
|
+
<button
|
|
57
|
+
v-if="images.length > 1"
|
|
58
|
+
class="nav-btn nav-btn-right"
|
|
59
|
+
:class="{ 'nav-btn-disabled': currentIndex >= images.length - 1 }"
|
|
60
|
+
:disabled="currentIndex >= images.length - 1"
|
|
61
|
+
@click="goNext"
|
|
62
|
+
title="下一张"
|
|
63
|
+
>
|
|
64
|
+
<ChevronRight :size="28" />
|
|
65
|
+
</button>
|
|
40
66
|
</div>
|
|
41
67
|
</div>
|
|
42
68
|
</teleport>
|
|
43
69
|
</template>
|
|
44
70
|
|
|
45
71
|
<script setup>
|
|
46
|
-
import { ref } from 'vue'
|
|
47
|
-
import { ZoomIn, ZoomOut, RotateCcw, X } from 'lucide-vue-next'
|
|
72
|
+
import { ref, computed } from 'vue'
|
|
73
|
+
import { ZoomIn, ZoomOut, RotateCcw, X, ChevronLeft, ChevronRight } from 'lucide-vue-next'
|
|
48
74
|
|
|
49
75
|
// Props
|
|
50
76
|
const props = defineProps({
|
|
@@ -52,14 +78,33 @@ const props = defineProps({
|
|
|
52
78
|
type: Boolean,
|
|
53
79
|
default: false
|
|
54
80
|
},
|
|
81
|
+
// 兼容单图模式
|
|
55
82
|
imageContent: {
|
|
56
83
|
type: String,
|
|
57
84
|
default: ''
|
|
85
|
+
},
|
|
86
|
+
// 图片列表(多图切换)
|
|
87
|
+
images: {
|
|
88
|
+
type: Array,
|
|
89
|
+
default: () => []
|
|
90
|
+
},
|
|
91
|
+
// 当前图片索引
|
|
92
|
+
currentIndex: {
|
|
93
|
+
type: Number,
|
|
94
|
+
default: 0
|
|
58
95
|
}
|
|
59
96
|
})
|
|
60
97
|
|
|
61
98
|
// Emits
|
|
62
|
-
const emit = defineEmits(['close'])
|
|
99
|
+
const emit = defineEmits(['close', 'update:currentIndex'])
|
|
100
|
+
|
|
101
|
+
// 当前展示内容:优先使用列表模式
|
|
102
|
+
const currentContent = computed(() => {
|
|
103
|
+
if (props.images.length > 0) {
|
|
104
|
+
return props.images[props.currentIndex] || ''
|
|
105
|
+
}
|
|
106
|
+
return props.imageContent
|
|
107
|
+
})
|
|
63
108
|
|
|
64
109
|
// 缩放和拖拽状态
|
|
65
110
|
const scale = ref(1)
|
|
@@ -95,6 +140,21 @@ function resetZoom() {
|
|
|
95
140
|
translateY.value = 0
|
|
96
141
|
}
|
|
97
142
|
|
|
143
|
+
// 切换图片时重置缩放
|
|
144
|
+
function goPrev() {
|
|
145
|
+
if (props.currentIndex > 0) {
|
|
146
|
+
resetZoom()
|
|
147
|
+
emit('update:currentIndex', props.currentIndex - 1)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function goNext() {
|
|
152
|
+
if (props.currentIndex < props.images.length - 1) {
|
|
153
|
+
resetZoom()
|
|
154
|
+
emit('update:currentIndex', props.currentIndex + 1)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
98
158
|
// 关闭
|
|
99
159
|
function close() {
|
|
100
160
|
resetZoom()
|
|
@@ -111,12 +171,9 @@ function handleOverlayClick(event) {
|
|
|
111
171
|
// 处理滚轮缩放
|
|
112
172
|
function handleWheel(event) {
|
|
113
173
|
event.preventDefault()
|
|
114
|
-
|
|
115
|
-
// 使用比例缩放,每次滚轮缩放 2%,体感更平滑
|
|
116
174
|
const zoomFactor = 0.02
|
|
117
175
|
const direction = event.deltaY > 0 ? -1 : 1
|
|
118
176
|
const newScale = Math.max(minScale, Math.min(maxScale, scale.value * (1 + direction * zoomFactor)))
|
|
119
|
-
|
|
120
177
|
if (newScale !== scale.value) {
|
|
121
178
|
scale.value = newScale
|
|
122
179
|
}
|
|
@@ -124,8 +181,7 @@ function handleWheel(event) {
|
|
|
124
181
|
|
|
125
182
|
// 处理鼠标按下
|
|
126
183
|
function handleMouseDown(event) {
|
|
127
|
-
if (event.target.closest('.zoom-toolbar')) return
|
|
128
|
-
|
|
184
|
+
if (event.target.closest('.zoom-toolbar') || event.target.closest('.nav-btn')) return
|
|
129
185
|
isDragging.value = true
|
|
130
186
|
lastMouseX.value = event.clientX
|
|
131
187
|
lastMouseY.value = event.clientY
|
|
@@ -135,13 +191,10 @@ function handleMouseDown(event) {
|
|
|
135
191
|
// 处理鼠标移动
|
|
136
192
|
function handleMouseMove(event) {
|
|
137
193
|
if (!isDragging.value) return
|
|
138
|
-
|
|
139
194
|
const deltaX = event.clientX - lastMouseX.value
|
|
140
195
|
const deltaY = event.clientY - lastMouseY.value
|
|
141
|
-
|
|
142
196
|
translateX.value += deltaX
|
|
143
197
|
translateY.value += deltaY
|
|
144
|
-
|
|
145
198
|
lastMouseX.value = event.clientX
|
|
146
199
|
lastMouseY.value = event.clientY
|
|
147
200
|
}
|
|
@@ -154,7 +207,6 @@ function handleMouseUp() {
|
|
|
154
207
|
// 监听键盘事件
|
|
155
208
|
function handleKeyDown(event) {
|
|
156
209
|
if (!props.visible) return
|
|
157
|
-
|
|
158
210
|
switch (event.key) {
|
|
159
211
|
case 'Escape':
|
|
160
212
|
close()
|
|
@@ -169,10 +221,15 @@ function handleKeyDown(event) {
|
|
|
169
221
|
case '0':
|
|
170
222
|
resetZoom()
|
|
171
223
|
break
|
|
224
|
+
case 'ArrowLeft':
|
|
225
|
+
goPrev()
|
|
226
|
+
break
|
|
227
|
+
case 'ArrowRight':
|
|
228
|
+
goNext()
|
|
229
|
+
break
|
|
172
230
|
}
|
|
173
231
|
}
|
|
174
232
|
|
|
175
|
-
// 添加键盘监听
|
|
176
233
|
if (typeof window !== 'undefined') {
|
|
177
234
|
window.addEventListener('keydown', handleKeyDown)
|
|
178
235
|
}
|
|
@@ -261,6 +318,53 @@ if (typeof window !== 'undefined') {
|
|
|
261
318
|
text-align: center;
|
|
262
319
|
}
|
|
263
320
|
|
|
321
|
+
.image-counter {
|
|
322
|
+
font-size: 12px;
|
|
323
|
+
font-weight: 600;
|
|
324
|
+
color: #718096;
|
|
325
|
+
padding: 0 4px;
|
|
326
|
+
white-space: nowrap;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/* 左右切换按钮 */
|
|
330
|
+
.nav-btn {
|
|
331
|
+
position: absolute;
|
|
332
|
+
top: 50%;
|
|
333
|
+
transform: translateY(-50%);
|
|
334
|
+
z-index: 10000;
|
|
335
|
+
pointer-events: auto;
|
|
336
|
+
display: flex;
|
|
337
|
+
align-items: center;
|
|
338
|
+
justify-content: center;
|
|
339
|
+
width: 44px;
|
|
340
|
+
height: 44px;
|
|
341
|
+
border: none;
|
|
342
|
+
border-radius: 50%;
|
|
343
|
+
background: rgba(255, 255, 255, 0.9);
|
|
344
|
+
color: #2d3748;
|
|
345
|
+
cursor: pointer;
|
|
346
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
347
|
+
transition: all 0.2s;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.nav-btn:hover:not(:disabled) {
|
|
351
|
+
background: #fff;
|
|
352
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.nav-btn-disabled {
|
|
356
|
+
opacity: 0.3;
|
|
357
|
+
cursor: not-allowed;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.nav-btn-left {
|
|
361
|
+
left: 20px;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.nav-btn-right {
|
|
365
|
+
right: 20px;
|
|
366
|
+
}
|
|
367
|
+
|
|
264
368
|
.image-wrapper {
|
|
265
369
|
transition: transform 0.2s ease-out;
|
|
266
370
|
cursor: grab;
|
|
@@ -284,4 +388,4 @@ if (typeof window !== 'undefined') {
|
|
|
284
388
|
width: auto !important;
|
|
285
389
|
height: auto !important;
|
|
286
390
|
}
|
|
287
|
-
</style>
|
|
391
|
+
</style>
|
|
@@ -160,15 +160,24 @@ export function useDocManager() {
|
|
|
160
160
|
}
|
|
161
161
|
return
|
|
162
162
|
}
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
onZoom(`<img src="${target.src}" alt="${target.alt || ''}" style="max-width: 100%; height: auto;" />`)
|
|
166
|
-
return
|
|
167
|
-
}
|
|
168
|
-
// Mermaid 图表放大
|
|
163
|
+
// 图片/Mermaid 放大(收集所有可放大元素,支持左右切换)
|
|
164
|
+
const isImg = target.tagName === 'IMG' && target.classList.contains('zoomable-image')
|
|
169
165
|
const mermaidEl = target.closest('.mermaid')
|
|
170
|
-
|
|
171
|
-
|
|
166
|
+
const isMermaid = mermaidEl && mermaidEl.classList.contains('zoomable-image')
|
|
167
|
+
if (isImg || isMermaid) {
|
|
168
|
+
const container = document.querySelector('.markdown-content')
|
|
169
|
+
if (!container) return
|
|
170
|
+
// 收集所有可放大元素
|
|
171
|
+
const allZoomable = [...container.querySelectorAll('.zoomable-image')]
|
|
172
|
+
const images = allZoomable.map(el => {
|
|
173
|
+
if (el.tagName === 'IMG') {
|
|
174
|
+
return `<img src="${el.src}" alt="${el.alt || ''}" style="max-width: 100%; height: auto;" />`
|
|
175
|
+
}
|
|
176
|
+
return el.innerHTML
|
|
177
|
+
})
|
|
178
|
+
const clickedEl = isImg ? target : mermaidEl
|
|
179
|
+
const index = allZoomable.indexOf(clickedEl)
|
|
180
|
+
onZoom({ images, index: Math.max(index, 0) })
|
|
172
181
|
}
|
|
173
182
|
}
|
|
174
183
|
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
Document, Packer, Paragraph, TextRun, HeadingLevel,
|
|
4
4
|
Table, TableRow, TableCell, WidthType, BorderStyle,
|
|
5
5
|
ImageRun, AlignmentType, ShadingType,
|
|
6
|
+
ExternalHyperlink,
|
|
6
7
|
convertInchesToTwip
|
|
7
8
|
} from 'docx'
|
|
8
9
|
import { saveAs } from 'file-saver'
|
|
@@ -83,7 +84,7 @@ const HEADING_MAP = {
|
|
|
83
84
|
H6: HeadingLevel.HEADING_6,
|
|
84
85
|
}
|
|
85
86
|
|
|
86
|
-
// 从 DOM 节点提取内联文本 runs
|
|
87
|
+
// 从 DOM 节点提取内联文本 runs(支持超链接、高亮、上下标、内联图片)
|
|
87
88
|
function extractTextRuns(node, inherited = {}) {
|
|
88
89
|
const runs = []
|
|
89
90
|
if (!node) return runs
|
|
@@ -112,16 +113,45 @@ function extractTextRuns(node, inherited = {}) {
|
|
|
112
113
|
...inherited,
|
|
113
114
|
}))
|
|
114
115
|
} else if (tag === 'A') {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
const href = child.getAttribute('href') || ''
|
|
117
|
+
// 生成真正的可点击超链接
|
|
118
|
+
if (href && (href.startsWith('http') || href.startsWith('mailto:'))) {
|
|
119
|
+
runs.push(new ExternalHyperlink({
|
|
120
|
+
children: [new TextRun({
|
|
121
|
+
text: child.textContent,
|
|
122
|
+
style: 'Hyperlink',
|
|
123
|
+
color: '4a6cf7',
|
|
124
|
+
underline: {},
|
|
125
|
+
...inherited,
|
|
126
|
+
})],
|
|
127
|
+
link: href,
|
|
128
|
+
}))
|
|
129
|
+
} else {
|
|
130
|
+
runs.push(new TextRun({
|
|
131
|
+
text: child.textContent,
|
|
132
|
+
color: '4a6cf7',
|
|
133
|
+
underline: {},
|
|
134
|
+
...inherited,
|
|
135
|
+
}))
|
|
136
|
+
}
|
|
121
137
|
} else if (tag === 'DEL' || tag === 'S') {
|
|
122
138
|
runs.push(...extractTextRuns(child, { ...inherited, strike: true }))
|
|
139
|
+
} else if (tag === 'MARK') {
|
|
140
|
+
// 高亮文本
|
|
141
|
+
runs.push(...extractTextRuns(child, {
|
|
142
|
+
...inherited,
|
|
143
|
+
shading: { type: ShadingType.CLEAR, fill: 'ffff00' },
|
|
144
|
+
}))
|
|
145
|
+
} else if (tag === 'SUP') {
|
|
146
|
+
runs.push(...extractTextRuns(child, { ...inherited, superScript: true }))
|
|
147
|
+
} else if (tag === 'SUB') {
|
|
148
|
+
runs.push(...extractTextRuns(child, { ...inherited, subScript: true }))
|
|
123
149
|
} else if (tag === 'BR') {
|
|
124
150
|
runs.push(new TextRun({ break: 1 }))
|
|
151
|
+
} else if (tag === 'IMG') {
|
|
152
|
+
// 内联图片标记为占位符(实际图片在段落级处理)
|
|
153
|
+
const alt = child.getAttribute('alt') || '[图片]'
|
|
154
|
+
runs.push(new TextRun({ text: alt, color: '999999', italics: true }))
|
|
125
155
|
} else {
|
|
126
156
|
runs.push(...extractTextRuns(child, inherited))
|
|
127
157
|
}
|
|
@@ -175,7 +205,7 @@ function parseList(listEl, level = 0) {
|
|
|
175
205
|
const isOrdered = listEl.tagName === 'OL'
|
|
176
206
|
const idx = Array.from(listEl.children).indexOf(li)
|
|
177
207
|
|
|
178
|
-
//
|
|
208
|
+
// 提取文本(排除嵌套列表,支持 li 内 p 包裹的多段落)
|
|
179
209
|
const textRuns = []
|
|
180
210
|
if (prefix) {
|
|
181
211
|
textRuns.push(new TextRun({ text: prefix, font: 'Consolas', size: 20 }))
|
|
@@ -184,21 +214,32 @@ function parseList(listEl, level = 0) {
|
|
|
184
214
|
if (child.nodeType === Node.TEXT_NODE) {
|
|
185
215
|
const t = child.textContent.trim()
|
|
186
216
|
if (t) textRuns.push(new TextRun({ text: t }))
|
|
187
|
-
} else if (child.nodeType === Node.ELEMENT_NODE
|
|
188
|
-
|
|
189
|
-
|
|
217
|
+
} else if (child.nodeType === Node.ELEMENT_NODE) {
|
|
218
|
+
const cTag = child.tagName
|
|
219
|
+
if (cTag === 'UL' || cTag === 'OL') continue // 嵌套列表单独处理
|
|
220
|
+
if (cTag === 'INPUT') continue // 跳过 checkbox 本身
|
|
221
|
+
// li 内的 p 标签:提取内容并在段落间加换行
|
|
222
|
+
if (cTag === 'P') {
|
|
223
|
+
if (textRuns.length > 0) {
|
|
224
|
+
textRuns.push(new TextRun({ break: 1 }))
|
|
225
|
+
}
|
|
226
|
+
textRuns.push(...extractTextRuns(child))
|
|
227
|
+
} else {
|
|
228
|
+
textRuns.push(...extractTextRuns(child))
|
|
229
|
+
}
|
|
190
230
|
}
|
|
191
231
|
}
|
|
192
232
|
|
|
193
|
-
const bullet = isOrdered ? `${idx + 1}. ` : '
|
|
194
|
-
const
|
|
233
|
+
const bullet = isOrdered ? `${idx + 1}. ` : '\u2022 '
|
|
234
|
+
const baseIndent = 360
|
|
235
|
+
const indent = baseIndent + level * 360
|
|
195
236
|
items.push(new Paragraph({
|
|
196
237
|
children: [
|
|
197
|
-
new TextRun({ text:
|
|
238
|
+
new TextRun({ text: bullet }),
|
|
198
239
|
...textRuns,
|
|
199
240
|
],
|
|
200
241
|
spacing: { before: 40, after: 40 },
|
|
201
|
-
indent: { left: indent },
|
|
242
|
+
indent: { left: indent, hanging: 240 },
|
|
202
243
|
}))
|
|
203
244
|
|
|
204
245
|
// 嵌套列表
|
|
@@ -228,14 +269,45 @@ async function parseDomToDocx(contentEl) {
|
|
|
228
269
|
continue
|
|
229
270
|
}
|
|
230
271
|
|
|
231
|
-
//
|
|
272
|
+
// 段落(处理内嵌图片)
|
|
232
273
|
if (tag === 'P') {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
274
|
+
// 检查段落内是否有图片
|
|
275
|
+
const imgs = node.querySelectorAll('img')
|
|
276
|
+
if (imgs.length > 0) {
|
|
277
|
+
// 先输出文本部分
|
|
278
|
+
const runs = extractTextRuns(node)
|
|
279
|
+
const textOnly = runs.filter(r => !(r instanceof ImageRun))
|
|
280
|
+
if (textOnly.length > 0) {
|
|
281
|
+
elements.push(new Paragraph({
|
|
282
|
+
children: textOnly,
|
|
283
|
+
spacing: { before: 80, after: 80 },
|
|
284
|
+
}))
|
|
285
|
+
}
|
|
286
|
+
// 再逐个输出图片
|
|
287
|
+
for (const img of imgs) {
|
|
288
|
+
try {
|
|
289
|
+
const imgData = await fetchImageAsBytes(img.src)
|
|
290
|
+
if (imgData) {
|
|
291
|
+
elements.push(new Paragraph({
|
|
292
|
+
children: [new ImageRun({
|
|
293
|
+
data: imgData.bytes,
|
|
294
|
+
transformation: { width: imgData.width, height: imgData.height },
|
|
295
|
+
type: 'png',
|
|
296
|
+
})],
|
|
297
|
+
spacing: { before: 120, after: 120 },
|
|
298
|
+
alignment: AlignmentType.CENTER,
|
|
299
|
+
}))
|
|
300
|
+
}
|
|
301
|
+
} catch { /* 跳过无法加载的图片 */ }
|
|
302
|
+
}
|
|
303
|
+
} else {
|
|
304
|
+
const runs = extractTextRuns(node)
|
|
305
|
+
if (runs.length > 0) {
|
|
306
|
+
elements.push(new Paragraph({
|
|
307
|
+
children: runs,
|
|
308
|
+
spacing: { before: 80, after: 80 },
|
|
309
|
+
}))
|
|
310
|
+
}
|
|
239
311
|
}
|
|
240
312
|
continue
|
|
241
313
|
}
|
|
@@ -354,18 +426,72 @@ async function parseDomToDocx(contentEl) {
|
|
|
354
426
|
continue
|
|
355
427
|
}
|
|
356
428
|
|
|
357
|
-
//
|
|
429
|
+
// 引用块:递归处理内部块级元素,保留完整结构
|
|
358
430
|
if (tag === 'BLOCKQUOTE') {
|
|
359
|
-
const
|
|
360
|
-
elements.push(new Paragraph({
|
|
361
|
-
children: runs,
|
|
362
|
-
spacing: { before: 80, after: 80 },
|
|
431
|
+
const bqStyle = {
|
|
363
432
|
indent: { left: 400 },
|
|
364
433
|
border: {
|
|
365
434
|
left: { style: BorderStyle.SINGLE, size: 6, color: 'cccccc' },
|
|
366
435
|
},
|
|
367
436
|
shading: { type: ShadingType.CLEAR, fill: 'fafafa' },
|
|
368
|
-
}
|
|
437
|
+
}
|
|
438
|
+
if (node.children.length > 0) {
|
|
439
|
+
for (const child of node.children) {
|
|
440
|
+
const childTag = child.tagName
|
|
441
|
+
// 嵌套引用块:增加缩进
|
|
442
|
+
if (childTag === 'BLOCKQUOTE') {
|
|
443
|
+
const innerChildren = child.children.length > 0 ? child.children : [child]
|
|
444
|
+
for (const inner of innerChildren) {
|
|
445
|
+
const runs = extractTextRuns(inner)
|
|
446
|
+
if (runs.length > 0) {
|
|
447
|
+
elements.push(new Paragraph({
|
|
448
|
+
children: runs,
|
|
449
|
+
spacing: { before: 80, after: 80 },
|
|
450
|
+
indent: { left: 800 },
|
|
451
|
+
border: {
|
|
452
|
+
left: { style: BorderStyle.SINGLE, size: 6, color: 'aaaaaa' },
|
|
453
|
+
},
|
|
454
|
+
shading: { type: ShadingType.CLEAR, fill: 'f5f5f5' },
|
|
455
|
+
}))
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
} else if (childTag === 'UL' || childTag === 'OL') {
|
|
459
|
+
// 引用块内的列表
|
|
460
|
+
const listItems = parseList(child)
|
|
461
|
+
for (const item of listItems) {
|
|
462
|
+
elements.push(item)
|
|
463
|
+
}
|
|
464
|
+
} else if (childTag === 'PRE' || child.classList?.contains('code-block-wrapper')) {
|
|
465
|
+
// 引用块内的代码块,走正常代码块逻辑但加引用样式
|
|
466
|
+
const runs = extractTextRuns(child)
|
|
467
|
+
if (runs.length > 0) {
|
|
468
|
+
elements.push(new Paragraph({
|
|
469
|
+
children: runs,
|
|
470
|
+
spacing: { before: 80, after: 80 },
|
|
471
|
+
...bqStyle,
|
|
472
|
+
}))
|
|
473
|
+
}
|
|
474
|
+
} else {
|
|
475
|
+
const runs = extractTextRuns(child)
|
|
476
|
+
if (runs.length > 0) {
|
|
477
|
+
elements.push(new Paragraph({
|
|
478
|
+
children: runs,
|
|
479
|
+
spacing: { before: 80, after: 80 },
|
|
480
|
+
...bqStyle,
|
|
481
|
+
}))
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
} else {
|
|
486
|
+
const runs = extractTextRuns(node)
|
|
487
|
+
if (runs.length > 0) {
|
|
488
|
+
elements.push(new Paragraph({
|
|
489
|
+
children: runs,
|
|
490
|
+
spacing: { before: 80, after: 80 },
|
|
491
|
+
...bqStyle,
|
|
492
|
+
}))
|
|
493
|
+
}
|
|
494
|
+
}
|
|
369
495
|
continue
|
|
370
496
|
}
|
|
371
497
|
|