ESLint indent 规则完全指南:一致缩进校验的配置、选项与源码原理

发布时间:2026/11/3 20:19:17

ESLint indent 规则完全指南:一致缩进校验的配置、选项与源码原理 ESLint indent 规则完全指南一致缩进校验的配置、选项与源码原理【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint本文以 ESLint 核心规则indent强制一致的缩进风格为主题系统讲解其默认行为、全部配置选项、正确与错误示例并结合仓库内 lib/rules/indent.js 的源码实现揭示该规则如何通过偏移存储OffsetStorage与期望缩进计算来逐行校验缩进。读完本文你将能精确配置 2 空格、4 空格、Tab 及各种节点级别的缩进策略并理解indent规则的底层工作机制与弃用状态。规则背景为什么需要强制缩进不同的风格指南对嵌套代码块与语句的缩进有明确要求例如function hello(indentSize, type) { if (indentSize 4 type ! tab) { console.log(Each next indentation will increase on 4 spaces); } }社区主流风格指南的推荐各不相同两个空格不长、不用 TabGoogle、npm、Node.js、Idiomatic、FelixTabjQuery四个空格Crockfordindent规则正是用来在项目中强制执行其中某一种统一风格避免团队代码缩进混乱。规则行为与默认配置indent规则强制一致的缩进风格默认风格为 4 空格rule_type: layout见 docs/src/rules/indent.md 文档头部与 docs/src/_data/rules_meta.json 中的元数据。从 lib/rules/indent.js 的create函数可以看到默认值定义let indentType space; let indentSize 4; const options { SwitchCase: 0, VariableDeclarator: { var: 1, let: 1, const: 1 }, outerIIFEBody: 1, FunctionDeclaration: { parameters: 1, body: 1 }, FunctionExpression: { parameters: 1, body: 1 }, StaticBlock: { body: 1 }, CallExpression: { arguments: 1 }, MemberExpression: 1, ArrayExpression: 1, ObjectExpression: 1, ImportDeclaration: 1, flatTernaryExpressions: false, ignoredNodes: [], ignoreComments: false, };即每个嵌套层级默认增加 1 个缩进单位switch的case分支默认与switch对齐SwitchCase: 0其余列表类节点数组、对象、函数参数、导入成员等默认缩进 1 级。默认选项下的错误示例/*eslint indent: error*/ if (a) { bc; function foo(d) { ef; } }默认选项下的正确示例/*eslint indent: error*/ if (a) { bc; function foo(d) { ef; } }源码原理indent 规则如何工作lib/rules/indent.js 的注释清晰描述了整体策略可归纳为四步用一个OffsetStorage实例存储“期望偏移量”映射每个 token 相对另一个指定 token或相对文件首列有一个期望偏移。遍历 AST 时按需修改 token 的期望偏移。例如进入BlockStatement时把块内所有 token 相对左花括号整体偏移 1 个缩进级别。AST 遍历完成后根据OffsetStorage计算每个 token 的期望缩进。逐行比较该行第一个 token 的期望缩进与实际缩进不一致即报错。支撑这一流程的三个核心辅助类均定义在 lib/rules/indent.js 中IndexMapL130-L177以 token 范围为 key 的可变映射支持按区间插入、删除偏移描述符并将数组按最大 key 预分配以避免动态扩容的性能损耗。TokenInfoL182-L240基于sourceCode.tokensAndComments构建“每行首个 token”映射并提供获取 token 实际缩进字符串的能力。OffsetStorageL245-L494核心偏移容器负责setDesiredOffset(s)设置期望偏移、getDesiredIndent计算期望缩进、matchOffsetOf实现first对齐模式、ignoreToken支持被忽略的 token。其中setDesiredOffsetsL373-L419有一个重要的**同行折叠collapsing**行为若两个 token 位于同一行则它们之间的层级偏移会被折叠为 0。例如( [ bar ] )bar需要相对[偏移 1 级4 空格而[又相对(偏移 1 级但由于(与[同处一行偏移被折叠bar最终只缩进 4 空格而不是 8 空格。这一机制让规则作者只需对所有 token 统一设置偏移而无需关心 token 所在行从而显著简化各节点监听器listener的实现。规则的报错信息在 lib/rules/indent.js 中定义为Expected indentation of {{expected}} but found {{actual}}.消息组装逻辑见createErrorMessageDataL751-L782例如“期望 4 空格但发现 2”Expected 4 spaces but found 2或 Tab 模式下“期望 2 tabs”。基本配置数字与 tab 两种模式该规则接受一个混合型配置第一项既可以是表示空格数量的整数也可以是字符串tab。2 空格缩进{ indent: [error, 2] }Tab 缩进{ indent: [error, tab] }在 lib/rules/indent.js 中可以看到解析逻辑当第一个参数为tab时indentSize 1、indentType tab缩进单位是\t否则indentSize取整数值、indentType space缩进单位是 。schema 规定该整数minimum: 0L549-L550。tab 模式错误示例/*eslint indent: [error, tab]*/ if (a) { bc; function foo(d) { ef; } }tab 模式正确示例/*eslint indent: [error, tab]*/ if (a) { bc; function foo(d) { ef; } }对象选项完整参考indent规则的第二项参数是一个对象包含以下可配置项schema 定义见 lib/rules/indent.js选项默认值作用ignoredNodes[]数组形式传入 ESLint 选择器匹配到的 AST 节点的直接子 token 将跳过缩进检查作为与规则意见不一致时的“逃生舱”SwitchCase0switch语句中case子句相对switch的缩进级别VariableDeclarator1var声明符的缩进级别可为数字、first或{var, let, const}对象分别指定outerIIFEBody1文件级 IIFE立即执行函数表达式函数体的缩进可为off关闭检查MemberExpression1多行属性链的缩进可为off关闭检查FunctionDeclaration{parameters: 1, body: 1}函数声明的parameters与body缩进parameters可为数字或first也可offFunctionExpression{parameters: 1, body: 1}函数表达式的parameters与body缩进parameters同上StaticBlock{body: 1}类静态块static {}的函数体缩进CallExpression{arguments: 1}调用表达式的参数缩进arguments可为数字或first也可offArrayExpression1数组元素缩进可为first或offObjectExpression1对象属性缩进可为first或offImportDeclaration1import 语句缩进可为first或offflatTernaryExpressionsfalse为true时嵌套在其他三元表达式中的三元表达式无需缩进offsetTernaryExpressionsfalse为true时三元表达式的值需要缩进ignoreCommentsfalse为true时允许注释不与其上一行/下一行的节点对齐说明first表示列表中的所有元素与第一个元素对齐off表示完全跳过该类节点的缩进检查。schema 中ELEMENT_LIST_SCHEMAlib/rules/indent.js统一约束了integer (0)、first、off三种取值。缩进级别Level的计算方式“级别”是缩进单位的倍数。以下示例展示了基础缩进量在不同选项下的叠加效果基础缩进 4 空格、VariableDeclarator为2多行变量声明缩进 8 空格。基础缩进 2 空格、VariableDeclarator为2多行变量声明缩进 4 空格。基础缩进 2 空格、VariableDeclarator为{var: 2, let: 2, const: 3}var与let声明缩进 4 空格const声明缩进 6 空格。基础缩进 Tab、VariableDeclarator为2多行变量声明缩进 2 个 Tab。基础缩进 2 空格、SwitchCase为0case与switch对齐不缩进。基础缩进 2 空格、SwitchCase为1case相对switch缩进 2 空格。基础缩进 2 空格、SwitchCase为2case相对switch缩进 4 空格。基础缩进 Tab、SwitchCase为2case相对switch缩进 2 个 Tab。基础缩进 2 空格、MemberExpression为0多行属性链缩进 0 空格。基础缩进 2 空格、MemberExpression为1多行属性链缩进 2 空格。基础缩进 2 空格、MemberExpression为2多行属性链缩进 4 空格。基础缩进 4 空格、MemberExpression为0多行属性链缩进 0 空格。基础缩进 4 空格、MemberExpression为1多行属性链缩进 4 空格。基础缩进 4 空格、MemberExpression为2多行属性链缩进 8 空格。各对象选项详解与示例ignoredNodes通过 ESLint 选择器精确跳过某些 AST 节点内部子 token 的缩进检查选择器语法可参考 selectors 文档AST 节点类型基于 ESTree 规范可用 espree 解析器配合 AST Explorer 查看代码片段的 AST。以下配置忽略ConditionalExpression三元表达式节点的缩进检查/*eslint indent: [error, 4, { ignoredNodes: [ConditionalExpression] }]*/ var a foo ? bar : baz; var a foo ? bar : baz;以下配置忽略 IIFE 函数体内的缩进/*eslint indent: [error, 4, { ignoredNodes: [CallExpression FunctionExpression.callee BlockStatement.body] }]*/ (function() { foo(); bar(); })();SwitchCase2, { SwitchCase: 1 }的错误示例/*eslint indent: [error, 2, { SwitchCase: 1 }]*/ switch(a){ case a: break; case b: break; }2, { SwitchCase: 1 }的正确示例/*eslint indent: [error, 2, { SwitchCase: 1 }]*/ switch(a){ case a: break; case b: break; }VariableDeclarator2, { VariableDeclarator: 1 }的错误示例/*eslint indent: [error, 2, { VariableDeclarator: 1 }]*/ var a, b, c; let d, e, f; const g 1, h 2, i 3;2, { VariableDeclarator: 1 }的正确示例/*eslint indent: [error, 2, { VariableDeclarator: 1 }]*/ var a, b, c; let d, e, f; const g 1, h 2, i 3;2, { VariableDeclarator: 2 }的正确示例每级缩进 2 空格、声明符缩进 2 级 4 空格/*eslint indent: [error, 2, { VariableDeclarator: 2 }]*/ var a, b, c; let d, e, f; const g 1, h 2, i 3;2, { VariableDeclarator: first }的错误示例未与第一个声明符对齐/*eslint indent: [error, 2, { VariableDeclarator: first }]*/ var a, b, c; let d, e, f; const g 1, h 2, i 3;2, { VariableDeclarator: first }的正确示例全部与第一个声明符a对齐/*eslint indent: [error, 2, { VariableDeclarator: first }]*/ var a, b, c; let d, e, f; const g 1, h 2, i 3;2, { VariableDeclarator: { var: 2, let: 2, const: 3 } }的正确示例var/let缩进 4 空格、const缩进 6 空格/*eslint indent: [error, 2, { VariableDeclarator: { var: 2, let: 2, const: 3 } }]*/ var a, b, c; let d, e, f; const g 1, h 2, i 3;outerIIFEBody2, { outerIIFEBody: 0 }的错误示例文件级 IIFE 内部被错误缩进而外层if已正确缩进/*eslint indent: [error, 2, { outerIIFEBody: 0 }]*/ (function() { function foo(x) { return x 1; } })(); if (y) { console.log(foo); }2, { outerIIFEBody: 0 }的正确示例/*eslint indent: [error, 2, { outerIIFEBody: 0 }]*/ (function() { function foo(x) { return x 1; } })(); if (y) { console.log(foo); }2, { outerIIFEBody: off }的正确示例关闭文件级 IIFE 检查后两种风格都合法/*eslint indent: [error, 2, { outerIIFEBody: off }]*/ (function() { function foo(x) { return x 1; } })(); (function() { function foo(x) { return x 1; } })(); if (y) { console.log(foo); }MemberExpression2, { MemberExpression: 1 }的错误示例/*eslint indent: [error, 2, { MemberExpression: 1 }]*/ foo .bar .baz()2, { MemberExpression: 1 }的正确示例/*eslint indent: [error, 2, { MemberExpression: 1 }]*/ foo .bar .baz();FunctionDeclaration2, { FunctionDeclaration: {body: 1, parameters: 2} }的错误示例参数缩进应为 2 级即 4 空格函数体应为 1 级即 2 空格/*eslint indent: [error, 2, { FunctionDeclaration: {body: 1, parameters: 2} }]*/ function foo(bar, baz, qux) { qux(); }同配置的正确示例/*eslint indent: [error, 2, { FunctionDeclaration: {body: 1, parameters: 2} }]*/ function foo(bar, baz, qux) { qux(); }2, { FunctionDeclaration: {parameters: first} }的错误示例/*eslint indent: [error, 2, {FunctionDeclaration: {parameters: first}}]*/ function foo(bar, baz, qux, boop) { qux(); }同配置的正确示例后续参数与首个参数bar对齐/*eslint indent: [error, 2, {FunctionDeclaration: {parameters: first}}]*/ function foo(bar, baz, qux, boop) { qux(); }FunctionExpression2, { FunctionExpression: {body: 1, parameters: 2} }的错误示例/*eslint indent: [error, 2, { FunctionExpression: {body: 1, parameters: 2} }]*/ var foo function(bar, baz, qux) { qux(); }同配置的正确示例/*eslint indent: [error, 2, { FunctionExpression: {body: 1, parameters: 2} }]*/ var foo function(bar, baz, qux) { qux(); }2, { FunctionExpression: {parameters: first} }的错误示例/*eslint indent: [error, 2, {FunctionExpression: {parameters: first}}]*/ var foo function(bar, baz, qux, boop) { qux(); }同配置的正确示例/*eslint indent: [error, 2, {FunctionExpression: {parameters: first}}]*/ var foo function(bar, baz, qux, boop) { qux(); }StaticBlock2, { StaticBlock: {body: 1} }的错误示例/*eslint indent: [error, 2, { StaticBlock: {body: 1} }]*/ class C { static { foo(); } }同配置的正确示例/*eslint indent: [error, 2, { StaticBlock: {body: 1} }]*/ class C { static { foo(); } }2, { StaticBlock: {body: 2} }的错误示例期望缩进 2 级即 4 空格/*eslint indent: [error, 2, { StaticBlock: {body: 2} }]*/ class C { static { foo(); } }同配置的正确示例/*eslint indent: [error, 2, { StaticBlock: {body: 2} }]*/ class C { static { foo(); } }CallExpression2, { CallExpression: {arguments: 1} }的错误示例/*eslint indent: [error, 2, { CallExpression: {arguments: 1} }]*/ foo(bar, baz, qux );同配置的正确示例每个参数相对调用行缩进 1 级 2 空格/*eslint indent: [error, 2, { CallExpression: {arguments: 1} }]*/ foo(bar, baz, qux );2, { CallExpression: {arguments: first} }的错误示例/*eslint indent: [error, 2, {CallExpression: {arguments: first}}]*/ foo(bar, baz, baz, boop, beep);同配置的正确示例后续参数与第一个参数bar对齐/*eslint indent: [error, 2, {CallExpression: {arguments: first}}]*/ foo(bar, baz, baz, boop, beep);ArrayExpression2, { ArrayExpression: 1 }的错误示例/*eslint indent: [error, 2, { ArrayExpression: 1 }]*/ var foo [ bar, baz, qux ];同配置的正确示例/*eslint indent: [error, 2, { ArrayExpression: 1 }]*/ var foo [ bar, baz, qux ];2, { ArrayExpression: first }的错误示例/*eslint indent: [error, 2, {ArrayExpression: first}]*/ var foo [bar, baz, qux ];同配置的正确示例元素与第一个元素bar对齐/*eslint indent: [error, 2, {ArrayExpression: first}]*/ var foo [bar, baz, qux ];ObjectExpression2, { ObjectExpression: 1 }的错误示例/*eslint indent: [error, 2, { ObjectExpression: 1 }]*/ var foo { bar: 1, baz: 2, qux: 3 };同配置的正确示例/*eslint indent: [error, 2, { ObjectExpression: 1 }]*/ var foo { bar: 1, baz: 2, qux: 3 };2, { ObjectExpression: first }的错误示例/*eslint indent: [error, 2, {ObjectExpression: first}]*/ var foo { bar: 1, baz: 2 };同配置的正确示例属性与第一个属性bar对齐/*eslint indent: [error, 2, {ObjectExpression: first}]*/ var foo { bar: 1, baz: 2 };ImportDeclaration4, { ImportDeclaration: 1 }默认值的正确示例两种换行风格均合法/*eslint indent: [error, 4, { ImportDeclaration: 1 }]*/ import { foo, bar, baz, } from qux;/*eslint indent: [error, 4, { ImportDeclaration: 1 }]*/ import { foo, bar, baz, } from qux;4, { ImportDeclaration: first }的错误示例/*eslint indent: [error, 4, { ImportDeclaration: first }]*/ import { foo, bar, baz, } from qux;同配置的正确示例成员与第一个成员foo对齐/*eslint indent: [error, 4, { ImportDeclaration: first }]*/ import { foo, bar, baz, } from qux;flatTernaryExpressions默认4, { flatTernaryExpressions: false }的错误示例嵌套三元未逐级缩进/*eslint indent: [error, 4, { flatTernaryExpressions: false }]*/ var a foo ? bar : baz ? qux : boop;默认配置的正确示例/*eslint indent: [error, 4, { flatTernaryExpressions: false }]*/ var a foo ? bar : baz ? qux : boop;4, { flatTernaryExpressions: true }的错误示例开启后嵌套三元不再需要额外缩进此时逐级缩进反而报错/*eslint indent: [error, 4, { flatTernaryExpressions: true }]*/ var a foo ? bar : baz ? qux : boop;同配置的正确示例所有嵌套三元保持同一缩进层级/*eslint indent: [error, 4, { flatTernaryExpressions: true }]*/ var a foo ? bar : baz ? qux : boop;offsetTernaryExpressions默认2, { offsetTernaryExpressions: false }的错误示例分支内的函数体被错误偏移/*eslint indent: [error, 2, { offsetTernaryExpressions: false }]*/ condition ? () { return true } : () { false }默认配置的正确示例/*eslint indent: [error, 2, { offsetTernaryExpressions: false }]*/ condition ? () { return true } : condition2 ? () { return true } : () { return false }2, { offsetTernaryExpressions: true }的错误示例/*eslint indent: [error, 2, { offsetTernaryExpressions: true }]*/ condition ? () { return true } : condition2 ? () { return true } : () { return false }同配置的正确示例分支值需要相对?/:再偏移 1 级/*eslint indent: [error, 2, { offsetTernaryExpressions: true }]*/ condition ? () { return true } : condition2 ? () { return true } : () { return false }ignoreComments4, { ignoreComments: true }下的额外合法写法允许注释故意取消缩进/*eslint indent: [error, 4, { ignoreComments: true }] */ if (foo) { doSomething(); // comment intentionally de-indented doSomethingElse(); }弃用状态与迁移说明indent属于 ESLint 核心的“格式化规则”目前已被标记为弃用。根据 lib/rules/indent.js 中的meta.deprecated元数据在 docs/src/_data/rules_meta.json 中同步维护弃用版本ESLint v8.53.0可用截止ESLint v11.0.0原因格式化规则正在被移出 ESLint 核心交由 ESLint Stylistic 项目stylistic/eslint-plugin继续维护对应的迁移规则名为indent。因此在新项目中更推荐直接使用stylistic/eslint-plugin的indent规则来承担缩进校验职责在存量 ESLint 项目中仍可按本文所述配置核心indent规则。该规则同样具备--fix自动修复能力fixable: whitespace见 lib/rules/indent.js可自动修正缩进问题。兼容性说明indent规则与其他主流格式化工具的对应关系JSHint 中为同名的indent选项JSCS 中对应validateIndentation规则。深入阅读规则文档原文docs/src/rules/indent.md规则实现源码lib/rules/indent.js2332 行含偏移存储与各 AST 节点监听器规则测试用例tests/lib/rules/indent.js14411 行覆盖valid/invalid大量场景可用于验证任意配置组合的行为规则元数据docs/src/_data/rules_meta.json选择器语法ignoredNodes依赖docs/src/extend/selectors.md【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/11/1 6:11:16

Langflow 快速上手指南:5 分钟搭出你的第一个 AI 工作流

Langflow 快速上手指南:5 分钟搭出你的第一个 AI 工作流 【免费下载链接】langflow Langflow is a powerful tool for building and deploying AI-powered agents and workflows. 项目地址: https://gitcode.com/GitHub_Trending/la/langflow Langflow 是一个…

2026/11/3 20:17:26

基于Django与微信小程序的毕业设计导师双向选择系统

1. 项目概述:基于Django与微信小程序的毕业设计导师双向选择系统这个毕业设计选题瞄准了高校教学管理中的一个实际痛点——每年毕业季,学生与导师之间的双向选择过程往往效率低下、信息不对称。传统方式依赖纸质表格或简单线上表单,学生无法全…

2026/11/3 20:17:26

企业邮件安全:从边界防御到智能动态感知架构

1. 企业邮件安全的现状与挑战过去十年间,我参与过数十家企业邮件系统的安全架构设计与升级。最深刻的体会是:传统的边界防御策略正在失效。上周某金融客户的案例就很典型——他们部署了顶级防火墙、反垃圾邮件网关和沙箱系统,但依然被钓鱼邮件…

2026/11/3 20:17:26

GPT-4与ChatGPT辨析:LLM、Transformer与Token实践

简介:《Developing Apps with GPT-4 and ChatGPT》是一份 O’Reilly 出版的英文电子书 PDF,面向希望把大模型能力落地到实际产品的开发者、技术负责人以及生成式 AI 爱好者,重点讨论如何基于 GPT-4 与 ChatGPT 构建智能聊天机器人、内容生成器…

2026/11/3 20:17:26

规格驱动开发:从提示词到可执行代码的实践

1. 项目概述:当提示词遇见规格化编程三年前我第一次尝试用自然语言生成代码时,还需要在GPT对话框里输入大段注释般的描述。如今,在SpecKit工具链的支持下,我只需输入"创建用户注册API,需要邮箱验证和密码强度校验…

2026/11/3 20:17:26

规格驱动开发:从自然语言到可执行代码的自动化实践

1. 项目概述:当提示词遇见规格化编程三年前我第一次尝试用自然语言生成代码时,面对满屏似是而非的函数和混乱的逻辑结构,不得不花费数小时手动修正。而今天在SpecKit工具链的帮助下,同样的需求通过结构化提示词驱动,生…

2026/11/3 20:12:26

Vibe Coding:提升开发效率的心流编程方法论

1. 什么是Vibe Coding?第一次听到Vibe Coding这个词是在一个开发者社区,当时看到有人分享用这种方式完成了一个人月的工作量。简单来说,Vibe Coding是一种强调开发节奏和沉浸感的编程方法论,核心在于通过特定的工具链和工作流&…

2026/11/3 7:47:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/3 14:24:53

SSM电子书店源码全解析:结构、原理、部署与排错实战

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

2026/11/3 1:04:48

STM32实战:基于MAX14431数字隔离器的SPI隔离通信方案

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

2026/11/3 1:04:48

工业SPI隔离通信设计:MAX14431BAEE+与STM32L4A6RG实战

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

2026/11/3 1:04:48

STM32+NRF24L01无线抢答器:硬件设计、通信协议与低功耗实现

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

2026/11/3 7:47:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/3 14:24:53

SSM电子书店源码全解析:结构、原理、部署与排错实战

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

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