Files
vibe_coding/.cursor/rules/011-vue.mdc
2026-03-05 21:27:11 +08:00

201 lines
6.5 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
description: "Vue 3 + Vite 前端开发规范TypeScript。管理端使用 Element Plus用户端使用 Headless UI + Tailwind禁止 Element Plus"
globs:
- "**/*.vue"
- "Case-Database-Frontend-user/src/**/*.ts"
- "Case-Database-Frontend-admin/src/**/*.ts"
alwaysApply: false
---
# Vue 3 / Vite Standards + 双前端上下文
> **双前端区分**:管理端 (`Case-Database-Frontend-admin/`) 使用 Element Plus
> 用户端 (`Case-Database-Frontend-user/`) 使用 Headless UI + Tailwind CSS**禁止引入 Element Plus**。
## 组件与脚本约束
- 默认使用 `<script setup>` + Composition API
- Props/Emits 显式声明,避免隐式双向修改
- 页面与组件职责分离:页面编排,组件渲染
- 可复用逻辑抽离到 composables / utils
### Emits 命名约定
- `defineEmits` 中自定义事件使用 **camelCase**`addItem`、`removeItem`
- `update:xxx` 前缀保留给 v-model 双向绑定约定
- 模板中 `@add-item` 和 `@addItem` 均可Vue 自动转换),但 defineEmits 源头必须 camelCase
```vue
<!-- ❌ kebab-case -->
defineEmits<{ 'add-item': []; 'remove-item': [id: number] }>()
<!-- ✅ camelCase -->
defineEmits<{ addItem: []; removeItem: [id: number] }>()
```
## 状态与数据流
- 全局状态使用 Pinia局部状态留在组件内
- 单向数据流:`props down, events up`
- 远端请求统一经 service/api 层,不在模板内混写
### Props 数据流模式(⚠️ 必须遵守)
子组件**禁止**直接修改 prop 或 prop 的嵌套属性。变更必须通过 emit 通知父组件。
**模式 A子组件接收对象 prop需修改其字段**
```vue
<!-- ❌ 直接 v-model 绑定 prop 字段(触发 vue/no-mutating-props -->
<AppInput v-model="form.username" />
<!-- ✅ 拆分为 :model-value + emit -->
<AppInput
:model-value="form.username"
@update:model-value="emit('update:form', { ...props.form, username: $event })"
/>
```
**模式 B子组件中继 v-model 到子子组件**
```vue
<!-- ❌ 用 v-model 直接绑定动态 prop key -->
<RadioGroup v-model="radios[f.key]" />
<!-- ✅ 拆分并向上 emit -->
<RadioGroup
:model-value="radios[f.key]"
@update:model-value="emit('update:radio', f.key, $event)"
/>
```
**模式 C事件回调中修改 prop隐蔽变体**
```vue
<!-- ❌ 在事件处理中直接赋值 prop 属性 -->
<LogicTable @update="(k, v) => logics[k] = v" />
<!-- ✅ 转发为 emit -->
<LogicTable @update="(k, v) => emit('update:logic', k, v)" />
```
**父组件响应模式(状态持有方):**
```vue
<!-- 父组件持有 reactive 状态,处理 update 事件 -->
<ChildForm
:form="formData"
@update:form="Object.assign(formData, $event)"
/>
<!-- 或逐字段更新 -->
<FilterBody
:radios="radios"
@update:radio="(key, val) => radios[key] = val"
/>
```
## 性能与可维护性
- 列表必须 `key` 稳定;重计算走 `computed`
- 禁止在模板调用高开销函数
### SFC 行数限制(强制)
- template <= 80 行script <= 60 行,整个 SFC <= 150 行
- 超出 script 60 行 → 提取 composable (`use<Name>.ts`)
- 超出 SFC 150 行 → 拆分子组件
### 组件设计决策(新建和修改时均适用)
- >=3 个布尔 prop → 拆分为显式变体组件
- 多子组件共享状态 → provide/inject
- script > 60 行 → 提取 `use<Name>.ts` composable
- 同一 UI 结构出现 >=3 次 → 提取基础组件
## 可访问性与一致性
- 表单项必须有可读 label / 提示
- 错误状态、空状态、加载状态必须可见
- 命名、目录、导出方式保持一致
## 验证清单
- [ ] ESLint 无错误
- [ ] 子组件不直接修改 prop无 `v-model="prop.field"` 或 `prop[key] = val` 模式)
- [ ] defineEmits 使用 camelCase 命名(非 kebab-case
- [ ] 关键交互具备加载/错误处理
- [ ] 组件结构清晰,可复用逻辑已抽离
- [ ] UI 语义与可访问性基础达标
---
## 双前端上下文规则
Agent 在生成代码前必须先判断目标前端:
- 路径含 `Case-Database-Frontend-user/` → **用户端模式**
- 路径含 `Case-Database-Frontend-admin/` → **管理端模式**
- 路径含 `Case-Database-Frontend-shared/` → **共享层模式**(纯 JS无 UI 依赖)
### 用户端规则
- **移动端优先**:断点顺序 `默认(xs) → sm → md → lg → xl`
- 触摸目标最小 44x44px交互元素间距 >= 8px
- UI 组件使用 Tailwind CSS + 自定义组件(非 Element Plus
- 图片必须指定 `width`、`height`、`alt`,推荐 WebP
- 路由懒加载:每个页面独立 chunk
- 禁止在用户端引入 Element Plus
- 权限模型:登录态 + 会员等级
- API 基础路径:`/api/`
- 首屏 LCP 目标 <= 2.5s(移动网络),单路由 chunk <= 200KB
### 管理端规则
- **桌面端优先**:以 1280px 宽度为基准,支持 >= 1024px
- 优先使用 Element Plus 标准组件(`el-table`、`el-form`、`el-dialog`
- 每个操作必须有权限检查(`v-permission`
- 危险操作必须有二次确认(`el-popconfirm`
- 表单使用 `el-form` + `:rules` 验证,弹窗关闭时重置
- 分页统一 `el-pagination`,默认 page_size: 10
- 表格操作列固定右侧(`fixed="right"`
- API 基础路径:`/admin/`
- 401 → 清除 Token → 登录页403 → Toast "无权限"
### 共享层规则
- 只允许纯 TypeScript禁止引入 Vue、Element Plus、axios
- 导出的函数/常量必须同时适配用户端和管理端
- 修改后需同步通知两个前端的相关使用方
## ESLint 配置注意事项
### vue/block-order 等 Vue 专属规则必须限定文件范围
`vue/block-order` 规则依赖 Vue 文件解析器,**不能**放在全局 `rules` 中,否则对 `.md`、`.json`、`.ts` 等非 Vue 文件生效时会崩溃:
```js
// ❌ 错误全局规则对所有文件生效README.md 会 TypeError 崩溃
export default antfu({
rules: {
'vue/block-order': ['error', { order: ['script', 'template', 'style'] }],
},
});
// ✅ 正确:限定在 *.vue 文件
export default antfu({ ... }, {
files: ['**/*.vue'],
rules: {
'vue/block-order': ['error', { order: ['script', 'template', 'style'] }],
},
});
```
所有 `vue/*` 前缀的规则若要手动覆盖,都必须放在 `files: ['**/*.vue']` 覆盖块内。
## Tier 3 深度参考
- `.cursor/rules/references/011-vue-deep.md` — 完整 Vue 规范与反模式示例
- `docs/architecture/frontend-strategy.md` — 完整双前端架构说明
- `docs/guides/ui-specs-user.md` — 用户端 UI 规范
- `docs/guides/ui-specs-admin.md` — 管理端 UI 规范