youlaitech/youlai-skills

vue-admin

This skill should be used when developing Vue 3 applications with Composition API, creating admin pages with usePageTable composables, configuring routes/menus/permissions/page caching, implementing API request layers with axios, or using Element Plus compo…

View source
Original skill document

Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.

Vue 3 开发规范

技术栈

Vue 3 (Composition API) · TypeScript · Vite · Pinia · Vue Router · UnoCSS · SCSS · Element Plus

目录结构

src/
├── api/                        # API 请求层(index.ts + types.ts)
├── components/                 # 全局复用组件
├── composables/                # 组合式函数(usePageTable 等)
├── constants/                  # 常量(StorageKeys 等)
├── directives/                 # 自定义指令(v-hasPerm 等)
├── lang/                       # 国际化(index.ts + package/)
├── layouts/                    # 布局组件
├── router/                     # 路由 + 守卫
├── stores/                     # Pinia(扁平结构,无 modules/)
├── styles/                     # 全局样式
├── utils/                      # 工具函数(request.ts, auth.ts)
├── views/                      # 页面(与路由对应)
│   └── system/role/
│       └── index.vue          # 单文件:搜索+表格+弹窗
└── settings.ts                # 项目配置

命名规范

类型风格示例
变量camelCaseuserName
常量UPPERSNAKECASEMAX_COUNT
函数camelCase,动词开头fetchUserList
类/接口/类型PascalCaseUserInfo
枚举PascalCaseStatusEnum
枚举值UPPERSNAKECASEStatusEnum.ACTIVE
布尔值is/has/can/should 前缀isLoading
Vue 组件文件PascalCaseUserForm.vue
页面文件index.vuesystem/user/index.vue
TS/JS 模块kebab-caseformat-date.ts
Composablesuse + camelCaseusePageTable
Storeuse + 模块名 + StoreuseUserStore

禁止前端类型用 VO/DTO 后缀。语义命名:UserItem(列表项)、UserForm(表单)、UserQueryParams(查询参数)、UserDetail(详情)。

方法命名与 handle 规则

场景命名
查询/加载fetchList / loadOptions
打开/关闭弹窗openDialog / closeDialog
提交/保存submitForm / saveX
新增/编辑/删除createX / updateX / deleteX
重置resetForm / resetQuery
事件入口(流程编排)handleSubmit / handleEditClick

handle 判断标准:函数只做一件事 → 不用 handle(可复用);组合多个动作 + 流程控制 → 用 handle(事件入口)。

typescript
// 单一动作 → 不用 handle
function openDialog() { dialogState.visible = true; }

// 流程编排 → 使用 handle
async function handleSubmit() {
  const valid = await validateForm();
  if (!valid) return;
  await submitForm(formData);
  closeDialog();
  fetchList();
}

fetch/load 已隐含异步语义,不加 Async 后缀。

CSS / UnoCSS / SCSS 边界

UnoCSS 只处理无语义微调(间距、对齐),结构性样式归 BEM + SCSS。

