jsharness 1.0.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.
Files changed (68) hide show
  1. package/.harness/README.md +199 -0
  2. package/.harness/agents/code-reviewer/contract.yaml +64 -0
  3. package/.harness/agents/developer/contract.yaml +72 -0
  4. package/.harness/agents/gate-controller/contract.yaml +64 -0
  5. package/.harness/agents/project-manager/contract.yaml +77 -0
  6. package/.harness/agents/prompt-templates.md +352 -0
  7. package/.harness/agents/requirements-analyst/contract.yaml +64 -0
  8. package/.harness/agents/solution-designer/contract.yaml +75 -0
  9. package/.harness/agents/tester/contract.yaml +92 -0
  10. package/.harness/config/models.yaml +67 -0
  11. package/.harness/dev-map/backend/api-definition.md +131 -0
  12. package/.harness/dev-map/backend/auth-security.md +131 -0
  13. package/.harness/dev-map/backend/conventions-java.md +471 -0
  14. package/.harness/dev-map/backend/conventions.md +192 -0
  15. package/.harness/dev-map/backend/database.md +106 -0
  16. package/.harness/dev-map/backend/structure.md +140 -0
  17. package/.harness/dev-map/decisions.md +275 -0
  18. package/.harness/dev-map/frontend/api-integration.md +139 -0
  19. package/.harness/dev-map/frontend/components.md +178 -0
  20. package/.harness/dev-map/frontend/conventions.md +416 -0
  21. package/.harness/dev-map/frontend/state-management.md +170 -0
  22. package/.harness/dev-map/frontend/structure.md +103 -0
  23. package/.harness/dev-map/overview.md +267 -0
  24. package/.harness/docs/integration-test-plan.md +248 -0
  25. package/.harness/docs/team-guidelines/README.md +161 -0
  26. package/.harness/docs/team-guidelines/arch-team.md +811 -0
  27. package/.harness/docs/team-guidelines/collaboration.md +556 -0
  28. package/.harness/docs/team-guidelines/pm-team.md +337 -0
  29. package/.harness/docs/team-guidelines/qa-team.md +562 -0
  30. package/.harness/docs/team-guidelines/rd-team.md +714 -0
  31. package/.harness/docs/training-materials.md +280 -0
  32. package/.harness/gate/baseline.js +220 -0
  33. package/.harness/gate/checks/build-gates-frontend.js +152 -0
  34. package/.harness/gate/checks/build-gates-java.js +155 -0
  35. package/.harness/gate/checks/build-gates.js +119 -0
  36. package/.harness/gate/checks/engineering-consistency.js +138 -0
  37. package/.harness/gate/checks/security-quality.js +129 -0
  38. package/.harness/gate/checks/static-compliance.js +313 -0
  39. package/.harness/gate/checks/test-compliance.js +114 -0
  40. package/.harness/gate/index.js +315 -0
  41. package/.harness/mcp/config.yaml +435 -0
  42. package/.harness/rules/global/coding-standard.md +232 -0
  43. package/.harness/rules/global/commit-convention.md +165 -0
  44. package/.harness/rules/global/process-discipline.md +192 -0
  45. package/.harness/rules/global/security-baseline.md +306 -0
  46. package/.harness/rules/project/frontend-vue3.md +293 -0
  47. package/.harness/rules/project/java-backend.md +460 -0
  48. package/.harness/rules/project/web-specific.md +231 -0
  49. package/.harness/skills/build.md +192 -0
  50. package/.harness/skills/code-review.md +251 -0
  51. package/.harness/skills/docker-build.md +227 -0
  52. package/.harness/skills/docs-update.md +164 -0
  53. package/.harness/skills/java-build.md +261 -0
  54. package/.harness/skills/lint-check.md +482 -0
  55. package/.harness/skills/task-board-maintenance.md +105 -0
  56. package/.harness/skills/test-api.md +461 -0
  57. package/.harness/skills/test-e2e.md +431 -0
  58. package/.harness/skills/test-unit.md +649 -0
  59. package/.harness/skills/vue-frontend-build.md +344 -0
  60. package/.harness/specs/quality-feedback/implementation-guide.md +350 -0
  61. package/.harness/task-board.md +121 -0
  62. package/.harness/workflow/definition.yaml +504 -0
  63. package/.harness/workflow/validate.js +320 -0
  64. package/.harness/workflow/variants.yaml +253 -0
  65. package/README.md +237 -0
  66. package/bin/jsharness.js +53 -0
  67. package/lib/index.mjs +778 -0
  68. package/package.json +1 -0
