Skip to content

开发规范

本文档定义 vue3-element-admin 项目的编码规范,基于 Vue 官方风格指南、TypeScript 最佳实践及业界标杆项目经验整理。

参考来源

来源说明
Vue 官方风格指南Vue 组件命名、Props 定义等
TypeScript 官方规范类型定义最佳实践
Airbnb JavaScript Style GuideJS 命名、导入顺序等
Google TypeScript Style GuideTS 编码规范
VueUseComposables 命名参考
Element Plus组件库源码参考
Vben Admin企业级项目结构参考

命名规范

JavaScript / TypeScript 变量

类型风格示例参考
变量camelCaseuserName, isLoadingAirbnb
常量UPPER_SNAKE_CASEMAX_COUNT, API_BASE_URLAirbnb
函数camelCasegetUserInfo, handleClickAirbnb
PascalCaseUserService, ApiClientAirbnb
枚举PascalCaseStatusEnum, ThemeModeTypeScript
枚举值UPPER_SNAKE_CASEStatusEnum.ACTIVEGoogle TS
类型/接口PascalCaseUserInfo, ApiResponseTypeScript
泛型参数单字母大写或描述性T, TData, TResponseTypeScript
typescript
// ✅ 正确示例
const userName = "admin";
const MAX_RETRY_COUNT = 3;

function getUserById(id: number): Promise<UserInfo> {}

enum StatusEnum {
  ACTIVE = 1,
  INACTIVE = 0,
}

interface UserInfo {
  id: number;
  name: string;
}

type ApiResponse<T> = {
  code: number;
  data: T;
  message: string;
};

布尔值命名

使用 ishascanshould 等前缀,表意清晰。

typescript
// ✅ 正确
const isLoading = ref(false);
const hasPermission = computed(() => /* ... */);
const canEdit = ref(true);
const shouldRefresh = ref(false);

// ❌ 避免
const loading = ref(false);      // 不够明确
const permission = ref(true);    // 名词,不是布尔语义

参考:Airbnb - Naming Conventions

方法命名规范(Vue 业务场景)

函数/方法统一使用 camelCase以动词开头,体现“动作 + 业务对象 + 场景”。布尔状态使用 is/has/can/should 等前缀。该约定偏向可读性与可维护性,适用于页面、组件、Store、Composables。

核心原则

  • 方法/函数用动词:描述行为,例如 fetchUserListsubmitUserForm
  • 状态/布尔用 is/has/can/should:描述状态,例如 isLoadinghasMorecanEdit
  • 分层约定(本项目推荐)
    • 业务动作用语义动词(fetch/submit/delete/create/update 等),可被多个场景复用。
    • 事件入口/流程编排使用 handleX/onX(例如点击、分页变化、弹窗确认),负责组织多个业务动作与 UI 状态变更。

命名约定表(推荐)

场景推荐命名示例说明
打开/关闭弹窗/抽屉openX / closeX / toggleXopenUserModal() / closeUserDrawer()open/close 明确动作;toggle 用于切换
显示/隐藏(UI 表现)showX / hideXshowToast() / hideTooltip()更偏展示层(toast/tooltip)
表单提交/确认/保存(业务动作)submitX / confirmX / saveXsubmitUserForm() / saveDraft()业务动作命名,submit 多为提交到后端;save 常用于本地或草稿
查询/加载数据fetchX / loadX / searchXfetchUserList() / loadDeptOptions()fetch/load 表示异步取数;search 强调关键字
筛选/重置筛选applyFilter(s) / resetFilter(s)applyFilters() / resetFilters()和查询行为分离,便于复用
新增/编辑/删除createX / updateX / deleteXcreateUser() / deleteUser(id)handleDeleteClick 更语义化
批量操作batchX / batchDeleteXbatchDeleteUsers()明确“批量”语义
状态切换enableX / disableX / toggleXStatustoggleUserStatus()避免 change 过于泛化
事件入口/流程编排(可选)handleX / onXhandleSubmit() / onRowClick()当函数负责编排多个动作时使用,通常会调用 submitX/openX/closeX/fetchX 等业务动作
ComposablesuseXusePagination() / useDialog()Vue 社区约定

