Files
vibe_coding/.cursor/skills/component-scaffold/SKILL.md
2026-03-05 21:27:11 +08:00

198 lines
8.3 KiB
Markdown
Raw 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.
---
name: component-scaffold
version: 4.1.0
description: "生成 Vue 3 SFC 组件脚手架,含单元测试和类型安全 Props。当需要新建组件、拆分子组件或创建复合组件时使用。管理端用 Element Plus用户端用 Headless UI。"
requires: [vue-testing]
---
> ⚠️ 核心执行流程已在 `.cursor/rules/skill-component-scaffold.mdc` 中由 Cursor 自动注入。
> 本文件提供完整模板、代码示例和边缘场景处理,供 Agent 按需深入 Read。
# Vue 3 Component Scaffold
> **⚠️ 前端识别**:生成组件前必须确认目标前端。
> - **管理端** (`Case-Database-Frontend-admin/`):使用 Element Plus + Tailwind
> - **用户端** (`Case-Database-Frontend-user/`):使用 Headless UI + Tailwind**禁止 Element Plus**
## 触发条件
用户要求创建新的 Vue 组件、页面组件、UI 元素或交互模块。
## 执行流程
### 0. 加载规范(⚠️ 必须最先执行)
依次读取 `.cursor/rules/010-typescript.mdc``.cursor/rules/011-vue.mdc``.cursor/rules/019-modular.mdc`,提取类型注解要求(隐式 any 禁令、ref 泛型规范、Composable 类型规范、script setup、defineProps/Emits、组件分类、拆分阈值、Composable 提取规则。
> **Tier 3**Vue 3 完整 API 见 `references/vue-api-reference.md`。
### 0.5 ⚠️ 生成前强制结构规划(禁止跳过)
**写代码前必须先输出文件结构和组件职责说明。**
#### A. 检查是否已有可复用的基础组件
在生成新组件前,先扫描以下路径,**避免重复造轮子**
- `src/components/core/` — 通用基础组件(按钮、输入框、卡片等)
- `src/components/custom/` — 业务定制组件
若已有 `FormInput.vue``BaseCard.vue` 等,**直接复用,不重新生成**。
#### B. 用户端表单输入:优先复用 FormInput 模式
用户端页面中表单输入框若出现 ≥3 个,必须使用 `FormInput` 基础组件:
```vue
<!-- src/components/core/FormInput/FormInput.vue -->
<script setup>
defineProps({
modelValue: { type: String, default: '' },
type: { type: String, default: 'text' },
placeholder: { type: String, default: '' },
icon: { type: Object, default: null }, // Lucide 图标组件
disabled: { type: Boolean, default: false },
error: { type: String, default: '' },
})
defineEmits(['update:modelValue'])
</script>
<template>
<div class="relative">
<component :is="icon" v-if="icon"
class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 dark:text-gray-500 w-5 h-5" />
<input
:type="type"
:value="modelValue"
:placeholder="placeholder"
:disabled="disabled"
@input="$emit('update:modelValue', $event.target.value)"
:class="[
'w-full border-b py-3 outline-none transition-colors',
icon ? 'pl-10 pr-4' : 'px-4',
'bg-transparent border-gray-300 dark:border-[#333333]',
'text-gray-900 dark:text-white placeholder:text-gray-400 dark:placeholder:text-gray-600',
'focus:border-[#C41E3A] dark:focus:border-[#C41E3A]',
error ? 'border-red-500' : '',
]"
data-testid="form-input"
/>
<p v-if="error" class="mt-1 text-xs text-red-500">{{ error }}</p>
</div>
</template>
```
> 若项目中尚无 `FormInput`,在生成使用它的页面前先生成该核心组件。
#### C. 重复 UI 模式检测
检查需求中是否存在重复结构:
| 场景 | 检测标准 | 操作 |
|------|---------|------|
| 表单输入项 | ≥3 个相同结构的 input 组 | 提取 `FormInput.vue` |
| 内容卡片 | ≥3 个相同布局的卡片 | 提取 `CaseCard.vue` / `DesignerCard.vue` |
| 操作按钮组 | ≥2 处相同按钮组合 | 提取 `ActionGroup.vue` |
| 阶段/Tab 面板 | ≥2 个结构相同的面板 | 提取 `StagePanel.vue` |
#### D. 输出组件文件结构(代码前必须输出)
```
# 本次将生成以下文件:
src/components/<type>/<ComponentName>/
├── <ComponentName>.vue ← 主组件(目标 ≤ 120 行)
├── <ComponentName>.test.ts ← 单元测试
└── index.ts ← barrel export
# 依赖(已存在 / 需新建):
- FormInput.vue: [已存在 / 需新建]
- BaseCard.vue: [已存在 / 不需要]
```
---
### 1. 确认组件规格
| 字段 | 必填 | 默认值 |
|------|------|--------|
| 组件名 | ✅ | — |
| 组件类型 | ❌ | UI 组件 |
| 所在目录 | ❌ | `src/components/` |
| Props / Emits | ❌ | 根据需求推断 |
类型与目录:`core``src/components/core/``custom``custom/``layout``layouts/``page``views/<module>/components/``form``custom/`
### 2. 扫描项目模式
读取 `src/components/` 确认 Props 声明、样式方案、命名前缀约定、已有的 core 组件列表。
### 3. 生成文件结构
按步骤 0.5 输出的结构逐文件生成,**禁止将多个组件合并到一个文件**。
```
src/components/<type>/<ComponentName>/
├── <ComponentName>.vue
├── <ComponentName>.test.ts
└── index.ts
```
### 4. 组件模板
根据类型选择:基础 UI / 表格 / 表单对话框 / 复合组件。完整模板与设计决策树见 **Tier 3**
**单组件行数限制**
- template 区域 ≤ 80 行
- script 区域 ≤ 60 行(超出提取 composable
- 整个 SFC ≤ 150 行(超出必须拆分子组件)
### 4.5 设计决策树(必须执行)
- ≥3 布尔 prop→ 显式变体组件
- 多子组件共享状态?→ provide/inject
- script 逻辑 > 60 行?→ 提取 `use<ComponentName>.ts` composable**必须遵循 `010-typescript.mdc` 的 Composable 类型规范**:参数有类型、`ref` 有泛型、业务类型用 `import type`
- >2 处复用?→ `src/components/` + slot
- 同一 UI 结构重复 ≥3 次?→ 提取基础组件(见步骤 0.5C
- 否则 → 基础 UI 模板
### 5. 测试与 Barrel export
测试至少包含渲染测试。`index.ts` 导出:`export { default as ComponentName } from './ComponentName.vue'`。详细测试见 `vue-testing` 技能。
## 验证
1. [ ] ESLint 无报错
2. [ ] Props 使用对象语法 `defineProps({ key: { type, default } })`**若模板可直接访问 props 字段则不保存返回值**(避免 `unused-vars` 报错;仅当 script 中需要访问 `props.xxx` 时才保存:`const props = defineProps(...)`
3. [ ] Emits 使用数组语法,事件名使用 camelCase非 kebab-case
4. [ ] 包含 `data-testid`
5. [ ] 测试至少一个渲染测试
6. [ ] 管理端Element Plus 用于组件Tailwind 用于布局用户端Headless UI + Tailwind禁止 Element Plus
7. [ ] barrel export 正确
8. [ ] **单 SFC ≤ 150 行**
9. [ ] **重复 UI 结构≥3 次)已提取为基础组件**
10. [ ] **表单输入 ≥3 个已复用 FormInput.vue**(用户端)
11. [ ] **子组件模板中无 `v-model` 直接绑定 prop 嵌套属性**(参见 `011-vue.mdc` Props 数据流模式)
12. [ ] **提取的 composableuse*.ts参数有类型注解、`ref([])` / `ref(null)` 有泛型标注**(参见 `010-typescript.mdc` Composable 类型规范)
### Red Flags触发则停止并重构
- ❌ 未输出文件结构直接写代码 → 停止,先输出步骤 0.5D 的结构
- ❌ 单 SFC > 150 行 → 拆分子组件
- ❌ 相同 Tailwind 输入框结构写了 ≥3 次 → 提取 FormInput.vue
- ❌ Options API → 改用 `<script setup>`
-`const props = defineProps(...)` 但 script 中从不访问 `props.xxx` → 去掉 `const props =`,避免 `unused-vars` lint 报错
-`catch (e) { ... }` 但 catch 块中不使用 `e` → 改为 `catch { ... }`(无参 catch避免 `unused-vars` 报错
- ❌ 直接修改 props → 通过 emit含隐蔽变体`v-model="prop.field"``v-model="prop[key]"`、事件回调中 `prop[k] = v`
- ❌ watch deep:true 滥用 → 精准监听
- ❌ script 逻辑 > 60 行未提取 → 拆分为 use*.ts
- ❌ composable 参数无类型 / `ref([])` 无泛型 → 补全类型注解(`strict: true` 下必报错)
- ❌ ≥3 布尔 prop 控制渲染 → 拆分变体
- ❌ prop drilling 传递同状态 → 改用 provide/inject
## Tier 3 深度参考
| 文件 | 内容 |
|------|------|
| `references/component-templates.md` | 基础/表格/表单/复合组件模板、决策树、Prop 反模式、错误处理、UI 反模式 |
| `references/vue-api-reference.md` | Vue 3 完整 API 索引 |
| `references/naming-conventions.md` | 组件命名规范 |