树形表格展开/收起节点属性设计:.NET Core前后端联动实践
在日常后台管理系统里树形表格TreeGrid的出镜率极高——组织架构、菜单权限、分类目录、商品类目几乎都离不开它。我最近把内部组件库 watercloud 里的树形表格节点属性重新梳理了一遍尤其是“展开/收起节点”这一块踩了不少坑也沉淀了一些自认为还算靠谱的做法。今天就把这套基于 .NET Core 的树形表格展开/收起节点属性设计思路、接口约定、前端联动和排查经验一次性讲清楚。很多人拿到树形表格先关注“树怎么画”但真正决定用户体验的是节点怎么开、怎么收以及开了之后子节点数据从哪来。watercloud 的定位很简单它不是一个完整的商业控件而是一套前后端约定我们用它统一了多个 .NET Core 项目中的树形表格交互。这篇文章适合谁看后端同学想设计树形接口、前端同学想理解节点属性如何驱动 UI、以及全栈同学想自己造一套不依赖重型组件库的树形表格都可以参考。1. 树形表格的核心场景与需求拆解1.1 为什么节点展开/收起是树形表格的灵魂普通表格是“平铺数据集”行与行之间没有层级关系渲染时一维循环就行。树形表格就不一样了每个节点除了自身数据还带着父亲、孩子、兄弟的关系。展开和收起本质上是“是否显示子节点”的状态切换但这个切换背后还藏着三个问题。第一不只是视觉问题。如果数据是一次性加载到前端的展开/收起只是 display 的切换简单。但如果数据是懒加载的展开意味着要发起新的网络请求收起可能还要决定是否释放数据。第二不只是当前节点问题。展开一个父节点后它的子节点里可能还有子节点子节点是否展开、是否已加载、是否处于 loading 状态这些信息需要维护在每个节点对象上。第三状态管理分散。用户点击一个箭头前端要改当前节点的 expanded展开后要加载 children加载完要更新 loaded如果用户快速点击多个节点还要处理并发竞态。如果不把节点属性设计清楚代码很快就会堆满判断分支改一处坏一处。所以树形表格的灵魂不在表格的边框和斑马纹而在这些节点状态属性。做 watercloud 前我们也用过现成的第三方树形表格但遇到大目录、权限树、自定义操作按钮时总要绕很多弯去扩展。后来干脆自己定义一套节点属性把展开/收起逻辑收敛到几个核心字段里前后端都遵守同一套约定反而稳定很多。1.2 三种展开模式与实际业务选择不同业务对“展开/收起”的预期完全不一样。我梳理下来最常见的就三种模式全部展开适合层级少、节点数量少的场景比如团队组织结构图希望一眼看全。实现最简单后端一次性返回整棵树前端直接渲染。默认展开到第 N 级适合层级多但总量可控的场景比如地区选择器先展示到省省下的市、县再展开。需要后端在返回树时计算深度并在节点上加层级标记。按需懒加载适合数据量大、层级深的场景比如文件目录、商品分类库。开始只返回根节点用户点开哪个节点才去请求它的直接子节点。这个是工程中最常见也最麻烦的模式本文后半部分大部分内容都是在讲它。这三种模式其实可以共存默认返回前 N 级N 级以上的节点走懒加载。watercloud 的节点属性里专门设计了expanded和loaded两个布尔值来做区分。“已展开”和“已加载”是两件不同的事这一点很多初学者会混淆。注意expanded表示当前节点在 UI 上处于展开状态loaded表示当前节点的子节点数据已经存在于前端。展开但未加载时前端必须发起请求收起但已加载时再次展开就不需要请求了。这样拆开之后前端渲染逻辑就变成一张清晰的状态表叶子节点isLeaftrue不显示展开箭头非叶子节点未展开显示“”展开但未加载时显示“加载中”并触发请求展开且已加载则直接显示子节点。2. 树形数据的后端设计让节点天生具备展开能力2.1 表结构设计父子关系的两种方案树形数据落到数据库最简单也最通用的是邻接表一张表里用Id和ParentId自关联。比如一个菜单表CREATE TABLE TreeMenu ( Id BIGINT PRIMARY KEY, ParentId BIGINT NULL, Name NVARCHAR(128) NOT NULL, SortOrder INT NOT NULL DEFAULT 0, NodeType INT NOT NULL DEFAULT 0, IsDisabled BIT NOT NULL DEFAULT 0 ); CREATE INDEX IX_TreeMenu_ParentId ON TreeMenu(ParentId);根节点的ParentId为 NULL。这种结构的优点是非常直观插入、删除节点都很方便缺点是查询某个节点的所有后代需要递归。如果业务中频繁需要“查询整棵子树”比如组织架构里要按部门汇总人数可以考虑再加一张“祖先-后代”关系表闭包表但日常菜单、分类场景邻接表完全够用。watercloud 在多个项目里统一采用邻接表加ParentId索引理由很简单维护成本低而且树形表格展示恰好只关心“当前节点下的直接子节点”不需要递归返回所有后代。只有在懒加载模式下每次请求只查ParentId 当前节点Id的行邻接表优势一下就体现出来了。2.2 节点 JSON 属性约定前后端要协作接口返回的数据结构必须稳定。watercloud 返回的树节点 JSON 统一包含以下字段{ id: 1, name: 一级分类, level: 0, isLeaf: false, expanded: false, loaded: false, loading: false, disabled: false, children: null }解释一下每个属性的用途id节点唯一标识懒加载请求时用它作为父节点 ID。name展示名称实际项目中会扩展成title、icon、badgeText等其他展示属性。level从 0 开始的深度前端用它计算缩进。isLeaf是否叶子节点。叶子节点不显示展开箭头。后端要根据“数据库里当前节点是否有子节点”来算而不是根据children是否为空。因为在懒加载模式下叶子节点可能根本没查过子节点。expanded是否展开。服务端可以通过它表达默认展开策略。loaded子节点数据是否已返回。全量返回时设为 true懒加载时根节点设为 false。loading是否正在加载子节点这个字段通常由前端临时维护但为了方便调试也可以由响应返回。disabled是否禁止操作比如某些节点不允许展开或不允许勾选。children子节点数组。懒加载且未加载时它可以是null前端在这个字段为 null 且isLeaffalse时显示“展开箭头”。这里容易踩坑的是isLeaf。很多初学实现喜欢用Array.isArray(children) children.length 0来判断叶子节点这个逻辑在“后端一次性返回全树”时没问题但在懒加载场景会错得很离谱一个父节点从来没有请求过子节点它的 children 是 null按这个判断它会被当成叶子展开箭头直接消失。所以 watercloud 强制要求后端必须显式返回isLeaf且不能由前端猜测。2.3 递归构造树的实践如果接口要一次性返回整棵树比如“默认展开前两级”的场景就需要后端递归构造树形结构。C# 里我常用两种写法递归方法和迭代栈。递归方法最直观public TreeNode BuildTree(ListTreeNode allNodes, long? parentId) { var children allNodes .Where(n n.ParentId parentId) .OrderBy(n n.SortOrder) .Select(n BuildTree(allNodes, n.Id)) .ToList(); var node new TreeNode(); node.Id parentId ?? 0; node.Children children; // 根据节点实际数据填充字段这里略去 return node; }但递归有个隐患如果表数据出现环比如某行ParentId指向了自己的后代会栈溢出。尤其是在调试环境里测试脏数据时这个问题极具迷惑性。为了避免它我会在递归函数里加一个最大深度限制private const int MaxDepth 100; public void BuildTreeRecursive(long parentId, int currentDepth) { if (currentDepth MaxDepth) { throw new InvalidOperationException(树形数据疑似存在循环或过深请检查 ParentId); } // 查子节点继续递归 }对于层级深的场景用“哈希集合 迭代栈”构建树可以完全避免递归深度问题。做法是先把所有节点放进字典然后遍历一遍把每个节点挂到对应父节点的 Children 集合里。核心代码如下public ListTreeNode BuildTreeByDictionary(ListTreeNode flatNodes) { var dict flatNodes.ToDictionary(n n.Id); var roots new ListTreeNode(); foreach (var node in flatNodes) { if (node.ParentId null) { roots.Add(node); } else { if (dict.TryGetValue(node.ParentId.Value, out var parent)) { parent.Children ?? new ListTreeNode(); parent.Children.Add(node); } } } return roots; }这种方法只有一次循环性能比递归好而且不存在爆栈风险。不过要注意字典法要求传入的节点列表里有完整的父子关系如果漏传了父节点子节点会挂不上而丢失。所以在调用前最好先校验所有ParentId是否都在集合中。3. 展开与收起节点属性的精细控制3.1 节点核心状态属性expanded / loaded / loading / isLeaf先整理一张属性速查表这是 watercloud 前后端约定的基础建议贴到项目文档里属性类型默认值含义谁来维护expandedbooleanfalse节点在 UI 上是否展开前端为主服务端可设置默认值loadedbooleanfalse子节点数据是否已加载到前端前端为主全量接口可置 trueloadingbooleanfalse子节点是否正在异步加载前端临时维护isLeafbooleanfalse是否为叶子节点后端必填disabledbooleanfalse是否禁用展开/操作后端返回childrenarray/nullnull子节点集合或 null后端返回 / 前端加载后填充这四个状态属性最大的价值是让前端渲染逻辑变成纯粹的状态机。比如点击一个父节点的箭头对应逻辑可以归纳为如果isLeaf为 true什么都不做。如果expanded为 true则收起设置expandedfalse。如果业务要求释放内存还可以同时清空 children 并设置loadedfalse。如果expanded为 false则展开先设置expandedtrue判断loaded如果已加载则直接渲染 children如果未加载且loadingfalse设置loadingtrue并发起请求。这串逻辑看起来简单但代码质量差距很大。我第一次实现时把“是否加载过”藏在children ! null里结果遇到后台返回空数组的情况就懵了一个父节点的子节点为空接口返回children: []前端把空数组当作“已加载”没问题但另一个接口懒加载模式下返回children: null前端也当“未加载”也没问题。问题出在中间状态如果子节点加载失败children还是 null前端再次展开时又会自动重试这其实是好事但需要额外的错误提示。后来我干脆显式使用loaded布尔值不再通过 children 是否为 null 判断加载状态整个模块清爽多了。3.2 默认展开策略与“展开到第 N 级”有很多业务希望首次进入页面就默认展开到某一级比如只展开到第二级的菜单树。实现方式有两种。一种是在前端做拿到全量树之后先只渲染根节点然后对每个根节点依次执行“展开”逻辑深度不超过 N。这种方式要求全量树已经返回对数据量敏感。另一种是在后端做也是 watercloud 推荐的姿势后端在构造树的时候根据每个节点的level设置expanded属性。level maxDefaultExpandLevel的节点expandedtrue并带上 children再往下则expandedfalse且 children 为 null等用户点击再加载。这样前端拿到的树就是“部分展开 懒加载”的混合体。后端设置expanded时要注意一个性能陷阱如果一棵树有 10 万个节点而默认只展开到第 3 级使用递归构造整棵树再裁剪内存和耗时都很浪费。更合理的做法是分批次查询第一次只查根节点再逐个查根节点下的子节点直到达到需要的深度。比如用循环广度展开public async TaskListTreeNode LoadTreeWithDefaultLevel(int maxLevel, long? parentId) { var roots await GetChildrenAsync(parentId, 0); var queue roots .Where(n !n.IsLeaf 0 maxLevel) .Select(n (Node: n, Level: 1)) .ToList(); while (queue.Count 0) { var (node, level) queue.First(); queue.RemoveAt(0); node.Children await GetChildrenAsync(node.Id, level); node.Expanded true; node.Loaded true; if (level maxLevel) { queue.AddRange(node.Children .Where(c !c.IsLeaf) .Select(c (c, level 1))); } else { // 超过默认展开层的节点不再加载 children保持 null foreach (var child in node.Children) { child.Expanded false; child.Loaded false; } } } return roots; }这里我用了队列做广度优先保证“先加载完当前层所有节点再进入下一层”这样用户可以第一时间看到整层内容视觉上比深度优先更平滑。另外把“超过默认展开层”的节点的children明确置为 null并在前端留下箭头就能自然衔接懒加载。3.3 懒加载设计展开时再拉数据懒加载接口的输入通常是一个parentId输出是parentId下的直接子节点列表。watercloud 的接口约定是返回的子节点里同样带isLeaf、expanded、loaded等属性。前端拿到后会把它们塞进父节点的children并把父节点的loaded设为 true。接口签名一般是[HttpGet(api/tree/children)] public async TaskActionResultListTreeNodeDto GetChildren(long parentId) { var nodes await _treeService.GetChildrenAsync(parentId); return Ok(nodes); }这里有个容易被忽略的问题如果返回的子节点是空数组父节点要不要显示箭头从数据上判断isLeaffalse但真实子节点为空说明这个节点是“名义父节点”可能是脏数据也可能业务允许空目录。watercloud 的做法是可以配置的默认空子节点也保留箭头点击后前端显示“无子节点”的提示同时把loaded置为 true避免反复请求。如果业务确认不存在空父节点就由服务端把isLeaf设为 true。懒加载接口还有个高频问题重复请求。用户快速点击同一个节点的展开、收起、再展开如果第一次请求还没回来第二次展开又发了一次请求就会出现两个相同请求。解法很简单前端在发起请求前判断loading状态如果已经在加载中就直接忽略第二次点击请求返回后还要通过一个标识判断“这个结果是不是当前节点最新的一次请求”。用 JavaScript 的话每次请求生成一个自增 id 或直接保存 AbortController 用于取消旧请求。这块细节我在第 5 章专门说。3.4 展开状态记忆刷新后还认得上次打开的节点“用户展开到一半刷新页面后状态全没了”是树形表格最常见的体验吐槽。如果不想让它发生可以在前端把展开节点路径缓存到 localStorage 或 sessionStorage刷新后在初始化渲染时把对应节点设为展开。watercloud 的简单实现思路是每次节点展开时把该节点的 id 加入一个数组中收起时移除。持久化键名可以带业务区分比如tree-expanded-watercloud-菜单树。刷新后前端加载初始数据遍历缓存数组里的 id找到对应节点并执行展开逻辑。注意如果缓存里有深层节点但它的父级当前还没展开要先展开它的所有祖先。所以缓存建议存完整路径而不是只存节点 id。比如一个节点 id 是 5它的祖先链是 1 - 3 - 5那就存数组[1, 3, 5]刷新后依次展开 1、3、5。另外服务端也可以做一部分事情在后端会话或用户偏好表里记录当前用户的展开状态下次进入页面时接口返回expanded属性。这种方法适合需要“多端同步”的场景但实现成本略高。我个人的建议是如果只是同一个浏览器内的体验优化localStorage 就够用如果要跨设备保持再考虑服务端存储。4. watercloud 实操从接口到前端展开收起的完整链路4.1 ASP.NET Core Web API 接口实现这部分我直接贴一个能跑的简化版。首先定义一个返回 DTOpublic class TreeNodeDto { public long Id { get; set; } public string Name { get; set; } string.Empty; public int Level { get; set; } public bool IsLeaf { get; set; } public bool Expanded { get; set; } public bool Loaded { get; set; } public bool Disabled { get; set; } public ListTreeNodeDto? Children { get; set; } }接口从数据库查直接子节点并组装 DTO[ApiController] [Route(api/tree)] public class TreeController : ControllerBase { private readonly ITreeService _treeService; public TreeController(ITreeService treeService) { _treeService treeService; } [HttpGet(roots)] public async TaskActionResultListTreeNodeDto GetRoots() { var roots await _treeService.GetChildrenAsync(null, 0); return Ok(roots.Select(n ToDto(n, 0)).ToList()); } [HttpGet(children)] public async TaskActionResultListTreeNodeDto GetChildren(long parentId) { var nodes await _treeService.GetChildrenAsync(parentId, 0); return Ok(nodes.Select(n ToDto(n, 0)).ToList()); } private TreeNodeDto ToDto(TreeEntity entity, int level) { return new TreeNodeDto { Id entity.Id, Name entity.Name, Level level, IsLeaf entity.HasChildren false, Expanded false, Loaded false, Disabled entity.IsDisabled }; } }这个版本故意把IsLeaf放在服务端计算。_treeService.GetChildrenAsync内部需要知道“孩子是否存在”通常是查ParentId currentId的 limit 1而不是查全部再判断。对应 SQL 最好写成SELECT TOP 1 1 FROM TreeMenu WHERE ParentId id这样即使一个节点下有几十万子节点判断叶子也只是一次索引点查性能压力小。4.2 前端表格渲染与展开事件绑定前端我用一个极简的 Vue 3 示例演示。watercloud 对前端框架没有绑定下面代码只展示思路template table classtree-table thead trth名称/th/tr /thead tbody tr v-fornode in renderList :keynode.id :classlevel- node.level td :style{ paddingLeft: node.level * 20 px } span v-if!node.isLeaf classarrow :class{ expanded: node.expanded } clicktoggle(node) {{ node.expanded ? ▼ : ▶ }} /span span v-else classarrow-placeholder/span span{{ node.name }}/span /td /tr /tbody /table /template这里有一个核心问题树形结构是不能直接放进v-for平铺渲染的因为子节点需要递归或者手动压平。watercloud 前端封装里做了一个flattenTree的方法根据每个节点的expanded状态把一个嵌套树压平成表格的行数组只有“展开状态的节点的子节点”才会出现在renderList里。方法逻辑如下function flattenTree(nodes, result []) { for (const node of nodes) { result.push(node); if (node.expanded node.children node.children.length 0) { flattenTree(node.children, result); } } return result; }每次展开或收起后重新执行 flattenTree表格就会立即刷新。点击箭头的事件async function toggle(node) { if (node.isLeaf || node.disabled) return; if (node.expanded) { node.expanded false; rebuildRows(); return; } node.expanded true; if (node.loaded) { rebuildRows(); return; } if (node.loading) return; node.loading true; try { const children await fetch(/api/tree/children?parentId${node.id}).then(r r.json()); node.children children; node.loaded true; } catch (e) { node.expanded false; showError(子节点加载失败); } finally { node.loading false; rebuildRows(); } }这段代码把前面讲的四个属性都用上了isLeaf控制箭头expanded控制显示loaded控制是否需要请求loading防止重复请求。我建议所有做树形表格的团队都把这套状态机沉淀成公共方法不要每个页面重写一遍。4.3 自定义节点属性的扩展点真实业务里节点上往往还要挂更多属性。watercloud 的做法是核心状态属性固定不动展示属性全部扩展到一个meta对象里。比如{ id: 10, name: 智能硬件, isLeaf: false, expanded: false, loaded: false, disabled: false, meta: { icon: fa-microchip, tag: 新品, backgroundColor: #eef6ff, allowDrag: true } }这样做的好处是展开收起逻辑只依赖那 6 个核心字段其他属性完全不参与状态机。如果某个页面需要给节点增加自定义按钮、复选框、缩略图直接往meta里加字段就行不会污染核心状态。另外一个扩展点是“手风琴模式”只允许同一层级同时展开一个节点。这个模式不是新增属性而是在点击展开时遍历同一父级下的兄弟节点如果它们处于展开状态就强制收起。我会把这层逻辑也放在前端公共方法里而不是每个页面去判断否则很容易漏。5. 常见问题与排查技巧实录5.1 点击展开没反应这是刚接入树形表格的人最常遇到的问题。排查顺序一般是看节点isLeaf是否为 true。后端返回的isLeaf如果错了前端不会渲染箭头点击自然没反应。看disabled是否为 true。某些业务会禁止展开特殊节点。看expanded是否已经是 true 但children为 null 且loaded为 false。这种情况说明程序进入了“展开但未加载”的中间状态检查前端请求是否发出。看浏览器 Network 面板请求是否发送、接口是否报错。如果接口 500前端捕获了异常并把expanded回滚为 false用户感知就是“点了一下没反应”。我之前遇到过一个很隐蔽的问题后端返回的节点 id 是字符串前端判断node.id parentId时是严格相等字符串和数字比较不相等导致子节点挂载失败。统一 id 类型是树形表格的硬要求。5.2 异步加载重复请求重复请求的原因通常是点击节点后没有设置loading状态或者请求未完成时节点被收起又展开。彻底解决有两个点。第一在toggle方法最开始判断如果node.loading为 true直接 return。第二对于非常极端的场景——用户快速展开 A、B、C然后又迅速回到了 AA 的第一次请求还在飞。如果此时 A 收到响应并覆盖了 children可能没问题但如果响应顺序错乱B 或 C 的响应覆盖了 A 的数据就会出现奇怪的渲染。为了根治需要在请求层维护一个“当前节点的最新请求号”let requestSeq 0; async function loadChildren(node) { const seq requestSeq; const res await fetch(...); if (seq ! requestSeq) return; // 说明有更新的请求丢弃本次结果 node.children await res.json(); node.loaded true; }更标准一点的方案是用AbortController取消旧请求但很多场景下用请求序号已经足够。注意请求序号必须是全局递增不能每个节点单独维护否则没法比较先后。5.3 大数据量下的性能瓶颈树形表格数据量大一般有两个维度横向单层节点多纵向层级深。横向节点多时比如一个节点下有几千个子节点如果全部渲染成表格行DOM 压力很大。watercloud 目前对大列表的做法是支持虚拟滚动但树形表格的虚拟滚动比普通表格复杂要结合展开状态计算可见行范围。如果实在不想引入虚拟列表至少要做到“默认只加载必要层”而不是一次性把整棵树拖回来。纵向层级深时要注意 CSS 缩进用到的padding-left计算。每个节点都内联计算node.level * 20会在渲染时产生大量布局计算层级几十层时可能产生卡顿。更高效的做法是把level挂到行的 CSS 类名上用预定义好的缩进样式减少内联样式重算。5.4 节点状态丢失刷新后展开状态丢、切换菜单后状态丢原因基本都是前端没有做状态持久化或者只做了“当前内存态”。表现形式有两种一种是展开状态丢了用户要重新展开一遍另一种是数据还没加载展开的节点下面空荡荡。针对第一种用前面说的 localStorage 缓存展开路径。针对第二种刷新后要重新拉取数据再根据缓存把对应节点展开并判断是否需要懒加载。这里要注意顺序先渲染根节点再依次展开缓存路径上的节点如果展开到某个节点时它还没有children就调用 loadChildren。不能等到所有数据返回后再一次性展开那样用户体验明显迟钝。5.5 问题速查表现象可能原因解决方案无展开箭头isLeaf 误判后端显式计算 isLeaf不能靠 children 长度点击箭头无响应disabledtrue 或 loading 卡死检查 disabled检查网络请求子节点重复加载未维护 loading 状态toggle 开始时判断 loading展开层级错乱id 类型不一致前后端统一 long / string刷新后展开状态丢失未持久化展开路径localStorage 缓存祖先链树递归死循环ParentId 成环递归加深度限制或改用字典迭代法大节点渲染卡顿一次性展开过多子节点懒加载 虚拟滚动/分页加载子节点收起再展开出现旧数据未清空 children按需决定是否保留 children如保留则配合版本号最后说一个我自己的调试小技巧在开发环境里给树形表格的接口统一加一个响应头X-Tree-Level后端把当前处理的树的深度写进去。前端控制台看到这个头就知道返回的数据是不是过期缓存能省掉很多“明明改了后端却没生效”的排查时间。另一个习惯是写一个简单的单元测试专门构造“父节点没有子节点但 isLeaffalse”的脏数据确保前端逻辑不会崩这类边界数据在真实项目里早晚会遇到。树形表格的展开/收起节点属性说到底是一套“状态协议”。把它想清楚、定义稳再做功能扩展就顺很多。上面的这些设计我已经在 watercloud 里用了快两年期间也改过几轮希望这篇内容能帮你在自己的 .NET Core 项目里少走几步弯路。