常见争议与建议

  • handleX/onX vs 直接动词

    • 推荐:优先用有业务语义的动词(deleteUserexportUsers)。
    • 使用 handle/on 的场景:当它只负责“桥接 UI 事件 + 组织流程”,例如 handleSubmit 内部调用 validateFormsubmitUserFormcloseDialog
  • 异步方法是否加 Async 后缀

    • fetch/load/search 本身已隐含异步语义,一般不必再加 Async
    • 只有在同名同步/异步同时存在且易混淆时,才考虑 xxxAsync

示例(Composition API)

typescript
const isUserModalOpen = ref(false);
const isLoading = ref(false);

function openUserModal() {
  isUserModalOpen.value = true;
}

function closeUserModal() {
  isUserModalOpen.value = false;
}

async function fetchUserList() {
  // ...
}

async function submitUserForm(payload: any) {
  // ...
}

async function deleteUser(id: number) {
  // ...
}

function handleSubmit() {
  // 仅作为事件入口/流程编排时使用 handle
  // validateForm() -> submitUserForm() -> closeUserModal() -> fetchUserList()
}

示例(弹窗/抽屉的分层)

弹窗场景建议把“开关动作”和“事件入口”区分开:

typescript
const isUserDialogOpen = ref(false);

function openUserDialog() {
  isUserDialogOpen.value = true;
}

function closeUserDialog() {
  isUserDialogOpen.value = false;
}

async function handleCreateClick() {
  // resetFormData() / loadOptions() ...
  openUserDialog();
}

async function handleEditClick(id: number) {
  // await fetchUserDetail(id) / fillFormData() ...
  openUserDialog();
}

async function handleDialogConfirm() {
  // validateForm() -> submitUserForm() -> closeUserDialog() -> fetchUserList()
}

避免: doSomething / click / ok / data / submit(无业务语义),以及 dlg / btn / qry 等缩写。

ESLint / TypeScript 命名约束(可选)

可通过 @typescript-eslint/naming-convention 强制布尔变量使用 is/has/can 前缀:

jsonc
// .eslintrc.cjs / .eslintrc.js (片段)
{
  "rules": {
    "@typescript-eslint/naming-convention": [
      "error",
      {
        "selector": "variable",
        "types": ["boolean"],
        "format": ["camelCase"],
        "prefix": ["is", "has", "can"],
      },
      {
        "selector": "function",
        "format": ["camelCase"],
      },
    ],
  },
}

CSS 类名

风格适用场景示例
kebab-case自定义类名.user-card, .nav-menu
BEM复杂组件.user-card__header, .user-card--active
vue
<template>
  <div class="user-card">
    <div class="user-card__header">标题</div>
    <div class="user-card__body user-card__body--highlight">内容</div>
  </div>
</template>

<style lang="scss" scoped>
.user-card {
  &__header {
    /* ... */
  }
  &__body {
    &--highlight {
      /* ... */
    }
  }
}
</style>

参考:BEM 官方文档Element Plus 源码

CSS 变量

scss
// ✅ 正确:使用 -- 前缀,kebab-case
:root {
  --color-primary: #409eff;
  --font-size-base: 14px;
  --spacing-sm: 8px;
  --spacing-md: 16px;
}

// 使用
.button {
  color: var(--color-primary);
  font-size: var(--font-size-base);
}

参考:CSS Custom Properties


文件命名规范

总览

文件类型命名风格示例参考
Vue 组件PascalCaseUserCard.vue, PageHeader.vueVue 官方
页面组件kebab-caseindex.vue, user-list.vueVue 官方
TS/JS 模块kebab-caseuser-service.ts, format-date.ts社区惯例
多词模块kebab-casetags-view.ts, dict-sync.ts文件系统兼容
类型文件kebab-caseuser.ts, api-response.ts社区惯例
测试文件源文件名 + .teststorage.test.tsVitest
样式文件kebab-casevariables.scss, reset.scss社区惯例

Vue 组件文件

src/components/
├── UserCard.vue          # PascalCase(单文件组件)
├── PageHeader/
│   ├── index.vue         # 目录组件入口
│   └── PageHeaderNav.vue # 子组件

参考:Vue 风格指南 - 单文件组件文件名

页面文件

src/views/
├── dashboard/
│   └── index.vue         # 页面入口
├── system/
│   ├── user/
│   │   ├── index.vue     # 用户列表页
│   │   └── components/   # 页面私有组件
│   │       └── UserForm.vue
│   └── role/
│       └── index.vue

API 文件

src/api/
├── system/
│   ├── user.ts           # 用户相关 API
│   ├── role.ts           # 角色相关 API
│   └── menu.ts
├── auth.ts               # 认证 API(单文件不建目录)
├── file.ts
└── types.ts              # 公共类型(可选)

