Vue 项目流程图实战:G6 从环境搭建到性能优化全指南
1. 为什么流程图这件事我最后选了 G6 而不是手写 SVG先说结论在 Vue 项目里做流程图能选现成图库就别自己画。我最早接手一个后台配置系统需求是让运营同学能拖拽节点、连线、保存成 JSON还要支持缩放和导出图片。当时第一反应是不就是画几个矩形加箭头吗于是用 SVG 手写了一套。结果两周后需求变成节点要能折叠、连线要能自动避让、要支持撤销重做我当场就想把键盘砸了。手写 SVG 的问题不在于画不出来而在于状态管理会失控。每个节点的坐标、每条线的路径、拖拽时的临时状态、缩放后的坐标换算这些东西一旦超过二十个节点维护成本是指数级上升的。后来我换成了 G6同样的需求大概三天就跑通了核心链路。G6 是阿里开源的一款图可视化引擎专门处理关系图、流程图、拓扑图这类场景。它把节点、边、锚点、交互行为都抽象成了可配置的模型你只需要描述有哪些节点、它们怎么连剩下的渲染、布局、交互它帮你兜底。在 Vue 里用它核心要解决的是生命周期对齐和数据响应这两件事这也是后面几节要重点聊的。这篇文章适合两类人一类是刚接触图可视化、想知道 G6 到底能干什么的 Vue 开发者另一类是已经用过 G6 但被数据更新不生效容器尺寸拿不到节点拖拽后位置对不上这些问题折磨过的同学。我会把踩过的坑和验证过的方案都摊开讲代码可以直接抄。2. 环境搭建版本选择与容器初始化的那些细节2.1 G6 版本差异带来的 API 断层G6 目前主流是 4.x 和 5.x 两个大版本这俩的 API 差异大到什么程度呢——4.x 里注册自定义节点用G6.registerNode5.x 改成了G6.register而且配置项结构也变了。我建议新项目直接上 5.x老项目如果已经跑在 4.x 上就别折腾迁移除非你有充足的重构时间。安装本身没什么难度npm install antv/g6 --save但这里有个坑G6 依赖的渲染层在不同版本下不一样4.x 默认用 Canvas5.x 支持 Canvas 和 SVG 两种渲染器。如果你的流程图节点数量超过 500 个建议用 Canvas如果节点少但样式复杂比如带渐变、阴影、富文本SVG 渲染出来的清晰度更好。切换方式是在初始化时传renderer参数。2.2 Vue 组件里容器的尺寸陷阱这是新手最容易翻车的地方。G6 初始化时必须知道容器的宽高但 Vue 的mounted钩子里如果容器是通过flex或者百分比布局撑开的clientWidth很可能拿到 0。我见过太多人在这里卡半天以为是 G6 的问题其实是 DOM 还没完成布局计算。我的做法是给容器一个明确的初始尺寸或者用ResizeObserver监听尺寸变化后再初始化// Vue 3 Composition API 写法 import { onMounted, ref, onBeforeUnmount } from vue import G6 from antv/g6 const containerRef ref(null) let graph null let resizeObserver null onMounted(() { const container containerRef.value // 关键先确保容器有尺寸 if (!container.clientWidth || !container.clientHeight) { console.warn(容器尺寸为 0请检查布局) return } graph new G6.Graph({ container: container, width: container.clientWidth, height: container.clientHeight, fitView: true, modes: { default: [drag-canvas, zoom-canvas, drag-node] } }) // 监听容器尺寸变化同步给 G6 resizeObserver new ResizeObserver(() { if (!graph || graph.get(destroyed)) return graph.changeSize(container.clientWidth, container.clientHeight) }) resizeObserver.observe(container) }) onBeforeUnmount(() { // 必须销毁否则内存泄漏 if (resizeObserver) resizeObserver.disconnect() if (graph !graph.get(destroyed)) graph.destroy() })注意graph.destroy()一定要调用而且要在组件卸载前调用。我遇到过页面切换十几次后浏览器卡死的情况排查下来就是 G6 实例没销毁Canvas 一直在后台跑。2.3 数据格式节点和边的标准结构G6 的数据模型很直白一个nodes数组加一个edges数组const data { nodes: [ { id: node1, x: 100, y: 200, label: 开始 }, { id: node2, x: 300, y: 200, label: 审批 } ], edges: [ { source: node1, target: node2, label: 提交 } ] }id是唯一标识source和target指向节点的id。这里有个经验id 不要用数组下标因为删除节点后下标会错位导致连线指向错误的节点。用业务主键或者 UUID 都行。3. 从零跑通第一个流程图节点、连线与布局3.1 内置节点类型够不够用G6 内置了circle、rect、ellipse、diamond、triangle等基础图形还有modelRect这种带标题和描述的复合节点。大部分流程图场景rect加个label就能满足。但如果你要做审批流那种标题状态负责人三行信息的节点内置的就不够看了得自定义。先看一个用内置节点跑通的最小示例graph.data({ nodes: [ { id: start, x: 150, y: 100, label: 发起申请, type: circle }, { id: check, x: 150, y: 250, label: 主管审批, type: rect }, { id: end, x: 150, y: 400, label: 完成, type: circle } ], edges: [ { source: start, target: check }, { source: check, target: end } ] }) graph.render()跑起来你会发现节点挤在一起连线是直的整体很丑。这时候需要配置布局和边的样式。3.2 布局算法怎么选Dagre 还是 Force流程图最常用的是dagre布局它专门处理有向无环图会自动分层、对齐、避免连线交叉。配置方式const graph new G6.Graph({ // ...其他配置 layout: { type: dagre, rankdir: TB, // 从上到下LR 是从左到右 nodesep: 40, // 同层节点间距 ranksep: 60, // 层间距 controlPoints: true // 让连线带控制点更平滑 } })rankdir这个参数很关键。审批流一般用TBtop to bottom数据流向图用LRleft to right。我试过在节点很多的时候用LR横向会拉得很长用户得一直拖画布体验不好。force布局是力导向的适合展示网状关系但流程图别用因为它每次渲染位置都不一样用户会懵。3.3 边的样式让连线不再像蜘蛛网默认的直线边在节点错位时很难看我一般会改成折线或者带箭头的曲线const defaultEdge { type: polyline, // 折线也可以用 quadratic 曲线 style: { stroke: #b0b0b0, lineWidth: 1.5, endArrow: { path: G6.Arrow.triangle(8, 8, 6), fill: #b0b0b0 } }, labelCfg: { autoRotate: true, style: { fontSize: 12, fill: #666 } } }polyline会自动根据节点位置计算折线路径比直线规整很多。endArrow是箭头配置G6.Arrow.triangle的三个参数分别是宽度、长度和偏移量调一调就能找到顺眼的效果。4. 自定义节点把业务信息塞进方框里4.1 什么时候必须自定义内置节点只能显示一个label但真实业务里节点往往要展示多个字段。比如一个任务节点要显示任务名、负责人、截止时间、状态标签。这时候就得自定义节点用G6.registerNode注册一个带多个文本和图形的节点类型。4.2 用 rect 组合出多行信息节点下面这个例子注册了一个task-node包含标题、负责人、状态三个区域G6.registerNode(task-node, { draw(cfg, group) { const width 180 const height 70 const keyShape group.addShape(rect, { attrs: { x: 0, y: 0, width, height, radius: 6, fill: #fff, stroke: cfg.status done ? #52c41a : #d9d9d9, lineWidth: 2 }, name: task-box }) // 标题 group.addShape(text, { attrs: { x: 12, y: 24, text: cfg.label || 未命名任务, fontSize: 14, fontWeight: 500, fill: #262626 }, name: title }) // 负责人 group.addShape(text, { attrs: { x: 12, y: 48, text: 负责人${cfg.owner || 待分配}, fontSize: 12, fill: #8c8c8c }, name: owner }) // 状态标签 group.addShape(rect, { attrs: { x: width - 60, y: 12, width: 48, height: 20, radius: 10, fill: cfg.status done ? #f6ffed : #fff7e6 }, name: status-bg }) group.addShape(text, { attrs: { x: width - 36, y: 26, text: cfg.status done ? 已完成 : 进行中, fontSize: 11, textAlign: center, fill: cfg.status done ? #52c41a : #fa8c16 }, name: status-text }) return keyShape }, // 定义锚点位置决定连线从哪进出 getAnchorPoints() { return [ [0.5, 0], // 上 [1, 0.5], // 右 [0.5, 1], // 下 [0, 0.5] // 左 ] } })这里有几个细节值得说。keyShape是必须返回的它决定了节点的点击热区和包围盒。getAnchorPoints定义了四个锚点连线会自动吸附到最近的锚点。如果不定义G6 会用默认的中心点连线会穿过节点很难看。4.3 状态驱动的样式更新节点状态变化时比如从进行中变成已完成不需要重新渲染整个图调用graph.updateItem就行graph.updateItem(node1, { status: done })G6 会重新执行draw方法但只更新这个节点。不过要注意如果你的draw里用了cfg.xxx判断样式更新后必须保证cfg里有这个字段否则会报错。5. 交互实战拖拽、连线、右键菜单5.1 拖拽节点的边界控制drag-node模式默认允许节点拖到画布外用户一不小心就把节点拖没了。我一般会加个边界限制modes: { default: [ drag-canvas, zoom-canvas, { type: drag-node, enableDelegate: false, // 拖拽结束后触发 afterDrag(e) { const node e.item const model node.getModel() const canvasWidth graph.get(width) const canvasHeight graph.get(height) // 限制在画布内 const x Math.max(0, Math.min(model.x, canvasWidth)) const y Math.max(0, Math.min(model.y, canvasHeight)) graph.updateItem(node, { x, y }) } } ] }afterDrag里拿到的model.x和model.y是节点中心坐标所以严格来说边界应该是[节点宽/2, 画布宽 - 节点宽/2]我这里简化了实际项目里要按节点尺寸算。5.2 手动连线从锚点拖出边G6 提供了create-edge交互但默认行为是点击两个节点就连线不够直观。更符合用户习惯的是从锚点拖拽出边。这需要监听mouseenter和mouseleave事件动态显示锚点graph.on(node:mouseenter, (e) { const node e.item const group node.getContainer() // 显示锚点小圆点 const anchors node.getAnchorPoints() anchors.forEach((anchor, index) { group.addShape(circle, { attrs: { x: anchor.x, y: anchor.y, r: 4, fill: #1890ff, stroke: #fff, lineWidth: 2, cursor: crosshair }, name: anchor-${index}, // 标记为锚点方便后续识别 isAnchor: true }) }) }) graph.on(node:mouseleave, (e) { const group e.item.getContainer() // 移除所有锚点 group.get(children).forEach(shape { if (shape.get(isAnchor)) shape.remove() }) })然后在锚点上监听mousedown进入连线模式mousemove时画一条临时线mouseup在目标节点上时创建边。这套逻辑代码量不小但用户体验最好。如果项目时间紧直接用create-edge也能凑合。5.3 右键菜单与节点操作流程图里删除节点、重命名、复制是高频操作右键菜单比工具栏按钮更顺手。G6 本身不提供菜单组件需要自己用 DOM 实现graph.on(node:contextmenu, (e) { e.preventDefault() const { item, canvasX, canvasY } e // 把画布坐标转成屏幕坐标 const point graph.getCanvasByPoint(canvasX, canvasY) showContextMenu({ x: point.x, y: point.y, items: [ { label: 重命名, action: () renameNode(item) }, { label: 复制, action: () copyNode(item) }, { label: 删除, action: () graph.removeItem(item) } ] }) })菜单本身用一个绝对定位的div渲染点击其他地方时隐藏。这里要注意contextmenu事件必须preventDefault否则浏览器默认菜单会弹出来。6. 数据同步Vue 响应式和 G6 的配合方式6.1 为什么不能直接把响应式数据丢给 G6很多人第一反应是graph.data(this.nodeList)然后期望nodeList变化时图自动更新。G6 不是响应式的它只在调用graph.data()和graph.render()时读取一次数据。后续数据变了图不会动。正确做法是监听数据变化手动调用 G6 的更新方法import { watch } from vue watch(nodeList, (newVal) { if (!graph) return // 方式一全量更新简单但性能差 graph.changeData({ nodes: newVal, edges: edgeList.value }) // 方式二增量更新性能好但逻辑复杂 // 对比新旧数据找出新增、删除、修改的节点 }, { deep: true })changeData会重新计算布局节点位置会重置。如果用户手动拖过节点位置就丢了。所以更稳妥的做法是增量更新function syncNodes(newNodes) { const currentNodes graph.getNodes().map(n n.getModel()) const currentIds new Set(currentNodes.map(n n.id)) const newIds new Set(newNodes.map(n n.id)) // 删除 currentIds.forEach(id { if (!newIds.has(id)) graph.removeItem(id) }) // 新增和更新 newNodes.forEach(node { if (currentIds.has(node.id)) { graph.updateItem(node.id, node) } else { graph.addItem(node, node) } }) }6.2 保存与回显JSON 序列化的坑保存流程图时graph.save()返回的数据包含节点的x、y、style等运行时信息直接存数据库会很臃肿。我一般只存业务字段function serialize() { const nodes graph.getNodes().map(node { const model node.getModel() return { id: model.id, label: model.label, owner: model.owner, status: model.status, x: model.x, y: model.y } }) const edges graph.getEdges().map(edge { const model edge.getModel() return { source: model.source, target: model.target, label: model.label } }) return { nodes, edges } }回显时把这些数据塞回graph.data()再render()就行。但要注意如果保存时用了dagre布局回显时节点位置是布局算出来的和保存的x、y可能不一致。解决办法是回显时关掉布局直接用保存的坐标graph.updateLayout({ type: preset })preset布局就是用数据里的坐标不做任何计算。7. 性能优化节点上百之后怎么不卡7.1 关闭不必要的交互和动画节点超过 200 个后drag-canvas和zoom-canvas会明显掉帧。这时候可以按需开启const graph new G6.Graph({ // ... modes: { default: nodeCount 200 ? [drag-canvas] // 只保留拖拽关掉缩放 : [drag-canvas, zoom-canvas, drag-node] }, animate: false, // 关掉动画 animateCfg: { duration: 0 } })animate: false能省不少性能代价是节点更新时没有过渡效果。数据量大时这个取舍是值得的。7.2 视口裁剪与虚拟渲染G6 4.x 之后支持viewport裁剪只渲染可视区域内的节点const graph new G6.Graph({ // ... minZoom: 0.2, maxZoom: 4, // 开启视口裁剪 enabledOptimize: true })不过这个特性在不同版本里名字不一样5.x 里改成了optimize配置。用之前建议查一下当前版本的文档别照搬网上的配置。7.3 用 Canvas 替代 SVG前面提过节点多的时候用 Canvas 渲染。切换方式const graph new G6.Graph({ renderer: canvas, // 默认就是 canvas显式写出来更清楚 // ... })Canvas 的缺点是导出图片时清晰度不如 SVG但 G6 提供了toDataURL方法可以指定倍率const dataURL graph.toDataURL({ pixelRatio: 2, // 2 倍图导出后更清晰 backgroundColor: #fff })8. 几个让我加班到深夜的坑8.1 节点拖拽后连线不跟随这个问题通常是因为拖拽时只更新了节点的x、y但边的路径没有重算。G6 默认会自动重算但如果你自定义了边的draw方法就得手动处理。我的建议是不要轻易重写边的 draw用style配置能解决 90% 的样式需求。8.2 容器隐藏后重新显示图变形Tab 切换或者弹窗关闭再打开时容器尺寸从 0 变回正常值但 G6 还记着旧尺寸。解决办法是在容器显示后调用graph.changeSize(container.clientWidth, container.clientHeight) graph.fitView()fitView会自动缩放让所有节点可见配合changeSize一起用效果最好。8.3 自定义节点里的文本换行G6 的text图形不支持自动换行长文本会溢出。需要自己计算换行位置function wrapText(text, maxWidth, fontSize) { const lines [] let current for (const char of text) { const test current char // 粗略估算中文按 fontSize 算英文按 0.6 倍 const width test.length * fontSize * 0.6 if (width maxWidth) { lines.push(current) current char } else { current test } } if (current) lines.push(current) return lines }然后逐行addShape(text)每行 y 坐标递增fontSize * 1.4。这个估算方法不精确但够用。要精确的话得用canvas.measureText性能开销大。8.4 事件冒泡导致误触节点上的点击事件会冒泡到画布如果你同时监听了node:click和canvas:click点节点时两个都会触发。解决办法是在节点事件里e.stopPropagation()或者用e.target判断事件源。9. 写在最后的一点个人习惯我现在做 G6 项目会先花半小时把数据模型定死——节点有哪些字段、边有哪些字段、状态怎么流转。这个模型定好了后面的自定义节点、交互、保存回显都是围绕它转不会乱。反过来如果一开始就急着画图写到一半发现字段不够用改起来就是牵一发动全身。另外G6 的官方示例库是个宝库但别直接复制粘贴。它的示例为了展示效果往往把配置写得很全实际项目里很多配置是冗余的。我的做法是先把示例跑起来然后一条条删配置删到图出问题为止这样能快速搞清楚每个配置到底管什么。还有一个习惯每次升级 G6 版本前先在测试环境跑一遍所有交互。G6 的小版本升级偶尔会有 breaking change尤其是自定义节点那块。我吃过一次亏升级后锚点位置全偏了排查了两小时才发现是getAnchorPoints的返回值格式变了。

