$npx -y skills add youlaitech/youlai-skills --skill vueVue 3 + TypeScript development standards. Use this skill when building Vue 3 applications with Composition API, Pinia, UnoCSS and SCSS. Covers naming, CSS architecture, component structure, API patterns and code quality. UI-library agnostic (Element Plus, Ant Design Vue, etc).
| 1 | # Vue 3 开发规范 |
| 2 | |
| 3 | ## 技术栈 |
| 4 | |
| 5 | Vue 3 (Composition API) · TypeScript · Vite · Pinia · Vue Router · UnoCSS · SCSS |
| 6 | |
| 7 | ## 目录结构 |
| 8 | |
| 9 | ``` |
| 10 | src/ |
| 11 | ├── api/ # API 请求层 |
| 12 | │ ├── common.ts # 公共类型(ApiResult, PageResult 等) |
| 13 | │ └── system/user/ |
| 14 | │ ├── index.ts # API 对象 |
| 15 | │ └── types.ts # 请求/响应类型 |
| 16 | ├── components/ # 全局复用组件 |
| 17 | ├── composables/ # 组合式函数(use 前缀) |
| 18 | ├── constants/ # 常量 |
| 19 | ├── directives/ # 自定义指令 |
| 20 | ├── enums/ # 枚举 |
| 21 | ├── lang/ # 国际化 |
| 22 | ├── layouts/ # 布局组件 |
| 23 | ├── plugins/ # 插件注册 |
| 24 | ├── router/ # 路由 + 守卫 |
| 25 | ├── stores/ # Pinia(扁平结构,无 modules/ 子目录) |
| 26 | ├── styles/ # 全局样式 |
| 27 | ├── utils/ # 工具函数 |
| 28 | ├── views/ # 页面(与路由对应) |
| 29 | │ └── system/user/ |
| 30 | │ ├── index.vue |
| 31 | │ └── components/ |
| 32 | └── settings.ts |
| 33 | ``` |
| 34 | |
| 35 | ## 命名规范 |
| 36 | |
| 37 | | 类型 | 风格 | 示例 | 理由 | |
| 38 | |------|------|------|------| |
| 39 | | 变量 | camelCase | `userName` | JS 惯例 | |
| 40 | | 常量 | UPPER_SNAKE_CASE | `MAX_COUNT` | 区分不可变值 | |
| 41 | | 函数 | camelCase,动词开头 | `fetchUserList` | 动词表行为 | |
| 42 | | 类/接口/类型 | PascalCase | `UserInfo` | 区分类型与实例 | |
| 43 | | 枚举 | PascalCase | `StatusEnum` | — | |
| 44 | | 枚举值 | UPPER_SNAKE_CASE | `StatusEnum.ACTIVE` | 区分枚举成员 | |
| 45 | | 布尔值 | is/has/can/should 前缀 | `isLoading` | 一眼识别为布尔判断,名词有歧义 | |
| 46 | | Vue 组件文件 | PascalCase | `UserForm.vue` | 与 HTML 原生元素区分 | |
| 47 | | 页面文件 | index.vue | `system/user/index.vue` | 路由目录入口 | |
| 48 | | TS/JS 模块 | kebab-case | `format-date.ts` | 文件系统大小写兼容 | |
| 49 | | Composables | use + camelCase | `usePageTable` | Vue 官方约定 | |
| 50 | | Store | use + 模块名 + Store | `useUserStore` | Pinia 约定 | |
| 51 | |
| 52 | **禁止**:前端类型用 `VO/DTO` 后缀。前端无需区分数据传输层级,语义命名更直观:`UserItem`(列表项)、`UserForm`(表单)、`UserQueryParams`(查询参数)、`UserDetail`(详情)。 |
| 53 | |
| 54 | ## 方法命名与 handle 规则 |
| 55 | |
| 56 | | 场景 | 命名 | |
| 57 | |------|------| |
| 58 | | 查询/加载 | `fetchList` / `loadOptions` | |
| 59 | | 打开/关闭弹窗 | `openDialog` / `closeDialog` | |
| 60 | | 提交/保存 | `submitForm` / `saveX` | |
| 61 | | 新增/编辑/删除 | `createX` / `updateX` / `deleteX` | |
| 62 | | 重置 | `resetForm` / `resetQuery` | |
| 63 | | 表单校验 | `validateForm` | |
| 64 | | 导入/导出 | `importX` / `exportX` | |
| 65 | | **事件入口(流程编排)** | `handleSubmit` / `handleDelete` / `handleEditClick` | |
| 66 | |
| 67 | **handle 判断标准**:函数只做一件事 → 不用 handle(可复用);组合多个动作 + 流程控制 → 用 handle(事件入口)。 |
| 68 | |
| 69 | ```typescript |
| 70 | // ✅ 单一动作 → 不用 handle,可被多处复用 |
| 71 | function openDialog() { dialogState.visible = true; } |
| 72 | |
| 73 | // ✅ 流程编排 → 使用 handle,作为事件入口 |
| 74 | async function handleSubmit() { |
| 75 | const valid = await validateForm(); |
| 76 | if (!valid) return; |
| 77 | await submitForm(formData); |
| 78 | closeDialog(); |
| 79 | fetchList(); |
| 80 | } |
| 81 | ``` |
| 82 | |
| 83 | `fetch/load` 本身已隐含异步语义,不加 `Async` 后缀。单次调用的逻辑直接内联,不过度抽取。 |
| 84 | |
| 85 | ## CSS / UnoCSS / SCSS 边界 |
| 86 | |
| 87 | **核心原则:UnoCSS 只处理无语义微调(间距、对齐),结构性样式归 BEM + SCSS。** |
| 88 | |
| 89 | | 场景 | 方案 | |
| 90 | |------|------| |
| 91 | | 全局页面骨架 | 全局类(如 `page-*`),复用不重复造 | |
| 92 | | 有结构语义的元素 | BEM + SCSS | |
| 93 | | 无语义的布局微调 | UnoCSS(间距、flex 对齐等) | |
| 94 | | 穿透/动画/媒体查询 | SCSS(`:deep()`、`@keyframes`、`@media`) | |
| 95 | |
| 96 | **BEM 元素上可附加少量无语义原子类(间距、对齐),但结构性样式(颜色、背景、圆角、阴影)必须收敛到 SCSS**: |
| 97 | |
| 98 | ```vue |
| 99 | <!-- ✅ 无 BEM 类 → 纯 UnoCSS --> |
| 100 | <div class="flex-y-center gap-10px">...</div> |
| 101 | |
| 102 | <!-- ✅ BEM + 1-2 个无语义原子类(间距)可接受 --> |
| 103 | <div class="user-card mt-4">...</div> |
| 104 | |
| 105 | <!-- ❌ BEM + 大量原子类(结构+语义混写) --> |
| 106 | <div class="user-card flex flex-col gap-4 p-4 bg-white rounded shadow">...</div> |
| 107 | ``` |
| 108 | |
| 109 | - 同一元素原子类 ≤ 3 个,超过提取 BEM |
| 110 | - 颜色用 UI 库 CSS 变量或项目变量,禁止硬编码 |
| 111 | - 状态用 `is-*`:`is-collapsed`、`is-loading`;变体用 BEM Modifier:`layout--top`、`todo-row--done` |
| 112 | - BEM 格式:`block__element--modifier`,kebab-case,Block 带页面前缀:`user-card`、`role-permission` |
| 113 | |
| 114 | ## 组件规范 |
| 115 | |
| 116 | - SFC 块顺序:`template` → `script setup` → `style scoped` |
| 117 | - script 内部顺序:导入(Vue → 第三方 → 类型 → 内部 → 相对路径)→ Props/Emits → 状态 → 计算属性 → 监听器 → 生命周期 → 方法 → defineExpose |
| 118 | - Props 优先 TypeScript 类型声明 + `withDefaults`,不与运行时声明混用 |
| 119 | - 组件 ≤ 300 行,使用 `<script setup>` |
| 120 | |
| 121 | ## 类型与 API 约定 |
| 122 | |
| 123 | ### 公共类型(api/common.ts) |
| 124 | |
| 125 | ```typescript |
| 126 | interface ApiResult<T = unknown> { code: string; data: T; msg: string; } |
| 127 | interface BaseQueryParams { pageNum: number; pageSize: number; sortBy?: string; order?: string; } |
| 128 | interface PageResult<T> { list: T[]; total: number; } |
| 129 | interface OptionItem { value: string | number; label: string; children?: OptionItem[]; } |
| 130 | ``` |
| 131 | |
| 132 | ### 类型命名 |
| 133 | |
| 134 | | 语义 | 命名 | |
| 135 | |------|------| |
| 136 | | 列表项 | `UserItem` | |
| 137 | | 表单对象 | `UserForm` | |
| 138 | | 查询参数 | `UserQueryParams extends BaseQueryParams` | |
| 139 | | 详情 | `UserDetail` | |
| 140 | | 登录用户信息 | `UserInfo` | |
| 141 | |
| 142 | ### API 定义 |
| 143 | |
| 144 | ```typescript |
| 145 | const USER_BASE_URL = "/api/v1/users"; |
| 146 | |
| 147 | const UserAPI = { |
| 148 | /** 获取用户分页列表。 */ |
| 149 | getPage(q: UserQueryParams) { return request<unknown, PageResult<UserItem>>({ url: USER_BASE_URL, method: "get", params: q }); }, |
| 150 | /** 获取表单详情。 */ |
| 151 | getFormData(id: string) { return request<unknown, UserForm>({ url: `${USER_BASE_URL}/${id}/form`, method: "get" }); }, |
| 152 | /** 新增。 */ |
| 153 | create(data: UserForm) { return request({ url: USER_BASE_URL, method: |