Store 文件

src/store/
├── index.ts
└── modules/
    ├── user.ts           # 用户状态
    ├── app.ts            # 应用状态
    ├── permission.ts     # 权限状态
    ├── settings.ts       # 设置状态
    └── tags-view.ts      # 标签页状态(多词用 kebab-case)

类型文件

类型文件的组织方式有两种主流实践,本项目采用集中式管理

src/types/
├── api/                  # API 相关类型
│   ├── user.ts          # 用户相关类型
│   ├── role.ts          # 角色相关类型
│   └── common.ts        # 公共类型(分页、响应等)
├── ui/                   # UI 相关类型
│   ├── settings.ts      # 设置类型
│   └── tagsview.ts      # 标签页类型
├── index.ts             # 统一导出
└── global.d.ts          # 全局类型声明

两种组织方式对比

方式代表项目优点缺点
集中式Vben Admin、本项目统一管理、便于复用跳转文件多
就近式Element Plus、VueUse改代码时类型就在旁边类型分散

本项目选择集中式的原因

  1. API 类型被多处引用(Store、组件、工具函数)
  2. 便于统一维护和查找
  3. 避免循环引用问题

注:两种方式都是业界认可的实践,没有绝对的对错。选择哪种取决于项目规模和团队偏好。

Composables 文件

src/composables/
├── table/
│   └── useTableSelection.ts    # use 前缀
├── websocket/
│   ├── index.ts                # 统一导出
│   ├── useStomp.ts
│   ├── useDictSync.ts
│   └── useOnlineCount.ts
└── index.ts                    # 统一导出

参考:VueUse 源码结构


组件规范

组件命名

vue
<!-- ✅ 正确:PascalCase,多词命名 -->
<template>
  <UserCard />
  <PageHeader />
  <DictSelect />
</template>

<!-- ❌ 避免:单词命名(除非是基础组件如 Button) -->
<template>
  <Card />
  <Header />
</template>

参考:Vue 风格指南 - 组件名应该是多词的

Props 定义

typescript
// ✅ 正确:使用 defineProps 配合 TypeScript
interface Props {
  /** 用户ID */
  userId: number;
  /** 是否显示头像 */
  showAvatar?: boolean;
  /** 尺寸 */
  size?: "small" | "medium" | "large";
}

const props = withDefaults(defineProps<Props>(), {
  showAvatar: true,
  size: "medium",
});

// ❌ 避免:运行时声明(缺少类型推导)
const props = defineProps({
  userId: Number,
  showAvatar: Boolean,
});

参考:Vue 官方文档 - Props 类型声明

Emits 定义

typescript
// ✅ 正确:类型化的 emits
interface Emits {
  (e: "update:modelValue", value: string): void;
  (e: "change", value: string, oldValue: string): void;
  (e: "submit"): void;
}

const emit = defineEmits<Emits>();

// 使用
emit("update:modelValue", newValue);
emit("change", newValue, oldValue);

组件结构顺序

vue
<script setup lang="ts">
// 1. Vue 核心
import { ref, computed, watch, onMounted } from "vue";
import { useRoute, useRouter } from "vue-router";

// 2. 第三方库
import { ElMessage, ElMessageBox } from "element-plus";
import { useDebounceFn } from "@vueuse/core";
import dayjs from "dayjs";

// 3. 类型导入
import type { FormInstance, FormRules } from "element-plus";
import type { UserInfo } from "@/types";

// 4. 内部模块 - Store
import { useUserStore } from "@/store";

// 5. 内部模块 - API
import UserAPI from "@/api/system/user";

// 6. 内部模块 - 工具函数
import { formatDate } from "@/utils";

// 7. 相对路径 - 组件
import UserCard from "./components/UserCard.vue";

// 8. Props / Emits 定义
interface Props {
  userId: number;
}
const props = defineProps<Props>();
const emit = defineEmits<{ (e: "change"): void }>();

// 9. 响应式状态
const userStore = useUserStore();
const isLoading = ref(false);
const userInfo = ref<UserInfo | null>(null);

// 10. 计算属性
const displayName = computed(() => userInfo.value?.name ?? "未知");

// 11. 监听器
watch(() => props.userId, fetchUser);

// 12. 生命周期
onMounted(() => {
  fetchUser();
});

// 13. 方法
async function fetchUser() {
  // ...
}