相关新闻

Prim与Kruskal算法实战指南:从图论原理到高并发系统落地

Prim与Kruskal算法实战指南:从图论原理到高并发系统落地

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 5:24:31 阅读更多 →
老年人服药提醒App:Android闹钟调度与SQLite完整实现

老年人服药提醒App:Android闹钟调度与SQLite完整实现

简介:这套源码面向Java毕业设计选题,实现的是老年人服药提醒App,适用于需要完成完整移动端与服务端项目的计算机专业学生。项目基于Java 1.8与Android开发,涉及Activity、Service等四大组件,后端采用MySQL 5.7管理用户…

2026/10/13 5:24:31 阅读更多 →
Linux运维必知:反斜杠\cp绕过alias命令别名的原理与实践

Linux运维必知:反斜杠\cp绕过alias命令别名的原理与实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 5:24:31 阅读更多 →

最新新闻

学习 Python 基础语法与工程环境操作流程

学习 Python 基础语法与工程环境操作流程

前言一、基础概念虚拟环境:可以理解成每个项目专属的「Python 独立工具箱」。每个项目单独一套 Python 解释器和第三方安装包,项目之间互不干扰,不会出现 A 项目装的包覆盖 B 项目依赖的情况,删掉环境文件夹就能一键重置&#xff…

