当前位置: 首页 > news >正文

Javascript网页设计实例:通过JS实现上传Markdown转化为脑图并下载脑图

功能预览

在这里插入图片描述

深度与密度测试

对于测试部分,分别对深度和密度进行了测试:

注意!!!!!!!只实现了识别Markdown中的#代表的层级,所以不能使用其余标识符!!!
注意!!!!!!!只实现了识别Markdown中的#代表的层级,所以不能使用其余标识符!!!
注意!!!!!!!只实现了识别Markdown中的#代表的层级,所以不能使用其余标识符!!!
注意!!!!!!!只实现了识别Markdown中的#代表的层级,所以不能使用其余标识符!!!
注意!!!!!!!只实现了识别Markdown中的#代表的层级,所以不能使用其余标识符!!!

测试数据:

# 量子力学基础 ## 1. 基础概念 
### 1.1 波粒二象性 
#### 1.1.1 光的波粒二象性 
##### 1.1.1.1 光电效应 
###### 1.1.1.1.1 光电效应的实验现象 
###### 1.1.1.1.2 爱因斯坦的光电效应理论 
##### 1.1.1.2 康普顿效应 
#### 1.1.2 微观粒子的波粒二象性 
##### 1.1.2.1 德布罗意假设 
##### 1.1.2.2 电子衍射实验 
### 1.2 不确定性原理 
#### 1.2.1 海森堡不确定性原理 
##### 1.2.1.1 位置与动量的不确定性关系 
##### 1.2.1.2 能量与时间的不确定性关系 
#### 1.2.2 理解与应用 
##### 1.2.2.1 对微观世界的解释 
##### 1.2.2.2 在量子计算中的意义 
## 2. 量子态与量子叠加 
### 2.1 量子态的描述 
#### 2.1.1 状态向量与希尔伯特空间 
##### 2.1.1.1 状态向量的概念 
##### 2.1.1.2 希尔伯特空间的基本性质 
#### 2.1.2 波函数与薛定谔方程 
##### 2.1.2.1 波函数的物理意义 
##### 2.1.2.2 薛定谔方程的形式与解法 
### 2.2 量子叠加原理 
#### 2.2.1 叠加态的概念 
##### 2.2.1.1 叠加态的数学表示 
##### 2.2.1.2 叠加态的实验验证(双缝实验)
#### 2.2.2 叠加态的应用 
##### 2.2.2.1 在量子通信中的应用 
##### 2.2.2.2 在量子计算中的应用 
## 3. 量子纠缠与非局域性 
### 3.1 量子纠缠的概念 
#### 3.1.1 纠缠态的定义与分类 
##### 3.1.1.1 Bell态 
##### 3.1.1.2 GHZ态 
#### 3.1.2 纠缠态的实验验证 
##### 3.1.2.1 EPR悖论 
##### 3.1.2.2 Bell不等式与实验结果 
### 3.2 非局域性与量子通信 
#### 3.2.1 非局域性的物理意义 
##### 3.2.1.1 非局域性的实验验证 
##### 3.2.1.2 非局域性在量子隐形传态中的应用 
#### 3.2.2 量子通信的基本原理 
##### 3.2.2.1 量子密钥分发(QKD)
##### 3.2.2.2 量子隐形传态(Quantum Teleportation)

测试结果:
在这里插入图片描述

一、工具概述

功能就是上传Markdown格式文件,然后转换为脑图,然后下载,没有添加其余功能了。

我觉得还可以添加:
1、为脑图添加标题。
2、现在的脑图颜色、连接方式单一,可以增加更多的样式。
3。。。。。。。。。再想想。

算了,本来也就是做一个样例,再想下去就快想出来一个成品了。。。。

半残不残的挺好的。。。。

另外,现在没做优化,所以,如果你直接copy代码的话,可能会出现一些内存占用的情况。


二、代码结构划分

1. HTML 结构

<div class="container"><div class="upload-area"><!-- 文件上传区域 --><label for="markdownFile" class="upload-label">上传 Markdown 文件</label><input type="file" id="markdownFile" accept=".md,.markdown" hidden></div><div class="mindmap-container"><canvas id="canvas"></canvas></div>
</div>
  • 定义了页面的基本布局,包括文件上传区域和画布容器。
  • 使用 canvas 元素作为图形渲染的主要载体。

2. CSS 样式

:root {--primary-color: #2196F3;--secondary-color: #4CAF50;--background: #f8f9fa;
}body {font-family: 'Segoe UI', system-ui, sans-serif;margin: 0;background: var(--background);
}
  • 定义了主题颜色、字体和背景样式。
  • 实现了响应式布局和交互效果(如悬停动画)。

3. JavaScript 核心逻辑

class MindmapRenderer {constructor(canvas) {this.canvas = canvas;this.ctx = canvas.getContext('2d');this.scale = 1;this.offsetX = 0;this.offsetY = 0;this.nodes = [];this.initEvents();}
  • MindmapRenderer 类负责整个渲染流程,包括节点绘制、布局计算、交互操作(缩放和平移)。
  • parseMarkdown 函数将 Markdown 文件解析为树状节点结构。
  • exportHighResImage 函数实现高分辨率图片导出功能。

三、功能实现

1. 核心功能

(1)Markdown 转思维导图