// 14. 暴露(如需要)
defineExpose({ refresh: fetchUser });
</script>

<template>
  <!-- 模板内容 -->
</template>

<style lang="scss" scoped>
/* 样式 */
</style>

参考:Vue 风格指南 - 组件选项顺序


导入顺序规范

按以下顺序组织导入语句,组间空行分隔:

typescript
// 1. Node 内置模块
import { resolve } from "path";

// 2. 第三方库
import { ref, computed, watch } from "vue";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import axios from "axios";

// 3. 类型导入(type-only imports)
import type { RouteRecordRaw } from "vue-router";
import type { UserInfo } from "@/types";

// 4. 内部模块 - 绝对路径(按层级)
import { useUserStore } from "@/store";
import UserAPI from "@/api/system/user";
import { formatDate } from "@/utils";
import { STORAGE_KEYS } from "@/constants";

// 5. 内部模块 - 相对路径
import UserCard from "./components/UserCard.vue";
import { useLocalState } from "./composables";

// 6. 样式文件
import "./styles/index.scss";

参考:Airbnb - Importseslint-plugin-import


接口与类型约定

本节约定 vue3-element-admin 在对接后端时,TypeScript 类型与接口层的命名方式。

命名原则

  • 前端类型不使用 DTO/VO/BO 命名
  • 统一使用“语义 + 场景”的类型命名
  • 字段命名以接口返回为准(推荐 camelCase),尽量避免在前端做字段映射

TypeScript 类型命名

语义TypeScript 命名
创建 / 修改请求UserCreateRequest / UserUpdateRequest
查询参数UserQueryParams
列表项UserItem
详情UserDetail
分页结果PageResult<UserItem>

分页与列表约定

  • 列表与分页统一使用:GET /api/v1/{resources}
  • 分页参数:pageNum / pageSize

相关链接

类型定义规范

Interface vs Type

场景推荐原因
对象结构interface可扩展、错误信息更友好
联合类型typeinterface 不支持
函数类型type语法更简洁
工具类型typePartial<T>, Pick<T, K>
typescript
// ✅ 对象结构用 interface
interface UserInfo {
  id: number;
  name: string;
  email: string;
}

// ✅ 可扩展
interface AdminInfo extends UserInfo {
  permissions: string[];
}

// ✅ 联合类型用 type
type Status = "pending" | "success" | "error";
type ID = string | number;

// ✅ 函数类型用 type
type Formatter = (value: number) => string;
type AsyncFn<T> = () => Promise<T>;

// ✅ 工具类型
type PartialUser = Partial<UserInfo>;
type UserName = Pick<UserInfo, "name">;

参考:TypeScript 官方文档 - Interfaces vs Types

API 响应类型

typescript
// 通用响应结构
interface ApiResponse<T = unknown> {
  code: number;
  data: T;
  message: string;
}

// 分页响应
interface PageResult<T> {
  list: T[];
  total: number;
}

// 具体业务类型
interface UserInfo {
  id: number;
  username: string;
  nickname: string;
  avatar?: string;
  roles: string[];
}

// 查询参数
interface UserQuery {
  keywords?: string;
  status?: number;
  deptId?: number;
  pageNum: number;
  pageSize: number;
}

// API 函数返回类型
type UserPageResult = ApiResponse<PageResult<UserInfo>>;

类型文件组织

typescript
// src/types/api/user.ts

/** 用户信息 */
export interface UserInfo {
  id: number;
  username: string;
  nickname: string;
  avatar?: string;
  email?: string;
  mobile?: string;
  status: number;
  deptId: number;
  roles: string[];
}

/** 用户查询参数 */
export interface UserQuery {
  keywords?: string;
  status?: number;
  deptId?: number;
  pageNum: number;
  pageSize: number;
}

/** 用户表单 */
export interface UserForm {
  id?: number;
  username: string;
  nickname: string;
  password?: string;
  email?: string;
  mobile?: string;
  status: number;
  deptId: number;
  roleIds: number[];
}

Composables 规范

命名规范

规则示例说明
use 前缀useUserInfo, useTableSelectionVue 官方约定
动词 + 名词useFetchData, useLocalStorage表意清晰
返回对象{ data, loading, error }解构友好
typescript
// ✅ 正确命名
export function useTableSelection<T>() {
  const selectedItems = ref<T[]>([]);
  const hasSelected = computed(() => selectedItems.value.length > 0);

  function handleSelectionChange(items: T[]) {
    selectedItems.value = items;
  }

  function clearSelection() {
    selectedItems.value = [];
  }

  return {
    selectedItems,
    hasSelected,
    handleSelectionChange,
    clearSelection,
  };
}