2026/10/13 6:59:55 阅读更多 →
陈,读懂生理药理实验多用仪

陈,读懂生理药理实验多用仪

生理药理刺激多用仪属于多功能综合性实验设备,依托数字集成电路对振荡频率分频处理,输出多种标准频率与时间信号。设备可输出前沿陡直、平顶稳定的标准矩形刺激波,波宽与输出电压连续可调,支持单次、双次、定时、连续等多种刺激输…

2026/10/13 6:59:55 阅读更多 →
MCP基本使用

MCP基本使用

MCP版本用日期定义,最早的正式版本2024-11-05,截止当前最新的是2026-07-28(这一版协议有根本性改动,Python SDK需要使用 v2)。下面的概念是针对 2024-11-05 版的,2026-07-28 变成无状态协议了,下…

2026/10/13 6:59:55 阅读更多 →
企业微信 AI 客服答不准怎么办?兜底话术与置信度阈值

企业微信 AI 客服答不准怎么办?兜底话术与置信度阈值

客户问的是一款产品的偏门参数,AI 一本正经地答错了。这种错比不回复更伤:客户按错信息做了决定,回头算在品牌头上。答不准通常不是模型变笨,而是两件事没配好——该兜底的时候它硬答,该收进知识库的问题它够不着。## …

2026/10/13 6:59:55 阅读更多 →
恶意代码概述

恶意代码概述

1. 计算机病毒(Virus)核心特点:必须依附文件,需要人为触发,不能自己网上传播 原理:寄生在正常文件里(exe / 文档)。必须用户手动打开这个带毒文件,病毒才会激活&#xff…

2026/10/13 6:59:55 阅读更多 →
恶意代码检测毕设实战:PE特征提取与机器学习模型训练

恶意代码检测毕设实战:PE特征提取与机器学习模型训练

简介:这份资源是一套基于机器学习的恶意代码检测项目源码,面向计算机、人工智能、通信工程、自动化等专业的在校学生与教师,也适合作为毕设、课程设计或项目立项演示的参考。项目围绕恶意代码的特征提取、向量化与模型训练展开,涵…

2026/10/13 6:58:55 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →