201 lines
6.5 KiB
Plaintext
201 lines
6.5 KiB
Plaintext
---
|
||
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 规范
|