参考:VueUse 命名规范

参数设计

typescript
// ✅ 使用 options 对象,便于扩展
interface UseCounterOptions {
  initialValue?: number;
  min?: number;
  max?: number;
}

export function useCounter(options: UseCounterOptions = {}) {
  const { initialValue = 0, min = -Infinity, max = Infinity } = options;

  const count = ref(initialValue);

  function increment() {
    if (count.value < max) count.value++;
  }

  function decrement() {
    if (count.value > min) count.value--;
  }

  return { count, increment, decrement };
}

// 使用
const { count, increment } = useCounter({ initialValue: 10, max: 100 });

返回值设计

typescript
// ✅ 返回响应式引用和方法
export function useFetch<T>(url: string) {
  const data = ref<T | null>(null);
  const error = ref<Error | null>(null);
  const isLoading = ref(false);

  async function execute() {
    isLoading.value = true;
    error.value = null;
    try {
      const response = await fetch(url);
      data.value = await response.json();
    } catch (e) {
      error.value = e as Error;
    } finally {
      isLoading.value = false;
    }
  }

  return {
    data: readonly(data),
    error: readonly(error),
    isLoading: readonly(isLoading),
    execute,
    refresh: execute,
  };
}

Store 规范

命名规范

元素命名示例
Store 函数use + 模块名 + StoreuseUserStore
Store IDcamelCase"user", "tagsView"
StatecamelCaseuserInfo, isLoggedIn
GettercamelCasefullName, isAdmin
ActioncamelCase 动词fetchUser, logout

Setup Store 写法(推荐)

typescript
// src/store/modules/user.ts
import { defineStore } from "pinia";
import type { UserInfo } from "@/types";
import AuthAPI from "@/api/auth";

export const useUserStore = defineStore("user", () => {
  // State
  const userInfo = ref<UserInfo | null>(null);
  const token = ref<string>("");

  // Getters
  const isLoggedIn = computed(() => !!token.value);
  const userId = computed(() => userInfo.value?.id);
  const roles = computed(() => userInfo.value?.roles ?? []);

  // Actions
  async function login(username: string, password: string) {
    const { accessToken } = await AuthAPI.login({ username, password });
    token.value = accessToken;
  }

  async function fetchUserInfo() {
    userInfo.value = await AuthAPI.getUserInfo();
  }

  function logout() {
    token.value = "";
    userInfo.value = null;
  }

  return {
    // State
    userInfo,
    token,
    // Getters
    isLoggedIn,
    userId,
    roles,
    // Actions
    login,
    fetchUserInfo,
    logout,
  };
});

参考:Pinia 官方文档 - Setup Stores

Store Hook(在 setup 外使用)

typescript
// 定义 Hook
import { store } from "@/store";

export function useUserStoreHook() {
  return useUserStore(store);
}

// 在非 setup 上下文使用(如路由守卫)
import { useUserStoreHook } from "@/store";

router.beforeEach((to, from, next) => {
  const userStore = useUserStoreHook();
  if (!userStore.isLoggedIn) {
    next("/login");
  }
});

API 规范

文件结构

typescript
// src/api/system/user.ts
import request from "@/utils/request";
import type { UserInfo, UserQuery, UserForm, PageResult } from "@/types";

const USER_BASE_URL = "/api/v1/users";

class UserAPI {
  /** 获取用户分页列表 */
  static getPage(params: UserQuery) {
    return request.get<PageResult<UserInfo>>(`${USER_BASE_URL}/page`, { params });
  }

  /** 获取用户详情 */
  static getById(id: number) {
    return request.get<UserInfo>(`${USER_BASE_URL}/${id}`);
  }

  /** 新增用户 */
  static create(data: UserForm) {
    return request.post<void>(USER_BASE_URL, data);
  }

  /** 修改用户 */
  static update(id: number, data: UserForm) {
    return request.put<void>(`${USER_BASE_URL}/${id}`, data);
  }

  /** 删除用户 */
  static deleteByIds(ids: number[]) {
    return request.delete<void>(`${USER_BASE_URL}/${ids.join(",")}`);
  }
}

export default UserAPI;

命名约定