@@ -0,0 +1,178 @@
1
+ # 前端分区 — 组件库与复用模式
2
+
3
+ ## 通用 UI 组件库 (`components/ui/`) — Element Plus 二次封装
4
+
5
+ 本项目基于 **Element Plus** 进行二次封装,确保全局风格统一和便捷使用。
6
+
7
+ ### 已有组件清单
8
+
9
+ | 组件名 | 文件路径 | Props | 用途 | 基于 |
10
+ |--------|----------|-------|------|------|
11
+ | `AppButton` | `ui/button/AppButton.vue` | variant, size, loading, disabled | 按钮 | ElButton |
12
+ | `AppInput` | `ui/input/AppInput.vue` | type, error, prefix, suffix | 输入框 | ElInput |
13
+ | `AppModal` | `ui/modal/AppModal.vue` | v-model:visible, title, size | 弹窗 | ElDialog |
14
+ | `AppTable` | `ui/table/AppTable.vue` | columns, data, pagination | 数据表格 | ElTable |
15
+ | `AppCard` | `ui/card/AppCard.vue` | title, actions | 卡片容器 | 自定义 |
16
+ | `AppEmptyState` | `ui/empty-state/AppEmptyState.vue` | icon, title, description | 空状态占位 | 自定义 |
17
+ | `AppConfirmDialog` | `ui/confirm-dialog/AppConfirmDialog.vue` | title, message, onConfirm | 确认对话框 | ElMessageBox |
18
+ | `AppAvatar` | `ui/avatar/AppAvatar.vue` | src, name, size | 用户头像 | 自定义 |
19
+
20
+ ### 组件设计规范
21
+
22
+ ```vue
23
+ <!-- ✅ 正确:组件接口清晰,支持多态 -->
24
+ <script setup lang="ts">
25
+ interface AppButtonProps {
26
+ variant?: 'primary' | 'secondary' | 'ghost' | 'danger'
27
+ size?: 'sm' | 'md' | 'lg'
28
+ loading?: boolean
29
+ disabled?: boolean
30
+ }
31
+
32
+ const props = withDefaults(defineProps<AppButtonProps>(), {
33
+ variant: 'primary',
34
+ size: 'md',
35
+ })
36
+
37
+ const emit = defineEmits<{
38
+ click: [event: MouseEvent]
39
+ }>()
40
+
41
+ const handleClick = (e: MouseEvent) => {
42
+ if (!props.disabled && !props.loading) {
43
+ emit('click', e)
44
+ }
45
+ }
46
+ </script>
47
+
48
+ <template>
49
+ <ElButton
50
+ :type="variant"
51
+ :size="size"
52
+ :loading="loading"
53
+ :disabled="disabled"
54
+ @click="handleClick"
55
+ >
56
+ <slot />
57
+ </ElButton>
58
+ </template>
59
+ ```
60
+
61
+ ```typescript
62
+ // ✅ 正确:统一导出模式
63
+ // components/ui/button/index.ts
64
+ export { default as AppButton } from './AppButton.vue'
65
+ export type { AppButtonProps } from './AppButton.vue'
66
+ ```
67
+
68
+ ## 业务功能组件 (`components/features/`)
69
+
70
+ ### 组织方式
71
+
72
+ 按**功能域**组织,每个功能域一个文件夹:
73
+
74
+ ```
75
+ features/
76
+ ├── user-management/ # 用户管理相关
77
+ │ ├── UserList.vue # 用户列表
78
+ │ ├── UserDetail.vue # 用户详情
79
+ │ ├── UserForm.vue # 新建/编辑表单
80
+ │ ├── UserAvatar.vue # 头像组件
81
+ │ └── index.ts
82
+ ├── data-dashboard/ # 数据看板相关
83
+ │ ├── StatsCard.vue # 统计卡片
84
+ │ ├── TrendChart.vue # 趋势图表
85
+ │ └── DataTable.vue # 数据表格
86
+ └── notification/ # 通知相关
87
+ ├── NotificationList.vue
88
+ └── NotificationBadge.vue
89
+ ```
90
+
91
+ ## 复用模式
92
+
93
+ ### 1. 组合模式(Composition / Slots)
94
+
95
+ ```vue
96
+ <!-- 将复杂 UI 拆分为原子组件的组合 -->
97
+ <template>
98
+ <AppDataTable>
99
+ <AppDataTable.Header>
100
+ <AppDataTable.Column id="name" sortable>姓名</AppDataTable.Column>
101
+ <AppDataTable.Column id="email">邮箱</AppDataTable.Column>
102
+ </AppDataTable.Header>
103
+ <AppDataTable.Body>
104
+ <!-- 内容由 slot 注入 -->
105
+ </AppDataTable.Body>
106
+ </AppDataTable>
107
+ </template>
108
+ ```
109
+
110
+ ### 2. Slots / Scoped Slots 模式
111
+
112
+ ```vue
113
+ <!-- 让父组件控制内部渲染逻辑 -->
114
+ <template>
115
+ <AppCard>
116
+ <template #header>
117
+ <AppCard.Title>标题</AppCard.Title>
118
+ </template>
119
+
120
+ <!-- 子内容由使用者决定 -->
121
+ <slot />
122
+
123
+ <template #footer>
124
+ <AppButton @click="$emit('save')">保存</AppButton>
125
+ </template>
126
+ </AppCard>
127
+ </template>
128
+ ```
129
+
130
+ ### 3. Composable + 组件分离(逻辑复用)
131
+
132
+ ```vue
133
+ <!-- 逻辑抽离为 Composable,组件只负责渲染 -->
134
+ <script setup lang="ts">
135
+ // composables/useUserList.ts
136
+ function useUserList() {
137
+ const { data: users, loading, error, refresh } = useFetch('/api/users')
138
+
139
+ if (loading.value) return { isLoading: true }
140
+ if (error.value) return { isError: true, error, retry: refresh }
141
+
142
+ return { users, isLoading: false, isError: false, refresh }
143
+ }
144
+ </script>
145
+
146
+ <template>
147
+ <!-- UserList.vue 只负责展示 -->
148
+ <div v-loading="isLoading">
149
+ <AppEmptyState v-if="isError" :title="'加载失败'" @retry="retry" />
150
+ <template v-else>
151
+ <UserCard v-for="user in users" :key="user.id" :user="user" />
152
+ </template>
153
+ </div>
154
+ </template>
155
+ ```
156
+
157
+ ## 禁止的反模式
158
+
159
+ ```vue
160
+ <!-- ❌ 禁止:在组件中直接 fetch(应使用 Composable) -->
161
+ <script setup lang="ts">
162
+ const userId = defineProp<string>('id')
163
+ const user = ref(null)
164
+
165
+ onMounted(async () => {
166
+ const res = await fetch(`/api/users/${userId}`)
167
+ user.value = await res.json()
168
+ }) // 应使用 useUser(id) Composable
169
+ </script>
170
+
171
+ <!-- ❌ 禁止:硬编码中文字符串(应使用 i18n) -->
172
+ <template>
173
+ <h1>用户信息</h1><!-- 应使用 {{ t('user.profile') }} -->
174
+ </template>
175
+
176
+ <!-- ❌ 禁止:巨型单文件组件 (>300 行) -->
177
+ <!-- 应拆分为子组件或提取 Composable -->
178
+ ```
@@ -0,0 +1,416 @@
1
+ # 前端分区 — 编码惯例 (Vue3 增强)
2
+
3
+ > **来源**: `files/frontend-project-conventions/SKILL.md` (v1.0.0)
4
+ > **归档日期**: 2026-05-21
5
+ > **技术栈**: Vue 3 + TypeScript + Vite + Pinia + Vue Router + Element Plus
6
+
7
+ ## 技术栈版本速查
8
+
9
+ | 组件 | 推荐版本 | 说明 |
10
+ |------|---------|------|
11
+ | Vue | ^3.4.x | Composition API 正式稳定版 |
12
+ | TypeScript | ^5.3.x | strict mode 强制 |
13
+ | Vite | ^5.x | 构建工具 |
14
+ | Pinia | ^2.x | 官方状态管理 |
15
+ | Vue Router | ^4.x | 路由管理 |
16
+ | Element Plus | ^2.x | UI 组件库 |
17
+ | Axios | ^1.x | HTTP 客户端 |
18
+ | Vitest | ^1.x | 测试框架 |
19
+
20
+ ---
21
+
22
+ ## 文件命名
23
+
24
+ | 类型 | 规范 | 示例 |
25
+ |------|------|------|
26
+ | Vue 单文件组件 | PascalCase | `UserProfile.vue` |
27
+ | Hook / 组合式函数 | camelCase + `use` 前缀 | `useUserProfile.ts` |
28
+ | 工具函数 | camelCase | `formatDate.ts` |
29
+ | 全局类型定义 | camelCase + types | `user.types.ts` |
30
+ | 常量/枚举 | UPPER_SNAKE_CASE | `API_ENDPOINTS.ts` |
31
+ | 样式文件 | 与组件同名 | `UserProfile.styles.ts` |
32
+ | 测试文件 | 组件名 + `.test/.spec` | `UserProfile.test.tsx` |
33
+
34
+ ---
35
+
36
+ ## 标准目录结构详解
37
+
38
+ ```
39
+ src/
40
+ ├── api/ # API 接口封装层
41
+ │ ├── client.ts # Axios 实例创建、拦截器配置
42
+ │ ├── modules/ # 按业务模块拆分的 API 调用
43
+ │ │ └── user.ts # 用户相关 API(list/create/update/delete)
44
+ │ └── types/ # API 请求和响应的 TS 类型定义
45
+ │ └── user.d.ts # UserAPI 的 ReqVO/RespVO 类型
46
+
47
+ ├── assets/ # 静态资源
48
+ │ ├── styles/ # 全局样式(variables.css / reset.css / global.css)
49
+ │ └── images/ # 图片资源(logo / icons / illustrations)
50
+
51
+ ├── components/ # 可复用组件
52
+ │ ├── ui/ # 通用 UI 组件(无业务逻辑)
53
+ │ │ ├── Button/
54
+ │ │ │ ├── Button.vue
55
+ │ │ │ ├── Button.test.ts
56
+ │ │ │ └── index.ts
57
+ │ │ └── Modal/
58
+ │ └── business/ # 业务组件(含业务逻辑)
59
+ │ └── UserProfileCard/
60
+
61
+ ├── pages/ # 页面级组件(路由对应)
62
+ │ ├── user/
63
+ │ │ ├── UserList.vue # 列表页
64
+ │ │ ├── UserDetail.vue # 详情页
65
+ │ │ └── UserCreate.vue # 创建/编辑页
66
+ │ └── dashboard/
67
+
68
+ ├── router/ # 路由配置
69
+ │ ├── index.ts # 路由实例 + 首屏路由
70
+ │ ├── routes/ # 按模块拆分的路由定义
71
+ │ │ ├── user.ts
72
+ │ │ └── dashboard.ts
73
+ │ └── guards/ # 导航守卫(权限校验 / 登录拦截)
74
+ │ └── auth.ts
75
+
76
+ ├── store/ # Pinia Store
77
+ │ ├── index.ts # Store 入口
78
+ │ └── modules/ # 按业务模块拆分
79
+ │ ├── user.ts # 用户状态(登录信息 / 权限列表)
80
+ │ └── app.ts # 应用全局状态(侧边栏 / 主题)
81
+
82
+ ├── types/ # 全局共享类型定义
83
+ │ ├── user.d.ts # 用户相关类型接口
84
+ │ ├── api.d.ts # 通用 API 类型(PaginatedResult / CommonResult)
85
+ │ └── env.d.ts # 环境变量类型声明
86
+
87
+ ├── utils/ # 工具函数
88
+ │ ├── format.ts # 格式化(日期/金额/手机号脱敏)
89
+ │ ├── request.ts # 封装请求方法(get/post/put/delete)
90
+ │ └── storage.ts # localStorage 封装
91
+
92
+ ├── composables/ # Vue3 组合式函数(可复用逻辑)
93
+ │ ├── useAuth.ts # 认证状态管理
94
+ │ ├── usePagination.ts # 分页逻辑封装
95
+ │ └── usePermission.ts # 权限判断
96
+
97
+ ├── layouts/ # 布局组件
98
+ │ ├── DefaultLayout.vue # 默认布局(Header + Sidebar + Content)
99
+ │ └── BlankLayout.vue # 空白布局(登录页等)
100
+
101
+ └── App.vue # 根组件
102
+ ```
103
+
104
+ ---
105
+
106
+ ## 标准组件模板
107
+
108
+ ```vue
109
+ <!--
110
+ * ComponentName.vue — {功能描述}
111
+ *
112
+ * @description {详细说明组件的功能和使用场景}
113
+ * @props {Type} propName - prop 说明
114
+ * @emits eventName - 触发时机和参数
115
+ * @author frontend-team
116
+ -->
117
+ <script setup lang="ts">
118
+ /**
119
+ * 外部依赖
120
+ */
121
+ import { ref, computed, onMounted } from 'vue';
122
+ import { useRouter } from 'vue-router';
123
+ import { ElMessage } from 'element-plus';
124
+
125
+ /**
126
+ * 类型定义
127
+ */
128
+ interface Props {
129
+ id: string;
130
+ title: string;
131
+ loading?: boolean;
132
+ }
133
+
134
+ interface Emits {
135
+ (e: 'update', value: string): void;
136
+ (e: 'delete', id: string): void;
137
+ }
138
+
139
+ /**
140
+ * Props & Emits
141
+ */
142
+ const props = withDefaults(defineProps<Props>(), {
143
+ loading: false,
144
+ });
145
+ const emit = defineEmits<Emits>();
146
+
147
+ /**
148
+ * 响应式状态
149
+ */
150
+ const isLoading = ref(false);
151
+ const dataList = ref<Item[]>([]);
152
+
153
+ /**
154
+ * 计算属性
155
+ */
156
+ const hasData = computed(() => dataList.value.length > 0);
157
+
158
+ /**
159
+ * 方法
160
+ */
161
+ const handleFetch = async () => {
162
+ isLoading.value = true;
163
+ try {
164
+ // 业务逻辑
165
+ } catch (error) {
166
+ ElMessage.error('操作失败');
167
+ } finally {
168
+ isLoading.value = false;
169
+ }
170
+ };
171
+
172
+ const handleClick = () => {
173
+ emit('update', props.id);
174
+ };
175
+
176
+ /**
177
+ * 生命周期
178
+ */
179
+ onMounted(() => {
180
+ handleFetch();
181
+ });
182
+ </script>
183
+
184
+ <template>
185
+ <div class="component-name">
186
+ <div v-loading="isLoading">
187
+ <!-- 内容区域 -->
188
+ </div>
189
+ </div>
190
+ </template>
191
+
192
+ <style scoped lang="scss">
193
+ .component-name {
194
+ /* 样式 */
195
+ }
196
+ </style>
197
+ ```
198
+
199
+ ---
200
+
201
+ ## API 封装模式
202
+
203
+ ```typescript
204
+ // api/client.ts — Axios 实例配置
205
+ import axios from 'axios';
206
+ import { ElMessage } from 'element-plus';
207
+ import type { AxiosRequestConfig } from 'axios';
208
+
209
+ // 创建实例
210
+ const client = axios.create({
211
+ baseURL: import.meta.env.VITE_API_BASE_URL,
212
+ timeout: 15000,
213
+ headers: { 'Content-Type': 'application/json' },
214
+ });
215
+
216
+ // 请求拦截器:自动附加 Token
217
+ client.interceptors.request.use((config) => {
218
+ const token = localStorage.getItem('token');
219
+ if (token && config.headers) {
220
+ config.headers.Authorization = `Bearer ${token}`;
221
+ }
222
+ return config;
223
+ });
224
+
225
+ // 响应拦截器:统一错误处理
226
+ client.interceptors.response.use(
227
+ (response) => response.data,
228
+ (error) => {
229
+ const message = error.response?.data?.message || '网络异常';
230
+ ElMessage.error(message);
231
+ return Promise.reject(error);
232
+ },
233
+ );
234
+
235
+ export default client;
236
+
237
+ // api/modules/user.ts — 具体模块 API
238
+ import client from '../client';
239
+ import type { UserPageReqVO, UserRespVO, PageResult } from './types/user';
240
+
241
+ export const UserAPI = {
242
+ /** 分页查询用户 */
243
+ page(params: UserPageReqVO) {
244
+ return client.post<CommonResult<PageResult<UserRespVO>>>('/user/page', params);
245
+ },
246
+ /** 创建用户 */
247
+ create(data: UserCreateReqVO) {
248
+ return client.post<CommonResult<Long>>('/user/create', data);
249
+ },
250
+ };
251
+ ```
252
+
253
+ ---
254
+
255
+ ## Router 配置约定
256
+
257
+ ```typescript
258
+ // router/routes/user.ts — 懒加载路由
259
+ import type { RouteRecordRaw } from 'vue-router';
260
+
261
+ const userRoutes: RouteRecordRaw[] = [
262
+ {
263
+ path: '/user',
264
+ component: () => import('@/layouts/DefaultLayout.vue'),
265
+ meta: { title: '用户管理', requiresAuth: true },
266
+ children: [
267
+ {
268
+ path: '',
269
+ name: 'UserList',
270
+ component: () => import('@/pages/user/UserList.vue'),
271
+ meta: { title: '用户列表' },
272
+ },
273
+ {
274
+ path: ':id',
275
+ name: 'UserDetail',
276
+ component: () => import('@/pages/user/UserDetail.vue'),
277
+ meta: { title: '用户详情' },
278
+ },
279
+ ],
280
+ },
281
+ ];
282
+
283
+ export default userRoutes;
284
+ ```
285
+
286
+ **路由规范**:
287
+ - 所有页面组件必须 **懒加载** (`() => import(...)`)
288
+ - 路由元数据 `meta` 包含 `title` 和 `requiresAuth`
289
+ - 权限控制通过路由守卫统一处理
290
+ - 菜单结构与路由结构保持一致
291
+
292
+ ---
293
+
294
+ ## Store 组织方式
295
+
296
+ ```typescript
297
+ // store/modules/user.ts — 标准 Store 结构
298
+ import { defineStore } from 'pinia';
299
+ import type { UserInfo } from '@/types/user';
300
+
301
+ interface UserState {
302
+ userInfo: UserInfo | null;
303
+ token: string;
304
+ permissions: string[];
305
+ }
306
+
307
+ export const useUserStore = defineStore('user', {
308
+ state: (): UserState => ({
309
+ userInfo: null,
310
+ token: localStorage.getItem('token') || '',
311
+ permissions: [],
312
+ }),
313
+
314
+ getters: {
315
+ isLoggedIn: (state) => !!state.token,
316
+ isAdmin: (state) => state.permissions.includes('admin'),
317
+ displayName: (state) => state.userInfo?.name || '未登录',
318
+ },
319
+
320
+ actions: {
321
+ async login(credentials: LoginCredentials) {
322
+ // ...
323
+ },
324
+ logout() {
325
+ this.$reset();
326
+ localStorage.removeItem('token');
327
+ },
328
+ },
329
+ });
330
+ ```
331
+
332
+ **Store 规范**:
333
+ - State 字段必须有明确 TS 类型
334
+ - Getters 用于派生计算数据
335
+ - Actions 用于异步操作或批量修改
336
+ - 禁止在组件外直接修改 Store state
337
+
338
+ ---
339
+
340
+ ## Import 排序
341
+
342
+ ```tsx
343
+ // 1. Vue 相关
344
+ import { ref, computed, onMounted } from 'vue';
345
+ import { useRouter } from 'vue-router';
346
+
347
+ // 2. UI 库
348
+ import { ElMessage, ElMessageBox } from 'element-plus';
349
+
350
+ // 3. 外部库(按字母序)
351
+ import axios from 'axios';
352
+
353
+ // 4. 内部共享模块(@/ 别名)
354
+ import { useUserStore } from '@/store/modules/user';
355
+ import { UserAPI } from '@/api/modules/user';
356
+ import type { User, UserRole } from '@/types/user';
357
+
358
+ // 5. 相对路径导入
359
+ import { Avatar } from './Avatar';
360
+ import type { UserProfileProps } from './types';
361
+ ```
362
+
363
+ ---
364
+
365
+ ## 组件编写惯例
366
+
367
+ ### 函数组件 + 解构 Props
368
+ (见上方「标准组件模板」)
369
+
370
+ ### 条件渲染
371
+
372
+ ```tsx
373
+ // ✅ 推荐:提前返回
374
+ function UserDetail({ user }: { user: User | null }) {
375
+ if (!user) return <EmptyState />;
376
+ if (user.isBanned) return <BannedNotice />;
377
+
378
+ return (
379
+ <div>
380
+ <h1>{user.name}</h1>
381
+ {/* 主要内容 */}
382
+ </div>
383
+ );
384
+ }
385
+ ```
386
+
387
+ ### 列表渲染
388
+
389
+ ```tsx
390
+ // ✅ 推荐:带 key + 空状态处理
391
+ function UserList({ users }: { users: User[] }) {
392
+ if (users.length === 0) {
393
+ return <EmptyState title="暂无用户" description="点击上方按钮添加" />;
394
+ }
395
+
396
+ return (
397
+ <ul>
398
+ {users.map(user => (
399
+ <li key={user.id}>
400
+ <UserCard user={user} />
401
+ </li>
402
+ ))}
403
+ </ul>
404
+ );
405
+ }
406
+ ```
407
+
408
+ ---
409
+
410
+ ## 注释与文档规范
411
+
412
+ - **组件级注释**: 文件顶部 JSDoc,含名称、描述、props、events
413
+ - **函数级注释**: 复杂逻辑函数前加 JSDoc(@param / @returns / @throws)
414
+ - **行内注释**: 关键步骤解释"为什么",关注 why 而非 what
415
+ - **TODO 格式**: `// TODO: [场景] - 待[动作]`
416
+ - **禁止注释与代码不一致**