Skip to content

代码格式化开发文档

1. 概述

梵医云前端项目使用多种工具进行代码格式化和质量检查,包括Prettier、ESLint、Stylelint和EditorConfig,确保代码风格统一和质量可控。

1.1 代码格式化工具

工具用途配置文件
Prettier代码格式化prettier.config.js
ESLintJavaScript/TypeScript代码检查.eslintrc.js
StylelintCSS/SCSS代码检查stylelint.config.js
EditorConfig编辑器配置.editorconfig
HuskyGit钩子.husky/
lint-staged暂存文件检查package.json

1.2 格式化特点

  • 自动格式化:保存时自动格式化代码
  • Git钩子:提交前自动检查代码
  • 统一风格:团队代码风格统一
  • 类型安全:TypeScript类型检查
  • Vue支持:完整的Vue 3支持

2. Prettier配置

2.1 配置文件

配置文件:[prettier.config.js](file:///e:/git.fanyicloud.com.cn/fanyi-cloud-ui/prettier.config.js)

javascript
module.exports = {
  printWidth: 100,           // 每行代码长度(默认80)
  tabWidth: 2,               // 每个tab相当于多少个空格(默认2)
  useTabs: false,             // 是否使用tab进行缩进(默认false)
  semi: false,               // 声明结尾使用分号(默认true)
  vueIndentScriptAndStyle: false,
  singleQuote: true,          // 使用单引号(默认false)
  quoteProps: 'as-needed',    // 对象属性引号策略
  bracketSpacing: true,       // 对象字面量的大括号间使用空格(默认true)
  trailingComma: 'none',      // 多行使用拖尾逗号(默认none)
  jsxSingleQuote: false,
  arrowParens: 'always',     // 箭头函数参数括号
  insertPragma: false,
  requirePragma: false,
  proseWrap: 'never',
  htmlWhitespaceSensitivity: 'strict',
  endOfLine: 'auto',
  rangeStart: 0
}

2.2 配置说明

配置项说明
printWidth100每行最大字符数
tabWidth2缩进空格数
useTabsfalse使用空格而非tab
semifalse不使用分号
singleQuotetrue使用单引号
quoteProps'as-needed'按需添加属性引号
bracketSpacingtrue对象括号内添加空格
trailingComma'none'不使用尾随逗号
arrowParens'always'箭头函数始终使用括号

2.3 使用Prettier

命令行使用

bash
# 格式化所有文件
pnpm lint:format

# 格式化指定文件
pnpm prettier --write src/**/*.vue

# 检查文件格式
pnpm prettier --check src/**/*.vue

VS Code集成

在 [.vscode/settings.json](file:///e:/git.fanyicloud.com.cn/fanyi-cloud-ui/.vscode/settings.json) 中配置:

json
{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true,
  "prettier.printWidth": 100,
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[vue]": {
    "editor.defaultFormatter": "Vue.volar"
  }
}

格式化示例

格式化前:

javascript
const obj = { name: "张三", age: 25, address: "北京市" };
const arr = [1,2,3,4,5];
const fn = (a,b)=>{return a+b};

格式化后:

javascript
const obj = { name: '张三', age: 25, address: '北京市' }
const arr = [1, 2, 3, 4, 5]
const fn = (a, b) => {
  return a + b
}

3. ESLint配置

3.1 配置文件

配置文件:[.eslintrc.js](file:///e:/git.fanyicloud.com.cn/fanyi-cloud-ui/.eslintrc.js)

javascript
const { defineConfig } = require('eslint-define-config')
module.exports = defineConfig({
  root: true,
  env: {
    browser: true,
    node: true,
    es6: true
  },
  parser: 'vue-eslint-parser',
  parserOptions: {
    parser: '@typescript-eslint/parser',
    ecmaVersion: 2020,
    sourceType: 'module',
    jsxPragma: 'React',
    ecmaFeatures: {
      jsx: true
    }
  },
  extends: [
    'plugin:vue/vue3-recommended',
    'plugin:@typescript-eslint/recommended',
    'prettier',
    'plugin:prettier/recommended',
    '@unocss'
  ],
  rules: {
    'vue/no-setup-props-destructure': 'off',
    'vue/script-setup-uses-vars': 'error',
    'vue/no-reserved-component-names': 'off',
    '@typescript-eslint/ban-ts-ignore': 'off',
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/no-explicit-any': 'off',
    '@typescript-eslint/no-var-requires': 'off',
    '@typescript-eslint/no-empty-function': 'off',
    'vue/custom-event-name-casing': 'off',
    'no-use-before-define': 'off',
    '@typescript-eslint/no-use-before-define': 'off',
    '@typescript-eslint/ban-ts-comment': 'off',
    '@typescript-eslint/ban-types': 'off',
    '@typescript-eslint/no-non-null-assertion': 'off',
    '@typescript-eslint/explicit-module-boundary-types': 'off',
    '@typescript-eslint/no-unused-vars': 'off',
    'no-unused-vars': 'off',
    'space-before-function-paren': 'off',
    'vue/attributes-order': 'off',
    'vue/one-component-per-file': 'off',
    'vue/html-closing-bracket-newline': 'off',
    'vue/max-attributes-per-line': 'off',
    'vue/multiline-html-element-content-newline': 'off',
    'vue/singleline-html-element-content-newline': 'off',
    'vue/attribute-hyphenation': 'off',
    'vue/require-default-prop': 'off',
    'vue/require-explicit-emits': 'off',
    'vue/require-toggle-inside-transition': 'off',
    'vue/html-self-closing': 'off',
    'vue/first-attribute-linebreak': 'off',
    'vue/multi-word-component-names': 'off',
    'vue/no-v-html': 'off',
    'prettier/prettier': 'off',
    '@unocss/order': 'off',
    '@unocss/order-attributify': 'off'
  }
})

3.2 主要规则说明

规则说明
vue/script-setup-uses-varserror检查script setup中使用的变量
@typescript-eslint/no-explicit-anyoff允许使用any类型
@typescript-eslint/no-unused-varsoff关闭未使用变量检查
vue/no-v-htmloff允许使用v-html
prettier/prettieroff关闭Prettier的ESLint校验

3.3 使用ESLint

命令行使用

bash
# 检查代码
pnpm lint:eslint

# 自动修复
pnpm lint:eslint --fix

# 检查指定文件
pnpm eslint src/**/*.vue

VS Code集成

在 [.vscode/settings.json](file:///e:/git.fanyicloud.com.cn/fanyi-cloud-ui/.vscode/settings.json) 中配置:

json
{
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  }
}

常见错误及修复

错误:未使用的变量

typescript
// 错误
const unused = 123

// 修复:删除未使用的变量或使用下划线前缀
const _unused = 123

错误:未定义的变量

typescript
// 错误
console.log(undefinedVar)

// 修复:定义变量
const undefinedVar = 'value'
console.log(undefinedVar)

错误:Vue组件命名

vue
<!-- 错误:组件名不是多词 -->
<script setup>
const Header = defineComponent({
  name: 'Header'
})
</script>

<!-- 修复:使用多词组件名 -->
<script setup>
const PageHeader = defineComponent({
  name: 'PageHeader'
})
</script>

4. Stylelint配置

4.1 配置文件

配置文件:[stylelint.config.js](file:///e:/git.fanyicloud.com.cn/fanyi-cloud-ui/stylelint.config.js)

javascript
module.exports = {
  root: true,
  plugins: ['stylelint-order'],
  customSyntax: 'postcss-html',
  extends: ['stylelint-config-standard'],
  rules: {
    'selector-pseudo-class-no-unknown': [
      true,
      {
        ignorePseudoClasses: ['global', 'deep']
      }
    ],
    'at-rule-no-unknown': [
      true,
      {
        ignoreAtRules: ['function', 'if', 'each', 'include', 'mixin']
      }
    ],
    'media-query-no-invalid': null,
    'function-no-unknown': null,
    'no-empty-source': null,
    'named-grid-areas-no-invalid': null,
    'unicode-bom': 'never',
    'no-descending-specificity': null,
    'font-family-no-missing-generic-family-keyword': null,
    'declaration-colon-space-after': 'always-single-line',
    'declaration-colon-space-before': 'never',
    'declaration-block-trailing-semicolon': null,
    'rule-empty-line-before': [
      'always',
      {
        ignore: ['after-comment', 'first-nested']
      }
    ],
    'unit-no-unknown': [
      true,
      {
        ignoreUnits: ['rpx']
      }
    ],
    'order/order': [
      [
        'dollar-variables',
        'custom-properties',
        'at-rules',
        'declarations',
        {
          type: 'at-rule',
          name: 'supports'
        },
        {
          type: 'at-rule',
          name: 'media'
        },
        'rules'
      ],
      {
        severity: 'warning'
      }
    ],
    'order/properties-order': [
      'position',
      'top',
      'right',
      'bottom',
      'left',
      'z-index',
      'display',
      'float',
      'width',
      'height',
      // ... 更多属性顺序规则
    ]
  },
  ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'],
  overrides: [
    {
      files: ['*.vue', '**/*.vue', '*.html', '**/*.html'],
      extends: ['stylelint-config-recommended', 'stylelint-config-html'],
      rules: {
        'keyframes-name-pattern': null,
        'selector-class-pattern': null,
        'no-duplicate-selectors': null,
        'selector-pseudo-class-no-unknown': [
          true,
          {
            ignorePseudoClasses: ['deep', 'global']
          }
        ],
        'selector-pseudo-element-no-unknown': [
          true,
          {
            ignorePseudoElements: ['v-deep', 'v-global', 'v-slotted']
          }
        ]
      }
    }
  ]
}

4.2 主要规则说明

规则说明
declaration-colon-space-afteralways-single-line单行声明冒号后加空格
declaration-colon-space-beforenever冒号前不加空格
unit-no-unknowntrue禁止未知单位(忽略rpx)
order/order自定义属性顺序规则
order/properties-order自定义CSS属性顺序

4.3 使用Stylelint

命令行使用

bash
# 检查样式文件
pnpm lint:style

# 自动修复
pnpm lint:style --fix

# 检查指定文件
pnpm stylelint src/**/*.scss

VS Code集成

在 [.vscode/settings.json](file:///e:/git.fanyicloud.com.cn/fanyi-cloud-ui/.vscode/settings.json) 中配置:

json
{
  "stylelint.enable": true,
  "stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass"],
  "editor.codeActionsOnSave": {
    "source.fixAll.stylelint": "explicit"
  }
}

格式化示例

格式化前:

scss
.container{width:100%;height:200px;margin:0;padding:10px;background-color:#fff}

格式化后:

scss
.container {
  width: 100%;
  height: 200px;
  margin: 0;
  padding: 10px;
  background-color: #fff;
}

5. EditorConfig配置

5.1 配置文件

配置文件:[.editorconfig](file:///e:/git.fanyicloud.com.cn/fanyi-cloud-ui/.editorconfig)

ini
root = true

[*.{js,ts,vue}]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
max_line_length = 100

[*.md]
max_line_length = off
trim_trailing_whitespace = false

5.2 配置说明

配置项说明
charsetutf-8文件字符编码
end_of_linelf换行符类型
insert_final_newlinetrue文件末尾插入新行
indent_stylespace缩进风格
indent_size2缩进大小
max_line_length100最大行长度

5.3 VS Code支持

VS Code内置支持EditorConfig,确保安装了EditorConfig扩展:

bash
code --install-extension EditorConfig.EditorConfig

6. Git钩子配置

6.1 lint-staged配置

在 [package.json](file:///e:/git.fanyicloud.com.cn/fanyi-cloud-ui/package.json) 中配置:

json
{
  "lint-staged": {
    "*.{js,ts,vue}": [
      "eslint --fix",
      "prettier --write"
    ],
    "*.{css,scss}": [
      "stylelint --fix",
      "prettier --write"
    ]
  }
}

6.2 Husky配置

Husky用于在Git操作时自动执行检查:

bash
# 安装Husky
pnpm install husky --save-dev

# 初始化Husky
pnpm husky install

# 添加pre-commit钩子
pnpm husky add .husky/pre-commit "npx lint-staged"

6.3 提交前检查

提交代码时,Husky会自动执行以下检查:

  1. 对暂存的JS/TS/Vue文件执行ESLint检查和修复
  2. 对暂存的CSS/SCSS文件执行Stylelint检查和修复
  3. 对所有文件执行Prettier格式化

如果检查失败,提交将被阻止。

7. 使用指南

7.1 日常开发流程

bash
# 1. 开发代码
# ...

# 2. 格式化代码
pnpm lint:format

# 3. 检查代码
pnpm lint:eslint
pnpm lint:style

# 4. 提交代码(自动执行检查)
git add .
git commit -m "feat: 添加新功能"

7.2 VS Code自动格式化

配置VS Code在保存时自动格式化:

json
{
  "editor.formatOnSave": true,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit",
    "source.fixAll.stylelint": "explicit"
  }
}

7.3 忽略格式化

如果需要忽略某些文件的格式化,可以创建 .prettierignore 文件:

text
# 忽略构建文件
dist/
build/

# 忽略依赖
node_modules/

# 忽略配置文件
*.config.js

8. 最佳实践

8.1 代码风格规范

JavaScript/TypeScript

typescript
// 推荐:使用const和let
const name = '张三'
let count = 0

// 不推荐:使用var
var name = '张三'

// 推荐:使用箭头函数
const add = (a: number, b: number): number => {
  return a + b
}

// 不推荐:使用function
function add(a: number, b: number): number {
  return a + b
}

// 推荐:使用模板字符串
const message = `你好,${name}`

// 不推荐:使用字符串拼接
const message = '你好,' + name

Vue组件

vue
<!-- 推荐:使用script setup -->
<script setup lang="ts">
import { ref } from 'vue'

const count = ref(0)
</script>

<!-- 不推荐:使用Options API -->
<script>
export default {
  data() {
    return {
      count: 0
    }
  }
}
</script>

CSS/SCSS

scss
// 推荐:使用嵌套
.container {
  width: 100%;
  
  .header {
    padding: 10px;
  }
}

// 不推荐:扁平化
.container {
  width: 100%;
}

.container .header {
  padding: 10px;
}

8.2 命名规范

typescript
// 组件名:PascalCase
const UserProfile = defineComponent({})

// 变量名:camelCase
const userName = '张三'
const isLoggedIn = true

// 常量名:UPPER_SNAKE_CASE
const MAX_COUNT = 100
const API_BASE_URL = 'https://api.example.com'

// 函数名:camelCase
const getUserInfo = () => {}
const handleSubmit = () => {}

// 类名:PascalCase
class UserService {}
class HttpClient {}

// 接口名:PascalCase
interface UserInfo {}
interface ApiResponse {}

8.3 注释规范

typescript
// 单行注释:解释代码意图
const userAge = 25 // 用户年龄

// 多行注释:复杂逻辑说明
/**
 * 计算用户年龄
 * @param birthDate 出生日期
 * @returns 年龄
 */
const calculateAge = (birthDate: Date): number => {
  const today = new Date()
  const birth = new Date(birthDate)
  let age = today.getFullYear() - birth.getFullYear()
  const monthDiff = today.getMonth() - birth.getMonth()
  
  if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
    age--
  }
  
  return age
}

// TODO注释:标记待办事项
// TODO: 添加用户验证逻辑

// FIXME注释:标记需要修复的问题
// FIXME: 这个方法有性能问题,需要优化

8.4 导入顺序

typescript
// 1. Vue相关导入
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'

// 2. 第三方库导入
import axios from 'axios'
import dayjs from 'dayjs'

// 3. 项目内部导入
import { useUserStore } from '@/store/modules/user'
import { getUserInfo } from '@/api/user'

// 4. 类型导入
import type { UserInfo } from '@/types/user'

9. 常见问题

9.1 格式化不生效

问题原因

  • Prettier扩展未安装
  • 配置文件路径错误
  • VS Code设置冲突

解决方案

bash
# 确保安装Prettier扩展
code --install-extension esbenp.prettier-vscode

# 检查配置文件
ls -la | grep prettier

# 重启VS Code

9.2 ESLint和Prettier冲突

问题原因

  • ESLint规则与Prettier规则冲突
  • 未安装eslint-config-prettier

解决方案

javascript
// .eslintrc.js
module.exports = {
  extends: [
    'plugin:vue/vue3-recommended',
    'plugin:@typescript-eslint/recommended',
    'prettier', // 必须放在最后
    'plugin:prettier/recommended'
  ]
}

9.3 Git钩子不执行

问题原因

  • Husky未正确安装
  • Git钩子权限问题

解决方案

bash
# 重新安装Husky
pnpm install husky --save-dev
pnpm husky install

# 检查钩子文件
ls -la .husky/

# 手动设置钩子权限
chmod +x .husky/pre-commit

9.4 提交被阻止

问题原因

  • 代码格式不正确
  • 存在ESLint错误

解决方案

bash
# 手动修复代码
pnpm lint:eslint --fix
pnpm lint:style --fix
pnpm lint:format

# 如果无法修复,可以强制提交(不推荐)
git commit --no-verify -m "message"

10. 完整示例

10.1 Vue组件示例

vue
<template>
  <div class="user-profile">
    <h1 class="user-profile__name">{{ userName }}</h1>
    <p class="user-profile__email">{{ userEmail }}</p>
    <el-button @click="handleLogout">退出登录</el-button>
  </div>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/store/modules/user'

const router = useRouter()
const userStore = useUserStore()

const userInfo = ref<UserInfo | null>(null)

const userName = computed(() => userInfo.value?.name || '未知用户')
const userEmail = computed(() => userInfo.value?.email || '')

const handleLogout = () => {
  userStore.logout()
  router.push('/login')
}

onMounted(async () => {
  userInfo.value = await userStore.getUserInfo()
})
</script>

<style lang="scss" scoped>
.user-profile {
  padding: 20px;
  
  &__name {
    font-size: 24px;
    color: #333;
  }
  
  &__email {
    font-size: 14px;
    color: #666;
    margin-top: 10px;
  }
}
</style>

10.2 TypeScript模块示例

typescript
import axios from 'axios'
import type { UserInfo, ApiResponse } from '@/types'

const API_BASE_URL = import.meta.env.VITE_API_URL

export class UserService {
  /**
   * 获取用户信息
   * @param userId 用户ID
   * @returns 用户信息
   */
  static async getUserInfo(userId: number): Promise<UserInfo> {
    const response = await axios.get<ApiResponse<UserInfo>>(
      `${API_BASE_URL}/user/${userId}`
    )
    return response.data.data
  }

  /**
   * 更新用户信息
   * @param userId 用户ID
   * @param data 更新数据
   * @returns 更新后的用户信息
   */
  static async updateUser(
    userId: number,
    data: Partial<UserInfo>
  ): Promise<UserInfo> {
    const response = await axios.put<ApiResponse<UserInfo>>(
      `${API_BASE_URL}/user/${userId}`,
      data
    )
    return response.data.data
  }
}

注意:本文档持续更新中,如有问题请及时反馈。