操作方法名HTTP 方法
分页查询getPageGET
列表查询getListGET
详情查询getByIdGET
新增createPOST
修改updatePUT
删除deleteByIdsDELETE
导出exportGET/POST
导入importPOST

注释规范

JSDoc 注释

typescript
/**
 * 格式化文件大小
 * @param bytes - 字节数
 * @param decimals - 小数位数,默认 2
 * @returns 格式化后的字符串,如 "1.50 MB"
 * @example
 * formatFileSize(1024) // "1.00 KB"
 * formatFileSize(1048576) // "1.00 MB"
 */
export function formatFileSize(bytes: number, decimals = 2): string {
  if (bytes === 0) return "0 B";
  const k = 1024;
  const sizes = ["B", "KB", "MB", "GB", "TB"];
  const i = Math.floor(Math.log(Math.abs(bytes)) / Math.log(k));
  return `${(bytes / Math.pow(k, i)).toFixed(decimals)} ${sizes[i]}`;
}

组件注释

vue
<script setup lang="ts">
/**
 * 字典选择器组件
 *
 * @description 基于 el-select 封装,自动加载字典数据
 * @example
 * <DictSelect v-model="form.status" dict-code="sys_status" />
 */

interface Props {
  /** 绑定值 */
  modelValue?: string | number;
  /** 字典编码 */
  dictCode: string;
  /** 是否可清空 */
  clearable?: boolean;
}
</script>

行内注释

typescript
// ✅ 解释"为什么",而非"是什么"
const timeout = 30000; // 后端接口响应较慢,需要较长超时时间

// ❌ 避免无意义注释
const count = 0; // 设置 count 为 0

Git 提交规范

Commit Message 格式

<type>(<scope>): <subject>

<body>

<footer>

Type 类型

类型说明
feat新功能
fix修复 Bug
docs文档更新
style代码格式(不影响功能)
refactor重构(非新功能、非修复)
perf性能优化
test测试相关
chore构建/工具变动
revert回滚

示例

bash
# 新功能
feat(user): 添加用户导入功能

# 修复
fix(auth): 修复 token 过期后未跳转登录页的问题

# 文档
docs: 更新开发规范文档

# 重构
refactor(store): 使用 setup store 语法重构 user store

参考:Conventional CommitsAngular Commit Guidelines


目录结构规范

src/
├── api/                    # API 接口
│   ├── system/            # 系统管理模块
│   │   ├── user.ts
│   │   └── role.ts
│   └── auth.ts            # 认证接口
├── assets/                 # 静态资源
│   ├── images/
│   └── icons/
├── components/             # 公共组件
│   ├── DictSelect/        # 复杂组件用目录
│   │   └── index.vue
│   └── Pagination.vue     # 简单组件单文件
│   └── Pagination.vue     # 简单组件单文件
├── composables/            # 组合式函数
│   ├── table/
│   └── websocket/
├── constants/              # 常量定义
├── directives/             # 自定义指令
├── enums/                  # 枚举定义
├── hooks/                  # 生命周期钩子(可选)
├── layouts/                # 布局组件
├── router/                 # 路由配置
├── store/                  # 状态管理
│   ├── modules/
│   └── index.ts
├── styles/                 # 全局样式
├── types/                  # 类型定义
│   ├── api/
│   └── ui/
├── utils/                  # 工具函数
├── views/                  # 页面组件
│   └── system/
│       └── user/
│           ├── index.vue
│           └── components/
├── App.vue
└── main.ts

ESLint / Prettier 配置

项目已配置 ESLint + Prettier,确保代码风格一致:

bash
# 检查代码
pnpm lint:eslint

# 格式化代码
pnpm lint:prettier

# 检查样式
pnpm lint:stylelint

# 全部检查
pnpm lint

关键规则

  • 使用 2 空格缩进
  • 使用双引号
  • 语句末尾不加分号(Prettier 默认)
  • 组件名必须多词
  • Props 必须有类型定义
  • 未使用的变量报错

总结

类别规范
变量camelCase
常量UPPER_SNAKE_CASE
类/接口/类型PascalCase
文件(TS/JS)kebab-case
文件(Vue 组件)PascalCase
CSS 类名kebab-case / BEM
Composablesuse 前缀 + camelCase
Storeuse + 模块名 + Store
API 方法动词 + 名词
Git CommitConventional Commits

遵循这些规范,可以保证代码的一致性、可读性和可维护性。

基于 MIT 许可发布