增加图片导出功能
This commit is contained in:
Generated
+50
@@ -15,6 +15,7 @@
|
||||
"@codemirror/view": "^6.36.4",
|
||||
"dompurify": "^3.2.5",
|
||||
"highlight.js": "^11.11.1",
|
||||
"html2canvas": "^1.4.1",
|
||||
"markdown-it": "^14.1.0",
|
||||
"markdown-it-multimd-table": "^4.2.3",
|
||||
"mermaid": "^11.15.0",
|
||||
@@ -2878,6 +2879,15 @@
|
||||
"node": "18 || 20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/base64-arraybuffer": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
|
||||
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz",
|
||||
@@ -3406,6 +3416,15 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/css-line-break": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/css-line-break/-/css-line-break-2.1.0.tgz",
|
||||
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz",
|
||||
@@ -5171,6 +5190,19 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/html2canvas": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmmirror.com/html2canvas/-/html2canvas-1.4.1.tgz",
|
||||
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"css-line-break": "^2.1.0",
|
||||
"text-segmentation": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/http-cache-semantics": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
|
||||
@@ -6802,6 +6834,15 @@
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/text-segmentation": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/text-segmentation/-/text-segmentation-1.0.3.tgz",
|
||||
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-async-pool": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz",
|
||||
@@ -6996,6 +7037,15 @@
|
||||
"dev": true,
|
||||
"license": "(WTFPL OR MIT)"
|
||||
},
|
||||
"node_modules/utrie": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/utrie/-/utrie-1.0.2.tgz",
|
||||
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-arraybuffer": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "14.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-14.0.0.tgz",
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"@codemirror/view": "^6.36.4",
|
||||
"dompurify": "^3.2.5",
|
||||
"highlight.js": "^11.11.1",
|
||||
"html2canvas": "^1.4.1",
|
||||
"markdown-it": "^14.1.0",
|
||||
"markdown-it-multimd-table": "^4.2.3",
|
||||
"mermaid": "^11.15.0",
|
||||
|
||||
+110
-1
@@ -17,7 +17,7 @@ import {
|
||||
import { BACKGROUNDS, FONTS, validBackgroundId, validFontId, type ViewMode } from './appearance'
|
||||
import type { Tab } from './types'
|
||||
import { AppModals } from './AppModals'
|
||||
import { clamp, newTab, tabLabel, isDirty, loadViewMode, viewModeLabel, loadPreviewLineHeight, normalizePathKey } from './utils'
|
||||
import { clamp, newTab, tabLabel, isDirty, loadViewMode, viewModeLabel, loadPreviewLineHeight, normalizePathKey, exportElementToImageData } from './utils'
|
||||
import {
|
||||
BG_KEY, FONT_KEY, VIEW_KEY, BROWSE_KEY, SIDEBAR_W_KEY, SIDEBAR_HIDDEN_KEY,
|
||||
STATS_W_KEY, STATS_HIDDEN_KEY, SPLIT_KEY, PREVIEW_W_KEY, PREVIEW_LINE_KEY, SESSION_KEY, AUTO_SAVE_KEY
|
||||
@@ -127,6 +127,13 @@ export default function App(): React.ReactElement {
|
||||
const [searchVisible, setSearchVisible] = React.useState(false)
|
||||
const [searchQuery, setSearchQuery] = React.useState('')
|
||||
const [dragOver, setDragOver] = React.useState(false)
|
||||
const [imagePreviewData, setImagePreviewData] = React.useState<string | null>(null)
|
||||
const [imagePreviewFileName, setImagePreviewFileName] = React.useState('')
|
||||
const [imageScale, setImageScale] = React.useState(1)
|
||||
const [imagePosition, setImagePosition] = React.useState({ x: 0, y: 0 })
|
||||
const [isDragging, setIsDragging] = React.useState(false)
|
||||
const [dragStart, setDragStart] = React.useState({ x: 0, y: 0 })
|
||||
const [isExportingImage, setIsExportingImage] = React.useState(false)
|
||||
|
||||
const activeTab = tabs.find((t) => t.id === activeId) ?? null
|
||||
const activeDoc = activeTab?.content ?? ''
|
||||
@@ -997,6 +1004,60 @@ ${previewHtml}
|
||||
window.setTimeout(() => setStatusText(''), 2000)
|
||||
}, [activeDoc, activeTab, previewHtml])
|
||||
|
||||
const handleExportImage = React.useCallback(async (): Promise<void> => {
|
||||
const articleEl =
|
||||
viewMode === 'read'
|
||||
? readPreviewArticleRef.current
|
||||
: viewMode === 'live'
|
||||
? livePreviewArticleRef.current
|
||||
: hybridPreviewArticleRef.current
|
||||
if (!articleEl) {
|
||||
console.error('Article element not found for viewMode:', viewMode)
|
||||
setStatusText('无法获取文档内容')
|
||||
window.setTimeout(() => setStatusText(''), 2000)
|
||||
return
|
||||
}
|
||||
const fileName = `${activeTab?.filePath ? tabLabel(activeTab).replace(/\.\w+$/, '') : 'document'}.png`
|
||||
try {
|
||||
setIsExportingImage(true)
|
||||
const imageData = await exportElementToImageData(articleEl)
|
||||
setImagePreviewData(imageData)
|
||||
setImagePreviewFileName(fileName)
|
||||
setIsExportingImage(false)
|
||||
} catch (error) {
|
||||
console.error('Error exporting image:', error)
|
||||
setIsExportingImage(false)
|
||||
const errorMessage = error instanceof Error ? error.message : '未知错误'
|
||||
setStatusText(`导出图片失败: ${errorMessage}`)
|
||||
window.setTimeout(() => setStatusText(''), 3000)
|
||||
}
|
||||
}, [activeTab, viewMode])
|
||||
|
||||
const handleCloseImagePreview = React.useCallback((): void => {
|
||||
setImagePreviewData(null)
|
||||
setImageScale(1)
|
||||
setImagePosition({ x: 0, y: 0 })
|
||||
setIsExportingImage(false)
|
||||
setIsDragging(false)
|
||||
}, [])
|
||||
|
||||
const handleResetZoom = React.useCallback((): void => {
|
||||
setImageScale(1)
|
||||
setImagePosition({ x: 0, y: 0 })
|
||||
}, [])
|
||||
|
||||
const handleDownloadImage = React.useCallback((): void => {
|
||||
if (!imagePreviewData) return
|
||||
const a = document.createElement('a')
|
||||
a.href = imagePreviewData
|
||||
a.download = imagePreviewFileName
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
setStatusText('图片下载成功')
|
||||
window.setTimeout(() => setStatusText(''), 3000)
|
||||
}, [imagePreviewData, imagePreviewFileName])
|
||||
|
||||
const deleteFileAt = React.useCallback(
|
||||
async (filePath: string): Promise<void> => {
|
||||
if (!api) return
|
||||
@@ -1349,6 +1410,14 @@ ${previewHtml}
|
||||
>
|
||||
导出 HTML
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void handleExportImage()}
|
||||
disabled={!activeDoc.trim()}
|
||||
title="导出当前文档为图片"
|
||||
>
|
||||
📷 导出图片
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAutoSave((v) => !v)}
|
||||
@@ -1509,6 +1578,46 @@ ${previewHtml}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{isExportingImage && (
|
||||
<div className="image-export-loading">
|
||||
<div className="image-export-loading-spinner"></div>
|
||||
<div className="image-export-loading-text">正在生成图片...</div>
|
||||
<div className="image-export-loading-hint">请稍候</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{imagePreviewData && (
|
||||
<div className="image-preview-overlay" onClick={handleCloseImagePreview}>
|
||||
<div className="image-preview-modal" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="image-preview-header">
|
||||
<h3 className="image-preview-title">📷 图片预览</h3>
|
||||
<button
|
||||
className="image-preview-close"
|
||||
onClick={handleCloseImagePreview}
|
||||
title="关闭"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className="image-preview-content">
|
||||
<img
|
||||
src={imagePreviewData}
|
||||
alt="预览"
|
||||
className="image-preview-img"
|
||||
/>
|
||||
</div>
|
||||
<div className="image-preview-footer">
|
||||
<button
|
||||
className="image-preview-download"
|
||||
onClick={handleDownloadImage}
|
||||
>
|
||||
⬇ 下载图片
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2654,3 +2654,253 @@ html[data-bg='22'] .app-root {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 30%, transparent);
|
||||
}
|
||||
|
||||
/* 图片导出加载遮罩 */
|
||||
.image-export-loading {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.85);
|
||||
backdrop-filter: blur(8px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1001;
|
||||
animation: loadingFadeIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes loadingFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.image-export-loading-spinner {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border: 4px solid rgba(255, 255, 255, 0.1);
|
||||
border-top-color: #0ea5e9;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.image-export-loading-text {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
margin-bottom: 8px;
|
||||
animation: loadingPulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes loadingPulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.image-export-loading-hint {
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
/* 图片预览弹窗 */
|
||||
.image-preview-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
backdrop-filter: blur(4px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
padding: 24px;
|
||||
animation: overlayFadeIn 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
@keyframes overlayFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
backdrop-filter: blur(0px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
}
|
||||
|
||||
.image-preview-modal {
|
||||
background: #f5f5f5;
|
||||
border-radius: 16px;
|
||||
max-width: 90vw;
|
||||
max-height: 90vh;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 25px 80px rgba(0, 0, 0, 0.4);
|
||||
animation: modalSlideUp 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
@keyframes modalSlideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(40px) scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.image-preview-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.image-preview-title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.image-preview-header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.image-preview-zoom-out,
|
||||
.image-preview-zoom-in,
|
||||
.image-preview-reset,
|
||||
.image-preview-close {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
color: #111827;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.image-preview-zoom-out:hover,
|
||||
.image-preview-zoom-in:hover,
|
||||
.image-preview-reset:hover {
|
||||
border-color: #0ea5e9;
|
||||
background: rgba(14, 165, 233, 0.1);
|
||||
}
|
||||
|
||||
.image-preview-close:hover {
|
||||
border-color: #ef4444;
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.image-preview-scale {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
min-width: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.image-preview-content {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f3f4f6;
|
||||
}
|
||||
|
||||
.image-preview-img {
|
||||
max-width: 100%;
|
||||
max-height: 60vh;
|
||||
width: auto;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
animation: imageFadeIn 0.6s cubic-bezier(0.4, 0, 0.2, 1) 0.2s both;
|
||||
}
|
||||
|
||||
@keyframes imageFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.image-preview-footer {
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.image-preview-download {
|
||||
padding: 12px 32px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
background: #0ea5e9;
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.image-preview-download:hover {
|
||||
background: #0284c7;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(14, 165, 233, 0.3);
|
||||
}
|
||||
|
||||
/* 搜索高亮动画 */
|
||||
.search-highlight {
|
||||
background-color: rgba(250, 204, 21, 0.5);
|
||||
border-radius: 2px;
|
||||
animation: searchPulse 1s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes searchPulse {
|
||||
0%, 100% {
|
||||
background-color: rgba(250, 204, 21, 0.5);
|
||||
}
|
||||
50% {
|
||||
background-color: rgba(250, 204, 21, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,3 +47,100 @@ export function loadPreviewLineHeight(): string {
|
||||
if (!Number.isFinite(n)) return '1.5'
|
||||
return String(Math.min(3, Math.max(1, n)))
|
||||
}
|
||||
|
||||
export async function exportElementToImageData(element: HTMLElement): Promise<string> {
|
||||
const html2canvas = (await import('html2canvas')).default
|
||||
|
||||
// 创建一个完全独立的 iframe 来渲染内容
|
||||
const iframe = document.createElement('iframe')
|
||||
iframe.style.cssText = 'position:absolute;left:-9999px;top:-9999px;width:800px;height:600px;border:none;'
|
||||
document.body.appendChild(iframe)
|
||||
|
||||
const iframeDoc = iframe.contentDocument!
|
||||
const iframeWin = iframe.contentWindow!
|
||||
|
||||
// 复制必要的样式
|
||||
const computedStyle = iframeWin.getComputedStyle(element)
|
||||
const bgColor = computedStyle.backgroundColor || '#ffffff'
|
||||
const color = computedStyle.color || '#333333'
|
||||
const fontFamily = computedStyle.fontFamily || 'sans-serif'
|
||||
const fontSize = computedStyle.fontSize || '16px'
|
||||
const lineHeight = computedStyle.lineHeight || '1.5'
|
||||
|
||||
// 写入干净的 HTML 和 CSS
|
||||
iframeDoc.open()
|
||||
iframeDoc.write(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: ${fontFamily};
|
||||
font-size: ${fontSize};
|
||||
line-height: ${lineHeight};
|
||||
color: ${color};
|
||||
background: ${bgColor};
|
||||
padding: 20px;
|
||||
}
|
||||
h1 { font-size: 2em; margin: 0.5em 0; }
|
||||
h2 { font-size: 1.5em; margin: 0.5em 0; }
|
||||
h3 { font-size: 1.25em; margin: 0.5em 0; }
|
||||
p { margin: 0.5em 0; }
|
||||
pre, code {
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
font-size: 0.9em;
|
||||
background: #f5f5f5;
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 3px;
|
||||
}
|
||||
pre {
|
||||
padding: 1em;
|
||||
overflow-x: auto;
|
||||
}
|
||||
blockquote {
|
||||
border-left: 3px solid #ddd;
|
||||
padding-left: 1em;
|
||||
margin: 1em 0;
|
||||
color: #666;
|
||||
}
|
||||
img { max-width: 100%; height: auto; }
|
||||
table { border-collapse: collapse; width: 100%; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px; }
|
||||
a { color: #0066cc; }
|
||||
hr { border: none; border-top: 1px solid #ddd; margin: 1em 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>${element.innerHTML}</body>
|
||||
</html>
|
||||
`)
|
||||
iframeDoc.close()
|
||||
|
||||
// 等待 iframe 加载
|
||||
await new Promise<void>(resolve => {
|
||||
iframeDoc.body ? resolve() : iframe.addEventListener('load', () => resolve(), { once: true })
|
||||
})
|
||||
|
||||
// 等待任何异步内容(如图片)加载
|
||||
await new Promise<void>(resolve => setTimeout(resolve, 500))
|
||||
|
||||
try {
|
||||
const canvas = await html2canvas(iframeDoc.body, {
|
||||
useCORS: true,
|
||||
allowTaint: true,
|
||||
backgroundColor: '#ffffff',
|
||||
scale: 2,
|
||||
logging: false,
|
||||
width: iframeDoc.body.scrollWidth + 40,
|
||||
height: iframeDoc.body.scrollHeight + 40
|
||||
})
|
||||
|
||||
return canvas.toDataURL('image/png')
|
||||
} finally {
|
||||
// 清理
|
||||
if (iframe.parentNode) {
|
||||
iframe.parentNode.removeChild(iframe)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user