You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue3集成Eslint保存时代码缩进自动还原问题求助

解决Vue3项目中ESLint缩进异常问题

问题现象

  • .ts文件和Vue文件的style部分格式正常,但template中的组件props、整个script部分存在缩进问题
  • 保存文件时,代码会先被格式化为预期的缩进样式,随即又还原为无缩进状态

示例代码(script部分)

初始状态:

type MessagesType = {
  title: string;
  message: string;
  type?: string;
};

预期状态:

type MessagesType = {
    title: string;
    message: string;
    type?: string;
  };

无论初始是哪种状态,保存后都会还原为无缩进的初始状态。

项目配置文件

.eslintrc.js

module.exports = {
  root: true,
  env: {
    commonjs: true,
    jest: true,
    es2022: true,
  },

  parser: 'vue-eslint-parser',
  parserOptions: {
    parser: '@typescript-eslint/parser',
  },
  plugins: ['@typescript-eslint', 'import'],

  extends: [
    'eslint:recommended',
    'plugin:import/recommended',
    'plugin:import/typescript',
    'plugin:vue/vue3-recommended',
    'plugin:@typescript-eslint/recommended',
  ],

  rules: {
    // global rules:
    'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'warn',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'warn',
    'no-async-promise-executor': 'warn',
    'no-trailing-spaces': 'warn',
    'no-template-curly-in-string': 'warn',
    'no-multi-spaces': 'warn',


    // typescript rules:
    '@typescript-eslint/no-non-null-assertion': 'off',
    '@typescript-eslint/no-this-alias': 'off',
    '@typescript-eslint/member-delimiter-style': 'warn',
    '@typescript-eslint/type-annotation-spacing': 'warn',
    '@typescript-eslint/consistent-type-definitions': ['warn', 'type'],
    '@typescript-eslint/no-require-imports': 'warn',
    '@typescript-eslint/prefer-for-of': 'warn',
    '@typescript-eslint/ban-ts-comment': 'warn',

    'semi': 'off',
    '@typescript-eslint/semi': 'warn',

    'comma-dangle': 'off',
    '@typescript-eslint/comma-dangle': ['warn', 'always-multiline'],

    'indent': 'off',
    '@typescript-eslint/indent': ['warn', 2],

    'object-curly-spacing': 'off',
    '@typescript-eslint/object-curly-spacing': ['error', 'always'],

    'quotes': 'off',
    '@typescript-eslint/quotes': ['warn', 'single'],

    'comma-spacing': 'off',
    '@typescript-eslint/comma-spacing': 'warn',

    'block-spacing': 'off',
    '@typescript-eslint/block-spacing': 'warn',

    'func-call-spacing': 'off',
    '@typescript-eslint/func-call-spacing': 'warn',

    'key-spacing': 'off',
    '@typescript-eslint/key-spacing': 'warn',

    'keyword-spacing': 'off',
    '@typescript-eslint/keyword-spacing': 'warn',

    'space-before-blocks': 'off',
    '@typescript-eslint/space-before-blocks': 'warn',

    'space-before-function-paren': 'off',
    '@typescript-eslint/space-before-function-paren': ['warn', {
      'anonymous': 'always',
      'named': 'never',
      'asyncArrow': 'always',
    }],

    'space-infix-ops': 'off',
    '@typescript-eslint/space-infix-ops': 'warn',

    'no-extra-semi': 'off',
    '@typescript-eslint/no-extra-semi': 'warn',

    'no-unused-expressions': 'off',
    '@typescript-eslint/no-unused-expressions': 'warn',

    '@typescript-eslint/consistent-type-imports': ['warn', {
      prefer: 'type-imports',
      disallowTypeAnnotations: true,
      fixStyle: 'inline-type-imports',
    }],


    // Vue specific rules:
    'vue/attribute-hyphenation': 'off',
    'vue/v-on-event-hyphenation': 'off',
    'vue/script-indent': ['warn', 2, { 'baseIndent': 1 }],
    'vue/max-attributes-per-line': ['warn', {
      'singleline': {
        'max': 4,
      },
      'multiline': {
        'max': 1,
      },
    }],
    'vue/no-unused-components': 'warn',
    'vue/no-unused-vars': 'warn',
    'vue/component-name-in-template-casing': 'warn',
    'vue/component-options-name-casing': 'warn',
    'vue/custom-event-name-casing': 'warn',
    'vue/define-macros-order': 'warn',
    'vue/define-props-declaration': ['error', 'runtime'],
    'vue/no-unused-refs': 'warn',
    'vue/padding-line-between-blocks': 'warn',
    'vue/prefer-prop-type-boolean-first': 'warn',


    // import rules:
    'import/no-unresolved': 'warn',
    'import/order': ['warn',
      {
        'groups': [
          ['builtin', 'external'],
          'internal',
          ['sibling', 'parent', 'index'],
          // 'type',
        ],
        'newlines-between': 'always',
        'warnOnUnassignedImports': true,
      },
    ],
    'import/consistent-type-specifier-style': ['warn', 'prefer-inline'],
    'import/no-duplicates': ['warn', { 'prefer-inline': true }],
  },

  settings: {
    'import/parsers': {
      '@typescript-eslint/parser': ['.ts', '.tsx'],
    },
    'import/resolver': {
      'typescript': {
        'alwaysTryTypes': true,
      },
    },
  },

  overrides: [
    {
      // disable original/ts indent rule for .vue files, to allow vue/script-indent handle them (otherwise both will)
      files: ['*.vue'],
      rules: {
        'indent': 'off',
        '@typescript-eslint/indent': 'off',
      },
    },

    {
      files: [
        '*.spec.{j,t}s',
      ],
      rules: {
        'no-unused-expressions': 'off',
      },
    },
    {
      files: [
        '**/__tests__/*.{j,t}s?(x)',
        '**/tests/unit/**/*.spec.{j,t}s?(x)',
      ],
      env: {
        jest: true,
      },
    },
  ],

};

