- 验证错误捕获机制完整(同步和异步) - 验证友好错误提示已实现 - 验证原始代码查看功能 - 验证错误类型识别和行号提取 - 验证完整的 CSS 样式(日间/夜间模式) - 创建测试文档和总结文档 - 更新任务状态为已完成 - 满足需求 2.5, 7.1-7.4
791 lines
19 KiB
Markdown
791 lines
19 KiB
Markdown
# Design Document: Mermaid 代码块渲染修复
|
||
|
||
## Overview
|
||
|
||
本设计旨在修复 Argon WordPress 主题中 Mermaid 图表渲染的关键问题。当前实现存在以下核心问题:
|
||
|
||
1. **PJAX 加载后显示原始文本**:页面切换后 Mermaid 不渲染
|
||
2. **语法解析错误**:flowchart、erDiagram 等语法解析失败
|
||
3. **功能缺失**:缺少导出、全屏查看等功能
|
||
4. **交互体验差**:工具栏遮挡内容、缩放不流畅
|
||
|
||
设计方案采用模块化架构,确保代码质量和可维护性。
|
||
|
||
## Architecture
|
||
|
||
### 核心架构原则
|
||
|
||
1. **模块化设计**:将功能拆分为独立模块,易于维护和扩展
|
||
2. **错误隔离**:每个模块独立的错误处理,互不影响
|
||
3. **性能优先**:使用批量渲染、延迟加载等优化技术
|
||
4. **用户体验**:提供流畅的交互和友好的错误提示
|
||
|
||
### 架构图
|
||
|
||
```
|
||
Mermaid 渲染系统
|
||
├── 检测模块 (MermaidDetector)
|
||
│ ├── 扫描代码块
|
||
│ ├── 识别 Mermaid 语法
|
||
│ └── 过滤已渲染的块
|
||
├── 渲染模块 (MermaidRenderer)
|
||
│ ├── 初始化 Mermaid
|
||
│ ├── 批量渲染
|
||
│ ├── 错误处理
|
||
│ └── 降级方案
|
||
├── 交互模块 (MermaidInteraction)
|
||
│ ├── 工具栏管理
|
||
│ ├── 缩放控制
|
||
│ ├── 拖拽控制
|
||
│ └── 全屏查看
|
||
├── 导出模块 (MermaidExporter)
|
||
│ ├── PNG 导出
|
||
│ ├── SVG 导出
|
||
│ └── 错误处理
|
||
└── 生命周期模块 (MermaidLifecycle)
|
||
├── PJAX 集成
|
||
├── 资源清理
|
||
└── 主题同步
|
||
```
|
||
|
||
## Components and Interfaces
|
||
|
||
### 1. Mermaid Detector (检测模块)
|
||
|
||
**职责**:检测页面中的 Mermaid 代码块
|
||
|
||
**接口**:
|
||
```javascript
|
||
/**
|
||
* 检测页面中的 Mermaid 代码块
|
||
* @returns {HTMLElement[]} Mermaid 代码块数组
|
||
*/
|
||
function detectMermaidBlocks() {
|
||
const blocks = [];
|
||
|
||
// 检测 <pre><code class="language-mermaid">
|
||
document.querySelectorAll('pre code.language-mermaid').forEach(code => {
|
||
if (!isRendered(code)) {
|
||
blocks.push(code);
|
||
}
|
||
});
|
||
|
||
// 检测 <pre><code class="mermaid">
|
||
document.querySelectorAll('pre code.mermaid').forEach(code => {
|
||
if (!isRendered(code)) {
|
||
blocks.push(code);
|
||
}
|
||
});
|
||
|
||
return blocks;
|
||
}
|
||
|
||
/**
|
||
* 检查代码块是否已渲染
|
||
* @param {HTMLElement} element - 代码块元素
|
||
* @returns {boolean} 是否已渲染
|
||
*/
|
||
function isRendered(element) {
|
||
return element.hasAttribute('data-mermaid-rendered') ||
|
||
element.closest('.mermaid-container') !== null;
|
||
}
|
||
```
|
||
|
||
|
||
### 2. Mermaid Renderer (渲染模块)
|
||
|
||
**职责**:渲染 Mermaid 图表
|
||
|
||
**接口**:
|
||
```javascript
|
||
/**
|
||
* 渲染所有 Mermaid 图表
|
||
* @returns {Promise<void>}
|
||
*/
|
||
async function renderAllMermaidCharts() {
|
||
const blocks = detectMermaidBlocks();
|
||
if (blocks.length === 0) return;
|
||
|
||
// 等待 Mermaid 库加载
|
||
if (!await waitForMermaid()) {
|
||
ArgonDebug.error('[Argon Mermaid] Mermaid 库加载失败');
|
||
return;
|
||
}
|
||
|
||
// 初始化 Mermaid
|
||
if (!initMermaid()) {
|
||
ArgonDebug.error('[Argon Mermaid] Mermaid 初始化失败');
|
||
return;
|
||
}
|
||
|
||
// 批量渲染
|
||
for (let i = 0; i < blocks.length; i++) {
|
||
await renderMermaidChart(blocks[i], i);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 等待 Mermaid 库加载
|
||
* @param {number} timeout - 超时时间(毫秒)
|
||
* @returns {Promise<boolean>} 是否加载成功
|
||
*/
|
||
function waitForMermaid(timeout = 5000) {
|
||
return new Promise((resolve) => {
|
||
if (typeof window.mermaid !== 'undefined') {
|
||
resolve(true);
|
||
return;
|
||
}
|
||
|
||
const startTime = Date.now();
|
||
const checkInterval = setInterval(() => {
|
||
if (typeof window.mermaid !== 'undefined') {
|
||
clearInterval(checkInterval);
|
||
resolve(true);
|
||
} else if (Date.now() - startTime > timeout) {
|
||
clearInterval(checkInterval);
|
||
resolve(false);
|
||
}
|
||
}, 100);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 初始化 Mermaid
|
||
* @returns {boolean} 是否初始化成功
|
||
*/
|
||
function initMermaid() {
|
||
if (typeof window.mermaid === 'undefined') {
|
||
return false;
|
||
}
|
||
|
||
try {
|
||
const theme = getMermaidTheme();
|
||
window.mermaid.initialize({
|
||
startOnLoad: false,
|
||
theme: theme,
|
||
securityLevel: 'loose',
|
||
flowchart: {
|
||
useMaxWidth: true,
|
||
htmlLabels: true
|
||
}
|
||
});
|
||
return true;
|
||
} catch (e) {
|
||
ArgonDebug.error('[Argon Mermaid] 初始化失败:', e);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 渲染单个图表
|
||
* @param {HTMLElement} element - 代码块元素
|
||
* @param {number} index - 图表索引
|
||
* @returns {Promise<void>}
|
||
*/
|
||
async function renderMermaidChart(element, index) {
|
||
const chartId = `mermaid-chart-${Date.now()}-${index}`;
|
||
const code = element.textContent.trim();
|
||
|
||
ArgonDebug.log(`[Argon Mermaid] 开始渲染: ${chartId}`);
|
||
|
||
try {
|
||
// 使用 mermaid.render API
|
||
const result = await window.mermaid.render(`mermaid-svg-${chartId}`, code);
|
||
|
||
// 创建容器
|
||
const container = createMermaidContainer(chartId, result.svg, code);
|
||
|
||
// 替换原始代码块
|
||
element.closest('pre').replaceWith(container);
|
||
|
||
// 标记已渲染
|
||
element.setAttribute('data-mermaid-rendered', 'true');
|
||
|
||
ArgonDebug.log(`[Argon Mermaid] 渲染成功: ${chartId}`);
|
||
} catch (error) {
|
||
ArgonDebug.error(`[Argon Mermaid] 渲染失败: ${chartId}`, error);
|
||
|
||
// 显示错误
|
||
showMermaidError(element, error, code);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. Mermaid Interaction (交互模块)
|
||
|
||
**职责**:管理图表交互功能
|
||
|
||
**接口**:
|
||
```javascript
|
||
/**
|
||
* 创建 Mermaid 容器
|
||
* @param {string} chartId - 图表 ID
|
||
* @param {string} svg - SVG 代码
|
||
* @param {string} code - 原始代码
|
||
* @returns {HTMLElement} 容器元素
|
||
*/
|
||
function createMermaidContainer(chartId, svg, code) {
|
||
const container = document.createElement('div');
|
||
container.className = 'mermaid-container';
|
||
container.setAttribute('data-chart-id', chartId);
|
||
container.setAttribute('data-original-code', code);
|
||
|
||
// 创建图表包装器
|
||
const wrapper = document.createElement('div');
|
||
wrapper.className = 'mermaid-wrapper';
|
||
wrapper.innerHTML = svg;
|
||
|
||
// 创建工具栏
|
||
const toolbar = createMermaidToolbar(chartId);
|
||
|
||
container.appendChild(toolbar);
|
||
container.appendChild(wrapper);
|
||
|
||
// 绑定交互事件
|
||
bindMermaidInteraction(container);
|
||
|
||
return container;
|
||
}
|
||
|
||
/**
|
||
* 创建工具栏
|
||
* @param {string} chartId - 图表 ID
|
||
* @returns {HTMLElement} 工具栏元素
|
||
*/
|
||
function createMermaidToolbar(chartId) {
|
||
const toolbar = document.createElement('div');
|
||
toolbar.className = 'mermaid-toolbar';
|
||
toolbar.innerHTML = `
|
||
<button class="mermaid-btn mermaid-zoom-in" title="放大">
|
||
<i class="fa fa-search-plus"></i>
|
||
</button>
|
||
<button class="mermaid-btn mermaid-zoom-out" title="缩小">
|
||
<i class="fa fa-search-minus"></i>
|
||
</button>
|
||
<button class="mermaid-btn mermaid-reset" title="重置">
|
||
<i class="fa fa-refresh"></i>
|
||
</button>
|
||
<button class="mermaid-btn mermaid-fullscreen" title="全屏">
|
||
<i class="fa fa-expand"></i>
|
||
</button>
|
||
<button class="mermaid-btn mermaid-export" title="导出">
|
||
<i class="fa fa-download"></i>
|
||
</button>
|
||
`;
|
||
return toolbar;
|
||
}
|
||
|
||
/**
|
||
* 绑定交互事件
|
||
* @param {HTMLElement} container - 容器元素
|
||
* @returns {void}
|
||
*/
|
||
function bindMermaidInteraction(container) {
|
||
const wrapper = container.querySelector('.mermaid-wrapper');
|
||
const svg = wrapper.querySelector('svg');
|
||
|
||
let scale = 1;
|
||
let translateX = 0;
|
||
let translateY = 0;
|
||
let isDragging = false;
|
||
let startX = 0;
|
||
let startY = 0;
|
||
|
||
// 缩放功能
|
||
container.querySelector('.mermaid-zoom-in').addEventListener('click', () => {
|
||
scale = Math.min(scale * 1.2, 5);
|
||
updateTransform();
|
||
});
|
||
|
||
container.querySelector('.mermaid-zoom-out').addEventListener('click', () => {
|
||
scale = Math.max(scale / 1.2, 0.5);
|
||
updateTransform();
|
||
});
|
||
|
||
container.querySelector('.mermaid-reset').addEventListener('click', () => {
|
||
scale = 1;
|
||
translateX = 0;
|
||
translateY = 0;
|
||
updateTransform();
|
||
});
|
||
|
||
// 鼠标滚轮缩放
|
||
wrapper.addEventListener('wheel', (e) => {
|
||
e.preventDefault();
|
||
const delta = e.deltaY > 0 ? 0.9 : 1.1;
|
||
scale = Math.max(0.5, Math.min(5, scale * delta));
|
||
updateTransform();
|
||
}, { passive: false });
|
||
|
||
// 拖拽功能
|
||
wrapper.addEventListener('mousedown', (e) => {
|
||
if (scale > 1) {
|
||
isDragging = true;
|
||
startX = e.clientX - translateX;
|
||
startY = e.clientY - translateY;
|
||
wrapper.style.cursor = 'grabbing';
|
||
}
|
||
});
|
||
|
||
document.addEventListener('mousemove', (e) => {
|
||
if (isDragging) {
|
||
translateX = e.clientX - startX;
|
||
translateY = e.clientY - startY;
|
||
updateTransform();
|
||
}
|
||
});
|
||
|
||
document.addEventListener('mouseup', () => {
|
||
if (isDragging) {
|
||
isDragging = false;
|
||
wrapper.style.cursor = scale > 1 ? 'grab' : 'default';
|
||
}
|
||
});
|
||
|
||
// 更新变换
|
||
function updateTransform() {
|
||
svg.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
|
||
svg.style.transformOrigin = 'center';
|
||
svg.style.transition = 'transform 0.2s ease';
|
||
wrapper.style.cursor = scale > 1 ? 'grab' : 'default';
|
||
}
|
||
|
||
// 全屏查看
|
||
container.querySelector('.mermaid-fullscreen').addEventListener('click', () => {
|
||
openMermaidFullscreen(container);
|
||
});
|
||
|
||
// 导出功能
|
||
container.querySelector('.mermaid-export').addEventListener('click', () => {
|
||
showExportMenu(container);
|
||
});
|
||
}
|
||
```
|
||
|
||
|
||
### 4. Mermaid Exporter (导出模块)
|
||
|
||
**职责**:导出图表为图片文件
|
||
|
||
**接口**:
|
||
```javascript
|
||
/**
|
||
* 显示导出菜单
|
||
* @param {HTMLElement} container - 容器元素
|
||
* @returns {void}
|
||
*/
|
||
function showExportMenu(container) {
|
||
const menu = document.createElement('div');
|
||
menu.className = 'mermaid-export-menu';
|
||
menu.innerHTML = `
|
||
<button class="export-png">导出 PNG</button>
|
||
<button class="export-svg">导出 SVG</button>
|
||
`;
|
||
|
||
menu.querySelector('.export-png').addEventListener('click', () => {
|
||
exportMermaidAsPNG(container);
|
||
menu.remove();
|
||
});
|
||
|
||
menu.querySelector('.export-svg').addEventListener('click', () => {
|
||
exportMermaidAsSVG(container);
|
||
menu.remove();
|
||
});
|
||
|
||
container.appendChild(menu);
|
||
|
||
// 点击外部关闭菜单
|
||
setTimeout(() => {
|
||
document.addEventListener('click', function closeMenu(e) {
|
||
if (!menu.contains(e.target)) {
|
||
menu.remove();
|
||
document.removeEventListener('click', closeMenu);
|
||
}
|
||
});
|
||
}, 0);
|
||
}
|
||
|
||
/**
|
||
* 导出为 PNG
|
||
* @param {HTMLElement} container - 容器元素
|
||
* @returns {void}
|
||
*/
|
||
function exportMermaidAsPNG(container) {
|
||
const svg = container.querySelector('svg');
|
||
const chartId = container.getAttribute('data-chart-id');
|
||
|
||
try {
|
||
// 创建 canvas
|
||
const canvas = document.createElement('canvas');
|
||
const ctx = canvas.getContext('2d');
|
||
|
||
// 获取 SVG 尺寸
|
||
const bbox = svg.getBBox();
|
||
canvas.width = bbox.width;
|
||
canvas.height = bbox.height;
|
||
|
||
// 将 SVG 转换为图片
|
||
const svgData = new XMLSerializer().serializeToString(svg);
|
||
const img = new Image();
|
||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||
const url = URL.createObjectURL(blob);
|
||
|
||
img.onload = () => {
|
||
ctx.drawImage(img, 0, 0);
|
||
URL.revokeObjectURL(url);
|
||
|
||
// 下载
|
||
canvas.toBlob((blob) => {
|
||
const link = document.createElement('a');
|
||
link.download = `${chartId}.png`;
|
||
link.href = URL.createObjectURL(blob);
|
||
link.click();
|
||
URL.revokeObjectURL(link.href);
|
||
});
|
||
};
|
||
|
||
img.src = url;
|
||
} catch (error) {
|
||
ArgonDebug.error('[Argon Mermaid] PNG 导出失败:', error);
|
||
alert('导出失败,请重试');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 导出为 SVG
|
||
* @param {HTMLElement} container - 容器元素
|
||
* @returns {void}
|
||
*/
|
||
function exportMermaidAsSVG(container) {
|
||
const svg = container.querySelector('svg');
|
||
const chartId = container.getAttribute('data-chart-id');
|
||
|
||
try {
|
||
const svgData = new XMLSerializer().serializeToString(svg);
|
||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||
const url = URL.createObjectURL(blob);
|
||
|
||
const link = document.createElement('a');
|
||
link.download = `${chartId}.svg`;
|
||
link.href = url;
|
||
link.click();
|
||
|
||
URL.revokeObjectURL(url);
|
||
} catch (error) {
|
||
ArgonDebug.error('[Argon Mermaid] SVG 导出失败:', error);
|
||
alert('导出失败,请重试');
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5. Mermaid Lifecycle (生命周期模块)
|
||
|
||
**职责**:管理 Mermaid 的生命周期
|
||
|
||
**接口**:
|
||
```javascript
|
||
/**
|
||
* 清理 Mermaid 实例
|
||
* @returns {void}
|
||
*/
|
||
function cleanupMermaidInstances() {
|
||
// 移除所有容器
|
||
document.querySelectorAll('.mermaid-container').forEach(container => {
|
||
// 移除事件监听器
|
||
const toolbar = container.querySelector('.mermaid-toolbar');
|
||
if (toolbar) {
|
||
toolbar.querySelectorAll('button').forEach(btn => {
|
||
btn.replaceWith(btn.cloneNode(true));
|
||
});
|
||
}
|
||
|
||
// 移除容器
|
||
container.remove();
|
||
});
|
||
|
||
ArgonDebug.log('[Argon Mermaid] 实例已清理');
|
||
}
|
||
|
||
/**
|
||
* PJAX 集成
|
||
* @returns {void}
|
||
*/
|
||
function initMermaidPjaxIntegration() {
|
||
// PJAX 开始前清理
|
||
$(document).on('pjax:beforeReplace', function() {
|
||
cleanupMermaidInstances();
|
||
});
|
||
|
||
// PJAX 完成后渲染
|
||
$(document).on('pjax:complete', function() {
|
||
renderAllMermaidCharts();
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 主题同步
|
||
* @returns {void}
|
||
*/
|
||
function syncMermaidTheme() {
|
||
const isDark = document.documentElement.classList.contains('darkmode');
|
||
const theme = isDark ? 'dark' : 'default';
|
||
|
||
if (typeof window.mermaid !== 'undefined') {
|
||
window.mermaid.initialize({
|
||
theme: theme
|
||
});
|
||
|
||
// 重新渲染所有图表
|
||
renderAllMermaidCharts();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取 Mermaid 主题
|
||
* @returns {string} 主题名称
|
||
*/
|
||
function getMermaidTheme() {
|
||
const isDark = document.documentElement.classList.contains('darkmode');
|
||
return isDark ? 'dark' : 'default';
|
||
}
|
||
```
|
||
|
||
### 6. Error Handler (错误处理模块)
|
||
|
||
**职责**:处理渲染错误
|
||
|
||
**接口**:
|
||
```javascript
|
||
/**
|
||
* 显示 Mermaid 错误
|
||
* @param {HTMLElement} element - 代码块元素
|
||
* @param {Error} error - 错误对象
|
||
* @param {string} code - 原始代码
|
||
* @returns {void}
|
||
*/
|
||
function showMermaidError(element, error, code) {
|
||
const errorContainer = document.createElement('div');
|
||
errorContainer.className = 'mermaid-error';
|
||
errorContainer.innerHTML = `
|
||
<div class="mermaid-error-header">
|
||
<i class="fa fa-exclamation-triangle"></i>
|
||
<span>Mermaid 图表渲染失败</span>
|
||
</div>
|
||
<div class="mermaid-error-message">
|
||
${error.message || '未知错误'}
|
||
</div>
|
||
<details class="mermaid-error-details">
|
||
<summary>查看原始代码</summary>
|
||
<pre><code>${escapeHtml(code)}</code></pre>
|
||
</details>
|
||
`;
|
||
|
||
element.closest('pre').replaceWith(errorContainer);
|
||
}
|
||
|
||
/**
|
||
* 转义 HTML
|
||
* @param {string} html - HTML 字符串
|
||
* @returns {string} 转义后的字符串
|
||
*/
|
||
function escapeHtml(html) {
|
||
const div = document.createElement('div');
|
||
div.textContent = html;
|
||
return div.innerHTML;
|
||
}
|
||
```
|
||
|
||
## Data Models
|
||
|
||
### MermaidChart (图表数据模型)
|
||
|
||
```javascript
|
||
/**
|
||
* Mermaid 图表数据模型
|
||
*/
|
||
class MermaidChart {
|
||
constructor(id, code, element) {
|
||
this.id = id; // 图表 ID
|
||
this.code = code; // 原始代码
|
||
this.element = element; // DOM 元素
|
||
this.rendered = false; // 是否已渲染
|
||
this.scale = 1; // 缩放级别
|
||
this.translateX = 0; // X 轴偏移
|
||
this.translateY = 0; // Y 轴偏移
|
||
this.error = null; // 错误信息
|
||
}
|
||
|
||
/**
|
||
* 标记为已渲染
|
||
*/
|
||
markRendered() {
|
||
this.rendered = true;
|
||
this.element.setAttribute('data-mermaid-rendered', 'true');
|
||
}
|
||
|
||
/**
|
||
* 设置错误
|
||
*/
|
||
setError(error) {
|
||
this.error = error;
|
||
}
|
||
|
||
/**
|
||
* 重置变换
|
||
*/
|
||
resetTransform() {
|
||
this.scale = 1;
|
||
this.translateX = 0;
|
||
this.translateY = 0;
|
||
}
|
||
}
|
||
```
|
||
|
||
### MermaidConfig (配置模型)
|
||
|
||
```javascript
|
||
/**
|
||
* Mermaid 配置模型
|
||
*/
|
||
class MermaidConfig {
|
||
constructor() {
|
||
this.theme = 'default'; // 主题
|
||
this.startOnLoad = false; // 自动加载
|
||
this.securityLevel = 'loose'; // 安全级别
|
||
this.flowchart = {
|
||
useMaxWidth: true,
|
||
htmlLabels: true
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 从全局配置加载
|
||
*/
|
||
loadFromGlobal() {
|
||
this.theme = getMermaidTheme();
|
||
}
|
||
|
||
/**
|
||
* 转换为 Mermaid 配置对象
|
||
*/
|
||
toMermaidConfig() {
|
||
return {
|
||
theme: this.theme,
|
||
startOnLoad: this.startOnLoad,
|
||
securityLevel: this.securityLevel,
|
||
flowchart: this.flowchart
|
||
};
|
||
}
|
||
}
|
||
```
|
||
|
||
## Error Handling
|
||
|
||
### 错误处理策略
|
||
|
||
1. **渲染错误**:显示友好的错误提示,保留原始代码
|
||
2. **库加载失败**:显示加载失败提示,提供重试选项
|
||
3. **导出失败**:显示导出失败提示,记录错误日志
|
||
4. **语法错误**:显示语法错误位置和错误信息
|
||
|
||
### 错误处理实现
|
||
|
||
```javascript
|
||
/**
|
||
* 安全执行函数
|
||
* @param {Function} fn - 要执行的函数
|
||
* @param {string} moduleName - 模块名称
|
||
* @returns {boolean} 是否执行成功
|
||
*/
|
||
function safeExecute(fn, moduleName) {
|
||
try {
|
||
fn();
|
||
ArgonDebug.log(`[Argon Mermaid] ${moduleName} 执行成功`);
|
||
return true;
|
||
} catch (error) {
|
||
ArgonDebug.error(`[Argon Mermaid] ${moduleName} 执行失败:`, error);
|
||
return false;
|
||
}
|
||
}
|
||
```
|
||
|
||
## Performance Optimization
|
||
|
||
### 优化策略
|
||
|
||
1. **批量渲染**:使用 requestAnimationFrame 批量渲染图表
|
||
2. **延迟加载**:优先渲染可见图表,延迟渲染视口外的图表
|
||
3. **缓存优化**:缓存已渲染的图表,避免重复渲染
|
||
4. **硬件加速**:使用 CSS transform 实现缩放和拖拽
|
||
|
||
### 性能优化实现
|
||
|
||
```javascript
|
||
/**
|
||
* 批量渲染图表
|
||
* @param {HTMLElement[]} blocks - 代码块数组
|
||
* @returns {Promise<void>}
|
||
*/
|
||
async function batchRenderCharts(blocks) {
|
||
for (let i = 0; i < blocks.length; i++) {
|
||
await new Promise(resolve => {
|
||
requestAnimationFrame(async () => {
|
||
await renderMermaidChart(blocks[i], i);
|
||
resolve();
|
||
});
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
## Testing Strategy
|
||
|
||
### 测试方法
|
||
|
||
1. **单元测试**:测试各个模块的功能
|
||
2. **集成测试**:测试模块之间的集成
|
||
3. **端到端测试**:测试完整的渲染流程
|
||
4. **性能测试**:测试渲染性能和内存占用
|
||
|
||
### 测试用例
|
||
|
||
```javascript
|
||
describe('Mermaid Renderer', () => {
|
||
it('should detect mermaid blocks', () => {
|
||
const blocks = detectMermaidBlocks();
|
||
expect(blocks.length).toBeGreaterThan(0);
|
||
});
|
||
|
||
it('should render mermaid chart', async () => {
|
||
const block = document.querySelector('code.language-mermaid');
|
||
await renderMermaidChart(block, 0);
|
||
expect(block.hasAttribute('data-mermaid-rendered')).toBe(true);
|
||
});
|
||
|
||
it('should handle render error', async () => {
|
||
const block = createMockBlock('invalid syntax');
|
||
await renderMermaidChart(block, 0);
|
||
expect(document.querySelector('.mermaid-error')).not.toBeNull();
|
||
});
|
||
});
|
||
```
|
||
|
||
## Code Quality Standards
|
||
|
||
### 代码规范
|
||
|
||
1. **命名规范**:使用驼峰命名法,函数名动词开头
|
||
2. **注释规范**:使用 JSDoc 注释,说明参数和返回值
|
||
3. **错误处理**:所有异步操作都要有错误处理
|
||
4. **性能优化**:避免频繁的 DOM 操作,使用批量处理
|
||
|
||
### 代码审查清单
|
||
|
||
- [ ] 所有函数都有 JSDoc 注释
|
||
- [ ] 所有异步操作都有错误处理
|
||
- [ ] 所有事件监听器都在清理时移除
|
||
- [ ] 使用 let/const 而不是 var
|
||
- [ ] 遵循项目代码规范(Tab 缩进、单引号等)
|
||
- [ ] 没有 console.log,使用 ArgonDebug
|
||
- [ ] 性能优化(批量 DOM 操作、requestAnimationFrame)
|