@bovinphang/frontend-craft 2.6.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +38 -1
- package/CHANGELOG.zh-CN.md +37 -1
- package/README.md +10 -6
- package/README.openclaw.md +3 -1
- package/README.openclaw.zh-CN.md +3 -1
- package/README.zh-CN.md +10 -6
- package/agents/fec-typescript-reviewer.md +1 -1
- package/docs/example-prompts.md +81 -69
- package/docs/ja-JP/README.md +56 -52
- package/docs/ko-KR/README.md +45 -41
- package/docs/project-structure.md +8 -4
- package/docs/superpowers/plans/2026-05-29-fec-debug.md +491 -0
- package/docs/superpowers/specs/2026-05-29-fec-debug-design.md +340 -0
- package/docs/zh-CN/example-prompts.md +81 -69
- package/docs/zh-CN/project-structure.md +8 -4
- package/docs/zh-TW/README.md +33 -29
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -3
- package/skills/eval_queries.json +154 -56
- package/skills/fec-backend-requirements-handoff/SKILL.md +45 -0
- package/skills/fec-code-review/SKILL.md +1 -1
- package/skills/fec-drawio-studio/SKILL.md +69 -0
- package/skills/fec-drawio-studio/data/THIRD_PARTY_NOTICES.md +10 -0
- package/skills/fec-drawio-studio/data/brand-icons.json +878 -0
- package/skills/fec-drawio-studio/data/shape-index.json.gz +0 -0
- package/skills/fec-drawio-studio/references/data-residency.md +27 -0
- package/skills/fec-drawio-studio/references/diagram-patterns.md +41 -0
- package/skills/fec-drawio-studio/references/layout-and-export.md +57 -0
- package/skills/fec-drawio-studio/references/troubleshooting.md +29 -0
- package/skills/fec-drawio-studio/references/xml-and-mermaid.md +54 -0
- package/skills/fec-drawio-studio/scripts/brand-symbols.mjs +99 -0
- package/skills/fec-drawio-studio/scripts/diagram-lint.mjs +143 -0
- package/skills/fec-drawio-studio/scripts/diagram-url.mjs +102 -0
- package/skills/fec-drawio-studio/scripts/layout-graph.mjs +107 -0
- package/skills/fec-drawio-studio/scripts/png-embed-fix.mjs +26 -0
- package/skills/fec-drawio-studio/scripts/scan-go-packages.mjs +36 -0
- package/skills/fec-drawio-studio/scripts/scan-js-modules.mjs +52 -0
- package/skills/fec-drawio-studio/scripts/scan-python-classes.mjs +30 -0
- package/skills/fec-drawio-studio/scripts/scan-python-modules.mjs +55 -0
- package/skills/fec-drawio-studio/scripts/scan-rust-modules.mjs +35 -0
- package/skills/fec-drawio-studio/scripts/scan-ts-modules.mjs +7 -0
- package/skills/fec-drawio-studio/scripts/shape-query.mjs +81 -0
- package/skills/fec-drawio-studio/scripts/studio-core.mjs +139 -0
- package/skills/fec-drawio-studio/styles/built-in/corporate.json +49 -0
- package/skills/fec-drawio-studio/styles/built-in/default.json +49 -0
- package/skills/fec-drawio-studio/styles/built-in/handdrawn.json +49 -0
- package/skills/fec-drawio-studio/styles/schema.json +112 -0
- package/skills/fec-e2e-testing/SKILL.md +3 -1
- package/skills/fec-e2e-testing/references/e2e-ci-reporting.md +62 -0
- package/skills/fec-e2e-testing/references/e2e-special-scenarios.md +52 -0
- package/skills/fec-e2e-testing/references/e2e-visual-regression.md +60 -0
- package/skills/fec-image-generation/SKILL.md +56 -0
- package/skills/fec-image-generation/references/artifact-routing.md +80 -0
- package/skills/fec-image-generation/references/diagram-workflows.md +43 -0
- package/skills/fec-image-generation/references/png-qa-autofix.md +30 -0
- package/skills/fec-image-generation/scripts/png-qa.mjs +431 -0
- package/skills/fec-implement-from-design/SKILL.md +7 -0
- package/skills/fec-legacy-to-modern-migration/SKILL.md +24 -168
- package/skills/fec-legacy-to-modern-migration/references/migration-execution-checklist.md +110 -0
- package/skills/fec-legacy-to-modern-migration/references/migration-strategy-and-mapping.md +48 -0
- package/skills/fec-motion-interaction/SKILL.md +7 -0
- package/skills/fec-nextjs-project-standard/SKILL.md +7 -1
- package/skills/fec-performance-optimization/SKILL.md +5 -1
- package/skills/fec-performance-optimization/references/framework-performance-patterns.md +38 -0
- package/skills/fec-performance-optimization/references/report-template.md +6 -0
- package/skills/fec-react-project-standard/SKILL.md +11 -3
- package/skills/fec-react-project-standard/references/react-performance-patterns.md +35 -0
- package/skills/fec-react-project-standard/references/react-project-structure.md +197 -0
- package/skills/fec-react-project-standard/references/react-quality-patterns.md +131 -0
- package/skills/fec-react-project-standard/references/react-runtime-patterns.md +293 -0
- package/skills/fec-responsive-layout/references/responsive-layout-patterns.md +11 -0
- package/skills/fec-source-driven-development/SKILL.md +26 -0
- package/skills/fec-tailwind-design-system/references/tailwind-system-patterns.md +5 -1
- package/skills/fec-typescript-project-standard/SKILL.md +54 -0
- package/skills/{fec-typescript-type-safety → fec-typescript-project-standard}/references/type-safety-patterns.md +1 -1
- package/skills/{fec-typescript-type-safety/SKILL.md → fec-typescript-project-standard/references/type-safety.md} +1 -10
- package/skills/fec-ui-design/SKILL.md +26 -8
- package/skills/fec-ui-design/data/style-archetypes.json +40 -0
- package/skills/fec-ui-design/data/ux-quality-rules.json +35 -0
- package/skills/fec-ui-design/references/design-intelligence.md +66 -6
- package/skills/fec-ui-design/references/pre-delivery-checklist.md +6 -0
- package/skills/fec-ui-design/scripts/design-system.mjs +119 -1
- package/skills/fec-vue3-project-standard/SKILL.md +11 -2
- package/skills/fec-vue3-project-standard/references/vue3-composables-routing.md +163 -0
- package/skills/fec-vue3-project-standard/references/vue3-performance-patterns.md +13 -0
- package/skills/fec-vue3-project-standard/references/vue3-project-structure.md +177 -0
- package/skills/fec-vue3-project-standard/references/vue3-state-api-quality.md +216 -0
- package/skills/fec-web-video-presentation/SKILL.md +61 -0
- package/skills/fec-web-video-presentation/data/starter-themes.json +59 -0
- package/skills/fec-web-video-presentation/references/chapter-craft.md +37 -0
- package/skills/fec-web-video-presentation/references/content-plan.md +38 -0
- package/skills/fec-web-video-presentation/references/recording-and-audio.md +33 -0
- package/skills/fec-web-video-presentation/references/theme-system.md +36 -0
- package/skills/fec-web-video-presentation/scripts/create-video-presentation.mjs +430 -0
- package/skills/metadata.json +340 -164
- package/skills/relations.json +116 -25
- package/templates/shared/rules/fec-ci-cd.md +20 -108
- package/templates/shared/rules/fec-design-system.md +47 -223
- package/templates/shared/rules/fec-error-handling.md +35 -179
- package/templates/shared/rules/fec-i18n.md +43 -91
- package/templates/shared/rules/fec-refactoring.md +4 -0
- package/templates/shared/rules/fec-rendering-patterns.md +28 -171
- package/templates/shared/rules/fec-responsive-design.md +34 -211
- package/templates/shared/rules/fec-state-management.md +41 -144
- package/templates/shared/rules/fec-typescript.md +47 -290
- package/skills/fec-e2e-testing/references/e2e-advanced.md +0 -176
- package/skills/fec-react-project-standard/references/react-project-details.md +0 -642
- package/skills/fec-vue3-project-standard/references/vue3-project-details.md +0 -547
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Vue 3 composables and routing patterns
|
|
2
|
+
|
|
3
|
+
## Composables 规范
|
|
4
|
+
|
|
5
|
+
### 设计原则
|
|
6
|
+
|
|
7
|
+
- 以 `use` 前缀命名
|
|
8
|
+
- 返回值使用对象,明确标注类型
|
|
9
|
+
- 内部处理 loading / error / data 三态
|
|
10
|
+
- 支持参数响应式(接受 `Ref` 或 getter)
|
|
11
|
+
- 库级 composable 要明确参数是否支持普通值、`Ref`、`computed` 或 getter,并在副作用内部解析最新值
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
export function useUserList(params: MaybeRef<QueryParams>) {
|
|
15
|
+
const data = ref<User[]>([]);
|
|
16
|
+
const loading = ref(false);
|
|
17
|
+
const error = ref<Error | null>(null);
|
|
18
|
+
|
|
19
|
+
async function fetch() {
|
|
20
|
+
loading.value = true;
|
|
21
|
+
error.value = null;
|
|
22
|
+
try {
|
|
23
|
+
const res = await getUserList(toValue(params));
|
|
24
|
+
data.value = res.list;
|
|
25
|
+
} catch (e) {
|
|
26
|
+
error.value = e as Error;
|
|
27
|
+
} finally {
|
|
28
|
+
loading.value = false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
watchEffect(() => {
|
|
33
|
+
fetch();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
data: readonly(data),
|
|
38
|
+
loading: readonly(loading),
|
|
39
|
+
error: readonly(error),
|
|
40
|
+
refetch: fetch,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Composable 使用原则
|
|
46
|
+
|
|
47
|
+
- 返回 `readonly` 引用防止外部意外修改
|
|
48
|
+
- 数据请求场景优先使用 VueQuery / VueUse 等库(如项目已引入)
|
|
49
|
+
- `onUnmounted` 中清理定时器、事件监听等副作用
|
|
50
|
+
- 避免在 composable 中直接操作 DOM
|
|
51
|
+
- 只读输入可接受普通值、ref、computed 或 getter;需要双向写入的输入只接受可写 ref 或明确的 setter。
|
|
52
|
+
- 如果参数本身是回调、比较器或 predicate,不要把它设计成 getter 型输入,否则解析时可能误调用业务函数。
|
|
53
|
+
|
|
54
|
+
### Vue Router / Pinia / 测试常见边界
|
|
55
|
+
|
|
56
|
+
- 路由参数变化不会重新创建同一个路由组件;依赖 `route.params` 的数据要 watch 明确字段,并处理取消和竞态。
|
|
57
|
+
- 导航守卫中避免旧式 `next()` 多分支重复调用;优先返回 `false`、重定向对象或抛出可处理错误。
|
|
58
|
+
- Pinia store 解构会丢失响应性时使用 `storeToRefs`;setup store 必须返回需要被外部追踪的 state、getter 和 action。
|
|
59
|
+
- UI 临时状态、表单输入、弹窗开关不进入全局 store;URL 查询、服务端缓存和本地组件状态先各归其位。
|
|
60
|
+
- Vue Test Utils 测试优先断言用户可见行为,不把快照作为唯一证明;异步更新使用 `await`、`flushPromises` 或框架推荐的等待方式。
|
|
61
|
+
- 测试使用 Pinia、Router、Teleport、Suspense、async setup 或 `defineAsyncComponent` 时,在测试夹具中显式安装插件、挂载目标容器并等待异步稳定。
|
|
62
|
+
|
|
63
|
+
## Slots 与 Provide/Inject
|
|
64
|
+
|
|
65
|
+
### Slots
|
|
66
|
+
|
|
67
|
+
- 用 `<slot>` 实现组件组合,而非过多 props
|
|
68
|
+
- 具名 slot 用于明确的布局区域
|
|
69
|
+
- 作用域 slot 传递数据给父组件自定义渲染
|
|
70
|
+
|
|
71
|
+
```vue
|
|
72
|
+
<template>
|
|
73
|
+
<div class="card">
|
|
74
|
+
<div class="card-header">
|
|
75
|
+
<slot name="header">{{ title }}</slot>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="card-body">
|
|
78
|
+
<slot :data="processedData" :loading="loading" />
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</template>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Provide/Inject
|
|
85
|
+
|
|
86
|
+
- 用于跨多层级的上下文共享(主题、配置、权限)
|
|
87
|
+
- 提供 InjectionKey 保证类型安全
|
|
88
|
+
- 不要用 provide/inject 替代 props 传递直接父子数据
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// keys.ts
|
|
92
|
+
export const ThemeKey: InjectionKey<Ref<Theme>> = Symbol("theme");
|
|
93
|
+
|
|
94
|
+
// Provider.vue
|
|
95
|
+
provide(ThemeKey, theme);
|
|
96
|
+
|
|
97
|
+
// Consumer.vue
|
|
98
|
+
const theme = inject(ThemeKey);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## 路由规范
|
|
102
|
+
|
|
103
|
+
### 路由组织
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// app/router.ts
|
|
107
|
+
const routes: RouteRecordRaw[] = [
|
|
108
|
+
{
|
|
109
|
+
path: "/",
|
|
110
|
+
component: MainLayout,
|
|
111
|
+
children: [
|
|
112
|
+
{
|
|
113
|
+
path: "",
|
|
114
|
+
name: "Dashboard",
|
|
115
|
+
component: () => import("@/pages/Dashboard/DashboardPage.vue"),
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
path: "users",
|
|
119
|
+
name: "UserList",
|
|
120
|
+
component: () => import("@/pages/UserList/UserListPage.vue"),
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
path: "users/:id",
|
|
124
|
+
name: "UserDetail",
|
|
125
|
+
component: () => import("@/pages/UserDetail/UserDetailPage.vue"),
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
path: "settings",
|
|
129
|
+
name: "Settings",
|
|
130
|
+
component: () => import("@/pages/Settings/SettingsPage.vue"),
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
path: "/login",
|
|
136
|
+
name: "Login",
|
|
137
|
+
component: () => import("@/pages/Login/LoginPage.vue"),
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
path: "/:pathMatch(.*)*",
|
|
141
|
+
name: "NotFound",
|
|
142
|
+
component: () => import("@/pages/NotFound.vue"),
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 路由原则
|
|
148
|
+
|
|
149
|
+
- 路由配置集中管理,每个路由必须有 `name`
|
|
150
|
+
- 页面组件使用动态 `import()` 按需加载
|
|
151
|
+
- 权限控制使用路由守卫(`beforeEach`),而非在每个页面内判断
|
|
152
|
+
- URL 参数(分页、筛选、排序)与路由状态同步
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// 导航守卫
|
|
156
|
+
router.beforeEach((to) => {
|
|
157
|
+
const authStore = useAuthStore();
|
|
158
|
+
if (to.meta.requiresAuth && !authStore.isLoggedIn) {
|
|
159
|
+
return { name: "Login", query: { redirect: to.fullPath } };
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Vue 3 performance patterns
|
|
2
|
+
|
|
3
|
+
This file keeps lightweight Vue project-standard performance guidance. Use the frontend performance workflow when the task needs metrics, traces, profiles, bundle analysis, network evidence, or a formal performance report.
|
|
4
|
+
|
|
5
|
+
## 性能
|
|
6
|
+
|
|
7
|
+
- 使用 `shallowRef` / `shallowReactive` 优化大型对象
|
|
8
|
+
- 大列表使用虚拟滚动
|
|
9
|
+
- 避免在 `v-for` 中使用 `v-if`(提取为 computed 过滤)
|
|
10
|
+
- 使用 `defineAsyncComponent` 懒加载重型组件
|
|
11
|
+
- `v-for` 必须有稳定的 `:key`
|
|
12
|
+
- 路由组件使用动态 `import()` 按需加载
|
|
13
|
+
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Vue 3 project structure and component standards
|
|
2
|
+
|
|
3
|
+
## 项目结构
|
|
4
|
+
|
|
5
|
+
以下为中大型 Vue 3 项目的业界最佳实践结构,按项目实际情况裁剪:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/
|
|
9
|
+
├── app/ # 应用入口与全局配置
|
|
10
|
+
│ ├── App.vue # 根组件
|
|
11
|
+
│ ├── main.ts # 应用启动入口
|
|
12
|
+
│ └── router.ts # 路由实例与配置
|
|
13
|
+
│
|
|
14
|
+
├── pages/ # 页面组件(与路由一一对应)
|
|
15
|
+
│ ├── Dashboard/
|
|
16
|
+
│ │ ├── DashboardPage.vue
|
|
17
|
+
│ │ ├── components/ # 页面私有组件
|
|
18
|
+
│ │ └── composables/ # 页面私有 composables
|
|
19
|
+
│ ├── UserList/
|
|
20
|
+
│ └── Settings/
|
|
21
|
+
│
|
|
22
|
+
├── layouts/ # 布局组件
|
|
23
|
+
│ ├── MainLayout.vue # 主布局(侧边栏 + 顶栏 + 内容区)
|
|
24
|
+
│ ├── AuthLayout.vue # 登录/注册页布局
|
|
25
|
+
│ └── BlankLayout.vue # 空白布局(错误页等)
|
|
26
|
+
│
|
|
27
|
+
├── features/ # 功能模块(按业务领域划分)
|
|
28
|
+
│ ├── auth/
|
|
29
|
+
│ │ ├── components/ # 模块组件
|
|
30
|
+
│ │ ├── composables/ # 模块 composables
|
|
31
|
+
│ │ ├── api.ts # 模块 API 调用
|
|
32
|
+
│ │ ├── types.ts # 模块类型定义
|
|
33
|
+
│ │ ├── constants.ts # 模块常量
|
|
34
|
+
│ │ └── index.ts # 模块公开导出
|
|
35
|
+
│ └── order/
|
|
36
|
+
│
|
|
37
|
+
├── components/ # 全局共享 UI 组件
|
|
38
|
+
│ ├── AppButton/
|
|
39
|
+
│ │ ├── AppButton.vue
|
|
40
|
+
│ │ └── __tests__/
|
|
41
|
+
│ ├── AppModal/
|
|
42
|
+
│ ├── AppForm/
|
|
43
|
+
│ └── AppErrorBoundary/
|
|
44
|
+
│
|
|
45
|
+
├── composables/ # 全局共享 composables
|
|
46
|
+
│ ├── useAuth.ts
|
|
47
|
+
│ ├── useDebounce.ts
|
|
48
|
+
│ └── useMediaQuery.ts
|
|
49
|
+
│
|
|
50
|
+
├── services/ # API 基础层
|
|
51
|
+
│ ├── request.ts # Axios/fetch 实例与拦截器
|
|
52
|
+
│ └── endpoints/ # API 端点定义(如按领域拆分)
|
|
53
|
+
│
|
|
54
|
+
├── stores/ # Pinia 状态管理
|
|
55
|
+
│ ├── authStore.ts
|
|
56
|
+
│ └── uiStore.ts
|
|
57
|
+
│
|
|
58
|
+
├── locales/ # 国际化语言包
|
|
59
|
+
│ ├── zh-CN.json # 中文
|
|
60
|
+
│ ├── en-US.json # 英文
|
|
61
|
+
│ └── index.ts # i18n 实例初始化(vue-i18n)
|
|
62
|
+
│
|
|
63
|
+
├── assets/ # 静态资源
|
|
64
|
+
│ ├── images/ # 图片(PNG、JPG、WebP)
|
|
65
|
+
│ ├── icons/ # SVG 图标
|
|
66
|
+
│ └── fonts/ # 自定义字体
|
|
67
|
+
│
|
|
68
|
+
├── config/ # 应用配置
|
|
69
|
+
│ ├── env.ts # 环境变量类型化封装
|
|
70
|
+
│ └── features.ts # Feature Flags 管理
|
|
71
|
+
│
|
|
72
|
+
├── types/ # 全局共享类型
|
|
73
|
+
│ ├── api.ts # API 响应/请求通用类型
|
|
74
|
+
│ ├── models.ts # 业务实体类型
|
|
75
|
+
│ └── global.d.ts # 全局类型扩展(组件类型、模块声明等)
|
|
76
|
+
│
|
|
77
|
+
├── utils/ # 纯工具函数
|
|
78
|
+
│ ├── format.ts # 日期、数字、货币格式化
|
|
79
|
+
│ ├── validators.ts # 表单校验规则
|
|
80
|
+
│ └── storage.ts # LocalStorage / SessionStorage 封装
|
|
81
|
+
│
|
|
82
|
+
├── directives/ # 自定义指令
|
|
83
|
+
│ ├── vPermission.ts # 权限指令
|
|
84
|
+
│ └── vClickOutside.ts # 点击外部关闭
|
|
85
|
+
│
|
|
86
|
+
├── plugins/ # Vue 插件注册
|
|
87
|
+
│ ├── i18n.ts # vue-i18n 插件配置
|
|
88
|
+
│ └── index.ts # 插件统一注册入口
|
|
89
|
+
│
|
|
90
|
+
├── styles/ # 全局样式与主题
|
|
91
|
+
│ ├── global.css # 全局基础样式(reset / normalize)
|
|
92
|
+
│ ├── variables.css # CSS 变量(颜色、间距、字号)
|
|
93
|
+
│ ├── breakpoints.ts # 响应式断点常量
|
|
94
|
+
│ └── themes/ # 主题定义
|
|
95
|
+
│ ├── light.css # 亮色主题变量
|
|
96
|
+
│ ├── dark.css # 暗色主题变量
|
|
97
|
+
│ └── index.ts # 主题切换逻辑
|
|
98
|
+
│
|
|
99
|
+
└── constants/ # 全局常量
|
|
100
|
+
├── routes.ts # 路由路径常量
|
|
101
|
+
└── config.ts # 业务常量(分页大小、超时时间等)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 关键原则
|
|
105
|
+
|
|
106
|
+
- `pages/` 做路由映射和布局组合,不放业务逻辑
|
|
107
|
+
- `layouts/` 定义页面骨架(侧边栏、顶栏、面包屑),由路由配置的 `component` 引用
|
|
108
|
+
- `features/` 按业务领域划分,模块内自包含(components + composables + api + types)
|
|
109
|
+
- `components/` 仅放无业务耦合的通用组件,可跨项目复用
|
|
110
|
+
- `composables/` 仅放通用逻辑(防抖、媒体查询等),业务 composables 放到对应 feature 中
|
|
111
|
+
- `locales/` 存放语言包 JSON 文件,模板中使用 `$t('key')` 而非硬编码文案
|
|
112
|
+
- `assets/` 存放静态资源,图标优先使用 SVG,图片优先使用 WebP/AVIF
|
|
113
|
+
- `config/` 封装环境变量和 Feature Flags,禁止组件中直接读取 `import.meta.env`
|
|
114
|
+
- `styles/themes/` 通过 CSS 变量实现主题切换,组件中引用变量而非硬编码颜色
|
|
115
|
+
- 每个模块通过 `index.ts` 管控公开 API,避免深层路径导入
|
|
116
|
+
|
|
117
|
+
## 组件设计规范
|
|
118
|
+
|
|
119
|
+
- 使用 `<script setup lang="ts">`
|
|
120
|
+
- 明确使用 `defineProps` / `defineEmits` 并附带类型
|
|
121
|
+
- 可复用逻辑优先提取到 composables
|
|
122
|
+
- 保持 template 可读,避免过深条件嵌套
|
|
123
|
+
- 优先使用计算属性,而不是重复维护状态
|
|
124
|
+
- 避免构建大型单体组件;**单文件规模**见 `templates/shared/rules/fec-vue.md`「**组件文件规模**」(约 300 行内为佳,逾 500 行或复杂度过高拆子组件与 Composables)
|
|
125
|
+
- 优先使用强类型的 props、emits 和暴露方法
|
|
126
|
+
- 遵循仓库的文件与目录命名规范
|
|
127
|
+
- 优先复用现有 UI 组件和 Token
|
|
128
|
+
|
|
129
|
+
### 组件分层
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
页面组件 (Pages) → 路由映射、布局组合
|
|
133
|
+
└── 容器组件 (Containers) → 数据获取、状态编排
|
|
134
|
+
└── 业务组件 (Features) → 领域逻辑展示
|
|
135
|
+
└── 通用组件 (UI) → 纯展示,无业务耦合
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## 注释规范
|
|
139
|
+
|
|
140
|
+
- **优先使用中文**:解释「为什么这样做」、业务约束、边界情况、非显而易见的权衡时,优先用中文撰写注释,便于团队与业务方阅读。
|
|
141
|
+
- **与代码语言一致时的例外**:对接第三方协议字段名、HTTP 头、规范中的英文术语时,注释里可保留英文专有名词,必要时中英文并列说明。
|
|
142
|
+
- **少而精**:能通过清晰命名与类型表达清楚的逻辑不写废话注释;复杂分支、临时兼容、性能取舍必须写清意图。
|
|
143
|
+
- **公开 API**:composable 或模块的对外契约可用 JSDoc(`@param` / `@returns` / `@example`),说明用中文即可,除非仓库统一要求英文。
|
|
144
|
+
|
|
145
|
+
## TypeScript 规范
|
|
146
|
+
|
|
147
|
+
通用 TypeScript 工程规则由 `fec-typescript-project-standard` 负责,包括 `tsconfig`、strictness、DTO / view model 边界、`unknown` 收窄、public API 类型、声明产物和类型测试。
|
|
148
|
+
|
|
149
|
+
Vue 规范只补充 SFC、Props / Emits、暴露方法、Provide / Inject 和 composable 响应性类型;复杂类型建模或跨框架类型契约先分流到 `fec-typescript-project-standard`。
|
|
150
|
+
|
|
151
|
+
### Vue 3 项目补充约定
|
|
152
|
+
|
|
153
|
+
```vue
|
|
154
|
+
<script setup lang="ts">
|
|
155
|
+
interface Props {
|
|
156
|
+
title: string;
|
|
157
|
+
items: Item[];
|
|
158
|
+
loading?: boolean;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
interface Emits {
|
|
162
|
+
(e: "select", item: Item): void;
|
|
163
|
+
(e: "delete", id: string): void;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
167
|
+
loading: false,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const emit = defineEmits<Emits>();
|
|
171
|
+
</script>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
- Props 和 Emits 使用 TypeScript interface 定义
|
|
175
|
+
- 使用 `withDefaults` 设置默认值
|
|
176
|
+
- `defineExpose` 暴露的方法需有类型约束
|
|
177
|
+
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Vue 3 state, API, and quality patterns
|
|
2
|
+
|
|
3
|
+
## 状态管理(Pinia)
|
|
4
|
+
|
|
5
|
+
| 状态类型 | 推荐方案 |
|
|
6
|
+
| ------------------ | ----------------------------- |
|
|
7
|
+
| 组件内临时 UI 状态 | `ref` / `reactive` |
|
|
8
|
+
| 跨组件共享业务状态 | Pinia store |
|
|
9
|
+
| 服务端数据缓存 | VueQuery / 自定义 composable |
|
|
10
|
+
| URL 驱动状态 | 路由参数 / `useRoute().query` |
|
|
11
|
+
| 表单状态 | VeeValidate / FormKit |
|
|
12
|
+
|
|
13
|
+
### Pinia Store 规范
|
|
14
|
+
|
|
15
|
+
使用 Composition API 风格(`setup store`):
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
// stores/authStore.ts
|
|
19
|
+
export const useAuthStore = defineStore("auth", () => {
|
|
20
|
+
const user = ref<User | null>(null);
|
|
21
|
+
const token = ref<string | null>(localStorage.getItem("token"));
|
|
22
|
+
|
|
23
|
+
const isLoggedIn = computed(() => !!token.value);
|
|
24
|
+
|
|
25
|
+
async function login(credentials: LoginParams) {
|
|
26
|
+
const res = await authApi.login(credentials);
|
|
27
|
+
user.value = res.user;
|
|
28
|
+
token.value = res.token;
|
|
29
|
+
localStorage.setItem("token", res.token);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function logout() {
|
|
33
|
+
user.value = null;
|
|
34
|
+
token.value = null;
|
|
35
|
+
localStorage.removeItem("token");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return { user: readonly(user), isLoggedIn, login, logout };
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Store 原则
|
|
43
|
+
|
|
44
|
+
- 每个 store 职责单一,按领域拆分
|
|
45
|
+
- 对外暴露 `readonly` 的状态,通过 action 修改
|
|
46
|
+
- 不要在 store 中存放 UI 临时状态(modal 开关、表单输入等)
|
|
47
|
+
- 服务端数据优先用请求库管理,而非手动存入 store
|
|
48
|
+
|
|
49
|
+
## API 层规范
|
|
50
|
+
|
|
51
|
+
### 请求实例
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// services/request.ts
|
|
55
|
+
const request = axios.create({
|
|
56
|
+
baseURL: import.meta.env.VITE_API_BASE_URL,
|
|
57
|
+
timeout: 10000,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
request.interceptors.request.use((config) => {
|
|
61
|
+
const authStore = useAuthStore();
|
|
62
|
+
if (authStore.token) {
|
|
63
|
+
config.headers.Authorization = `Bearer ${authStore.token}`;
|
|
64
|
+
}
|
|
65
|
+
return config;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
request.interceptors.response.use(
|
|
69
|
+
(res) => res.data,
|
|
70
|
+
(error) => {
|
|
71
|
+
if (error.response?.status === 401) {
|
|
72
|
+
const authStore = useAuthStore();
|
|
73
|
+
authStore.logout();
|
|
74
|
+
router.push({ name: "Login" });
|
|
75
|
+
}
|
|
76
|
+
return Promise.reject(normalizeError(error));
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### API 函数
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// features/user/api.ts
|
|
85
|
+
export function getUserList(
|
|
86
|
+
params: UserQueryParams,
|
|
87
|
+
): Promise<PageResult<User>> {
|
|
88
|
+
return request.get("/users", { params });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function updateUser(id: string, data: UpdateUserDTO): Promise<User> {
|
|
92
|
+
return request.put(`/users/${id}`, data);
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- API 函数按 feature 组织,而非全部堆在一个文件
|
|
97
|
+
- 请求参数和响应都有类型约束
|
|
98
|
+
- 拦截器统一处理认证、错误格式化
|
|
99
|
+
|
|
100
|
+
## 错误处理
|
|
101
|
+
|
|
102
|
+
### 全局错误捕获
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// main.ts
|
|
106
|
+
app.config.errorHandler = (err, instance, info) => {
|
|
107
|
+
reportError(err, { component: instance?.$options.name, info });
|
|
108
|
+
};
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 组件级错误
|
|
112
|
+
|
|
113
|
+
- 使用 `onErrorCaptured` 在父组件中捕获子组件错误
|
|
114
|
+
- 数据请求失败需有用户可见的提示和重试机制
|
|
115
|
+
- 不要吞掉错误(空 catch 块)
|
|
116
|
+
|
|
117
|
+
## 自定义指令
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
// directives/vPermission.ts
|
|
121
|
+
export const vPermission: Directive<HTMLElement, string> = {
|
|
122
|
+
mounted(el, binding) {
|
|
123
|
+
const authStore = useAuthStore();
|
|
124
|
+
if (!authStore.hasPermission(binding.value)) {
|
|
125
|
+
el.parentNode?.removeChild(el);
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
- 指令只处理 DOM 层面的操作
|
|
132
|
+
- 业务逻辑不要放在指令中
|
|
133
|
+
- 需要响应式更新时实现 `updated` 钩子
|
|
134
|
+
|
|
135
|
+
## 样式规范
|
|
136
|
+
|
|
137
|
+
- 使用 `<style scoped>` 隔离样式
|
|
138
|
+
- 深度选择器使用 `:deep()` 而非已废弃的 `::v-deep`
|
|
139
|
+
- 与项目现有样式体系保持一致
|
|
140
|
+
- 动态样式优先使用 `:class` / `:style` 绑定
|
|
141
|
+
- 复杂主题切换使用 CSS 变量
|
|
142
|
+
- 主题/全局变量通过 CSS 变量或 Token 管理
|
|
143
|
+
|
|
144
|
+
## 测试规范
|
|
145
|
+
|
|
146
|
+
### 必须测试
|
|
147
|
+
|
|
148
|
+
- 核心交互行为(点击、输入、提交)
|
|
149
|
+
- 条件渲染(loading / error / empty / data)
|
|
150
|
+
- Emits 的触发和 payload
|
|
151
|
+
- 关键 composables 的返回值
|
|
152
|
+
- Pinia store 的 action 和 getter
|
|
153
|
+
|
|
154
|
+
### 测试风格
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
describe("UserForm", () => {
|
|
158
|
+
it("should emit submit with valid data", async () => {
|
|
159
|
+
const wrapper = mount(UserForm);
|
|
160
|
+
|
|
161
|
+
await wrapper.find('[data-testid="username"]').setValue("test");
|
|
162
|
+
await wrapper.find("form").trigger("submit");
|
|
163
|
+
|
|
164
|
+
expect(wrapper.emitted("submit")?.[0]).toEqual([{ username: "test" }]);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("should show validation error on empty submit", async () => {
|
|
168
|
+
const wrapper = mount(UserForm);
|
|
169
|
+
await wrapper.find("form").trigger("submit");
|
|
170
|
+
expect(wrapper.text()).toContain("用户名不能为空");
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Store 测试
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
describe("authStore", () => {
|
|
179
|
+
beforeEach(() => setActivePinia(createPinia()));
|
|
180
|
+
|
|
181
|
+
it("should login and set user", async () => {
|
|
182
|
+
const store = useAuthStore();
|
|
183
|
+
await store.login({ username: "admin", password: "pass" });
|
|
184
|
+
expect(store.isLoggedIn).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
## 反模式
|
|
191
|
+
|
|
192
|
+
- 在模板驱动组件中内联大段业务逻辑
|
|
193
|
+
- 本该用 computed 却使用大范围 watcher
|
|
194
|
+
- 事件 payload 不明确
|
|
195
|
+
- 在纯展示组件中直接混入原始 API 调用
|
|
196
|
+
- ref 和 props 没有类型约束
|
|
197
|
+
- 用 `watch` + 手动赋值模拟 computed
|
|
198
|
+
- 在一个组件中混入无关职责
|
|
199
|
+
- 用大范围全局样式覆盖去解决局部 UI 问题
|
|
200
|
+
- 将所有状态推入 Pinia store,而非就近管理
|
|
201
|
+
- 在 `components/` 中放业务耦合组件
|
|
202
|
+
- 直接从 feature 内部深层路径导入,绕过 `index.ts`
|
|
203
|
+
|
|
204
|
+
## 输出检查清单
|
|
205
|
+
|
|
206
|
+
- [ ] 文件结构与项目约定一致(pages / features / components 分离)
|
|
207
|
+
- [ ] 使用 `<script setup lang="ts">`
|
|
208
|
+
- [ ] Props / Emits 类型完整
|
|
209
|
+
- [ ] 解释性注释是否优先使用中文且点到要害
|
|
210
|
+
- [ ] 可复用逻辑已提取到 composable
|
|
211
|
+
- [ ] Loading / Error / Empty 状态均已处理
|
|
212
|
+
- [ ] 路由组件使用动态 import 加载
|
|
213
|
+
- [ ] 状态管理方案合理(就近原则)
|
|
214
|
+
- [ ] API 调用有类型约束和统一错误处理
|
|
215
|
+
- [ ] 样式使用 scoped 隔离
|
|
216
|
+
- [ ] 关键行为有测试覆盖
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fec-web-video-presentation
|
|
3
|
+
description: Use when turning an article, talk script, lesson, product demo, or narrated explanation into a recordable 16:9 web presentation with step-driven scenes, optional narration alignment, theme tokens, and screen-recording guidance; Chinese triggers include 网页视频, 动态演示, 口播稿转视频, 可录屏演示, 16:9 演示.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 网页视频演示
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
把文章、口播稿、课程、产品讲解或技术分享转成可录屏的 16:9 网页演示。产物应像一个可点击推进的视频舞台:每一步承载一个口播节拍,视觉设计服务内容节奏,并能用浏览器稳定录制。
|
|
11
|
+
|
|
12
|
+
## Procedure
|
|
13
|
+
|
|
14
|
+
1. 判断输入形态
|
|
15
|
+
- 用户给文章或长文时,先拆成口播稿和 outline。
|
|
16
|
+
- 用户给现成口播稿时,保留原顺序,只补 outline、章节和 step。
|
|
17
|
+
- 用户只给主题但没有内容时,先要求素材、大纲或目标要点;不要替用户虚构整篇内容。
|
|
18
|
+
|
|
19
|
+
2. 生成内容计划
|
|
20
|
+
- 按 [content-plan.md](references/content-plan.md) 输出 script 与 outline。
|
|
21
|
+
- script 决定讲述顺序和每个节拍;outline 决定章节、step、估时、屏幕信息和素材清单。
|
|
22
|
+
- outline 只描述内容密度和画面任务,不提前写死具体动画实现。
|
|
23
|
+
|
|
24
|
+
3. 对齐演示系统
|
|
25
|
+
- 从 [theme-system.md](references/theme-system.md) 或 [starter-themes.json](data/starter-themes.json) 选择一个 starter theme,也可以按项目品牌重新定义 token。
|
|
26
|
+
- 使用 [create-video-presentation.mjs](scripts/create-video-presentation.mjs) 创建最小 Vite + React + TypeScript 演示骨架:
|
|
27
|
+
```bash
|
|
28
|
+
node skills/fec-web-video-presentation/scripts/create-video-presentation.mjs ./presentation --theme editorial-slate
|
|
29
|
+
```
|
|
30
|
+
- 如只想预览可选主题或计划写入内容:
|
|
31
|
+
```bash
|
|
32
|
+
node skills/fec-web-video-presentation/scripts/create-video-presentation.mjs --list-themes
|
|
33
|
+
node skills/fec-web-video-presentation/scripts/create-video-presentation.mjs --dry-run ./presentation --theme editorial-slate
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
4. 实现章节
|
|
37
|
+
- 每章按 [chapter-craft.md](references/chapter-craft.md) 实现。
|
|
38
|
+
- 每个 step 独占舞台,屏幕只承载当前口播节拍需要用户理解的核心内容。
|
|
39
|
+
- 章节必须有可见的视觉演示、图形、布局变化、状态推进或数据/媒体呈现;不要交付纯文本幻灯片。
|
|
40
|
+
- 第一章应作为风格锚点优先完成并验收,再继续其它章节。
|
|
41
|
+
|
|
42
|
+
5. 录屏和音频
|
|
43
|
+
- 使用 [recording-and-audio.md](references/recording-and-audio.md) 检查舞台比例、导航、录制模式和可选音频同步。
|
|
44
|
+
- 音频可以来自用户已有文件、宿主工具或项目自己的 TTS 流程;本技能不绑定特定 TTS 供应商。
|
|
45
|
+
|
|
46
|
+
6. 交付前验证
|
|
47
|
+
- 浏览器打开演示,检查 16:9 缩放、键盘/点击推进、step 数、文本溢出、隐藏控件、控制台错误和录屏路径。
|
|
48
|
+
- 若章节数量、顺序或 step 数变化,重置本地持久化 key 或清理旧进度,避免恢复到不存在的 step。
|
|
49
|
+
|
|
50
|
+
## Constraints
|
|
51
|
+
|
|
52
|
+
- 不把网页视频做成普通营销落地页、静态 PPT 或长滚动文章。
|
|
53
|
+
- 不复制参考项目的主题、模板章节、长提示词或脚手架实现;按当前项目生成原创骨架。
|
|
54
|
+
- 不在 outline 阶段锁死每个动画名称;动画应由章节内容关系驱动。
|
|
55
|
+
- 不使用假数据、假引用、假产品截图或不可检查媒体填充画面。
|
|
56
|
+
- 不让控件、页码、说明文字或调试面板污染录屏画面;必要控件默认隐藏或弱化。
|
|
57
|
+
- 不为音频合成引入项目外供应商约定;TTS 属于可选适配层。
|
|
58
|
+
|
|
59
|
+
## Expected Output
|
|
60
|
+
|
|
61
|
+
输出 script、outline、可运行的 16:9 网页演示骨架或章节实现,并说明主题、章节/step 数、录屏方式、可选音频路径和验证结果。最终演示应可通过点击或键盘稳定推进,视觉节奏贴合口播内容,且适合浏览器录屏。
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "editorial-slate",
|
|
4
|
+
"name": "Editorial Slate",
|
|
5
|
+
"bestFor": "technical explainers, product narratives, engineering talks",
|
|
6
|
+
"tokens": {
|
|
7
|
+
"stageBg": "#f6f1e8",
|
|
8
|
+
"stageFg": "#17201b",
|
|
9
|
+
"mutedFg": "#69736d",
|
|
10
|
+
"accent": "#a83d2d",
|
|
11
|
+
"accent2": "#2b6b72",
|
|
12
|
+
"panel": "#fffaf0",
|
|
13
|
+
"line": "#d9cab8",
|
|
14
|
+
"shadow": "0 24px 80px rgba(23, 32, 27, 0.12)",
|
|
15
|
+
"fontDisplay": "Georgia, 'Times New Roman', serif",
|
|
16
|
+
"fontBody": "Inter, ui-sans-serif, system-ui, sans-serif",
|
|
17
|
+
"radius": "10px",
|
|
18
|
+
"stepDuration": "4200ms"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "console-amber",
|
|
23
|
+
"name": "Console Amber",
|
|
24
|
+
"bestFor": "debug stories, infrastructure demos, security walkthroughs",
|
|
25
|
+
"tokens": {
|
|
26
|
+
"stageBg": "#10110f",
|
|
27
|
+
"stageFg": "#f5ead2",
|
|
28
|
+
"mutedFg": "#a99b80",
|
|
29
|
+
"accent": "#ffb84d",
|
|
30
|
+
"accent2": "#6bd5c8",
|
|
31
|
+
"panel": "#191a16",
|
|
32
|
+
"line": "#3c3529",
|
|
33
|
+
"shadow": "0 28px 90px rgba(0, 0, 0, 0.38)",
|
|
34
|
+
"fontDisplay": "ui-monospace, SFMono-Regular, Consolas, monospace",
|
|
35
|
+
"fontBody": "Inter, ui-sans-serif, system-ui, sans-serif",
|
|
36
|
+
"radius": "6px",
|
|
37
|
+
"stepDuration": "3800ms"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"id": "studio-paper",
|
|
42
|
+
"name": "Studio Paper",
|
|
43
|
+
"bestFor": "design reviews, lessons, product tours, calm keynote chapters",
|
|
44
|
+
"tokens": {
|
|
45
|
+
"stageBg": "#fbfbf7",
|
|
46
|
+
"stageFg": "#24211d",
|
|
47
|
+
"mutedFg": "#6f6a62",
|
|
48
|
+
"accent": "#2457a6",
|
|
49
|
+
"accent2": "#d95f37",
|
|
50
|
+
"panel": "#ffffff",
|
|
51
|
+
"line": "#dedbd2",
|
|
52
|
+
"shadow": "0 18px 64px rgba(36, 33, 29, 0.1)",
|
|
53
|
+
"fontDisplay": "Aptos Display, Avenir Next, ui-sans-serif, system-ui, sans-serif",
|
|
54
|
+
"fontBody": "Aptos, Avenir Next, ui-sans-serif, system-ui, sans-serif",
|
|
55
|
+
"radius": "14px",
|
|
56
|
+
"stepDuration": "4000ms"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
]
|