1. 从一次 Vue 项目缩进报错说起Expected indentation of 2 spaces but found 4 到底在说什么如果你正在用 VSCode 写 Vue某天npm run serve突然中断控制台刷出一行Expected indentation of 2 spaces but found 4别急着怀疑人生。这个报错的意思是ESLint 认为你的代码缩进应该是 2 个空格但它在文件里实际读到了 4 个空格。它属于 ESLint 的indent规则族在 Vue CLI 项目里通常由vue/standard这套规则集带进来。很多人第一反应是「我明明在 VSCode 里把 tabSize 设成 2 了为什么还报 4」问题就出在这里VSCode 的editor.tabSize只影响你按下 Tab 键时插入几个空格它并不等于 ESLint 的校验标准。ESLint 读的是文件里真实存在的空白字符以及它自己加载的规则配置。两者一旦不一致编辑器里看着整齐命令行一跑就炸。这个报错最典型的触发场景有三种。第一种项目里存在历史文件早期用 4 空格写的后来团队统一改成 2 空格但旧文件没批量格式化。第二种多人协作时有人本地 VSCode 没装 ESLint 插件保存时按自己的习惯缩进提交上来就是 4 空格。第三种.eslintrc.js里extends了vue/standard但settings.json的 formatter 没配对保存时格式化没生效红色波浪线一直挂着。它带来的实际影响不只是「难看」。Vue CLI 的vue-cli-service在serve和build阶段会跑 lint规则不通过就直接中断热更新起不来CI 流水线也会红。对个人开发者来说这意味着你改一行代码要手动对齐半天对团队来说这意味着每次 code review 都在吵空格而不是聊业务逻辑。所以这篇要解决的不是「怎么把这一行改对」而是「怎么让 VSCode 的编辑行为、ESLint 的校验规则、以及团队统一的校验通道三者对齐」。我会先给出.eslintrc.js和settings.json的可复制配置再把校验请求统一到 TaoToken 的 Key/API 通道上让本地和 CI 用同一套规则、同一个入口最后用真实请求验证报错消失。2. 前置准备把 ESLint 校验通道统一到 TaoToken 的 API Key 与 Base URL在动手改配置之前先把「校验请求走哪里」这件事定下来。很多团队的痛点是本地 ESLint 用一套规则CI 用另一套AI 辅助补全又走第三个通道结果同一个缩进问题在不同环节表现不一样。把校验相关的模型请求统一到 TaoToken好处是 Key 和 Base URL 只有一份规则和调用入口一致排查问题时不用在多个平台之间来回切。TaoToken 的官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 。注意 API 地址后面不加 UTM 参数保持干净。你需要先在控制台创建一个 API Key控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite Key 管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。创建好之后复制那串sk-开头的字符串后面配置里会用到。这里要强调一个概念TaoToken 在这里扮演的是「统一校验与模型调用通道」不是替代 ESLint 本身。ESLint 负责静态规则检查TaoToken 负责把需要模型参与的环节比如 AI 辅助格式化建议、规则解释、批量修复脚本里的模型调用收敛到一个 Key 上。这样团队里每个人拿到的 Base URL 和 Model ID 都一样不会出现「你本地能过、我本地报错」的割裂。如果你用的是 Claude Code 这类编码 Agent接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有 Base URL、Key、Model ID 三件套的完整说明。Claude Code 的 Anthropic 兼容入口在 https://taotoken.net/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_contentClaudeCodeAnthropicutm_campaignrewrite 需要长期跑 Agent 任务的可以看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。模型对话调试入口在 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 用来验证 Key 是否可用最方便。准备阶段你只需要确认三件事一是 Key 已经创建并能复制二是 Base URL 记牢是https://taotoken.net/api三是想好你要用的 Model ID比如做代码格式化建议时选一个擅长代码的模型。这三样东西在下一节的配置片段里会直接出现缺一个都会导致请求 401。3. 可复制配置.eslintrc.js 与 VSCode settings.json 的完整片段这一节是全文的核心配置直接抄就行但要注意路径和字段名必须和原文一致否则 ESLint 加载不到。先看项目根目录的.eslintrc.js。Vue CLI 创建的项目默认会生成这个文件如果你的是.eslintrc.json或.eslintrc.cjs字段结构一样只是导出方式不同。// .eslintrc.js module.exports { root: true, env: { node: true, browser: true, es2021: true }, extends: [ plugin:vue/essential, vue/standard ], parserOptions: { parser: babel-eslint, ecmaVersion: 2021, sourceType: module }, rules: { no-console: process.env.NODE_ENV production ? warn : off, no-debugger: process.env.NODE_ENV production ? warn : off, indent: [error, 2, { SwitchCase: 1 }], vue/html-indent: [error, 2], vue/script-indent: [error, 2, { baseIndent: 1 }] } }关键点在于indent这条规则显式写成[error, 2]把缩进标准钉死在 2 空格。vue/standard本身也要求 2 空格但显式写出来可以避免不同版本默认值漂移。vue/html-indent和vue/script-indent分别管模板和脚本块的缩进SwitchCase: 1表示 switch 的 case 缩进一级。接下来是 VSCode 的settings.json。你可以放在用户级全局生效也可以放在项目级.vscode/settings.json推荐团队共享。项目级配置如下{ editor.tabSize: 2, editor.insertSpaces: true, editor.detectIndentation: false, editor.formatOnSave: true, editor.codeActionsOnSave: { source.fixAll.eslint: explicit }, eslint.validate: [ javascript, javascriptreact, vue, html ], eslint.run: onType, [javascript]: { editor.defaultFormatter: dbaeumer.vscode-eslint }, [vue]: { editor.defaultFormatter: dbaeumer.vscode-eslint }, prettier.useTabs: false, prettier.tabWidth: 2 }这里有几个容易踩的坑。editor.detectIndentation必须设为false否则 VSCode 会「智能」地根据文件已有缩进覆盖你的 tabSize这正是很多人设了 2 却依然报 4 的元凶。source.fixAll.eslint设为explicit表示保存时显式触发 ESLint 修复比true更可控。eslint.validate里一定要包含vue否则.vue文件不会被校验。如果你用 TaoToken 做 AI 辅助的批量修复可以在项目里加一个.taotoken.json来统一通道参数避免每个脚本各写一份{ baseUrl: https://taotoken.net/api, apiKeyEnv: TAOTOKEN_API_KEY, modelId: claude-3-5-sonnet, timeoutMs: 30000 }注意apiKeyEnv指向环境变量名不要把 Key 明文写进文件提交到仓库。本地在.env.local或 shell 里设置TAOTOKEN_API_KEYsk-你的Key。这样 Base URL、Key、Model ID 三件套就齐了后面验证请求时直接读这个文件。配置改完记得重启 VSCode 的 ESLint 服务按CtrlShiftP输入ESLint: Restart ESLint Server。不重启的话旧规则还在内存里你会以为配置没生效。4. 验证请求确认报错消失、团队规则一致配置写完不能只看编辑器有没有波浪线要用真实请求和命令验证。第一步在终端跑一次 lint看Expected indentation of 2 spaces but found 4是否还在npx eslint src/**/*.{js,vue} --fix如果之前有 4 空格的文件--fix会自动改成 2 空格。跑完再执行一次不带--fix的检查npx eslint src/**/*.{js,vue}没有任何输出就说明通过了。如果还有报错会明确列出文件名和行号按行号去改。第二步验证 TaoToken 通道是否可用。用 curl 发一个最小请求确认 Key 和 Base URL 正确curl -X POST https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: claude-3-5-sonnet, messages: [ {role: user, content: 把这段 4 空格缩进的 JS 改成 2 空格function a() {\n return 1;\n}} ] }返回里能看到choices数组和模型输出就说明通道通了。如果返回 401说明 Key 没读到或写错了如果返回local proxy failed说明 Base URL 写成了本地代理地址改回https://taotoken.net/api。第三步在 VSCode 里做一次端到端验证。故意把一个.vue文件的缩进改成 4 空格保存。观察两件事红色波浪线是否出现以及保存后是否被自动修复成 2 空格。如果波浪线出现但没自动修复检查editor.codeActionsOnSave是否写对以及 ESLint 插件是否在运行状态底部状态栏有 ESLint 字样。第四步团队一致性验证。让同事拉取同一份.vscode/settings.json和.eslintrc.js各自跑一遍npx eslint src/**/*.{js,vue}输出应该完全一致。如果某人报错不同八成是他本地有用户级settings.json覆盖了项目级配置让他检查CtrlShiftP→Preferences: Open User Settings (JSON)里有没有冲突项。实测下来把editor.detectIndentation关掉这一步最容易被忽略但它是解决「设了 2 还报 4」的关键。很多人只改 tabSize 不改这一项等于没改。5. 常见报错排查401、local proxy failed、reading choices、OAuth 逐条对照配置和验证过程中会遇到几类典型报错这里逐条对照给出原因和修法。401 Unauthorized。出现在 curl 或脚本调用 TaoToken 时。原因通常是TAOTOKEN_API_KEY环境变量没设置或者 Key 复制时带了空格。检查方式echo $TAOTOKEN_API_KEY看是否有值。修法重新在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 复制 Key注意不要带首尾空格然后export TAOTOKEN_API_KEYsk-xxx重新设置。如果是 Windows PowerShell用$env:TAOTOKEN_API_KEYsk-xxx。local proxy failed。这个报错说明请求被发到了本地某个代理端口而不是https://taotoken.net/api。常见于之前配过其他工具留下的环境变量比如HTTP_PROXY或HTTPS_PROXY指向了127.0.0.1:xxxx。修法unset HTTP_PROXY HTTPS_PROXY清掉或者检查.taotoken.json里的baseUrl是否被误写成http://localhost:...。Base URL 必须是https://taotoken.net/api结尾不要多加/v1路径拼接由客户端负责。reading choices of undefined。这是解析响应时的报错意思是返回体里没有choices字段。原因通常是请求根本没成功返回的是错误对象但代码直接去读response.choices[0]。修法在读取前先判断response.error打印完整响应体。常见触发是 Model ID 写错比如写了一个 TaoToken 不支持的模型名返回错误但没有choices。对照文档里的 Model ID 列表改对即可。OAuth 相关报错。如果你用 Claude Code 或类似 Agent 接入可能会看到 OAuth token 失效的提示。这类工具默认走 Anthropic 官方 OAuth切到 TaoToken 需要改用 API Key 模式。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite Claude Code 专用入口在 https://taotoken.net/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_contentClaudeCodeAnthropicutm_campaignrewrite 。按文档把认证方式从 OAuth 改成 API KeyBase URL 填https://taotoken.net/apiModel ID 填文档里指定的值。ESLint 报错改了配置还在。这不是网络问题是缓存问题。ESLint 有缓存目录跑npx eslint --no-cache src/**/*.{js,vue}跳过缓存验证。如果跳过缓存后通过说明缓存脏了删掉node_modules/.cache/eslint再跑。保存时不自动修复。检查三点editor.formatOnSave是否为trueeditor.codeActionsOnSave里source.fixAll.eslint是否为explicitESLint 插件是否安装并启用。三者缺一不可。另外如果项目里同时装了 Prettier 和 ESLint 插件两者可能打架建议统一用 ESLint 作为 formatter把 Prettier 的editor.defaultFormatter让给dbaeumer.vscode-eslint。排查时记住一个原则先确认请求通不通curl 验证再确认规则对不对eslint 命令行验证最后确认编辑器行为保存自动修复。三层分开查比一上来就改配置高效得多。6. 把校验通道固定下来长期编码与 Agent 任务的接入建议配置改完、报错消失只是第一步。真正让团队不再反复踩缩进坑的是把校验通道固定成一份可复制的标准。我的做法是在项目根目录放三个文件.eslintrc.js管规则.vscode/settings.json管编辑器行为.taotoken.json管模型调用通道。新同事 clone 下来装好 ESLint 插件设一个TAOTOKEN_API_KEY环境变量就能和所有人跑出一致结果。如果你经常用编码 Agent 做批量格式化或规则迁移建议走 Coding Plan入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 它适合长期、多轮的 Agent 任务不用每次单独配 Key。临时验证模型是否可用用模型对话入口 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 最快发一句话就能确认通道状态。还有一个实用技巧把npx eslint src/**/*.{js,vue}写进package.json的scripts命名成lint:checkCI 里直接跑这个命令。这样本地和 CI 用的是同一份.eslintrc.js规则不会漂移。配合.taotoken.json里的 Base URL 和 Model IDAI 辅助修复脚本也能复用同一套参数不用在每个脚本里硬编码。最后提醒一句editor.detectIndentation这个开关建议直接写进项目级settings.json并提交到仓库。它不依赖个人习惯是团队一致性的底线。缩进报错看着小但它消耗的是每次提交前的对齐时间和 code review 的注意力把这些固定下来省下的时间够你多写好几个组件。