系统网站怎么做最好的推广平台是什么软件
HTML 工作流编辑器
以下是一个简单的工作流编辑器的HTML实现,包含基本的拖拽节点、连接线和可视化编辑功能:
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>工作流编辑器</title><style>body {font-family: Arial, sans-serif;margin: 0;padding: 0;overflow: hidden;background-color: #f5f5f5;}#toolbar {background-color: #333;color: white;padding: 10px;display: flex;justify-content: space-between;}#node-palette {width: 200px;background-color: #eee;height: calc(100vh - 60px);float: left;padding: 10px;overflow-y: auto;}.node-type {background-color: white;border: 1px solid #ccc;padding: 8px;margin-bottom: 10px;cursor: move;border-radius: 4px;text-align: center;}#editor-container {width: calc(100% - 220px);height: calc(100vh - 60px);float: right;position: relative;overflow: hidden;}#editor-surface {width: 2000px;height: 2000px;background-color: white;background-image: linear-gradient(#eee 1px, transparent 1px),linear-gradient(90deg, #eee 1px, transparent 1px);background-size: 20px 20px;position: relative;}.workflow-node {position: absolute;width: 120px;height: 60px;background-color: #4CAF50;color: white;border-radius: 6px;display: flex;align-items: center;justify-content: center;cursor: move;user-select: none;box-shadow: 0 2px 5px rgba(0,0,0,0.2);}.workflow-node.input {background-color: #2196F3;}.workflow-node.output {background-color: #f44336;}.workflow-node.decision {background-color: #FFC107;color: #333;}.connector {width: 12px;height: 12px;background-color: #333;border-radius: 50%;position: absolute;cursor: pointer;}.input-connector {left: -6px;top: 50%;transform: translateY(-50%);}.output-connector {right: -6px;top: 50%;transform: translateY(-50%);}.connection {position: absolute;pointer-events: none;z-index: -1;}button {padding: 8px 12px;margin: 0 5px;background-color: #4CAF50;color: white;border: none;border-radius: 4px;cursor: pointer;}button:hover {background-color: #45a049;}</style>
</head>
<body><div id="toolbar"><div><button id="save-btn">保存</button><button id="load-btn">加载</button><button id="clear-btn">清空</button></div><div><span id="status">就绪</span></div></div><div id="node-palette"><h3>节点类型</h3><div class="node-type input" draggable="true" data-type="input">输入节点</div><div class="node-type" draggable="true" data-type="process">处理节点</div><div class="node-type decision" draggable="true" data-type="decision">决策节点</div><div class="node-type output" draggable="true" data-type="output">输出节点</div></div><div id="editor-container"><div id="editor-surface"></div></div><script>document.addEventListener('DOMContentLoaded', function() {const editorSurface = document.getElementById('editor-surface');const nodePalette = document.getElementById('node-palette');const statusDisplay = document.getElementById('status');let selectedConnector = null;let nodes = [];let connections = [];// 从调色板拖拽节点nodePalette.querySelectorAll('.node-type').forEach(nodeType => {nodeType.addEventListener('dragstart', function(e) {e.dataTransfer.setData('text/plain', this.getAttribute('data-type'));});});// 在编辑面上放置节点editorSurface.addEventListener('dragover', function(e) {e.preventDefault();});editorSurface.addEventListener('drop', function(e) {e.preventDefault();const type = e.dataTransfer.getData('text/plain');if (!type) return;const rect = editorSurface.getBoundingClientRect();const x = e.clientX - rect.left;const y = e.clientY - rect.top;createNode(type, x, y);updateStatus(`已创建 ${type} 节点`);});// 创建节点function createNode(type, x, y) {const node = document.createElement('div');node.className = `workflow-node ${type}`;node.style.left = `${x}px`;node.style.top = `${y}px`;let label = '';switch(type) {case 'input': label = '输入'; break;case 'output': label = '输出'; break;case 'decision': label = '决策'; break;default: label = '处理';}node.textContent = label;// 添加连接点if (type !== 'input') {const inputConnector = document.createElement('div');inputConnector.className = 'connector input-connector';node.appendChild(inputConnector);inputConnector.addEventListener('mousedown', startConnection);}if (type !== 'output') {const outputConnector = document.createElement('div');outputConnector.className = 'connector output-connector';node.appendChild(outputConnector);outputConnector.addEventListener('mousedown', startConnection);}// 使节点可拖动makeDraggable(node);editorSurface.appendChild(node);nodes.push({element: node,x, y,type,id: Date.now().toString()});return node;}// 使节点可拖动function makeDraggable(element) {let offsetX, offsetY, isDragging = false;element.addEventListener('mousedown', function(e) {if (e.target.classList.contains('connector')) return;isDragging = true;const rect = element.getBoundingClientRect();offsetX = e.clientX - rect.left;offsetY = e.clientY - rect.top;element.style.zIndex = '10';e.preventDefault();});document.addEventListener('mousemove', function(e) {if (!isDragging) return;const rect = editorSurface.getBoundingClientRect();let x = e.clientX - rect.left - offsetX;let y = e.clientY - rect.top - offsetY;// 限制在编辑面内x = Math.max(0, Math.min(x, editorSurface.offsetWidth - element.offsetWidth));y = Math.max(0, Math.min(y, editorSurface.offsetHeight - element.offsetHeight));element.style.left = `${x}px`;element.style.top = `${y}px`;// 更新节点位置数据const node = nodes.find(n => n.element === element);if (node) {node.x = x;node.y = y;}// 更新连接线updateConnections();});document.addEventListener('mouseup', function() {isDragging = false;element.style.zIndex = '';});}// 开始创建连接function startConnection(e) {e.stopPropagation();selectedConnector = e.target;document.addEventListener('mousemove', drawTempConnection);document.addEventListener('mouseup', endConnection);}// 绘制临时连接线function drawTempConnection(e) {// 在实际应用中,这里会绘制一条临时连接线}// 结束连接创建function endConnection(e) {document.removeEventListener('mousemove', drawTempConnection);document.removeEventListener('mouseup', endConnection);if (!selectedConnector) return;const targetElement = document.elementFromPoint(e.clientX, e.clientY);if (!targetElement || !targetElement.classList.contains('connector')) {selectedConnector = null;return;}const sourceConnector = selectedConnector;const targetConnector = targetElement;// 检查是否可以连接(输入只能连输出,反之亦然)if ((sourceConnector.classList.contains('input-connector') && targetConnector.classList.contains('input-connector')) ||(sourceConnector.classList.contains('output-connector') && targetConnector.classList.contains('output-connector'))) {updateStatus("无法连接: 输入只能连接输出,输出只能连接输入");selectedConnector = null;return;}// 确定源和目标(输出->输入)let fromConnector, toConnector;if (sourceConnector.classList.contains('output-connector')) {fromConnector = sourceConnector;toConnector = targetConnector;} else {fromConnector = targetConnector;toConnector = sourceConnector;}createConnection(fromConnector, toConnector);selectedConnector = null;}// 创建永久连接function createConnection(fromConnector, toConnector) {const connection = document.createElement('div');connection.className = 'connection';editorSurface.appendChild(connection);const fromNode = fromConnector.parentElement;const toNode = toConnector.parentElement;const connectionObj = {element: connection,from: fromNode,to: toNode,fromConnector,toConnector};connections.push(connectionObj);updateConnection(connectionObj);updateStatus("已创建连接");}// 更新连接线位置function updateConnection(connection) {const fromRect = connection.from.getBoundingClientRect();const toRect = connection.to.getBoundingClientRect();const editorRect = editorSurface.getBoundingClientRect();const fromX = fromRect.left - editorRect.left + (connection.fromConnector.classList.contains('output-connector') ? fromRect.width : 0);const fromY = fromRect.top - editorRect.top + fromRect.height / 2;const toX = toRect.left - editorRect.left + (connection.toConnector.classList.contains('input-connector') ? 0 : toRect.width);const toY = toRect.top - editorRect.top + toRect.height / 2;// 简单的贝塞尔曲线连接const path = `M ${fromX} ${fromY} C ${(fromX + toX) / 2} ${fromY}, ${(fromX + toX) / 2} ${toY}, ${toX} ${toY}`;connection.element.innerHTML = `<svg width="${editorSurface.offsetWidth}" height="${editorSurface.offsetHeight}"><path d="${path}" stroke="#333" stroke-width="2" fill="none" marker-end="url(#arrowhead)" /></svg>`;}// 更新所有连接线function updateConnections() {connections.forEach(updateConnection);}// 工具栏按钮功能document.getElementById('save-btn').addEventListener('click', function() {const workflow = {nodes: nodes.map(node => ({id: node.id,type: node.type,x: node.x,y: node.y})),connections: connections.map(conn => ({from: nodes.findIndex(n => n.element === conn.from),to: nodes.findIndex(n => n.element === conn.to)}))};localStorage.setItem('workflow', JSON.stringify(workflow));updateStatus("工作流已保存");});document.getElementById('load-btn').addEventListener('click', function() {const saved = localStorage.getItem('workflow');if (!saved) {updateStatus("没有找到保存的工作流");return;}// 清空当前工作流editorSurface.innerHTML = '';nodes = [];connections = [];const workflow = JSON.parse(saved);// 重新创建节点workflow.nodes.forEach(nodeData => {const node = createNode(nodeData.type, nodeData.x, nodeData.y);const nodeObj = nodes[nodes.length - 1];nodeObj.id = nodeData.id;});// 重新创建连接workflow.connections.forEach(connData => {const fromNode = nodes[connData.from].element;const toNode = nodes[connData.to].element;const fromConnector = fromNode.querySelector('.output-connector');const toConnector = toNode.querySelector('.input-connector');if (fromConnector && toConnector) {createConnection(fromConnector, toConnector);}});updateStatus("工作流已加载");});document.getElementById('clear-btn').addEventListener('click', function() {editorSurface.innerHTML = '';nodes = [];connections = [];updateStatus("工作流已清空");});// 状态更新function updateStatus(message) {statusDisplay.textContent = message;setTimeout(() => {if (statusDisplay.textContent === message) {statusDisplay.textContent = "就绪";}}, 3000);}// 添加箭头标记定义const svgDefs = document.createElementNS('http://www.w3.org/2000/svg', 'svg');svgDefs.style.position = 'absolute';svgDefs.style.width = '0';svgDefs.style.height = '0';svgDefs.style.overflow = 'hidden';const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');const marker = document.createElementNS('http://www.w3.org/2000/svg', 'marker');marker.setAttribute('id', 'arrowhead');marker.setAttribute('markerWidth', '10');marker.setAttribute('markerHeight', '7');marker.setAttribute('refX', '9');marker.setAttribute('refY', '3.5');marker.setAttribute('orient', 'auto');const arrow = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');arrow.setAttribute('points', '0 0, 10 3.5, 0 7');arrow.setAttribute('fill', '#333');marker.appendChild(arrow);defs.appendChild(marker);svgDefs.appendChild(defs);document.body.appendChild(svgDefs);});</script>
</body>
</html>
功能说明
这个工作流编辑器包含以下功能:
- 节点调色板:提供不同类型的节点(输入、处理、决策、输出)
- 拖拽创建节点:可以从调色板拖拽节点到编辑区域
- 节点连接:可以通过连接点创建节点之间的连线
- 节点拖动:可以拖动已创建的节点
- 基本工具栏:包含保存、加载和清空功能
- 状态显示:显示当前操作状态
扩展建议
要使这个编辑器更实用,你可以考虑添加:
- 节点属性编辑功能
- 更复杂的连接线样式(带箭头、标签等)
- 撤销/重做功能
- 工作流验证
- 导出为JSON或其他格式
- 缩放和平移功能
- 网格对齐和吸附功能
这个示例使用了纯HTML/CSS/JavaScript实现,对于更复杂的工作流编辑器,你可能需要考虑使用专门的库如jsPlumb、GoJS或React Flow等。