  • 支持将 Markdown 文件中的标题(# 符号)层级结构转换为树状结构。
  • 示例:
    # 根节点 
    ## 子节点1 
    ### 孙节点1 
    ## 子节点2 
    
    转换后生成如下结构:
    根节点 
    ├── 子节点1 
    │   └── 孙节点1 
    └── 子节点2 
    

(2)节点渲染

  • 每个节点根据层级(1-10)使用不同的样式(颜色、字体大小、圆角等)。
  • 示例:
    const NODE_STYLES = {1: { bg: "#2962FF", text: "#fff", fontSize: 20 }, // 根节点样式 2: { bg: "#00C853", text: "#fff", fontSize: 19 }, // 子节点样式 // ...
    };
    

(3)交互操作

  • 缩放和平移:用户可以通过鼠标滚轮缩放画布,拖拽画布进行平移。
  • 导出图片:支持将当前视图导出为高分辨率的 JPEG 图片。

(4)自动布局

  • 使用递归算法计算节点位置和大小。
  • 动态调整子树宽度和高度,避免节点重叠。

2. 功能亮点

(1)动态布局算法

  • 子树测量:递归测量每个节点及其子树的宽度和高度。
  • 碰撞检测:当节点过多时,自动调整位置避免重叠。
  • 压缩因子:优化节点布局,减少垂直方向的空间占用。

(2)交互体验

  • 平滑缩放和平移:使用 CSS 3D 变换来实现流畅的操作。
  • 高分辨率导出:导出的图片保留所有细节,适合打印或分享。

(3)自适应设计

  • 画布自适应:根据内容自动调整画布大小。
  • 响应式布局:使用 ResizeObserver 监听容器大小变化并自动调整。

(4)性能优化

  • 渲染性能:通过合理布局减少重绘次数。
  • 事件处理:使用事件委托优化交互操作。

四、技术栈

1. 前端技术

  • HTML5 Canvas: 用于绘制复杂的图形和节点。
  • CSS3: 实现响应式布局和交互效果。
  • JavaScript ES6+: 使用现代 JavaScript 特性(如类、箭头函数等)。

2. 数据处理

  • Markdown 解析: 自行实现的解析器,支持多级标题嵌套。
  • 树形数据结构: 将 Markdown 文件转换为树形节点结构。

3. 交互技术

  • 事件监听: 处理鼠标拖拽、滚轮缩放等操作。
  • Canvas 缩放和平移: 使用 setTransform 方法实现复杂变换。

4. 图形绘制

  • Bezier 曲线: 绘制节点之间的连接线。
  • 文字换行: 在节点内实现文字自动换行。

五、当前缺点

1. 性能问题

  • 处理大量节点时,渲染性能可能下降。
  • 缩放和平移操作在复杂场景下可能出现延迟。

2. 功能限制

  • Markdown 支持有限: 仅支持标题(#)语法,不支持其他 Markdown 元素(如列表、图片等)。
  • 缺乏编辑功能: 无法直接在画布上编辑节点内容。
  • 导出格式单一: 仅支持 JPEG 格式导出。

3. 用户体验

  • 缺少加载进度提示,大文件上传时可能会出现卡顿。
  • 缩放和平移操作的手感有待优化(如增加惯性滚动)。

4. 代码结构

  • 部分逻辑耦合度较高,维护成本较高。
  • 缺少单元测试和文档注释,代码可读性有待提升。

六、未来改进方向

1. 功能扩展

  • 支持更多 Markdown 语法(如列表、图片、链接等)。
  • 增加节点编辑功能(如拖拽调整大小、修改文字内容)。
  • 支持更多导出格式(如 PNG、SVG)。

2. 性能优化

  • 使用 Web Workers 分担后台计算任务。
  • 优化碰撞检测算法,减少计算量。

3. 用户体验提升

  • 增加加载进度提示。
  • 优化缩放和平移操作的手感(如增加惯性滚动)。

4. 代码重构

  • 提取公共逻辑,降低代码耦合度。
  • 增加单元测试和文档注释。

七、完整代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Markdown转脑图</title>
<style>
:root {--primary-color: #2196F3;--secondary-color: #4CAF50;--background: #f8f9fa;
}body {font-family: 'Segoe UI', system-ui, sans-serif;margin: 0;background: var(--background);
}.container {max-width: 1800px;margin: 20px auto;padding: 20px;
}.upload-area {text-align: center;margin-bottom: 30px;position: relative;
}.mindmap-container {background: white;border-radius: 16px;box-shadow: 0 12px 32px rgba(0,0,0,0.1);overflow: hidden;height: 85vh;position: relative;
}#canvas {cursor: grab;transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}.upload-label {display: inline-flex;align-items: center;padding: 12px 28px;background: var(--primary-color);color: white;border-radius: 10px;cursor: pointer;transition: transform 0.2s, box-shadow 0.2s;font-weight: 500;
}.upload-label:hover {transform: translateY(-2px);box-shadow: 0 6px 16px rgba(33,150,243,0.25);
}
.export-btn {background: var(--secondary-color);margin-left: 15px;
}
.export-btn:hover {box-shadow: 0 6px 16px rgba(76,175,80,0.25);
}
</style>
</head>
<body>
<div class="container"><div class="upload-area"><label for="markdownFile" class="upload-label">📁 上传 Markdown 文件 </label><button class="upload-label export-btn" id="exportBtn">📷 导出图片</button><input type="file" id="markdownFile" accept=".md,.markdown" hidden></div><div class="mindmap-container"><canvas id="canvas"></canvas></div>
</div><script>
const NODE_STYLES = {1: { bg: "#2962FF",text: "#fff",minWidth: 160, paddingX: 24,paddingY: 16,fontSize: 20,rectRadius: 12},2: { bg: "#00C853",text: "#fff",minWidth: 148,paddingX: 22,paddingY: 14,fontSize: 19,rectRadius: 10 },3: { bg: "#AA00FF",text: "#fff",minWidth: 136,paddingX: 20,paddingY: 12,fontSize: 18,rectRadius: 9 },4: { bg: "#FF6D00",text: "#fff",minWidth: 124,paddingX: 18,paddingY: 10,fontSize: 17,rectRadius: 8 },5: { bg: "#6A1B9A",text: "#fff",minWidth: 112,paddingX: 16,paddingY: 9,fontSize: 16,rectRadius: 7 },6: { bg: "#D50000",text: "#fff",minWidth: 100,paddingX: 14,paddingY: 8,fontSize: 15,rectRadius: 6 },7: { bg: "#00897B", text: "#fff",minWidth: 88,paddingX: 12,paddingY: 7,fontSize: 14,rectRadius: 5 },8: { bg: "#546E7A", text: "#fff",minWidth: 76,paddingX: 10,paddingY: 6,fontSize: 13,rectRadius: 4 },9: { bg: "#757575", text: "#fff",minWidth: 64,paddingX: 8,paddingY: 5,fontSize: 12,rectRadius: 3 },10: { bg: "#BDBDBD", text: "#212121",minWidth: 52,paddingX: 6,paddingY: 4,fontSize: 11,rectRadius: 2 },default: { bg: "#607D8B", text: "#fff",minWidth: 60,paddingX: 4,paddingY: 4,fontSize: 10,rectRadius: 2 }
};class MindmapRenderer {constructor(canvas) {this.canvas  = canvas;this.ctx  = canvas.getContext('2d'); this.scale  = 1;this.offsetX  = 0;this.offsetY  = 0;this.nodes  = [];this.LAYOUT_CONFIG = {BASE_GAP_X: 60,BASE_GAP_Y: 25,DEPTH_REDUCTION: 1.4,MIN_SIBLING_GAP: 15,LINE_HEIGHT_RATIO: 1.2,DEPTH_WIDTH: 80 };this.initEvents(); }initEvents() {let isDragging = false;let lastX = 0, lastY = 0;const handleStart = e => {isDragging = true;lastX = e.clientX; lastY = e.clientY; this.canvas.style.cursor  = 'grabbing';};const handleMove = e => {if (isDragging) {const dx = (e.clientX  - lastX) / this.scale; const dy = (e.clientY  - lastY) / this.scale; this.offsetX  += dx;this.offsetY  += dy;lastX = e.clientX; lastY = e.clientY; this.render(); }};const handleEnd = () => {isDragging = false;this.canvas.style.cursor  = 'grab';};this.canvas.addEventListener('mousedown',  handleStart);document.addEventListener('mousemove',  handleMove);document.addEventListener('mouseup',  handleEnd);this.canvas.addEventListener('wheel',  e => {e.preventDefault(); const rect = this.canvas.getBoundingClientRect(); const mouseX = (e.clientX  - rect.left  - this.offsetX)  / this.scale; const mouseY = (e.clientY  - rect.top  - this.offsetY)  / this.scale; const zoom = e.deltaY  < 0 ? 1.1 : 0.9;this.scale  = Math.min(Math.max(this.scale  * zoom, 0.3), 5);this.offsetX  = (e.clientX  - rect.left  - mouseX * this.scale); this.offsetY  = (e.clientY  - rect.top  - mouseY * this.scale); this.render(); });}getNodeDepth(node) {let depth = 0;let current = node;while (current.parent)  {depth++;current = current.parent; }return depth;}measureSubtree(node) {const ctx = this.ctx; node.size  = this.calculateNodeSize(node,  ctx);if (node.children.length  === 0) {node.subtreeWidth  = node.size.width; node.subtreeHeight  = node.size.height; return;}let totalHeight = 0;let maxChildRight = 0; // 最大右侧位置 const depth = this.getNodeDepth(node); const dynamicGap = this.LAYOUT_CONFIG.BASE_GAP_Y * Math.pow(this.LAYOUT_CONFIG.DEPTH_REDUCTION,  depth);node.children.forEach((child,  index) => {this.measureSubtree(child); totalHeight += child.subtreeHeight; // 父右侧 + 间距 + 子节点宽度 const childRight = this.LAYOUT_CONFIG.DEPTH_WIDTH + child.size.width; maxChildRight = Math.max(maxChildRight,  childRight);if (index !== node.children.length  - 1) {totalHeight += dynamicGap;}});node.subtreeHeight  = Math.max(node.size.height,  totalHeight);// 子树宽度 = 父节点半宽 + 最大子节点右侧 node.subtreeWidth  = node.size.width  / 2 + maxChildRight;}calculateNodeSize(node, ctx) {const style = NODE_STYLES[node.level] || NODE_STYLES.default; ctx.font  = `${style.fontSize}px  'Segoe UI'`;const textMetrics = ctx.measureText(node.text); const contentWidth = textMetrics.width  + style.paddingX  * 2;const width = Math.max(style.minWidth,  contentWidth);const lineHeight = style.fontSize  * this.LAYOUT_CONFIG.LINE_HEIGHT_RATIO;const lines = Math.ceil(textMetrics.width  / (width - style.paddingX  * 2));const height = Math.max(style.minWidth  * 0.6, lines * lineHeight + style.paddingY  * 2);return { width, height };}calculateLayout(nodes) {const layoutNode = (node, startX, startY) => {node.x = startX;node.y = startY;if (node.children.length  === 0) return;let currentY = startY - node.subtreeHeight  / 2;const depth = this.getNodeDepth(node); const compressFactor = node.children.length  > 3 ? 0.9 : 1;node.children.forEach(child  => {// 修正子节点定位 const parentRightEdge = node.x + node.size.width  / 2;const childX = parentRightEdge + this.LAYOUT_CONFIG.DEPTH_WIDTH + child.size.width  / 2;const childY = currentY + child.subtreeHeight  / 2 * compressFactor;layoutNode(child, childX, childY);currentY += child.subtreeHeight  * compressFactor + this.LAYOUT_CONFIG.MIN_SIBLING_GAP;});};nodes.forEach(root  => {this.measureSubtree(root); layoutNode(root, 100, this.canvas.height  / 2 / this.scale); });this.resolveCollisions(nodes); }resolveCollisions(nodes) {const findConflicts = (nodeList) => {nodeList.forEach((node,  i) => {for (let j = i + 1; j < nodeList.length;  j++) {const other = nodeList[j];if (this.checkCollision(node,  other)) {const offset = node.size.height  + this.LAYOUT_CONFIG.MIN_SIBLING_GAP;other.y += offset;this.updateAncestorsPosition(other); }}if (node.children.length  > 0) {findConflicts(node.children); }});};findConflicts(nodes);}checkCollision(a, b) {return Math.abs(a.x  - b.x) < (a.size.width  + b.size.width)/2  &&Math.abs(a.y  - b.y) < (a.size.height  + b.size.height)/2; }updateAncestorsPosition(node) {let current = node.parent; while (current) {current.y = node.y;current = current.parent; }}drawNode(node) {const style = NODE_STYLES[node.level] || NODE_STYLES.default; const ctx = this.ctx; ctx.shadowColor  = 'rgba(0,0,0,0.1)';ctx.shadowOffsetX  = 2;ctx.shadowOffsetY  = 3;ctx.shadowBlur  = 6;ctx.beginPath(); ctx.roundRect( node.x - node.size.width  / 2,node.y - node.size.height  / 2,node.size.width, node.size.height, style.rectRadius  );ctx.fillStyle  = style.bg; ctx.fill(); ctx.shadowColor  = 'transparent';ctx.fillStyle  = style.text; ctx.textAlign  = "center";ctx.textBaseline  = "middle";ctx.font  = `${style.fontSize}px  'Segoe UI'`;this.wrapText(node.text,  node.x, node.y,node.size.width  - style.paddingX  * 2,style.fontSize  * 1.4 );}wrapText(text, x, y, maxWidth, lineHeight) {const words = text.split('  ');let currentLine = '';let currentY = y - (words.length  > 1 ? lineHeight / 2 : 0);for (const word of words) {const testLine = currentLine ? `${currentLine} ${word}` : word;const metrics = this.ctx.measureText(testLine); if (metrics.width  > maxWidth && currentLine) {this.ctx.fillText(currentLine,  x, currentY);currentLine = word;currentY += lineHeight;} else {currentLine = testLine;}}this.ctx.fillText(currentLine,  x, currentY);}drawConnection(parent, child) {const ctx = this.ctx; const parentRight = parent.x + parent.size.width  / 2;const childLeft = child.x - child.size.width  / 2;const controlX = (parentRight + childLeft) / 2;ctx.beginPath(); ctx.moveTo(parentRight,  parent.y);ctx.bezierCurveTo( controlX, parent.y,controlX, child.y,childLeft, child.y );ctx.strokeStyle  = parent.level  === 1 ? '#78909C' : '#B0BEC5';ctx.lineWidth  = 2;ctx.stroke(); }render() {this.ctx.save(); this.ctx.setTransform(1,  0, 0, 1, 0, 0);this.ctx.clearRect(0,  0, this.canvas.width,  this.canvas.height); this.ctx.scale(this.scale,  this.scale); this.ctx.translate(this.offsetX,  this.offsetY); this.traverseNodes(node  => this.drawNode(node)); this.traverseNodes(node  => {node.children.forEach(child  => this.drawConnection(node,  child));});this.ctx.restore(); }traverseNodes(callback) {const traverse = node => {callback(node);node.children.forEach(traverse); };this.nodes.forEach(traverse); }
}// 页面集成
const canvas = document.getElementById('canvas'); 
const renderer = new MindmapRenderer(canvas);
const container = document.querySelector('.mindmap-container'); function adaptiveResize() {const computeCanvasSize = () => {if (!renderer.nodes.length)  {return [container.clientWidth, container.clientHeight]; }let minX = Infinity, maxX = -Infinity;let minY = Infinity, maxY = -Infinity;renderer.traverseNodes(node  => {minX = Math.min(minX,  node.x - node.size.width/2); maxX = Math.max(maxX,  node.x + node.size.width/2); minY = Math.min(minY,  node.y - node.size.height/2); maxY = Math.max(maxY,  node.y + node.size.height/2); });return [Math.max(container.clientWidth,  (maxX - minX) * 1.2 * renderer.scale), Math.max(container.clientHeight,  (maxY - minY) * 1.2 * renderer.scale) ];};const [newWidth, newHeight] = computeCanvasSize();canvas.width  = newWidth;canvas.height  = newHeight;renderer.render(); 
}function resizeCanvas() {if (!renderer.nodes.length)  {canvas.width  = container.clientWidth; canvas.height  = container.clientHeight; return;}let minX = Infinity, maxX = -Infinity;let minY = Infinity, maxY = -Infinity;renderer.traverseNodes(node  => {minX = Math.min(minX,  node.x - node.size.width/2); maxX = Math.max(maxX,  node.x + node.size.width/2); minY = Math.min(minY,  node.y - node.size.height/2); maxY = Math.max(maxY,  node.y + node.size.height/2); });canvas.width  = Math.max(container.clientWidth,  (maxX - minX) * 1.2 * renderer.scale); canvas.height  = Math.max(container.clientHeight,  (maxY - minY) * 1.2 * renderer.scale); renderer.render(); 
}// 响应式处理 
const resizeObserver = new ResizeObserver(() => adaptiveResize());
resizeObserver.observe(container); // 文件处理 
document.getElementById('markdownFile').addEventListener('change',  async e => {const file = e.target.files[0]; if (!file) return;const text = await file.text(); renderer.nodes  = parseMarkdown(text);renderer.calculateLayout(renderer.nodes); adaptiveResize();
});// Markdown解析
function parseMarkdown(content) {const lines = content.split('\n').filter(l  => l.trim()); const rootNodes = [];const stack = [];let lastLevel = 0;lines.forEach(line  => {const match = line.match(/^(#+)\s*(.*)/); if (!match) return;const level = match[1].length;const node = {text: match[2].trim(),level: level,children: [],parent: null };// 层级关系处理 if (level > lastLevel) {if (stack.length  > 0) {node.parent  = stack[stack.length - 1];node.parent.children.push(node); }} else {while (stack.length  && stack[stack.length - 1].level >= level) {stack.pop(); }if (stack.length)  {node.parent  = stack[stack.length - 1];node.parent.children.push(node); }}if (!node.parent)  rootNodes.push(node); stack.push(node); lastLevel = level;});return rootNodes;
}function exportHighResImage() {const exportCanvas = document.createElement("canvas"); const exportCtx = exportCanvas.getContext("2d"); // 计算全图边界let minX = Infinity, maxX = -Infinity;let minY = Infinity, maxY = -Infinity;renderer.traverseNodes(node  => {const halfWidth = node.size.width  / 2;const halfHeight = node.size.height  / 2;minX = Math.min(minX,  node.x - halfWidth);maxX = Math.max(maxX,  node.x + halfWidth);minY = Math.min(minY,  node.y - halfHeight);maxY = Math.max(maxY,  node.y + halfHeight);});// 设置画布尺寸 const padding_left = 100;const padding_top = 80;exportCanvas.width  = (maxX - minX) + padding_left * 2;exportCanvas.height  = (maxY - minY) + padding_top * 2;// 填充白色背景exportCtx.fillStyle  = "#FFFFFF";exportCtx.fillRect(0,  0, exportCanvas.width,  exportCanvas.height); // 保存原始状态const originalScale = renderer.scale; const originalOffsetX = renderer.offsetX; const originalOffsetY = renderer.offsetY; const originalCtx = renderer.ctx; renderer.scale  = 1;renderer.offsetX  = -minX + padding_left;renderer.offsetY  = -minY + padding_top;renderer.ctx  = exportCtx;// 执行渲染renderer.render(); // 二次填充边缘透明区域exportCtx.globalCompositeOperation  = "destination-over";exportCtx.fillStyle  = "#FFFFFF";exportCtx.fillRect(0,  0, exportCanvas.width,  exportCanvas.height); // 导出为JPG const link = document.createElement("a"); link.download  = "mindmap.jpg"; link.href  = exportCanvas.toDataURL("image/jpeg",  1.0);link.click(); 
}// 绑定导出事件
document.getElementById('exportBtn').addEventListener('click',  exportHighResImage);window.addEventListener('resize',  resizeCanvas);
resizeCanvas();
</script>
</body>
</html>

相关文章:

Javascript网页设计实例:通过JS实现上传Markdown转化为脑图并下载脑图

功能预览 深度与密度测试 对于测试部分&#xff0c;分别对深度和密度进行了测试&#xff1a; 注意&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;只实现了识别Markdown中的#代表的层级&#xff0c;所以不能使用其余标识符&#xff0…...

【Spring生命周期】Bean元信息配置阶段

引言 本系列将详细讲解Spring生命周期的13个阶段&#xff0c;从源码角度帮助我们更好的理解Spring框架和bean生命周期全流程 Bean信息定义4种方式 API的方式Xml文件方式properties文件的方式注解的方式 在 Spring 框架中&#xff0c;Bean 元信息配置阶段是整个 Bean 生命周…...

【iOS】SwiftUI状态管理

State ObservedObject StateObject 的使用 import SwiftUIclass CountModel: ObservableObject {Published var count: Int 0 // 通过 Published 标记的变量会触发视图更新init() {print("TimerModel initialized at \(count)")} }struct ContentView: View {State…...

ubuntu上如何查看coredump文件默认保存在哪个路径?

在 Ubuntu 系统中&#xff0c;可以通过以下几种方式来查看 coredump 文件默认保存的路径&#xff1a; 1. 查看core_pattern配置 core_pattern是一个内核参数&#xff0c;它决定了 coredump 文件的保存位置和命名规则。可以通过以下命令查看其当前值&#xff1a; cat /proc/s…...

技术总结汇总

目录 数据库 数据库系统原理 MySQL Redis Java Java 基础 Java 容器 Java 并发 Java 虚拟机 Java I/O 系统设计 系统设计基础 微服务 分布式 集群和负载均衡 灾备和故障转移 限流 降级和熔断 缓存 消息队列 设计模式 DDD领域驱动设计 开发框架和中间件…...

Java-如何将其他地方拉取的jar包导入本地maven环境

背景 公司的一个老旧二开项目&#xff0c;原项目维护方不合作了&#xff0c;提供的项目源码提供给到公司。项目中用到了一些原维护方内部的jar包&#xff0c;导致二开时依赖的这些部分全部报错。虽然在项目中直接导入此jar包可以解决报红报错问题&#xff0c;但是在使用maven打…...

Unity 聊天气泡根据文本内容适配

第一步 拼接UI 1、对气泡图进行九宫图切割 2、设置底图pivot位置和对齐方式 pivot位置&#xff1a;&#xff08;0&#xff0c;1&#xff09; 对齐方式&#xff1a;左上对齐 3、设置文本pivot位置和对齐方式&#xff0c;并挂上布局组件 pivot设置和对齐方式和底图一样&#…...

【组态PLC】基于博图V16和组态王六层双部电梯组态设计【含PLC组态源码 M008期】

控制要求 1&#xff09;两台电梯同时运行时&#xff0c;共同享用一套外呼按钮。 2&#xff09;当两台电梯同时去响应外呼信号时&#xff0c;两台电梯自动定向启动前往相应的楼层&#xff0c;当某一台电梯先行到达指定层楼时&#xff0c;另外一台电梯必须就近停靠平层&#xf…...

Python--数据类型(中)

1. 列表&#xff08;list&#xff09; 1.1 定义与特性 定义&#xff1a;有序、可变的容器&#xff0c;支持多种数据类型混合存储。 user_list ["奥力给", 98, True] empty_list [] # 空列表可变性&#xff1a;列表内部元素可修改&#xff0c;区别于字符串和元组。…...

学习总结2.19

首先就是对dfs和bfs的熟悉&#xff0c;dfs是一种递归函数&#xff0c;通过不断深搜来达到目的&#xff0c;通常用于寻找多少未知量&#xff0c;相较于bfs&#xff0c;编译难度更低一点&#xff1b;bfs多用于寻找最短路径之类&#xff0c;相较于dfs代码多了一部分队列的代码&…...

[Vivado报错] [Runs 36-527] DCP does not exist

一、错误原因解析 此错误表明Vivado在指定路径未找到.dcp&#xff08;Design Checkpoint&#xff09;文件&#xff0c;通常由以下原因导致&#xff1a; 路径过长或特殊字符&#xff1a;Windows系统路径长度限制&#xff08;260字符&#xff09;可能导致文件生成失败&#xff…...

深度学习的集装箱箱号OCR识别技术,识别率99.9%

集装箱箱号OCR识别技术是一项结合计算机视觉和规则校验的复杂任务&#xff0c;以下是其关键要点及实现思路的总结&#xff1a; 1、集装箱号结构&#xff1a;11位字符&#xff0c;格式为公司代码(3字母)和序列号(6数字)以及校验码(1数字)和尺寸/类型代码(可选)&#xff0c;例如…...

基于java新闻管理系统,推荐一款开源cms内容管理系统ruoyi-fast-cms

一、项目概述 1.1 项目背景 在信息高速流通的当下&#xff0c;新闻媒体行业每天都要处理和传播海量信息。传统的新闻管理模式依赖人工操作&#xff0c;在新闻采集、编辑、发布以及后续管理等环节中&#xff0c;不仅效率低下&#xff0c;而且容易出现人为失误。同时&#xff0…...

网络安全要学python 、爬虫吗

网络安全其实并不复杂&#xff0c;只是比普通开发岗位要学习的内容多一点。无论是有过编程基础还是零基础的都可以学习的。网络安全目前可就业的岗位从技术上可分为两部分&#xff1a;web安全和二进制逆向安全。web安全是网络安全的入门方向&#xff0c;内容简单&#xff0c;就…...

ChatGPT行业热门应用提示词案例-AI绘画类

AI 绘画指令是一段用于指导 AI 绘画工具&#xff08;如 DALLE、Midjourney 等&#xff09;生成特定图像的文本描述。它通常包含场景、主体、风格、色彩、氛围等关键信息&#xff0c;帮助 AI 理解创作者的意图&#xff0c;从而生成符合要求的绘画作品。 ChatGPT 拥有海量的知识…...

网络安全钓鱼邮件测试 网络安全 钓鱼

&#x1f345; 点击文末小卡片 &#xff0c;免费获取网络安全全套资料&#xff0c;资料在手&#xff0c;涨薪更快 如今&#xff0c;网络安全是一个备受关注的话题&#xff0c;“网络钓鱼”这个词也被广泛使用。 即使您对病毒、恶意软件或如何在线保护自己一无所知&#xff0c;您…...

写一个python组件

写一个python组件 核心功能代码命令行接口打包配置安装与测试注意 写一个python组件&#xff0c;具体的&#xff1a;项目结构设计&#xff1a;定义你的项目的目录结构。编写核心功能代码&#xff1a;实现你想要的功能。创建命令行接口(CLI)&#xff1a;使用argparse或click库来…...

deepseek-v3在阿里云和腾讯云的使用中的差异

随着deepseek在各大云商上线&#xff0c;试用了下阿里云和腾讯云的deepseek服务&#xff0c;在回答经典数学问题9.9和9.11谁大时&#xff0c;发现还是有差异的。将相关的问题记录如下。 1、问题表现 笔者使用的openai的官方sdk go-openai。 因本文中测验主要使用阿里云和腾讯…...

DevOps自动化部署详解:从理念到实践

在软件开发日益快速迭代的今天&#xff0c;如何以高效、稳定且可重复的方式将代码变更从开发环境自动部署到生产环境成为企业竞争的重要因素。DevOps 正是在这一背景下应运而生&#xff0c;它打破开发、测试、运维之间的壁垒&#xff0c;通过自动化工具和流程&#xff0c;实现持…...

systemverilog刷题小记

1、systemverilog的队列特性 1.1队列的基础操作 方法语法时间复杂度示例结果头部插入q.push_front(item)O(1)q.push_front(5)[5, ...]尾部插入q.push_back(item)O(1)q.push_back(8)[..., 8]头部删除q.pop_front()O(1)x q.pop_front()[...]尾部删除q.pop_back()O(1)x q.pop_…...

jetbrains IDEA集成大语言模型

一、CodeGPT ‌CodeGPT‌是由CSDN打造的一款生成式AI产品&#xff0c;专为开发者量身定制。它能够提供强大的技术支持&#xff0c;帮助开发者在学习新技术或解决实际工作中的各种计算机和开发难题‌1。 idea集成 1.在线安装&#xff1a;直接在线安装 2.离线安装 JetBrains Mar…...

23. AI-大语言模型-DeepSeek赋能开发-Spring AI集成

文章目录 前言一、Spring AI 集成 DeepSeek1. 开发AI程序2. DeepSeek 大模型3. 集成 DeepSeek 大模型1. 接入前准备2. 引入依赖3. 工程配置4. 调用示例5. 小结 4. 集成第三方平台&#xff08;已集成 DeepSeek 大模型&#xff09;1. 接入前准备2. POM依赖3. 工程配置4. 调用示例…...

Ubuntu学习备忘

1. 打开Terminal快捷键 ctrl alt t 2.Ubuntu22.04的root没有默认初始密码&#xff0c; 为root设置密码&#xff0c;下面链接的step1, How to allow GUI root login on Ubuntu 22.04 Jammy Jellyfish Linux - LinuxConfig...

AI 编程助手 cursor的系统提示词 prompt

# Role 你是一名极其优秀具有10年经验的产品经理和精通java编程语言的架构师。与你交流的用户是不懂代码的初中生&#xff0c;不善于表达产品和代码需求。你的工作对用户来说非常重要&#xff0c;完成后将获得10000美元奖励。 # Goal 你的目标是帮助用户以他容易理解的…...

DAO设计模式

DAO设计模式 1.概念 ​ DAO&#xff08;Data Access Object&#xff09;模式是一种用于分离业务逻辑与数据访问逻辑的设计模式&#xff0c;其核心思想是通过抽象接口屏蔽底层数据操作细节&#xff0c;提升代码的可维护性和扩展性。 2.DAO模式的定义与核心思想 DAO模式属于J…...

为什么外贸办公需要跨境专线网络?

你好&#xff0c;今天我们来聊聊SD-WAN技术在出海企业办公中的应用以及其带来的诸多优势。当今出海企业在与海外分支机构或合作伙伴开展高效的网络通讯和数据传输时&#xff0c;面临着许多挑战。此时&#xff0c;SD-WAN作为一种新兴的网络优化技术&#xff0c;正在改变这些企业…...

挪车小程序挪车二维码php+uniapp

一款基于FastAdminThinkPHP开发的匿名通知车主挪车微信小程序&#xff0c;采用匿名通话的方式&#xff0c;用户只能在有效期内拨打车主电话&#xff0c;过期失效&#xff0c;从而保护车主和用户隐私。提供微信小程序端和服务端源码&#xff0c;支持私有化部署。 更新日志 V1.0…...

【Pandas】pandas Series reindex

Pandas2.2 Series Computations descriptive stats 方法描述Series.align(other[, join, axis, level, …])用于将两个 Series 对齐&#xff0c;使其具有相同的索引Series.case_when(caselist)用于根据条件列表对 Series 中的元素进行条件判断并返回相应的值Series.drop([lab…...

HTML/CSS中并集选择器

1.作用:选中多个选择器对应的元素,又称:分组选择器 所谓并集就是或者的含义. 2.语法:选择器1,选择器2,选择器3,......选择器n 多个选择器通过,连接,此处,的含义就是:或. .rich,.beauty{color: blue;} 3.注意事项 1.并集选择器,我们一般竖着写 2.任何形式的选择器,都可以作为并…...

一台服务器将docker image打包去另一天服务器安装这个镜像

一台服务器将docker image打到去另一天服务器安装这个镜像 1. 打包2.另一台服务器执行 1. 打包 docker save -o nebula-graph-studio.tar harbor1.vm.example.lan/dockerio/vesoft/nebula-graph-studioxxx.tar 是打包好的文件 后面的是 docker image 2.另一台服务器执行 docke…...

【分布式理论14】分布式数据库存储:分表分库、主从复制与数据扩容策略

文章目录 一、分表分库1. 数据分表的必要性与方式2. 数据分库原则与优势 二、主从复制1. 读写分离架构设计2. 数据复制方式3. MySQL实现主从复制4. MySQL主从复制实践与高可用方案 三、数据扩容 随着业务的不断发展和数据量的增长&#xff0c;传统的单机关系型数据库已经逐渐不…...

SIM盾构建安全底座的可行性分析

一、背景 1.1安全需求现状 在数字化时代&#xff0c;信息安全面临着日益严峻的挑战。各类网络攻击手段层出不穷&#xff0c;如数据泄露、恶意软件攻击、网络诈骗等&#xff0c;给个人、企业和社会带来了巨大的损失。为了保障信息系统的安全性&#xff0c;需要构建一个可靠的安…...

ai大模型加本地知识库

cherrystudio 搭建本地知识库&#xff0c;并配置Ollama 本地部署的DS模型&#xff0c;或在线的千问模型 千问文本模型推荐&#xff1a;...

DeepSeek核心算法解析:如何打造比肩ChatGPT的国产大模型

注&#xff1a;此文章内容均节选自充电了么创始人&#xff0c;CEO兼CTO陈敬雷老师的新书《自然语言处理原理与实战》&#xff08;人工智能科学与技术丛书&#xff09;【陈敬雷编著】【清华大学出版社】 文章目录 DeepSeek大模型技术系列一DeepSeek核心算法解析&#xff1a;如何…...

【插件】前端生成word 文件

文章目录 1、背景2、方式一&#xff1a;html-docx-js2.1 具体代码2.2 前端生成word文件的样式2.3 总结 3、方式二&#xff1a;pizzip docxtemplater3.1 具体代码3.2 前端生成word文件的样式3.3 总结 4、参考链接 1、背景 在实际开发中&#xff0c;业务需要&#xff0c;需要把数…...

软件测试用例设计方法之正交表

一、概念 能够使用最小的测试过程集合获得最大的测试覆盖率&#xff0c;从全面试验中挑选出有代表性的点进行测试。适用于配置类软件&#xff0c;组合比较多的情况。 正交表Ln(m^k)&#xff1a; 特点&#xff1a;均匀分散、整齐可比、高效、快速、经济 n&#xff1a;正交表的…...

数组和对象深浅拷贝

对象浅拷贝 方法一&#xff1a;使用 Object.assign() Object.assign() 方法用于将一个或多个源对象的所有可枚举属性复制到目标对象。它会返回目标对象。 const originalObj {name: John,age: 30,hobbies: [reading, running] };const shallowCopy Object.assign({}, orig…...

NPM环境搭建指南

NPM&#xff08;Node Package Manager&#xff09;是 Node.js 的包管理工具&#xff0c;堪称前端开发的基石。本文将手把手教你 在Mac、Windows、Linux三大系统上快速搭建NPM环境&#xff0c;并验证是否成功。 一、Mac系统安装NPM 方法1&#xff1a;通过Homebrew安装&#xff…...

C#功能测试

List 内部元素为引用 src[0]为"11" List<Source> src new List<Source>(); src.Add(new Source() { Name "1", Age 1, Description "1" }); src.Add(new Source() { Name "2", Age 2, Description "2"…...

Mongoose 详解

为 Node.js 与 MongoDB 之间提供了一个更高级、更便捷的交互方式。 一、安装 # 使用 npm 安装npm install mongoose 二、基本使用 1. 连接数据库 const mongoose require("mongoose");// 数据库连接 URIconst uri "mongodb://localhost:27017/myDatabase…...

《95015网络安全应急响应分析报告(2024)》

2025年2月&#xff0c;95015服务平台发布了最新一期的《95015网络安全应急响应分析报告&#xff08;2024&#xff09;》。报告分别从整体形势、受害者特征、攻击者特征等方面&#xff0c;对2024年95015平台接报的739起网络安全应急响应事件展开分析&#xff0c;并给出了7个年度…...

鸿蒙开发:V2版本装饰器之@Monitor装饰器

前言 本文代码案例基于Api13。 随着官方的迭代&#xff0c;在新的Api中&#xff0c;对于新的应用开发&#xff0c;官方已经建议直接使用V2所属的装饰器进行开发了&#xff0c;所以&#xff0c;能上手V2的尽量上手V2吧&#xff0c;毕竟&#xff0c;V2是V1的增强版本&#xff0c;…...

【雅思博客04】Silence please!

A: Those people in front of us are making so much noise. It’s so inconsiderate! B: Don’t worry about it; it’s not such a big deal. A: Oh... I can’t hear a thing! Excuse me, can you keep it down? C: Sure, sorry about that! A: Someone’s phone is ri…...

【DeepSeek系列】04 DeepSeek-R1:带有冷启动的强化学习

文章目录 1、简介2、主要改进点3、两个重要观点4、四阶段后训练详细步骤4.1 冷启动4.2 推理导向的强化学习4.3 拒绝采样和有监督微调4.4 针对所有场景的强化学习 5、蒸馏与强化学习对比6、评估6.1 DeepSeek-R1 评估6.2 蒸馏模型评估 7、结论8、局限性与未来方向 1、简介 DeepS…...

深入理解 Spring Bean 生命周期的执行流程

在 Java 开发领域&#xff0c;Spring 框架无疑是一颗璀璨的明星&#xff0c;它极大地简化了企业级应用的开发过程。而 Spring Bean 的生命周期&#xff0c;作为 Spring 框架的核心概念之一&#xff0c;对于理解 Spring 框架的运行机制和进行高效开发至关重要。本文将详细剖析 S…...

毕业设计—基于Spring Boot的社区居民健康管理平台的设计与实现

&#x1f393; 毕业设计大揭秘&#xff01;想要源码和文章&#xff1f;快来私信我吧&#xff01; Hey小伙伴们~ &#x1f44b; 毕业季又来啦&#xff01;是不是都在为毕业设计忙得团团转呢&#xff1f;&#x1f914; 别担心&#xff0c;我这里有个小小的福利要分享给你们哦&…...

捷米特 JM - RTU - TCP 网关应用 F - net 协议转 Modbus TCP 实现电脑控制流量计

一、项目背景 在某工业生产园区的供水系统中&#xff0c;为了精确监测和控制各个生产环节的用水流量&#xff0c;需要对分布在不同区域的多个流量计进行集中管理。这些流量计原本采用 F - net 协议进行数据传输&#xff0c;但园区的监控系统基于 Modbus TCP 协议进行数据交互&…...

深入解析 iOS 视频录制(三):完整录制流程的实现与整合

深入解析 iOS 视频录制&#xff08;一&#xff09;&#xff1a;录制管理核心MWRecordingController 类的设计与实现 深入解析iOS视频录制&#xff08;二&#xff09;&#xff1a;自定义UI的实现​​​​​​​ 深入解析 iOS 视频录制&#xff08;三&#xff09;&#xff1a;完…...

Python基于Flask的豆瓣Top250电影数据可视化分析与评分预测系统(附源码,技术说明)

博主介绍&#xff1a;✌IT徐师兄、7年大厂程序员经历。全网粉丝15W、csdn博客专家、掘金/华为云//InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3…...

AStar低代码平台RpcServiceBase的应用:客户端事务管理

由于AStar平台是基于RPC协议与AStar后端服务进行通讯&#xff0c;而又非常接近常规BS的编码方式&#xff0c;直接写SQL即可对数据库进行操作&#xff0c;那么如果有若干个访问需要在同一事务中进行的&#xff0c;如何处理&#xff1f;比如先生成临时表&#xff0c;再对临时表进…...