md2ui 1.0.19 → 1.0.21
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/README.md +65 -20
- package/bin/build.js +13 -2
- package/bin/md2ui.js +25 -12
- package/package.json +4 -4
- package/public/docs/02-Markdown/346/270/262/346/237/223/00-/345/237/272/347/241/200/350/257/255/346/263/225.md +2 -0
- package/public/docs/02-Markdown/346/270/262/346/237/223/06-Mermaid/345/244/215/346/235/202/345/233/276/350/241/250/346/265/213/350/257/225.md +1376 -0
- package/public/docs/02-Markdown/346/270/262/346/237/223/assets/img-1777383093712.png +0 -0
- package/public/docs/03-/345/257/274/350/210/252/344/270/216/345/270/203/345/261/200/05-/345/244/247/347/272/262/345/216/213/345/212/233/346/265/213/350/257/225.md +340 -0
- package/public/docs/07-/347/247/273/345/212/250/347/253/257/351/200/202/351/205/215/00-/345/223/215/345/272/224/345/274/217/345/270/203/345/261/200.md +4 -4
- package/src/App.vue +36 -61
- package/src/components/ImageZoom.vue +9 -123
- package/src/components/MermaidNodeView.vue +10 -2
- package/src/components/MobileSearch.vue +97 -0
- package/src/components/TableOfContents.vue +42 -6
- package/src/composables/useDocManager.js +134 -44
- package/src/composables/useDocTree.js +26 -50
- package/src/composables/useMarkdown.js +51 -140
- package/src/composables/useMermaidCache.js +15 -0
- package/src/composables/useScroll.js +317 -32
- package/src/composables/useSearch.js +12 -11
- package/src/config.js +1 -4
- package/src/services/DocService.js +0 -16
- package/src/style.css +235 -10
- package/src/utils/imageConverter.js +129 -0
- package/vite-plugin-doc-api.js +158 -157
- package/vite.config.js +5 -1
- package/src/components/SearchPanel.vue +0 -90
- package/src/components/TableBubbleMenu.vue +0 -177
- package/src/composables/useExportPdf.js +0 -102
package/src/style.css
CHANGED
|
@@ -248,7 +248,7 @@ body {
|
|
|
248
248
|
max-width: 960px;
|
|
249
249
|
margin: 0 auto;
|
|
250
250
|
background: var(--color-bg);
|
|
251
|
-
padding: 24px
|
|
251
|
+
padding: 24px 20px;
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
/* Markdown 内容样式 */
|
|
@@ -279,6 +279,19 @@ body {
|
|
|
279
279
|
.markdown-content h5,
|
|
280
280
|
.markdown-content h6 {
|
|
281
281
|
position: relative;
|
|
282
|
+
scroll-margin-top: 60px;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* 标题锚点跳转高亮闪烁动画 */
|
|
286
|
+
@keyframes heading-flash {
|
|
287
|
+
0% { background-color: transparent; }
|
|
288
|
+
15% { background-color: rgba(66, 184, 131, 0.15); }
|
|
289
|
+
100% { background-color: transparent; }
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.markdown-content .heading-flash {
|
|
293
|
+
animation: heading-flash 1.5s ease-out;
|
|
294
|
+
border-radius: 4px;
|
|
282
295
|
}
|
|
283
296
|
|
|
284
297
|
.markdown-content h1:hover .heading-anchor,
|
|
@@ -295,6 +308,44 @@ body {
|
|
|
295
308
|
text-decoration: none;
|
|
296
309
|
}
|
|
297
310
|
|
|
311
|
+
/* 锚点提示气泡(hover + 点击复制共用同一位置) */
|
|
312
|
+
.markdown-content .heading-anchor::after {
|
|
313
|
+
content: '复制链接';
|
|
314
|
+
position: absolute;
|
|
315
|
+
left: 0.7em;
|
|
316
|
+
bottom: calc(100% + 4px);
|
|
317
|
+
padding: 1px 6px;
|
|
318
|
+
background: var(--color-bg-tertiary);
|
|
319
|
+
color: var(--color-text-secondary);
|
|
320
|
+
font-size: 10px;
|
|
321
|
+
font-weight: 400;
|
|
322
|
+
border-radius: 3px;
|
|
323
|
+
white-space: nowrap;
|
|
324
|
+
pointer-events: none;
|
|
325
|
+
opacity: 0;
|
|
326
|
+
transition: opacity 0.15s;
|
|
327
|
+
z-index: 10;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.markdown-content .heading-anchor:hover::after {
|
|
331
|
+
opacity: 1;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/* 点击复制后切换为"已复制"状态 */
|
|
335
|
+
.markdown-content .heading-anchor.anchor-copied::after {
|
|
336
|
+
content: '已复制';
|
|
337
|
+
background: var(--color-success-bg, #dafbe1);
|
|
338
|
+
color: var(--color-success, #1a7f37);
|
|
339
|
+
opacity: 1;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.markdown-content .heading-anchor.anchor-copy-error::after {
|
|
343
|
+
content: '复制失败';
|
|
344
|
+
background: var(--color-error-bg, #ffebe9);
|
|
345
|
+
color: var(--color-error-text, #cf222e);
|
|
346
|
+
opacity: 1;
|
|
347
|
+
}
|
|
348
|
+
|
|
298
349
|
.markdown-content h1 {
|
|
299
350
|
font-size: 2em;
|
|
300
351
|
margin-bottom: 8px;
|
|
@@ -333,6 +384,22 @@ body {
|
|
|
333
384
|
font-weight: 600;
|
|
334
385
|
}
|
|
335
386
|
|
|
387
|
+
.markdown-content h5 {
|
|
388
|
+
font-size: 0.95em;
|
|
389
|
+
margin-top: 14px;
|
|
390
|
+
margin-bottom: 6px;
|
|
391
|
+
color: var(--color-text);
|
|
392
|
+
font-weight: 600;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.markdown-content h6 {
|
|
396
|
+
font-size: 0.9em;
|
|
397
|
+
margin-top: 12px;
|
|
398
|
+
margin-bottom: 6px;
|
|
399
|
+
color: var(--color-text-secondary);
|
|
400
|
+
font-weight: 600;
|
|
401
|
+
}
|
|
402
|
+
|
|
336
403
|
.markdown-content p {
|
|
337
404
|
margin-bottom: 12px;
|
|
338
405
|
color: var(--color-text);
|
|
@@ -1049,9 +1116,9 @@ body {
|
|
|
1049
1116
|
align-items: center;
|
|
1050
1117
|
justify-content: center;
|
|
1051
1118
|
gap: 1px;
|
|
1052
|
-
background:
|
|
1053
|
-
color: var(--color-
|
|
1054
|
-
border: 1px solid var(--color-
|
|
1119
|
+
background: #fff;
|
|
1120
|
+
color: var(--color-accent);
|
|
1121
|
+
border: 1px solid var(--color-accent);
|
|
1055
1122
|
border-radius: 6px;
|
|
1056
1123
|
cursor: pointer;
|
|
1057
1124
|
box-shadow: 0 1px 3px var(--color-shadow);
|
|
@@ -1060,9 +1127,9 @@ body {
|
|
|
1060
1127
|
}
|
|
1061
1128
|
|
|
1062
1129
|
.back-to-top:hover {
|
|
1063
|
-
background: var(--color-
|
|
1064
|
-
color:
|
|
1065
|
-
border-color: var(--color-
|
|
1130
|
+
background: var(--color-accent);
|
|
1131
|
+
color: #fff;
|
|
1132
|
+
border-color: var(--color-accent);
|
|
1066
1133
|
}
|
|
1067
1134
|
|
|
1068
1135
|
.progress-text {
|
|
@@ -1098,6 +1165,11 @@ body {
|
|
|
1098
1165
|
justify-content: space-between;
|
|
1099
1166
|
padding: 0 12px 8px;
|
|
1100
1167
|
border-bottom: 1px solid var(--color-border);
|
|
1168
|
+
position: sticky;
|
|
1169
|
+
top: 0;
|
|
1170
|
+
background: var(--color-bg);
|
|
1171
|
+
z-index: 1;
|
|
1172
|
+
padding-top: 0;
|
|
1101
1173
|
}
|
|
1102
1174
|
|
|
1103
1175
|
.toc-title {
|
|
@@ -1157,6 +1229,31 @@ body {
|
|
|
1157
1229
|
|
|
1158
1230
|
.toc-nav {
|
|
1159
1231
|
padding: 8px 0;
|
|
1232
|
+
position: relative;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
/* 左侧连续竖线 */
|
|
1236
|
+
.toc-track {
|
|
1237
|
+
position: absolute;
|
|
1238
|
+
left: 0;
|
|
1239
|
+
top: 8px;
|
|
1240
|
+
bottom: 8px;
|
|
1241
|
+
width: 2px;
|
|
1242
|
+
background: var(--color-border-light);
|
|
1243
|
+
border-radius: 1px;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
/* marker 滑块 — 平滑过渡 */
|
|
1247
|
+
.toc-marker {
|
|
1248
|
+
position: absolute;
|
|
1249
|
+
left: 0;
|
|
1250
|
+
width: 2px;
|
|
1251
|
+
background: var(--color-accent);
|
|
1252
|
+
border-radius: 1px;
|
|
1253
|
+
transition: top 0.25s cubic-bezier(0.65, 0, 0.35, 1),
|
|
1254
|
+
height 0.25s cubic-bezier(0.65, 0, 0.35, 1),
|
|
1255
|
+
opacity 0.25s;
|
|
1256
|
+
z-index: 1;
|
|
1160
1257
|
}
|
|
1161
1258
|
|
|
1162
1259
|
.toc-item {
|
|
@@ -1169,7 +1266,6 @@ body {
|
|
|
1169
1266
|
font-size: 12px;
|
|
1170
1267
|
line-height: 1.5;
|
|
1171
1268
|
transition: all 0.1s;
|
|
1172
|
-
border-left: 2px solid transparent;
|
|
1173
1269
|
}
|
|
1174
1270
|
|
|
1175
1271
|
.toc-item-text {
|
|
@@ -1216,7 +1312,6 @@ body {
|
|
|
1216
1312
|
|
|
1217
1313
|
.toc-item.active {
|
|
1218
1314
|
color: var(--color-accent);
|
|
1219
|
-
border-left-color: var(--color-accent);
|
|
1220
1315
|
font-weight: 500;
|
|
1221
1316
|
background: var(--color-accent-bg);
|
|
1222
1317
|
border-radius: 0 4px 4px 0;
|
|
@@ -1232,6 +1327,8 @@ body {
|
|
|
1232
1327
|
.toc-item.toc-level-2 { padding-left: 20px; }
|
|
1233
1328
|
.toc-item.toc-level-3 { padding-left: 28px; font-size: 11px; }
|
|
1234
1329
|
.toc-item.toc-level-4 { padding-left: 36px; font-size: 11px; }
|
|
1330
|
+
.toc-item.toc-level-5 { padding-left: 44px; font-size: 11px; }
|
|
1331
|
+
.toc-item.toc-level-6 { padding-left: 52px; font-size: 11px; }
|
|
1235
1332
|
|
|
1236
1333
|
/* ===== 拖拽条 ===== */
|
|
1237
1334
|
.resizer {
|
|
@@ -1514,7 +1611,7 @@ body {
|
|
|
1514
1611
|
|
|
1515
1612
|
@media (max-width: 768px) {
|
|
1516
1613
|
.markdown-content {
|
|
1517
|
-
padding: 16px;
|
|
1614
|
+
padding: 16px 20px;
|
|
1518
1615
|
width: 100%;
|
|
1519
1616
|
}
|
|
1520
1617
|
.back-to-top {
|
|
@@ -3522,3 +3619,131 @@ body {
|
|
|
3522
3619
|
padding: 12px 16px 24px;
|
|
3523
3620
|
}
|
|
3524
3621
|
}
|
|
3622
|
+
|
|
3623
|
+
/* ===== 移动端搜索页面 ===== */
|
|
3624
|
+
.mobile-search-page {
|
|
3625
|
+
flex: 1;
|
|
3626
|
+
display: flex;
|
|
3627
|
+
flex-direction: column;
|
|
3628
|
+
min-width: 0;
|
|
3629
|
+
background: var(--color-bg);
|
|
3630
|
+
overflow: hidden;
|
|
3631
|
+
}
|
|
3632
|
+
|
|
3633
|
+
.mobile-search-header {
|
|
3634
|
+
display: flex;
|
|
3635
|
+
align-items: center;
|
|
3636
|
+
gap: 8px;
|
|
3637
|
+
padding: 12px;
|
|
3638
|
+
border-bottom: 1px solid var(--color-border);
|
|
3639
|
+
flex-shrink: 0;
|
|
3640
|
+
}
|
|
3641
|
+
|
|
3642
|
+
.mobile-search-input-wrapper {
|
|
3643
|
+
flex: 1;
|
|
3644
|
+
display: flex;
|
|
3645
|
+
align-items: center;
|
|
3646
|
+
gap: 8px;
|
|
3647
|
+
padding: 8px 12px;
|
|
3648
|
+
background: var(--color-bg-secondary);
|
|
3649
|
+
border: 1px solid var(--color-border);
|
|
3650
|
+
border-radius: 8px;
|
|
3651
|
+
}
|
|
3652
|
+
|
|
3653
|
+
.mobile-search-input-wrapper:focus-within {
|
|
3654
|
+
border-color: var(--color-accent);
|
|
3655
|
+
box-shadow: 0 0 0 3px var(--color-accent-bg);
|
|
3656
|
+
}
|
|
3657
|
+
|
|
3658
|
+
.mobile-search-icon {
|
|
3659
|
+
color: var(--color-text-tertiary);
|
|
3660
|
+
flex-shrink: 0;
|
|
3661
|
+
}
|
|
3662
|
+
|
|
3663
|
+
.mobile-search-input {
|
|
3664
|
+
flex: 1;
|
|
3665
|
+
border: none;
|
|
3666
|
+
outline: none;
|
|
3667
|
+
background: transparent;
|
|
3668
|
+
font-size: 15px;
|
|
3669
|
+
color: var(--color-text);
|
|
3670
|
+
min-width: 0;
|
|
3671
|
+
}
|
|
3672
|
+
|
|
3673
|
+
.mobile-search-input::placeholder {
|
|
3674
|
+
color: var(--color-text-tertiary);
|
|
3675
|
+
}
|
|
3676
|
+
|
|
3677
|
+
.mobile-search-clear {
|
|
3678
|
+
display: flex;
|
|
3679
|
+
align-items: center;
|
|
3680
|
+
justify-content: center;
|
|
3681
|
+
width: 20px;
|
|
3682
|
+
height: 20px;
|
|
3683
|
+
border: none;
|
|
3684
|
+
background: var(--color-bg-tertiary);
|
|
3685
|
+
color: var(--color-text-secondary);
|
|
3686
|
+
border-radius: 50%;
|
|
3687
|
+
cursor: pointer;
|
|
3688
|
+
flex-shrink: 0;
|
|
3689
|
+
padding: 0;
|
|
3690
|
+
}
|
|
3691
|
+
|
|
3692
|
+
.mobile-search-cancel {
|
|
3693
|
+
border: none;
|
|
3694
|
+
background: transparent;
|
|
3695
|
+
color: var(--color-accent);
|
|
3696
|
+
font-size: 14px;
|
|
3697
|
+
cursor: pointer;
|
|
3698
|
+
white-space: nowrap;
|
|
3699
|
+
padding: 4px 0;
|
|
3700
|
+
}
|
|
3701
|
+
|
|
3702
|
+
.mobile-search-results {
|
|
3703
|
+
flex: 1;
|
|
3704
|
+
overflow-y: auto;
|
|
3705
|
+
-webkit-overflow-scrolling: touch;
|
|
3706
|
+
}
|
|
3707
|
+
|
|
3708
|
+
.mobile-search-item {
|
|
3709
|
+
display: flex;
|
|
3710
|
+
align-items: center;
|
|
3711
|
+
gap: 10px;
|
|
3712
|
+
padding: 12px 16px;
|
|
3713
|
+
cursor: pointer;
|
|
3714
|
+
border-bottom: 1px solid var(--color-border-light);
|
|
3715
|
+
transition: background 0.1s;
|
|
3716
|
+
}
|
|
3717
|
+
|
|
3718
|
+
.mobile-search-item:active,
|
|
3719
|
+
.mobile-search-item.active {
|
|
3720
|
+
background: var(--color-accent-bg);
|
|
3721
|
+
}
|
|
3722
|
+
|
|
3723
|
+
.mobile-search-item-icon {
|
|
3724
|
+
color: var(--color-text-tertiary);
|
|
3725
|
+
flex-shrink: 0;
|
|
3726
|
+
}
|
|
3727
|
+
|
|
3728
|
+
.mobile-search-item-title {
|
|
3729
|
+
flex: 1;
|
|
3730
|
+
font-size: 14px;
|
|
3731
|
+
color: var(--color-text);
|
|
3732
|
+
overflow: hidden;
|
|
3733
|
+
text-overflow: ellipsis;
|
|
3734
|
+
white-space: nowrap;
|
|
3735
|
+
}
|
|
3736
|
+
|
|
3737
|
+
.mobile-search-empty {
|
|
3738
|
+
padding: 32px 16px;
|
|
3739
|
+
text-align: center;
|
|
3740
|
+
color: var(--color-text-tertiary);
|
|
3741
|
+
font-size: 14px;
|
|
3742
|
+
}
|
|
3743
|
+
|
|
3744
|
+
.mobile-search-tip {
|
|
3745
|
+
padding: 24px 16px;
|
|
3746
|
+
text-align: center;
|
|
3747
|
+
color: var(--color-text-tertiary);
|
|
3748
|
+
font-size: 13px;
|
|
3749
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 图片格式转换工具(共享模块)
|
|
3
|
+
* 提供 imgToPngBlob / svgToPngBlob 方法
|
|
4
|
+
* 供 useMarkdown.js 和 ImageZoom.vue 共用
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 将图片 URL 转为 PNG blob(兼容同源和跨域)
|
|
9
|
+
* 策略1:fetch 获取 → 策略2:crossOrigin Image → 策略3:无 crossOrigin 直接画
|
|
10
|
+
*/
|
|
11
|
+
export async function imgToPngBlob(src) {
|
|
12
|
+
try {
|
|
13
|
+
const resp = await fetch(src)
|
|
14
|
+
const blob = await resp.blob()
|
|
15
|
+
if (blob.type === 'image/png') return blob
|
|
16
|
+
return blobToCanvasPng(blob)
|
|
17
|
+
} catch {
|
|
18
|
+
try {
|
|
19
|
+
return await loadImageToBlob(src, true)
|
|
20
|
+
} catch {
|
|
21
|
+
return loadImageToBlob(src, false)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 加载图片到 canvas 并转 PNG blob
|
|
28
|
+
* @param {string} src - 图片地址
|
|
29
|
+
* @param {boolean} useCors - 是否设置 crossOrigin
|
|
30
|
+
*/
|
|
31
|
+
function loadImageToBlob(src, useCors) {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
const image = new Image()
|
|
34
|
+
if (useCors) image.crossOrigin = 'anonymous'
|
|
35
|
+
image.onload = () => {
|
|
36
|
+
try {
|
|
37
|
+
const canvas = document.createElement('canvas')
|
|
38
|
+
canvas.width = image.naturalWidth
|
|
39
|
+
canvas.height = image.naturalHeight
|
|
40
|
+
canvas.getContext('2d').drawImage(image, 0, 0)
|
|
41
|
+
canvas.toBlob(b => b ? resolve(b) : reject(new Error('toBlob null')), 'image/png')
|
|
42
|
+
} catch (e) { reject(e) }
|
|
43
|
+
}
|
|
44
|
+
image.onerror = () => reject(new Error('图片加载失败'))
|
|
45
|
+
image.src = src
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 将任意图片 blob 通过 canvas 转为 PNG blob
|
|
51
|
+
*/
|
|
52
|
+
function blobToCanvasPng(srcBlob, scale = 1) {
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
const url = URL.createObjectURL(srcBlob)
|
|
55
|
+
const image = new Image()
|
|
56
|
+
image.onload = () => {
|
|
57
|
+
const canvas = document.createElement('canvas')
|
|
58
|
+
canvas.width = image.naturalWidth * scale
|
|
59
|
+
canvas.height = image.naturalHeight * scale
|
|
60
|
+
const ctx = canvas.getContext('2d')
|
|
61
|
+
if (scale !== 1) ctx.scale(scale, scale)
|
|
62
|
+
ctx.drawImage(image, 0, 0)
|
|
63
|
+
URL.revokeObjectURL(url)
|
|
64
|
+
canvas.toBlob(b => resolve(b), 'image/png')
|
|
65
|
+
}
|
|
66
|
+
image.onerror = () => { URL.revokeObjectURL(url); reject(new Error('图片加载失败')) }
|
|
67
|
+
image.src = url
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* SVG 转 PNG blob(处理 foreignObject 导致的 tainted canvas 问题)
|
|
73
|
+
* @param {SVGElement} svgEl - SVG DOM 元素
|
|
74
|
+
* @param {number} scale - 缩放倍数,默认 2
|
|
75
|
+
*/
|
|
76
|
+
export function svgToPngBlob(svgEl, scale = 2) {
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
const clone = svgEl.cloneNode(true)
|
|
79
|
+
// 从 viewBox 或属性中获取 SVG 实际尺寸
|
|
80
|
+
const viewBox = clone.getAttribute('viewBox')
|
|
81
|
+
let svgWidth, svgHeight
|
|
82
|
+
if (viewBox) {
|
|
83
|
+
const parts = viewBox.split(/[\s,]+/)
|
|
84
|
+
svgWidth = parseFloat(parts[2])
|
|
85
|
+
svgHeight = parseFloat(parts[3])
|
|
86
|
+
}
|
|
87
|
+
if (!svgWidth || !svgHeight) {
|
|
88
|
+
svgWidth = parseFloat(clone.getAttribute('width')) || svgEl.getBoundingClientRect().width || 800
|
|
89
|
+
svgHeight = parseFloat(clone.getAttribute('height')) || svgEl.getBoundingClientRect().height || 600
|
|
90
|
+
}
|
|
91
|
+
// 显式设置 width/height,确保 Image 加载时尺寸正确
|
|
92
|
+
clone.setAttribute('width', svgWidth)
|
|
93
|
+
clone.setAttribute('height', svgHeight)
|
|
94
|
+
// 将 foreignObject 替换为 text 元素,避免 canvas 被污染
|
|
95
|
+
clone.querySelectorAll('foreignObject').forEach(fo => {
|
|
96
|
+
const text = fo.textContent.trim()
|
|
97
|
+
const x = fo.getAttribute('x') || '0'
|
|
98
|
+
const y = fo.getAttribute('y') || '0'
|
|
99
|
+
const width = fo.getAttribute('width') || '100'
|
|
100
|
+
const height = fo.getAttribute('height') || '20'
|
|
101
|
+
const textEl = document.createElementNS('http://www.w3.org/2000/svg', 'text')
|
|
102
|
+
textEl.setAttribute('x', String(parseFloat(x) + parseFloat(width) / 2))
|
|
103
|
+
textEl.setAttribute('y', String(parseFloat(y) + parseFloat(height) / 2 + 5))
|
|
104
|
+
textEl.setAttribute('text-anchor', 'middle')
|
|
105
|
+
textEl.setAttribute('font-size', '14')
|
|
106
|
+
textEl.setAttribute('fill', '#455a64')
|
|
107
|
+
textEl.textContent = text
|
|
108
|
+
fo.parentNode.replaceChild(textEl, fo)
|
|
109
|
+
})
|
|
110
|
+
const svgData = new XMLSerializer().serializeToString(clone)
|
|
111
|
+
const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' })
|
|
112
|
+
const url = URL.createObjectURL(svgBlob)
|
|
113
|
+
const image = new Image()
|
|
114
|
+
image.onload = () => {
|
|
115
|
+
const w = svgWidth * scale
|
|
116
|
+
const h = svgHeight * scale
|
|
117
|
+
const canvas = document.createElement('canvas')
|
|
118
|
+
canvas.width = w
|
|
119
|
+
canvas.height = h
|
|
120
|
+
const ctx = canvas.getContext('2d')
|
|
121
|
+
ctx.scale(scale, scale)
|
|
122
|
+
ctx.drawImage(image, 0, 0, svgWidth, svgHeight)
|
|
123
|
+
URL.revokeObjectURL(url)
|
|
124
|
+
canvas.toBlob(b => b ? resolve(b) : reject(new Error('toBlob null')), 'image/png')
|
|
125
|
+
}
|
|
126
|
+
image.onerror = () => { URL.revokeObjectURL(url); reject(new Error('SVG加载失败')) }
|
|
127
|
+
image.src = url
|
|
128
|
+
})
|
|
129
|
+
}
|