您的位置:首页 > 科技 > IT业 > 棋牌游戏平台_在线制作logo图标软件_网站排名优化教程_网站查询seo

棋牌游戏平台_在线制作logo图标软件_网站排名优化教程_网站查询seo

2025/5/9 16:46:35 来源:https://blog.csdn.net/qq_58055766/article/details/145745559  浏览:    关键词:棋牌游戏平台_在线制作logo图标软件_网站排名优化教程_网站查询seo
棋牌游戏平台_在线制作logo图标软件_网站排名优化教程_网站查询seo

HomeTop.tsx

import React, { useState, useEffect, useRef } from 'react'
import useStore from '../../../store/state'
import { Graph, Path } from '@antv/x6'
import { History } from '@antv/x6-plugin-history'
import AlgoNode from '../../AntVX6/AlgoNode'
import { register } from '@antv/x6-react-shape'
import { Selection } from '@antv/x6-plugin-selection'
import { Snapline } from '@antv/x6-plugin-snapline'
import styles from './HomeTop.module.scss'
import { Space, Button } from 'antd'
import { IoExpandOutline } from 'react-icons/io5'
import { CiSaveDown1 } from 'react-icons/ci'
import { LuFileTerminal } from 'react-icons/lu'
import { SiStreamrunners } from 'react-icons/si'
import { TbArrowBackUp, TbArrowForwardUp } from 'react-icons/tb'
import { FaSearchPlus, FaSearchMinus } from 'react-icons/fa'
import { AiOutlineFullscreenExit } from 'react-icons/ai'
register({shape: 'dag-node',width: 180,height: 36,component: AlgoNode,ports: {groups: {left: {position: 'left',attrs: {circle: {r: 4,magnet: true,stroke: '#C2C8D5',strokeWidth: 1,fill: '#fff',},},},right: {position: 'right',attrs: {circle: {r: 4,magnet: true,stroke: '#C2C8D5',strokeWidth: 1,fill: '#fff',},},},},},
})
// 注册自定义边样式
Graph.registerEdge('dag-edge',{inherit: 'edge',attrs: {line: {stroke: '#3498DB',strokeWidth: 3,targetMarker: {name: 'block', // 箭头类型,可以是 block、classic、circle 等width: 25, // 箭头宽度height: 15, // 箭头高度fill: '#3498DB', // 箭头颜色},},},},true
)
//注册连接器的样式
Graph.registerConnector('algo-connector',(sourcePoint, targetPoint) => {const hgap = Math.abs(targetPoint.x - sourcePoint.x)const path = new Path()path.appendSegment(Path.createSegment('M', sourcePoint.x - 4, sourcePoint.y))path.appendSegment(Path.createSegment('L', sourcePoint.x + 12, sourcePoint.y))// 水平三阶贝塞尔曲线path.appendSegment(Path.createSegment('C',sourcePoint.x < targetPoint.x ? sourcePoint.x + hgap / 2 : sourcePoint.x - hgap / 2,sourcePoint.y,sourcePoint.x < targetPoint.x ? targetPoint.x - hgap / 2 : targetPoint.x + hgap / 2,targetPoint.y,targetPoint.x - 6,targetPoint.y))path.appendSegment(Path.createSegment('L', targetPoint.x + 2, targetPoint.y))return path.serialize()},true
)
const HomeTop: React.FC = () => {const graph = useRef<Graph | null>(null) // 使用 useRef 保存 graph 引用useEffect(() => {//为什么要放置在内部因为=> <div id = 'antVX6Container'可能还未挂载,就调用的方法,是用useEffect可以保证挂载后再调用graph.current = new Graph({container: document.getElementById('antVX6Container')!,autoResize: true,panning: true,mousewheel: true,background: {color: '#d9e4f5',},grid: {visible: true,type: 'doubleMesh',args: [{color: '#eee', // 主网格线颜色thickness: 1, // 主网格线宽度},{color: '#ddd', // 次网格线颜色thickness: 1, // 次网格线宽度factor: 4, // 主次网格线间隔},],},//连线交互connecting: {connector: 'algo-connector',snap: {radius: 50, //自动吸附,并设置自动吸附路径},allowBlank: false, // 是否允许连接到画布空白位置的点(就是能不能拉线连空白的地方)allowLoop: false, // 是否允许创建循环连线,即边的起始节点和终止节点为同一节点,就是能不能自我连线(箭头不能穿过仪器)allowNode: false, //是否允许边连接到节点(非节点上的连接桩),默认为 true 。(就是要让它必须连接到连接桩,连接到节点不行)allowEdge: false, //是否可以同一个起点终点,在箭头的线中间加一个箭头,就是一条线能一直加箭头allowMulti: true, // 是否可以一个起点连多个终点highlight: true, // 拖动边时,是否高亮显示所有可用的连接桩或节点,默认值为 false 。一般都会与 highlighting 联合使用。createEdge() {return graph.current!.createEdge({shape: 'dag-edge',attrs: {line: {strokeDasharray: '5 5',},},zIndex: -1,})},},//高亮器highlighting: {// 当连接桩可以被链接时,在连接桩外围渲染一个 2px 宽的红色矩形框magnetAvailable: {name: 'stroke',args: {padding: 4,attrs: {'stroke-width': 2,stroke: 'red',},},},},})//开启框选功能graph.current.use(new Selection({enabled: true, // 启用选择功能。当为 true 时,可以在画布上拖动来选择节点或边rubberband: true, // 启用橡皮筋选择框。当按下鼠标并拖动时,会显示一个矩形框来选择多个节点movable: true, // 启用拖动选中的节点。当选中节点后,可以拖动节点进行移动showNodeSelectionBox: false, // 显示节点的选中框。当节点被选中时,会出现一个框框显示节点被选中状态pointerEvents: 'none', // 禁用节点或边的指针事件,通常用于在某些情况下阻止鼠标事件,比如不希望选中框遮挡其他元素modifiers: 'alt', // 设置按住 `alt` 键时启用选择操作。默认是按住 `shift` 键进行多选,这里将其更改为按 `alt` 键}))//画布开启对齐线功能graph.current.use(new Snapline({enabled: true,}))//开启历史功能graph.current.use(new History({enabled: true,}))// 方法:改变连接桩的可见性const changePortsVisible = (visible: boolean) => {const container = document.getElementById('antVX6Container')if (!container) return // 确保容器存在const ports = container.querySelectorAll('.x6-port-body')const texts = container.querySelectorAll('.x6-port-label')for (let i = 0; i < texts.length; i++) {;(texts[i] as HTMLElement).style.visibility = visible ? 'visible' : 'hidden'}for (let i = 0, len = ports.length; i < len; i++) {;(ports[i] as HTMLElement).style.visibility = visible ? 'visible' : 'hidden'}}// 监听节点的鼠标进入事件,显示连接桩graph.current.on('node:mouseenter', ({ node }) => {changePortsVisible(true)node.addTools({name: 'button-remove',args: {x: '100%',y: 0,offset: { x: -10, y: 10 },},})})// 监听节点的鼠标离开事件,隐藏连接桩graph.current.on('node:mouseleave', ({ node }) => {changePortsVisible(false)node.removeTools()})// 监听节点数据变化事件graph.current.on('node:change:data', ({ node }) => {const edges = graph.current!.getIncomingEdges(node) // 获取入边const { status } = node.getData() as { status: string } // 获取节点状态edges?.forEach(edge => {if (status === 'running') {edge.attr('line/strokeDasharray', 5) // 设置虚线edge.attr('line/style/animation', 'running-line 30s infinite linear') // 添加动画} else {edge.attr('line/strokeDasharray', '') // 清除虚线edge.attr('line/style/animation', '') // 移除动画}})})}, [])const { algorihtm } = useStore()const [dragOver, setDragOver] = useState(false) // 判断是否正在拖拽// 拖拽区域的样式const style = {border: dragOver ? '2px dashed #000' : '2px solid transparent',}// 处理拖拽开始const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {e.preventDefault() // 必须阻止默认行为才能触发 drop 事件setDragOver(true)}// 处理拖拽结束const handleDragLeave = () => {setDragOver(false)}// 处理放置操作const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {e.preventDefault()setDragOver(false)console.log('HomeTop.tsx-handleDrop:算子信息' + algorihtm)// 获取鼠标在画布中的坐标const { x, y } = graph.current!.pageToLocal(e.pageX, e.pageY)// 设置节点的宽高const nodeWidth = 180const nodeHeight = 36// 调整坐标,使节点的中心在鼠标放置的位置const adjustedX = x - nodeWidth / 2const adjustedY = y - nodeHeight / 2graph.current!.addNode({id: String(algorihtm.key),shape: 'dag-node',data: { label: algorihtm.title, status: 'default' },x: adjustedX,y: adjustedY,ports: {items: [{id: 'port_1',group: 'left',},{id: 'port_2',group: 'right',},],},})}function runCode() {// 获取所有节点和边const nodes = graph.current!.getNodes() // 获取画布中所有的节点const edges = graph.current!.getEdges() // 获取画布中所有的连线// 构建图的邻接表、反向邻接表和入出度表const adjList: Record<string, string[]> = {} // 邻接表,用于存储每个节点指向的节点const reverseAdjList: Record<string, string[]> = {} // 反向邻接表,用于存储指向该节点的节点const indegree: Record<string, number> = {} // 入度表,记录每个节点的被连接次数const outdegree: Record<string, number> = {} // 出度表,记录每个节点指向其他节点的次数// 初始化邻接表和度表nodes.forEach(node => {const nodeId = node.id // 节点的唯一标识符adjList[nodeId] = [] // 初始化为空数组,表示暂时没有指向任何节点reverseAdjList[nodeId] = [] // 初始化为空数组,表示暂时没有被其他节点指向indegree[nodeId] = 0 // 初始入度为 0outdegree[nodeId] = 0 // 初始出度为 0})// 填充邻接表和度表edges.forEach(edge => {const source = (edge.getSource() as { cell: string }).cell // 获取边的起点节点 IDconst target = (edge.getTarget() as { cell: string }).cell // 获取边的终点节点 IDif (adjList[source] && adjList[target]) {// 确保 source 和 target 都在节点列表中adjList[source].push(target) // 起点的邻接表增加终点reverseAdjList[target].push(source) // 终点的反向邻接表增加起点indegree[target]++ // 终点的入度加 1outdegree[source]++ // 起点的出度加 1}})// 拓扑排序逻辑const queue: string[] = [] // 队列,用于存储入度为 0 的节点for (const nodeId in indegree) {if (indegree[nodeId] === 0) {// 找出所有入度为 0 的节点queue.push(nodeId) // 加入队列}}const topoOrder: string[] = [] // 用于存储拓扑排序的结果while (queue.length > 0) {const nodeId = queue.shift()! // 从队列中取出一个节点topoOrder.push(nodeId) // 将节点加入拓扑排序结果adjList[nodeId].forEach(neighbor => {// 遍历该节点的所有邻居节点indegree[neighbor]-- // 邻居节点的入度减 1if (indegree[neighbor] === 0) {// 如果邻居节点的入度变为 0queue.push(neighbor) // 加入队列}})}// 检查是否有环if (topoOrder.length !== nodes.length) {// 如果拓扑排序结果的节点数与总节点数不一致,说明有环\console.log('错误连接,出现环,请查看连接情况并修正!')return // 中断函数}// 检查未连接节点const allNodes = new Set(nodes.map(node => node.id)) // 获取所有节点的 ID 集合const reachableFromStart = new Set<string>() // 用于存储从起点可达的节点const reachableFromEnd = new Set<string>() // 用于存储从终点反向可达的节点// 深度优先搜索(DFS)函数const dfs = (start: string, visited: Set<string>, graph: Record<string, string[]>) => {if (visited.has(start)) return // 如果节点已经访问过,直接返回visited.add(start) // 标记当前节点为已访问graph[start].forEach(neighbor => dfs(neighbor, visited, graph)) // 遍历当前节点的所有邻居}// 从所有起点出发,检查哪些节点可达topoOrder.forEach(node => {dfs(node, reachableFromStart, adjList) // 正向 DFS 检查从起点可达的节点dfs(node, reachableFromEnd, reverseAdjList) // 反向 DFS 检查从终点反向可达的节点})// 找到未连接的节点const unconnectedNodes = Array.from(allNodes).filter(node => !reachableFromStart.has(node) && !reachableFromEnd.has(node))if (unconnectedNodes.length > 0) {// 如果有未连接的节点console.log('运行错误,存在未连接的算子: ${unconnectedNodes.join(', ')}`')return // 中断函数}// 导出拓扑排序结果console.log('Topological Order:', topoOrder)// 导出连接关系并按拓扑顺序输出const orderedConnections: Record<string, string[]> = {}topoOrder.forEach(nodeId => {const outgoingNodes = adjList[nodeId]if (outgoingNodes.length > 0) {orderedConnections[nodeId] = outgoingNodes}})// 导出节点数据const nodesData: Record<string,{ label: string; status: string; position: { x: number; y: number } }> = {}nodes.forEach(node => {const data = node.getData() // 获取节点的数据nodesData[node.id] = {label: data.label, // 节点的标签status: data.status, // 节点的状态position: node.getPosition(), // 节点的位置}})// 输出最终结果const result = {topoOrder, // 拓扑排序结果orderedConnections, // 按拓扑顺序排列的连接关系nodesData, // 节点数据}console.log('Result:', JSON.stringify(result, null, 2)) // 打印结果// 动态运行拓扑simulateExecution(topoOrder)}// 延时函数const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))async function simulateExecution(topoOrder: string[]) {for (let i = 0; i < topoOrder.length; i++) {const nodeId = topoOrder[i]const node = graph.current!.getCellById(nodeId)// 将当前节点设置为运行中状态node.setData({...node.getData(),status: 'running',})await delay(2000) // 模拟运行时间// 根据模拟逻辑设置状态const isSuccess = Math.random() > 0.1 // 80% 成功概率node.setData({...node.getData(),status: isSuccess ? 'success' : 'failed',})// 如果失败,终止后续执行if (!isSuccess) {console.log(`运行失败,节点 ${nodeId} 执行错误!`)break}}console.log(`运行完成!`)}return (<div style={{ height: '100%', width: '100%' }}><div className={styles['topBar']}><Space style={{ marginLeft: '20px' }}><divclassName={styles['controlItem']}onClick={() => graph.current!.zoomToFit({ maxScale: 2 })}><i><IoExpandOutline /></i><span>自适应放大</span></div><divclassName={styles['controlItem']}onClick={() => {graph.current!.toJSON()console.log(graph.current!.toJSON())}}><i><LuFileTerminal /></i><span>保存</span></div><divclassName={styles['controlItem']}onClick={() => {graph.current!.toJSON()console.log(graph.current!.toJSON({}))}}><i><CiSaveDown1 /></i><span>导出分析流</span></div></Space></div><div className={styles['topButton']}><Space size="large"><Buttonicon={<SiStreamrunners />}className={styles['topButtonRun']}onClick={runCode}></Button><Space><Buttonicon={<TbArrowBackUp />}className={styles['topButtonCancel']}onClick={() => {graph.current!.undo()graph.current!.undo()}}></Button><Buttonicon={<TbArrowForwardUp />}className={styles['topButtonCancel']}onClick={() => graph.current!.redo()}></Button></Space></Space></div><div className={styles['sideButton']}><Space direction="vertical" className={styles['sideButtonContent']}><FaSearchPlusclassName={styles['sideIcon']}onClick={() => graph.current!.zoom(0.2)}></FaSearchPlus><FaSearchMinusclassName={styles['sideIcon']}onClick={() => graph.current!.zoom(-0.2)}></FaSearchMinus><AiOutlineFullscreenExitclassName={styles['sideIcon']}onClick={() => graph.current!.zoomToFit({ maxScale: 2 })}></AiOutlineFullscreenExit></Space></div><divid="antVX6Container"style={style}onDragOver={handleDragOver}onDragLeave={handleDragLeave}onDrop={handleDrop} // 放置事件></div></div>)
}export default HomeTop

HomeTop.module.scss 

.topBar{display: flex;align-items: center;position: absolute;z-index: 1;background-color: white;  // 改为淡灰色height: 40px;border: 1px solid #ccc;    // 添加边框width: calc(100% - 280px);  // 宽度减小
}.controlItem {display: flex;align-items: center;margin-right: 20px;padding: 8px 12px; // 添加内边距cursor: pointer;background-color: #fff; // 背景颜色transition: background-color 0.3s, box-shadow 0.3s; // 添加过渡效果border-radius: 8px; // 圆角
}
.controlItem:hover {background-color: #f0f0f0;box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); // 悬停时增加阴影
}.controlItem span {font-size: 16px;color: #333;font-weight: bold;
}
.controlItem i {display: flex;align-items: center;font-size: 21px;color: #333;
}.topButton{position: absolute;z-index: 1;top: 110px;
}
.topButtonRun{border-radius: 16px;left: 20px;width: 60px !important;height: 60px;background-color: #0fdfb5 /* 设置背景为绿色 */;color: white /* 设置图标颜色为白色 */;font-size: 25px;
}.topButtonCancel{border-radius: 16px;left: 20px;width: 60px !important;height: 60px;color: black /* 设置图标颜色为白色 */;font-size: 25px;  
}
.sideButton {position: absolute;z-index: 1;background-color: white;width: 50px;top: 180px;left: 305px;display: flex;justify-content: center;align-items: center;border-radius: 12px;cursor: pointer;  /* 让鼠标变为点击手势 */box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);  /* 初始阴影效果 */transition: all 0.3s ease;  /* 平滑过渡效果 */
}/* 悬浮时的效果 */
.sideButton:hover {transform: scale(1.1);  /* 增大按钮尺寸 */box-shadow: 0px 6px 12px rgba(0, 0, 0, 0.2);  /* 增强阴影效果 */
}/* 按下按钮时的效果 */
.sideButton:active {transform: scale(1);  /* 返回原本大小 */box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.1);  /* 按下时的阴影效果 */
}/* 按钮图标样式 */
.sideIcon {margin-top: 15px;font-size: 20px;color: #333;font-weight: bold;transition: color 0.2s ease;  /* 图标颜色的过渡效果 */
}/* 悬浮时图标颜色变化 */
.sideIcon:hover {color: #007bff;  /* 改变颜色为蓝色 */
}

 

AlgoNode.tsx

import './AlgoNode.css'
import { Graph, Node } from '@antv/x6'
import logo from '../../assets/antVX6NodeIcon/logo.png'
import running from '../../assets/antVX6NodeIcon/running.png'
import success from '../../assets/antVX6NodeIcon/success.png'
import failed from '../../assets/antVX6NodeIcon/failed.png'interface NodeStatus {id: stringlabel?: stringstatus: 'default' | 'success' | 'failed' | 'running'
}
interface propsType {node: Nodegraph?: Graph
}
const image = {logo: logo,success: success,failed: failed,running: running,
}const AlgoNode = (props: propsType) => {const { node } = propsconst data = node?.getData() as NodeStatusconst { label, status = 'default' } = datareturn (<div className={`node ${status}`}><img src={image.logo} alt="logo" /><span className="label">{label}</span><span className="status">{status === 'success' && <img src={image.success} alt="success" />}{status === 'failed' && <img src={image.failed} alt="failed" />}{status === 'running' && <img src={image.running} alt="running" />}</span></div>)
}export default AlgoNode

AlgoNode.css

.node {display: flex;align-items: center;width: 100%;height: 100%;background-color: #fff;border: 1px solid #c2c8d5;border-left: 4px solid #5F95FF;border-radius: 4px;box-shadow: 0 2px 5px 1px rgba(0, 0, 0, 0.06);
}
.node img {width: 20px;height: 20px;flex-shrink: 0;margin-left: 8px;
}
.node .label {display: inline-block;flex-shrink: 0;width: 104px;margin-left: 8px;color: #666;font-size: 12px;
}
.node .status {flex-shrink: 0;
}
.node.success {border-left: 4px solid #52c41a;
}
.node.failed {border-left: 4px solid #ff4d4f;
}
.node.running .status img {animation: spin 1s linear infinite;
}
.x6-node-selected .node {border-color: #1890ff;border-radius: 2px;box-shadow: 0 0 0 4px #d4e8fe;
}
.x6-node-selected .node.success {border-color: #52c41a;border-radius: 2px;box-shadow: 0 0 0 4px #ccecc0;
}
.x6-node-selected .node.failed {border-color: #ff4d4f;border-radius: 2px;box-shadow: 0 0 0 4px #fedcdc;
}
.x6-edge:hover path:nth-child(2){stroke: #1890ff;stroke-width: 1px;
}.x6-edge-selected path:nth-child(2){stroke: #1890ff;stroke-width: 1.5px !important;
}@keyframes running-line {to {stroke-dashoffset: -1000;}
}
@keyframes spin {from {transform: rotate(0deg);}to {transform: rotate(360deg);}
}

 

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com