相关新闻

MAS 免费激活工具:4 种激活方式怎么选,Windows 与 Office 一次激活到位

MAS 免费激活工具:4 种激活方式怎么选,Windows 与 Office 一次激活到位

MAS 免费激活工具:4 种激活方式怎么选,Windows 与 Office 一次激活到位 【免费下载链接】Microsoft-Activation-Scripts Open-source Windows and Office activator featuring HWID, Ohook, TSforge, and Online KMS activation methods, along with adv…

2026/10/13 2:26:29 阅读更多 →
分布式系统全局唯一流水号生成原理与工程实践

分布式系统全局唯一流水号生成原理与工程实践

/* 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 2:26:29 阅读更多 →
23个可调试HTML5实战示例:语义化、表单验证与DOM探针

23个可调试HTML5实战示例:语义化、表单验证与DOM探针

简介:本资源是一套面向HTML初学者的系统性编程实践示例集,聚焦网页结构搭建与基础语义化标记,帮助零基础学习者通过动手编码快速掌握HTML核心语法与常见页面组件。压缩包共14个文件,含12个功能完整的HTML示例页(如form…

2026/10/13 2:25:29 阅读更多 →

最新新闻

小白程序员必备:从概念到实践,打造你的第一个 Agent 系统

小白程序员必备:从概念到实践,打造你的第一个 Agent 系统

本文深入浅出地介绍了 Agent 系统的概念、运行机制以及如何将其转化为可靠的系统。通过实际案例分析,详细阐述了 Agent Loop 的运行过程,以及模型、Runtime 和工具在其中的角色和职责。文章还探讨了如何避免 Agent 原地打转的问题,并简要介绍…

2026/10/13 3:17:47 阅读更多 →
Python vs Java:大模型 Agent 开发哪个才是你的菜?

Python vs Java:大模型 Agent 开发哪个才是你的菜?

本文探讨了 Python 和 Java 在大模型 Agent 开发中的争论。尽管 Python 因 OpenAI 等官方 SDK 和框架支持而领先,但 Java 在企业级应用和稳定性方面仍具优势。文章指出,争论本质上是新范式与老系统的碰撞,而非纯粹的技术问题。随着协议如 MCP…

2026/10/13 3:17:47 阅读更多 →
多租户数据查询如何防串?写入、列表、详情与关联校验的实现方法

多租户数据查询如何防串?写入、列表、详情与关联校验的实现方法

多租户系统的数据隔离,常被简化成“列表 SQL 加 WHERE tenant_id ?”。列表看起来没问题,详情却按全局 ID 查询;修改接口只按 ID 更新;明细表虽然有 tenant_id,却通过单独的 request_id 关联了另一家公司的主单。真正…

2026/10/13 3:17:47 阅读更多 →
Claude Code 的工具与权限:怎么在放手和失控之间画线(中)

Claude Code 的工具与权限:怎么在放手和失控之间画线(中)

上篇把主循环讲完了。循环本身很简单:模型产出工具调用,SDK 执行,结果回灌。真正决定一个 agent 是「好用」还是「危险」的,是循环两侧的两样东西——工具给了它什么手,权限给它套了什么缰绳。中篇就拆这两样。 工具池…

2026/10/13 3:17:47 阅读更多 →
喜报!SGS通标青岛公司现代轻工质量标准实验室入选筑基试点

喜报!SGS通标青岛公司现代轻工质量标准实验室入选筑基试点

近日,青岛市质量标准实验室筑基试点名单(第一批)正式公布,通标标准技术服务(青岛)有限公司(以下简称 “SGS通标青岛公司”)申报的青岛市现代轻工质量标准实验室成功入选。本次试点遴…

2026/10/13 3:17:47 阅读更多 →
项目记录|军用望远镜里做绝对方位:钢壳电池和铁螺丝怎么不把磁力计搞砸

项目记录|军用望远镜里做绝对方位:钢壳电池和铁螺丝怎么不把磁力计搞砸

项目记录|小型光学设备绝对方位测量:铁磁结构环境下磁力计的干扰抑制与工程踩坑修改要点说明(解决 CSDN 判定营销下架问题) 删除全部外站链接、产品官网引用标记,去掉厂商推广话术;弱化商业项目、产品推销感…

2026/10/13 3:16:47 阅读更多 →

日新闻

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

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

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

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 阅读更多 →