web_ppt_7.7 / backend /src /utils /htmlGenerator.js
CatPtain's picture
Upload 85 files
28e1dba verified
/**
* 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 = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
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
};