package.json

{
  "name": "login",
  "version": "3.3.3",
  "private": true,
  "scripts": {
    "start": "vite --port=8082",
    "build": "vite build",
    "lint:production": ""
  },
  "dependencies": {
    "@quasar/extras": "^1.16.6",
    "axios": "^1.5.0",
    "node": "^16.20.2",
    "pinia": "^2.1.6",
    "quasar": "^2.12.7",
    "vue": "^3.3.4",
    "vue-router": "^4.2.4"
  },
  "devDependencies": {
    "@quasar/vite-plugin": "^1.5.0",
    "@types/node": "^20.8.2",
    "@typescript-eslint/eslint-plugin": "^5.59.9",
    "@typescript-eslint/parser": "^5.62.0",
    "@vitejs/plugin-vue": "^4.2.3",
    "eslint": "^8.51.0",
    "eslint-import-resolver-typescript": "^3.6.1",
    "eslint-plugin-import": "^2.28.1",
    "eslint-plugin-vue": "^9.17.0",
    "sass": "^1.32.12",
    "typescript": "^5.0.2",
    "vite": "^4.4.5",
    "vue-tsc": "^1.8.5"
  },
  "pre-commit": [
    "lint:production"
  ]
}

解决步骤

1. 排查编辑器格式化工具冲突

  • 若使用VSCode,检查是否同时启用了ESLint和其他格式化工具(如内置TypeScript格式化、Prettier),规则不一致会导致保存时互相覆盖。
  • 在VSCode的settings.json中配置,指定ESLint为Vue和TypeScript文件的默认格式化工具:
    "[vue]": {
      "editor.defaultFormatter": "dbaeumer.vscode-eslint"
    },
    "[typescript]": {
      "editor.defaultFormatter": "dbaeumer.vscode-eslint"
    },
    "editor.codeActionsOnSave": {
      "source.fixAll.eslint": true
    }
    

2. 调整ESLint缩进规则

  • 针对Vue文件的script部分,完善vue/script-indent规则配置,确保和TypeScript缩进规则对齐:
    'vue/script-indent': ['warn', 2, { 
      baseIndent: 1,
      switchCase: 1,
      ignores: []
    }]
    
  • 确认@typescript-eslint/indent规则的缩进值为2,与Vue规则保持一致。

3. 同步TypeScript配置

  • 检查tsconfig.json中的格式化相关配置(如indentSize),确保和ESLint的缩进规则匹配。
  • 若项目使用.editorconfig文件,同步其中的indent_size设置为2。

4. 清除缓存并重启服务

  • 执行命令清除ESLint缓存:
    npx eslint --cache --clear
    
  • 重启VSCode和Vite开发服务,避免缓存导致规则不生效。

内容的提问来源于stack exchange,提问作者Narok

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 15:44:54