场景方案
全局页面骨架全局类(如 page-*
有结构语义的元素BEM + SCSS
无语义布局微调UnoCSS
穿透/动画/媒体查询SCSS

规则:

  • 同一元素原子类 ≤ 3 个,超过提取 BEM
  • 颜色用 CSS 变量,禁止硬编码
  • BEM 格式:block__element--modifier,kebab-case,带页面前缀
  • 状态用 is-*,变体用 BEM Modifier:layout--top

组件规范

  • SFC 块顺序:templatescript setupstyle scoped
  • script 内部顺序:导入 → Props/Emits → 状态 → 计算属性 → 监听器 → 生命周期 → 方法 → defineExpose
  • Props 优先 TypeScript 类型声明 + withDefaults
  • 组件 ≤ 300 行

类型与 API 约定

公共类型

typescript
interface ApiResult<T = unknown> { code: string; data: T; msg: string; }
interface BaseQueryParams { pageNum: number; pageSize: number; sortBy?: string; order?: string; }
interface PageResult<T> { list: T[]; total: number; }

API 定义

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

const UserAPI = {
  /** 获取用户分页数据 */
  getPage(q: UserQueryParams) { return request<unknown, PageResult<UserItem>>({ url: USER_BASE_URL, method: "get", params: q }); },
  /** 获取用户表单数据 */
  getFormData(id: string) { return request<unknown, UserForm>({ url: `${USER_BASE_URL}/${id}/form`, method: "get" }); },
  /** 新增用户 */
  create(data: UserForm) { return request({ url: USER_BASE_URL, method: "post", data }); },
  /** 更新用户 */
  update(id: string, data: UserForm) { return request({ url: `${USER_BASE_URL}/${id}`, method: "put", data }); },
  /** 批量删除用户 */
  deleteByIds(ids: string) { return request({ url: `${USER_BASE_URL}/${ids}`, method: "delete" }); },
};

export default UserAPI;
export * from "./types";

响应拦截器自动剥壳:code === "00000" 时返回 response.data.data。Token 过期(A0230)时 refreshTokenOnce() 单飞刷新。

页面开发模式

默认使用 Composables 模式开发页面。仅当明确指定使用 CURD 页面时,才使用 Config 驱动模式:

模式适用场景参考
Composables 模式(默认)列表页、表单页、带自定义交互的页面references/new-page-guide.md
简单页面仪表盘、设置页、详情页references/new-page-guide.md
Config 驱动 CURD(需指定)标准增删改查,无特殊交互references/curd-development.md

页面根节点统一用 class="page-container",内部分区:page-search(搜索区)、page-content(内容区)、page-toolbar(工具栏)。

Store

Setup Store 写法,扁平目录。组件外使用 useXxxStoreHook() 避免 Pinia 未初始化。

typescript
export const useUserStore = defineStore("user", () => {
  const userInfo = ref<UserInfo>({} as UserInfo);
  return { userInfo };
});

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

Composables

use 前缀 + camelCase。参数用 options 对象,返回响应式引用和方法。

注释

注释写"为什么"和"踩坑点",不写代码已经在说的。函数用 /** */,不用 //

格式

  • 函数/方法 → /** */ JSDoc
  • 类型/接口/属性 → 单行 /** */
  • 函数内部"为什么" → // 行内
  • 配置分组 → // 简短

函数注释看情况

typescript
/** 打开角色表单弹窗 */
function openDialog() { ... }

/**
 * 打开编辑角色弹窗
 *
 * @param roleId 角色 ID
 */
async function handleEditClick(roleId: string) { ... }

/**
 * 校验并提交角色表单
 *
 * 非自定义数据权限时丢弃部门 ID
 */
async function handleSubmit() { ... }

简单函数一行够了。有参数加 @param。有踩坑点就多行补一句——只补"为什么",不复述函数名已经能看出来的。

常见错误

typescript
// ❌ 复述代码:函数名已经说了"打开弹窗",注释是废话
/** 打开弹窗 */
function openDialog() { ... }

// ❌ 描述"做什么"而非"为什么"
/** 遍历列表并过滤状态为启用的项 */
const enabledList = list.filter(item => item.status === 1);

// ✅ 写"为什么"
// status=1 是启用,数据库默认值是 0(禁用)
const enabledList = list.filter(item => item.status === 1);

// ❌ 每个函数都写注释,哪怕函数名一目了然
/** 获取用户信息 */
function getUserInfo() { ... }

// ✅ 函数名能说清的不写
function getUserInfo() { ... }

反模式速查

反模式正确做法
类型用 VO/DTO 后缀语义命名:UserItemUserForm
硬编码颜色CSS 变量
原子类 > 3 个提取 BEM + SCSS
class UserAPI 静态方法const UserAPI = {} 对象字面量
stores/modules/ 子目录扁平 stores/ 结构
组件外直接用 useXxxStore()useXxxStoreHook()
手动给 CURD 按钮加 v-hasPermpermPrefix 自动拼接
meta.title 直接写中文写语言包 key
函数用 // 注释/** */ JSDoc 格式
注释复述"做什么"写"为什么"和约束

自查清单

  • [ ] 类型无 VO/DTO 后缀
  • [ ] 布尔值有 is/has/can/should 前缀
  • [ ] handle 仅用于流程编排
  • [ ] API 用 const XXXAPI = {} 对象字面量
  • [ ] BEM 带页面前缀,原子类 ≤ 3 个
  • [ ] 颜色用 CSS 变量
  • [ ] SFC 块顺序:template → script → style
  • [ ] Store 用 Setup Store + useXxxStoreHook()
  • [ ] 组件 ≤ 300 行
  • [ ] meta.title 使用语言包 key
  • [ ] 权限标识遵循 模块:资源:操作 格式
  • [ ] API 模块遵循 index.ts + types.ts 结构
  • [ ] 函数用 /** */ 注释,不用 //
  • [ ] 注释写"为什么",不复述"做什么"

参考文档

以下文档按需加载,涵盖具体开发场景的详细指南:

参考文件适用场景
references/new-page-guide.md新增接口与页面:创建 API 模块、Composables 模式 CURD、简单页面、模式选择
references/curd-development.mdConfig 驱动 CURD:配置接口、列模板、表单项、usePage()
references/router-menu.md配置路由和菜单:静态/动态路由、Meta 字段、多级菜单
references/permission.md权限控制:v-hasPerm 指令、hasPerm() 函数、权限标识命名
references/page-caching.md页面缓存:keepAlive 配置、缓存机制、刷新当前页
references/i18n.md菜单国际化:translateRouteTitle、语言包配置、语言切换
references/project-config.md项目配置:settings.ts、环境变量、StorageKey 管理
references/components.md常用组件:Upload、Dict、TableSelect、IconSelect