Spaces:
Running
Running
/** | |
* HTML Generator Utility | |
* 用于生成幻灯片的 HTML 内容 | |
*/ | |
/** | |
* 生成幻灯片的 HTML 内容 | |
* @param {Object} slideData - 幻灯片数据 | |
* @param {number} slideIndex - 幻灯片索引 | |
* @param {Object} options - 渲染选项 | |
* @returns {string} HTML 字符串 | |
*/ | |
export function generateSlideHTML(slideData, slideIndex = 0, options = {}) { | |
const { | |
width = 1000, | |
height = 562, | |
backgroundColor = '#ffffff', | |
scale = 1 | |
} = options; | |
if (!slideData || !slideData.slides || !slideData.slides[slideIndex]) { | |
throw new Error(`Invalid slide data or slide index: ${slideIndex}`); | |
} | |
const slide = slideData.slides[slideIndex]; | |
const slideBackground = slide.background || backgroundColor; | |
// 生成元素的 HTML | |
const elementsHTML = generateElementsHTML(slide.elements || [], { width, height, scale }); | |
return ` | |
<!DOCTYPE html> | |
<html lang="zh-CN"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Slide ${slideIndex + 1}</title> | |
<style> | |
* { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
} | |
body { | |
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
background: #f0f0f0; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
min-height: 100vh; | |
padding: 20px; | |
} | |
.slide-container { | |
width: ${width}px; | |
height: ${height}px; | |
background: ${slideBackground}; | |
position: relative; | |
overflow: hidden; | |
border-radius: 8px; | |
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); | |
transform: scale(${scale}); | |
transform-origin: center; | |
} | |
.slide-element { | |
position: absolute; | |
user-select: none; | |
} | |
.text-element { | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
word-wrap: break-word; | |
overflow-wrap: break-word; | |
} | |
.image-element { | |
background-size: contain; | |
background-repeat: no-repeat; | |
background-position: center; | |
} | |
.shape-element { | |
border-radius: 4px; | |
} | |
.line-element { | |
border: none; | |
background: currentColor; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="slide-container"> | |
${elementsHTML} | |
</div> | |
</body> | |
</html> | |
`.trim(); | |
} | |
/** | |
* 生成元素的 HTML | |
* @param {Array} elements - 元素数组 | |
* @param {Object} options - 渲染选项 | |
* @returns {string} 元素 HTML 字符串 | |
*/ | |
function generateElementsHTML(elements, options = {}) { | |
if (!Array.isArray(elements)) { | |
return ''; | |
} | |
return elements.map(element => { | |
try { | |
return generateElementHTML(element, options); | |
} catch (error) { | |
console.warn(`Failed to generate HTML for element:`, element, error); | |
return ''; | |
} | |
}).join('\n'); | |
} | |
/** | |
* 生成单个元素的 HTML | |
* @param {Object} element - 元素数据 | |
* @param {Object} options - 渲染选项 | |
* @returns {string} 元素 HTML 字符串 | |
*/ | |
function generateElementHTML(element, options = {}) { | |
if (!element || typeof element !== 'object') { | |
return ''; | |
} | |
const { | |
type, | |
left = 0, | |
top = 0, | |
width = 100, | |
height = 100, | |
rotate = 0, | |
opacity = 1 | |
} = element; | |
const baseStyle = ` | |
left: ${left}px; | |
top: ${top}px; | |
width: ${width}px; | |
height: ${height}px; | |
transform: rotate(${rotate}deg); | |
opacity: ${opacity}; | |
`; | |
switch (type) { | |
case 'text': | |
return generateTextElementHTML(element, baseStyle); | |
case 'image': | |
return generateImageElementHTML(element, baseStyle); | |
case 'shape': | |
return generateShapeElementHTML(element, baseStyle); | |
case 'line': | |
return generateLineElementHTML(element, baseStyle); | |
case 'chart': | |
return generateChartElementHTML(element, baseStyle); | |
case 'table': | |
return generateTableElementHTML(element, baseStyle); | |
default: | |
console.warn(`Unknown element type: ${type}`); | |
return ''; | |
} | |
} | |
/** | |
* 生成文本元素 HTML | |
*/ | |
function generateTextElementHTML(element, baseStyle) { | |
const { | |
content = '', | |
fontSize = 14, | |
fontFamily = 'Arial', | |
color = '#000000', | |
fontWeight = 'normal', | |
fontStyle = 'normal', | |
textDecoration = 'none', | |
textAlign = 'left', | |
lineHeight = 1.2 | |
} = element; | |
const textStyle = ` | |
font-size: ${fontSize}px; | |
font-family: ${fontFamily}; | |
color: ${color}; | |
font-weight: ${fontWeight}; | |
font-style: ${fontStyle}; | |
text-decoration: ${textDecoration}; | |
text-align: ${textAlign}; | |
line-height: ${lineHeight}; | |
`; | |
return ` | |
<div class="slide-element text-element" style="${baseStyle} ${textStyle}"> | |
${escapeHtml(content)} | |
</div> | |
`; | |
} | |
/** | |
* 生成图片元素 HTML | |
*/ | |
function generateImageElementHTML(element, baseStyle) { | |
const { src = '', alt = '' } = element; | |
if (!src) { | |
return ` | |
<div class="slide-element" style="${baseStyle} background: #f0f0f0; border: 2px dashed #ccc;"> | |
<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: #999;"> | |
图片加载失败 | |
</div> | |
</div> | |
`; | |
} | |
return ` | |
<div class="slide-element image-element" style="${baseStyle} background-image: url('${escapeHtml(src)}');"> | |
<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}" style="width: 100%; height: 100%; object-fit: contain; opacity: 0;" /> | |
</div> | |
`; | |
} | |
/** | |
* 生成形状元素 HTML | |
*/ | |
function generateShapeElementHTML(element, baseStyle) { | |
const { | |
fill = '#ffffff', | |
stroke = '#000000', | |
strokeWidth = 1, | |
borderRadius = 0 | |
} = element; | |
const shapeStyle = ` | |
background: ${fill}; | |
border: ${strokeWidth}px solid ${stroke}; | |
border-radius: ${borderRadius}px; | |
`; | |
return ` | |
<div class="slide-element shape-element" style="${baseStyle} ${shapeStyle}"> | |
</div> | |
`; | |
} | |
/** | |
* 生成线条元素 HTML | |
*/ | |
function generateLineElementHTML(element, baseStyle) { | |
const { | |
stroke = '#000000', | |
strokeWidth = 2 | |
} = element; | |
const lineStyle = ` | |
background: ${stroke}; | |
height: ${strokeWidth}px; | |
color: ${stroke}; | |
`; | |
return ` | |
<div class="slide-element line-element" style="${baseStyle} ${lineStyle}"> | |
</div> | |
`; | |
} | |
/** | |
* 生成图表元素 HTML | |
*/ | |
function generateChartElementHTML(element, baseStyle) { | |
// 简化的图表渲染,实际项目中可能需要更复杂的图表库 | |
return ` | |
<div class="slide-element" style="${baseStyle} background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 4px;"> | |
<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: #6c757d;"> | |
📊 图表 | |
</div> | |
</div> | |
`; | |
} | |
/** | |
* 生成表格元素 HTML | |
*/ | |
function generateTableElementHTML(element, baseStyle) { | |
const { data = [] } = element; | |
if (!Array.isArray(data) || data.length === 0) { | |
return ` | |
<div class="slide-element" style="${baseStyle} background: #f8f9fa; border: 1px solid #dee2e6;"> | |
<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: #6c757d;"> | |
📋 空表格 | |
</div> | |
</div> | |
`; | |
} | |
const tableHTML = data.map(row => { | |
if (!Array.isArray(row)) return ''; | |
const cellsHTML = row.map(cell => `<td style="border: 1px solid #dee2e6; padding: 8px;">${escapeHtml(String(cell))}</td>`).join(''); | |
return `<tr>${cellsHTML}</tr>`; | |
}).join(''); | |
return ` | |
<div class="slide-element" style="${baseStyle} overflow: auto;"> | |
<table style="width: 100%; border-collapse: collapse; font-size: 12px;"> | |
${tableHTML} | |
</table> | |
</div> | |
`; | |
} | |
/** | |
* HTML 转义函数 | |
* @param {string} text - 需要转义的文本 | |
* @returns {string} 转义后的文本 | |
*/ | |
function escapeHtml(text) { | |
if (typeof text !== 'string') { | |
return String(text); | |
} | |
const map = { | |
'&': '&', | |
'<': '<', | |
'>': '>', | |
'"': '"', | |
"'": ''' | |
}; | |
return text.replace(/[&<>"']/g, m => map[m]); | |
} | |
/** | |
* 生成完整的 PPT HTML(包含所有幻灯片) | |
* @param {Object} pptData - PPT 数据 | |
* @param {Object} options - 渲染选项 | |
* @returns {string} 完整的 HTML 字符串 | |
*/ | |
export function generatePPTHTML(pptData, options = {}) { | |
if (!pptData || !pptData.slides || !Array.isArray(pptData.slides)) { | |
throw new Error('Invalid PPT data'); | |
} | |
const { width = 1000, height = 562 } = options; | |
const slidesHTML = pptData.slides.map((slide, index) => { | |
try { | |
return generateSlideHTML(pptData, index, options); | |
} catch (error) { | |
console.warn(`Failed to generate HTML for slide ${index}:`, error); | |
return ` | |
<div class="slide-error" style="width: ${width}px; height: ${height}px; background: #f8d7da; border: 1px solid #f5c6cb; display: flex; align-items: center; justify-content: center; color: #721c24;"> | |
幻灯片 ${index + 1} 渲染失败 | |
</div> | |
`; | |
} | |
}).join('\n\n'); | |
return ` | |
<!DOCTYPE html> | |
<html lang="zh-CN"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>PPT Preview</title> | |
<style> | |
body { | |
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
background: #f0f0f0; | |
margin: 0; | |
padding: 20px; | |
} | |
.ppt-container { | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
gap: 20px; | |
} | |
.slide-wrapper { | |
position: relative; | |
} | |
.slide-number { | |
position: absolute; | |
top: -30px; | |
left: 0; | |
background: #007bff; | |
color: white; | |
padding: 4px 8px; | |
border-radius: 4px; | |
font-size: 12px; | |
font-weight: bold; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="ppt-container"> | |
${slidesHTML} | |
</div> | |
</body> | |
</html> | |
`.trim(); | |
} | |
export default { | |
generateSlideHTML, | |
generatePPTHTML | |
}; |