@deepstorm/cli 0.10.2 → 0.11.1
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/README.md +8 -8
- package/dist/build-registry.js +11 -5
- package/dist/cli.js +1256 -634
- package/dist/mcp/code-hosting/github.json +14 -1
- package/dist/mcp/docs-reference/context7.json +1 -10
- package/dist/mcp-skills/deepstorm-mcp-feishu-wiki-read/SKILL.md +8 -5
- package/dist/mcp-skills/deepstorm-mcp-feishu-wiki-write/SKILL.md +10 -8
- package/dist/mcp-skills/deepstorm-mcp-figma-read/SKILL.md +21 -20
- package/dist/mcp-skills/deepstorm-mcp-github-read/SKILL.md +17 -17
- package/dist/mcp-skills/deepstorm-mcp-github-write/SKILL.md +20 -20
- package/dist/mcp-skills/deepstorm-mcp-jira-read/SKILL.md +8 -8
- package/dist/mcp-skills/deepstorm-mcp-jira-write/SKILL.md +13 -12
- package/dist/mcp-skills/deepstorm-mcp-playwright-read/SKILL.md +10 -10
- package/dist/registry.json +10 -0
- package/dist/skills/atoll-ops/SKILL.md +4 -0
- package/dist/skills/reef-commit/SKILL.md +9 -5
- package/dist/skills/reef-commit/scripts/branch-check.mjs +5 -11
- package/dist/skills/reef-commit/scripts/check-openspec-status.mjs +6 -12
- package/dist/skills/reef-commit/scripts/collect-git-context.mjs +13 -11
- package/dist/skills/reef-gen-backend/variants/java/steps.md +7 -7
- package/dist/skills/reef-gen-backend/variants/nodejs/steps.md +7 -7
- package/dist/skills/reef-gen-backend/variants/python/steps.md +7 -7
- package/dist/skills/reef-gen-frontend/variants/angular/steps.md +5 -5
- package/dist/skills/reef-gen-frontend/variants/react/steps.md +6 -6
- package/dist/skills/reef-gen-frontend/variants/vue/steps.md +6 -6
- package/dist/skills/reef-harden/EXAMPLES.md +12 -10
- package/dist/skills/reef-harden/SKILL.md +6 -0
- package/dist/skills/reef-harden/scripts/find-change-dir.mjs +13 -13
- package/dist/skills/reef-pr/SKILL.md +7 -0
- package/dist/skills/reef-pr/scripts/create-pr.mjs +31 -29
- package/dist/skills/reef-scope/SKILL.md +6 -6
- package/dist/skills/reef-start/references/jira-start-subagent.md +7 -7
- package/dist/skills/reef-start/references/risk-routing-card.md +28 -25
- package/dist/skills/reef-start/references/stage-4-implementation.md +33 -21
- package/dist/skills/reef-start/references/superpowers-gate.md +9 -9
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/structured-output.md +2 -2
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/quick-reference.md +11 -0
- package/dist/skills/reef-style-backend/fragments/java/api-spec/jackson-polymorphism.md +36 -40
- package/dist/skills/reef-style-backend/fragments/java/api-spec/quick-reference.md +11 -10
- package/dist/skills/reef-style-backend/fragments/java/db-migration/liquibase/examples/database-migration.md +7 -7
- package/dist/skills/reef-style-backend/fragments/java/dependency-management/quick-reference.md +24 -21
- package/dist/skills/reef-style-backend/fragments/java/exception-handling/examples/error-code-enum.md +7 -7
- package/dist/skills/reef-style-backend/fragments/java/exception-handling/quick-reference.md +10 -8
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/dto-mapper.md +2 -2
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/service-entity.md +5 -5
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/testing.md +1 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/quick-reference.md +12 -12
- package/dist/skills/reef-style-backend/fragments/java/orm/hibernate/quick-reference.md +13 -12
- package/dist/skills/reef-style-backend/fragments/java/security-redlines/quick-reference.md +11 -9
- package/dist/skills/reef-style-backend/fragments/java/test/data-jpa-test/quick-reference.md +7 -7
- package/dist/skills/reef-style-backend/fragments/java/test/junit5/quick-reference.md +6 -6
- package/dist/skills/reef-style-backend/fragments/java/test/spring-mvc-test/quick-reference.md +8 -8
- package/dist/skills/reef-style-backend/fragments/java/test/spring-service-test/quick-reference.md +8 -7
- package/dist/skills/reef-style-backend/fragments/nodejs/eslint-config.json +1 -4
- package/dist/skills/reef-style-backend/fragments/nodejs/nestjs-structure.md +9 -9
- package/dist/skills/reef-style-backend/fragments/python/alembic-migration/quick-reference.md +2 -1
- package/dist/skills/reef-style-backend/fragments/python/api-spec/quick-reference.md +10 -10
- package/dist/skills/reef-style-backend/fragments/python/dependency-management/quick-reference.md +24 -22
- package/dist/skills/reef-style-backend/fragments/python/exception-handling/quick-reference.md +10 -9
- package/dist/skills/reef-style-backend/fragments/python/fastapi-quick-reference/quick-reference.md +3 -0
- package/dist/skills/reef-style-backend/fragments/python/langchain/quick-reference.md +10 -7
- package/dist/skills/reef-style-backend/fragments/python/pytest-testing/quick-reference.md +2 -0
- package/dist/skills/reef-style-backend/fragments/python/ruff-mypy-toolchain/quick-reference.md +2 -0
- package/dist/skills/reef-style-backend/fragments/python/security-redlines/quick-reference.md +11 -9
- package/dist/skills/reef-style-backend/fragments/python/sqlalchemy-orm/quick-reference.md +3 -0
- package/dist/skills/reef-style-backend/variants/java/examples/code-wrapping.md +3 -4
- package/dist/skills/reef-style-backend/variants/java/quick-reference.md +31 -29
- package/dist/skills/reef-style-backend/variants/nodejs/examples/module-example.md +2 -8
- package/dist/skills/reef-style-backend/variants/nodejs/quick-reference.md +35 -36
- package/dist/skills/reef-style-backend/variants/python/quick-reference.md +32 -31
- package/dist/skills/reef-style-frontend/fragments/css/tailwind/quick-reference.md +9 -7
- package/dist/skills/reef-style-frontend/fragments/test/vitest/examples/testing.md +9 -4
- package/dist/skills/reef-style-frontend/fragments/test/vitest/quick-reference.md +4 -4
- package/dist/skills/reef-style-frontend/fragments/test/vitest-react/examples/testing.md +135 -140
- package/dist/skills/reef-style-frontend/fragments/test/vitest-react/quick-reference.md +32 -32
- package/dist/skills/reef-style-frontend/fragments/test/vitest-vue/examples/testing.md +161 -162
- package/dist/skills/reef-style-frontend/fragments/test/vitest-vue/quick-reference.md +42 -42
- package/dist/skills/reef-style-frontend/fragments/ts-config/strict/quick-reference.md +16 -10
- package/dist/skills/reef-style-frontend/fragments/ui-lib/antd/examples/ui-components.md +72 -87
- package/dist/skills/reef-style-frontend/fragments/ui-lib/antd/quick-reference.md +14 -10
- package/dist/skills/reef-style-frontend/fragments/ui-lib/antd-vue/examples/ui-components.md +93 -111
- package/dist/skills/reef-style-frontend/fragments/ui-lib/antd-vue/quick-reference.md +29 -33
- package/dist/skills/reef-style-frontend/fragments/ui-lib/primeng/examples/ui-components.md +14 -23
- package/dist/skills/reef-style-frontend/variants/angular/examples/code-wrapping.md +15 -17
- package/dist/skills/reef-style-frontend/variants/angular/examples/component-types-pipes.md +22 -5
- package/dist/skills/reef-style-frontend/variants/angular/examples/entity-types.md +52 -22
- package/dist/skills/reef-style-frontend/variants/angular/examples/forms-layer.md +17 -20
- package/dist/skills/reef-style-frontend/variants/angular/examples/service-routing.md +8 -11
- package/dist/skills/reef-style-frontend/variants/angular/quick-reference.md +27 -27
- package/dist/skills/reef-style-frontend/variants/react/examples/code-wrapping.md +14 -27
- package/dist/skills/reef-style-frontend/variants/react/examples/component-types-pipes.md +45 -52
- package/dist/skills/reef-style-frontend/variants/react/examples/entity-types.md +44 -44
- package/dist/skills/reef-style-frontend/variants/react/examples/forms-layer.md +56 -85
- package/dist/skills/reef-style-frontend/variants/react/examples/service-routing.md +114 -88
- package/dist/skills/reef-style-frontend/variants/react/quick-reference.md +48 -42
- package/dist/skills/reef-style-frontend/variants/vue/examples/code-wrapping.md +24 -43
- package/dist/skills/reef-style-frontend/variants/vue/examples/component-types-pipes.md +48 -49
- package/dist/skills/reef-style-frontend/variants/vue/examples/entity-types.md +45 -45
- package/dist/skills/reef-style-frontend/variants/vue/examples/forms-layer.md +64 -68
- package/dist/skills/reef-style-frontend/variants/vue/examples/service-routing.md +85 -84
- package/dist/skills/reef-style-frontend/variants/vue/quick-reference.md +46 -51
- package/dist/skills/reef-testcase/SKILL.md +19 -19
- package/dist/skills/reef-testcase/references/coverage-dimensions.md +8 -0
- package/dist/skills/reef-testcase/references/test-case-template.md +16 -16
- package/dist/skills/sweep-explore/SKILL.md +824 -0
- package/dist/skills/sweep-explore/references/explore-flow-template.md +61 -0
- package/dist/skills/sweep-init/SKILL.md +4 -0
- package/dist/skills/sweep-init/scripts/flow-selector.mjs +6 -11
- package/dist/skills/sweep-init/scripts/init-project.mjs +68 -51
- package/dist/skills/sweep-plan/references/test-flow-template.md +12 -6
- package/dist/skills/sweep-record/SKILL.md +358 -0
- package/dist/skills/sweep-run/SKILL.md +59 -50
- package/dist/skills/sweep-run/scripts/env-manager.mjs +4 -5
- package/dist/skills/sweep-run/scripts/flow-parser.mjs +7 -6
- package/dist/skills/sweep-run/scripts/flow-selector.mjs +4 -15
- package/dist/skills/sweep-run/scripts/generate-report.mjs +1 -2
- package/dist/skills/sweep-run/scripts/mcp-manager.mjs +107 -14
- package/dist/skills/sweep-run/scripts/spec-compiler.mjs +19 -9
- package/dist/skills/tide-discuss/references/checklists.md +32 -32
- package/dist/skills/tide-discuss/references/data-format.md +85 -77
- package/dist/skills/tide-discuss/references/prd-template.md +21 -19
- package/dist/skills/tide-discuss/references/publish-flow.md +30 -11
- package/dist/skills/tide-discuss/references/role-prompts.md +5 -5
- package/package.json +5 -3
|
@@ -4,14 +4,31 @@
|
|
|
4
4
|
|
|
5
5
|
```typescript
|
|
6
6
|
type FormControl = TextControl | NumberControl | ChoiceControl;
|
|
7
|
-
interface TextControl {
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
interface TextControl {
|
|
8
|
+
type: 'TextControl';
|
|
9
|
+
label: string;
|
|
10
|
+
maxLength?: number;
|
|
11
|
+
}
|
|
12
|
+
interface NumberControl {
|
|
13
|
+
type: 'NumberControl';
|
|
14
|
+
min?: number;
|
|
15
|
+
max?: number;
|
|
16
|
+
}
|
|
17
|
+
interface ChoiceControl {
|
|
18
|
+
type: 'ChoiceControl';
|
|
19
|
+
options: Option[];
|
|
20
|
+
}
|
|
10
21
|
|
|
11
22
|
// Answer 多态(对应 Java 子类)
|
|
12
23
|
type Answer = UuidAnswer | TextAnswer | ChoiceAnswer | TableAnswer;
|
|
13
|
-
interface UuidAnswer {
|
|
14
|
-
|
|
24
|
+
interface UuidAnswer {
|
|
25
|
+
type: 'UuidAnswer';
|
|
26
|
+
value: string | null;
|
|
27
|
+
}
|
|
28
|
+
interface TextAnswer {
|
|
29
|
+
type: 'TextAnswer';
|
|
30
|
+
value: string | null;
|
|
31
|
+
}
|
|
15
32
|
```
|
|
16
33
|
|
|
17
34
|
## 2. 日期 Pipe
|
|
@@ -5,13 +5,27 @@
|
|
|
5
5
|
实体接口镜像后端实体层次,定义在 `shared/base.ts`:
|
|
6
6
|
|
|
7
7
|
```typescript
|
|
8
|
-
export interface ImmutableEntity {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
export interface ImmutableEntity {
|
|
9
|
+
id: number;
|
|
10
|
+
createdAt: LocalDateTime;
|
|
11
|
+
}
|
|
12
|
+
export interface Entity extends ImmutableEntity {
|
|
13
|
+
lastModifiedAt: LocalDateTime;
|
|
14
|
+
}
|
|
15
|
+
export interface ImmutableAuditable<T> extends ImmutableEntity {
|
|
16
|
+
createdBy: T;
|
|
17
|
+
}
|
|
18
|
+
export interface Auditable<T> extends Entity {
|
|
19
|
+
createdBy: T;
|
|
20
|
+
lastModifiedBy: T;
|
|
21
|
+
}
|
|
12
22
|
|
|
13
23
|
// UserSummary(审计字段常用类型)
|
|
14
|
-
export interface UserSummary {
|
|
24
|
+
export interface UserSummary {
|
|
25
|
+
id: number;
|
|
26
|
+
username: string;
|
|
27
|
+
fullName: string;
|
|
28
|
+
}
|
|
15
29
|
```
|
|
16
30
|
|
|
17
31
|
> `LocalDateTime` 来自 `@js-joda/core`,配合 `iso8601Interceptor` 自动从 JSON 反序列化。
|
|
@@ -39,7 +53,11 @@ export interface FormResponse extends Auditable<UserSummary> {
|
|
|
39
53
|
## 3. 分页响应
|
|
40
54
|
|
|
41
55
|
```typescript
|
|
42
|
-
export interface PageRequest {
|
|
56
|
+
export interface PageRequest {
|
|
57
|
+
page: number;
|
|
58
|
+
size: number;
|
|
59
|
+
sort: string;
|
|
60
|
+
}
|
|
43
61
|
export interface PagedResponse<T extends ImmutableEntity> {
|
|
44
62
|
items: T[];
|
|
45
63
|
totalItems: number;
|
|
@@ -63,9 +81,7 @@ export class DataObjectInstanceService {
|
|
|
63
81
|
list(objectId: signal<number | undefined>) {
|
|
64
82
|
return httpResource<PagedResponse<DataObjectInstance>>(() => {
|
|
65
83
|
const id = objectId();
|
|
66
|
-
return id !== undefined
|
|
67
|
-
? `/api/v1/apps/${this.appService.appId()}/objects/${id}/instances`
|
|
68
|
-
: undefined;
|
|
84
|
+
return id !== undefined ? `/api/v1/apps/${this.appService.appId()}/objects/${id}/instances` : undefined;
|
|
69
85
|
});
|
|
70
86
|
}
|
|
71
87
|
}
|
|
@@ -76,25 +92,39 @@ export class DataObjectInstanceService {
|
|
|
76
92
|
```typescript
|
|
77
93
|
// 用户类型(字符串字面量 discrimination)
|
|
78
94
|
interface BaseUser extends Entity {
|
|
79
|
-
type: string;
|
|
95
|
+
type: string;
|
|
96
|
+
username: string;
|
|
97
|
+
fullName: string;
|
|
98
|
+
role: Role;
|
|
99
|
+
disabled: boolean;
|
|
100
|
+
}
|
|
101
|
+
export interface InternalUser extends BaseUser {
|
|
102
|
+
type: 'InternalUser';
|
|
103
|
+
}
|
|
104
|
+
export interface CdyxUser extends BaseUser {
|
|
105
|
+
type: 'CdyxUser';
|
|
80
106
|
}
|
|
81
|
-
export interface InternalUser extends BaseUser { type: 'InternalUser'; }
|
|
82
|
-
export interface CdyxUser extends BaseUser { type: 'CdyxUser'; }
|
|
83
107
|
export type User = InternalUser | CdyxUser;
|
|
84
108
|
|
|
85
109
|
// 字段值类型(按 type 字段分发)
|
|
86
110
|
export type FieldValue = TextValue | NumberValue | BooleanValue;
|
|
87
|
-
export interface TextValue {
|
|
88
|
-
|
|
111
|
+
export interface TextValue {
|
|
112
|
+
type: 'text';
|
|
113
|
+
value: string | null;
|
|
114
|
+
}
|
|
115
|
+
export interface NumberValue {
|
|
116
|
+
type: 'number';
|
|
117
|
+
value: number | null;
|
|
118
|
+
}
|
|
89
119
|
```
|
|
90
120
|
|
|
91
121
|
## 5. 关键规则
|
|
92
122
|
|
|
93
|
-
| 规则
|
|
94
|
-
|
|
95
|
-
| **interface,不是 class** | 实体全是 `interface`,没有 `class`
|
|
96
|
-
| **类型参数 `T`**
|
|
97
|
-
| **必要字段**
|
|
98
|
-
| **枚举用 union**
|
|
99
|
-
| **字段注释**
|
|
100
|
-
| **参数顺序**
|
|
123
|
+
| 规则 | 说明 |
|
|
124
|
+
| ------------------------- | ------------------------------------------------ |
|
|
125
|
+
| **interface,不是 class** | 实体全是 `interface`,没有 `class` |
|
|
126
|
+
| **类型参数 `T`** | 审计字段的 `T` 通常是 `UserSummary` |
|
|
127
|
+
| **必要字段** | 字段不设可选(`?`),后端保证返回 |
|
|
128
|
+
| **枚举用 union** | 状态字段用 literal union,不用 TypeScript `enum` |
|
|
129
|
+
| **字段注释** | Entity/接口字段加 `/** */` 注释;简单的 DTO 不加 |
|
|
130
|
+
| **参数顺序** | `appId` → 父级 ID → 自身 ID → name/描述 |
|
|
@@ -12,18 +12,9 @@ import { form, FormField, FormRoot, required } from '@angular/forms/signals';
|
|
|
12
12
|
imports: [Button, Dialog, FormField, FormRoot, InputText],
|
|
13
13
|
template: `
|
|
14
14
|
<p-button label="创建" (onClick)="onCreate()" />
|
|
15
|
-
<p-dialog
|
|
16
|
-
[(visible)]="visible"
|
|
17
|
-
[header]="isEditing() ? '编辑' : '创建'"
|
|
18
|
-
modal
|
|
19
|
-
(onHide)="onHide()"
|
|
20
|
-
>
|
|
15
|
+
<p-dialog [(visible)]="visible" [header]="isEditing() ? '编辑' : '创建'" modal (onHide)="onHide()">
|
|
21
16
|
<form [formRoot]="form" (submit)="submitted.set(true)">
|
|
22
|
-
<input
|
|
23
|
-
pInputText
|
|
24
|
-
[formField]="form.name"
|
|
25
|
-
[pInvalid]="submitted() && form.name().invalid()"
|
|
26
|
-
/>
|
|
17
|
+
<input pInputText [formField]="form.name" [pInvalid]="submitted() && form.name().invalid()" />
|
|
27
18
|
@if (submitted() && form.name().invalid()) {
|
|
28
19
|
<app-error-messages [errors]="form.name().errors()" />
|
|
29
20
|
}
|
|
@@ -40,15 +31,21 @@ export class CrudDialogComponent {
|
|
|
40
31
|
readonly isEditing = signal(false);
|
|
41
32
|
readonly submitted = signal(false);
|
|
42
33
|
|
|
43
|
-
readonly form = form(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
34
|
+
readonly form = form(
|
|
35
|
+
signal({ name: '' }),
|
|
36
|
+
(path) => {
|
|
37
|
+
required(path.name, { message: '名称不能为空' });
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
submission: {
|
|
41
|
+
action: async () => {
|
|
42
|
+
await lastValueFrom(this.service.create(this.form().value()));
|
|
43
|
+
this.messageService.add({ severity: 'success', summary: '创建成功' });
|
|
44
|
+
this.visible.set(false);
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
);
|
|
52
49
|
|
|
53
50
|
onCreate() {
|
|
54
51
|
this.isEditing.set(false);
|
|
@@ -15,15 +15,11 @@ export class ItemService {
|
|
|
15
15
|
|
|
16
16
|
/** 写操作:HttpClient + finalize reload */
|
|
17
17
|
create(request: { name: string }) {
|
|
18
|
-
return this.http.post<Item>('/api/v1/items', request).pipe(
|
|
19
|
-
finalize(() => this.itemsResource.reload()),
|
|
20
|
-
);
|
|
18
|
+
return this.http.post<Item>('/api/v1/items', request).pipe(finalize(() => this.itemsResource.reload()));
|
|
21
19
|
}
|
|
22
20
|
|
|
23
21
|
delete(id: number) {
|
|
24
|
-
return this.http.delete(`/api/v1/items/${id}`).pipe(
|
|
25
|
-
finalize(() => this.itemsResource.reload()),
|
|
26
|
-
);
|
|
22
|
+
return this.http.delete(`/api/v1/items/${id}`).pipe(finalize(() => this.itemsResource.reload()));
|
|
27
23
|
}
|
|
28
24
|
}
|
|
29
25
|
```
|
|
@@ -59,7 +55,7 @@ export const routes: Routes = [
|
|
|
59
55
|
{
|
|
60
56
|
path: 'dashboard',
|
|
61
57
|
title: '仪表盘',
|
|
62
|
-
loadComponent: () => import('./dashboard/dashboard.component').then(m => m.DashboardComponent),
|
|
58
|
+
loadComponent: () => import('./dashboard/dashboard.component').then((m) => m.DashboardComponent),
|
|
63
59
|
},
|
|
64
60
|
],
|
|
65
61
|
},
|
|
@@ -72,24 +68,25 @@ export const routes: Routes = [
|
|
|
72
68
|
{
|
|
73
69
|
path: 'users',
|
|
74
70
|
title: '用户管理',
|
|
75
|
-
loadComponent: () => import('./admin/user-list/user-list.component').then(m => m.UserListComponent),
|
|
71
|
+
loadComponent: () => import('./admin/user-list/user-list.component').then((m) => m.UserListComponent),
|
|
76
72
|
},
|
|
77
73
|
{
|
|
78
74
|
path: 'role-mappings',
|
|
79
75
|
canActivate: [authGuard],
|
|
80
76
|
data: { requiredAuthority: 'ADMIN', requireRootTenant: true },
|
|
81
|
-
loadComponent: () =>
|
|
77
|
+
loadComponent: () =>
|
|
78
|
+
import('./admin/role-mappings/role-mappings.component').then((m) => m.RoleMappingsComponent),
|
|
82
79
|
},
|
|
83
80
|
],
|
|
84
81
|
},
|
|
85
82
|
{
|
|
86
83
|
path: 'login',
|
|
87
|
-
loadComponent: () => import('./login/login.component').then(m => m.LoginComponent),
|
|
84
|
+
loadComponent: () => import('./login/login.component').then((m) => m.LoginComponent),
|
|
88
85
|
canActivate: [authGuard],
|
|
89
86
|
},
|
|
90
87
|
{
|
|
91
88
|
path: '**',
|
|
92
|
-
loadComponent: () => import('./page-not-found/page-not-found.component').then(m => m.PageNotFoundComponent),
|
|
89
|
+
loadComponent: () => import('./page-not-found/page-not-found.component').then((m) => m.PageNotFoundComponent),
|
|
93
90
|
},
|
|
94
91
|
];
|
|
95
92
|
```
|
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
## 速查
|
|
8
8
|
|
|
9
|
-
| 场景
|
|
10
|
-
|
|
|
11
|
-
| 新建组件
|
|
12
|
-
| 新建表单
|
|
13
|
-
| 读数据
|
|
14
|
-
| 写数据
|
|
15
|
-
| Service 职责 | 只负责网络请求,不管理 UI 状态
|
|
16
|
-
| 路由
|
|
9
|
+
| 场景 | 决策 |
|
|
10
|
+
| ------------ | -------------------------------------------------- |
|
|
11
|
+
| 新建组件 | Standalone + OnPush + inline template + inject() |
|
|
12
|
+
| 新建表单 | Signal Forms(`form()`),非 Reactive Forms |
|
|
13
|
+
| 读数据 | `httpResource()` |
|
|
14
|
+
| 写数据 | `HttpClient` + `finalize(() => resource.reload())` |
|
|
15
|
+
| Service 职责 | 只负责网络请求,不管理 UI 状态 |
|
|
16
|
+
| 路由 | `loadComponent` 懒加载 |
|
|
17
17
|
|
|
18
18
|
## 新建组件
|
|
19
19
|
|
|
@@ -31,13 +31,13 @@ export class ExampleComponent {
|
|
|
31
31
|
}
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
| 规则
|
|
35
|
-
|
|
|
36
|
-
| Standalone | 禁止 NgModule,`imports: []` 显式导入
|
|
37
|
-
| `OnPush`
|
|
38
|
-
| `inject()` | 禁止构造函数注入
|
|
39
|
-
| `output()` | 禁止 `@Output()` + `EventEmitter`
|
|
40
|
-
| `host`
|
|
34
|
+
| 规则 | 要求 |
|
|
35
|
+
| ---------- | --------------------------------------------------- |
|
|
36
|
+
| Standalone | 禁止 NgModule,`imports: []` 显式导入 |
|
|
37
|
+
| `OnPush` | 模板中所有输入 immutable |
|
|
38
|
+
| `inject()` | 禁止构造函数注入 |
|
|
39
|
+
| `output()` | 禁止 `@Output()` + `EventEmitter` |
|
|
40
|
+
| `host` | 容器样式用 `host: { class: '...' }`,不包裹 `<div>` |
|
|
41
41
|
|
|
42
42
|
## Signal 状态
|
|
43
43
|
|
|
@@ -86,10 +86,10 @@ readonly form = form(signal({ name: '', email: '' }), (path) => {
|
|
|
86
86
|
|
|
87
87
|
## 注释规则
|
|
88
88
|
|
|
89
|
-
| 文件类型
|
|
90
|
-
|
|
91
|
-
| **类型定义**
|
|
92
|
-
| **Service**
|
|
89
|
+
| 文件类型 | 注释要求 |
|
|
90
|
+
| ------------- | ---------------------------------------- |
|
|
91
|
+
| **类型定义** | 复杂类型加行内注释说明字段含义 |
|
|
92
|
+
| **Service** | 简要说明每个方法的功能和返回类型 |
|
|
93
93
|
| **Component** | 关键组件加注释说明用途;复杂逻辑行内注释 |
|
|
94
94
|
|
|
95
95
|
## 前端实体类型
|
|
@@ -98,11 +98,11 @@ readonly form = form(signal({ name: '', email: '' }), (path) => {
|
|
|
98
98
|
|
|
99
99
|
## 常见坑
|
|
100
100
|
|
|
101
|
-
| 场景
|
|
102
|
-
|
|
103
|
-
| Blob 下载
|
|
104
|
-
| httpResource 初始化
|
|
105
|
-
| `form().reset()`
|
|
106
|
-
| Service 状态泄漏
|
|
107
|
-
| `model()` vs `output()` | 需双向绑定时用了 output()
|
|
108
|
-
| submit handler
|
|
101
|
+
| 场景 | 问题 | 正确做法 |
|
|
102
|
+
| ----------------------- | ---------------------------------------------- | -------------------------------------------------------- |
|
|
103
|
+
| Blob 下载 | 后端返回 JSON 时 err.error 被包装为 Blob | `err.error.text().then(text => JSON.parse(text))` |
|
|
104
|
+
| httpResource 初始化 | URL 返回 undefined 时 res.value() 为 undefined | `computed(() => res.value()?.items ?? [])` |
|
|
105
|
+
| `form().reset()` | 只传部分字段,缺失字段不被重置 | `form().reset({ name: '' })` 补全所有字段 |
|
|
106
|
+
| Service 状态泄漏 | 在 Service 中放 signal 管理 UI 状态 | Service 只做网络请求,状态放在 Component 中 |
|
|
107
|
+
| `model()` vs `output()` | 需双向绑定时用了 output() | 用 `model()`,父组件 `[(visible)]="xxx"` |
|
|
108
|
+
| submit handler | 未阻止默认提交行为 | 用 `FormRoot` 指令,或 `(submit)="check()"` + 检查 valid |
|
|
@@ -44,47 +44,34 @@
|
|
|
44
44
|
|
|
45
45
|
```tsx
|
|
46
46
|
// ✅ 简单的三元表达式保持行内
|
|
47
|
-
return <div>{loading ? <Spin /> : <UserTable users={users} />}</div
|
|
47
|
+
return <div>{loading ? <Spin /> : <UserTable users={users} />}</div>;
|
|
48
48
|
|
|
49
49
|
// ✅ 复杂条件使用 && 或三元换行
|
|
50
50
|
return (
|
|
51
51
|
<div>
|
|
52
|
-
{error &&
|
|
53
|
-
<Alert
|
|
54
|
-
type="error"
|
|
55
|
-
message={error.message}
|
|
56
|
-
closable
|
|
57
|
-
onClose={clearError}
|
|
58
|
-
/>
|
|
59
|
-
)}
|
|
52
|
+
{error && <Alert type="error" message={error.message} closable onClose={clearError} />}
|
|
60
53
|
{!loading && !error && <UserTable users={users} />}
|
|
61
54
|
</div>
|
|
62
|
-
)
|
|
55
|
+
);
|
|
63
56
|
```
|
|
64
57
|
|
|
65
58
|
## Hook 声明
|
|
66
59
|
|
|
67
60
|
```tsx
|
|
68
61
|
// ✅ Hook 声明连续排列,按逻辑分组
|
|
69
|
-
const [users, setUsers] = useState<User[]>([])
|
|
70
|
-
const [loading, setLoading] = useState(true)
|
|
71
|
-
const [search, setSearch] = useState('')
|
|
62
|
+
const [users, setUsers] = useState<User[]>([]);
|
|
63
|
+
const [loading, setLoading] = useState(true);
|
|
64
|
+
const [search, setSearch] = useState('');
|
|
72
65
|
|
|
73
66
|
// useEffect 依赖较长时换行
|
|
74
67
|
useEffect(() => {
|
|
75
|
-
fetchUsers(search)
|
|
76
|
-
}, [search]) // 短依赖数组行内
|
|
68
|
+
fetchUsers(search);
|
|
69
|
+
}, [search]); // 短依赖数组行内
|
|
77
70
|
|
|
78
71
|
// ✅ 长依赖数组换行
|
|
79
72
|
useEffect(() => {
|
|
80
|
-
fetchFilteredUsers({ search, role, status, page, size })
|
|
81
|
-
}, [
|
|
82
|
-
search,
|
|
83
|
-
role,
|
|
84
|
-
status,
|
|
85
|
-
page,
|
|
86
|
-
size,
|
|
87
|
-
])
|
|
73
|
+
fetchFilteredUsers({ search, role, status, page, size });
|
|
74
|
+
}, [search, role, status, page, size]);
|
|
88
75
|
```
|
|
89
76
|
|
|
90
77
|
## 函数 Props 换行
|
|
@@ -114,11 +101,11 @@ useEffect(() => {
|
|
|
114
101
|
```tsx
|
|
115
102
|
// ✅ Props 类型每个属性一行
|
|
116
103
|
interface UserFormProps {
|
|
117
|
-
user?: User
|
|
118
|
-
onSubmit: (values: CreateUserRequest) => Promise<void
|
|
119
|
-
onCancel: () => void
|
|
104
|
+
user?: User;
|
|
105
|
+
onSubmit: (values: CreateUserRequest) => Promise<void>;
|
|
106
|
+
onCancel: () => void;
|
|
120
107
|
}
|
|
121
108
|
|
|
122
109
|
// ✅ 联合类型换行
|
|
123
|
-
type UserStatus = 'active' | 'inactive' | 'locked' | 'pending'
|
|
110
|
+
type UserStatus = 'active' | 'inactive' | 'locked' | 'pending';
|
|
124
111
|
```
|
|
@@ -9,26 +9,29 @@ Angular Pipe 在 React 中用自定义 Hook 或纯函数替代:
|
|
|
9
9
|
```tsx
|
|
10
10
|
// ✅ 方案1: 纯函数(无状态转换)
|
|
11
11
|
function formatDate(date: string | Date, format: 'short' | 'long' = 'short'): string {
|
|
12
|
-
const d = typeof date === 'string' ? new Date(date) : date
|
|
12
|
+
const d = typeof date === 'string' ? new Date(date) : date;
|
|
13
13
|
if (format === 'short') {
|
|
14
|
-
return d.toLocaleDateString('zh-CN')
|
|
14
|
+
return d.toLocaleDateString('zh-CN');
|
|
15
15
|
}
|
|
16
16
|
return d.toLocaleDateString('zh-CN', {
|
|
17
|
-
year: 'numeric',
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
year: 'numeric',
|
|
18
|
+
month: 'long',
|
|
19
|
+
day: 'numeric',
|
|
20
|
+
hour: '2-digit',
|
|
21
|
+
minute: '2-digit',
|
|
22
|
+
});
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
function formatCurrency(amount: number, currency = 'CNY'): string {
|
|
23
26
|
return new Intl.NumberFormat('zh-CN', {
|
|
24
27
|
style: 'currency',
|
|
25
28
|
currency,
|
|
26
|
-
}).format(amount)
|
|
29
|
+
}).format(amount);
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
// 使用
|
|
30
33
|
const UserList: React.FC = () => {
|
|
31
|
-
const { users } = useUsers()
|
|
34
|
+
const { users } = useUsers();
|
|
32
35
|
return (
|
|
33
36
|
<Table
|
|
34
37
|
dataSource={users}
|
|
@@ -37,18 +40,16 @@ const UserList: React.FC = () => {
|
|
|
37
40
|
{ title: '金额', dataIndex: 'amount', render: (v: number) => formatCurrency(v) },
|
|
38
41
|
]}
|
|
39
42
|
/>
|
|
40
|
-
)
|
|
41
|
-
}
|
|
43
|
+
);
|
|
44
|
+
};
|
|
42
45
|
|
|
43
46
|
// ✅ 方案2: 自定义 Hook(有状态转换 — 如搜索/过滤)
|
|
44
47
|
function useUserFilter(users: User[], search: string): User[] {
|
|
45
48
|
return useMemo(() => {
|
|
46
|
-
if (!search.trim()) return users
|
|
47
|
-
const q = search.toLowerCase()
|
|
48
|
-
return users.filter(
|
|
49
|
-
|
|
50
|
-
)
|
|
51
|
-
}, [users, search])
|
|
49
|
+
if (!search.trim()) return users;
|
|
50
|
+
const q = search.toLowerCase();
|
|
51
|
+
return users.filter((u) => u.username.toLowerCase().includes(q) || u.email.toLowerCase().includes(q));
|
|
52
|
+
}, [users, search]);
|
|
52
53
|
}
|
|
53
54
|
```
|
|
54
55
|
|
|
@@ -57,32 +58,29 @@ function useUserFilter(users: User[], search: string): User[] {
|
|
|
57
58
|
```tsx
|
|
58
59
|
// hoc/withAuth.tsx
|
|
59
60
|
interface WithAuthProps {
|
|
60
|
-
requiredRole?: UserRole
|
|
61
|
+
requiredRole?: UserRole;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
export function withAuth<P extends object>(
|
|
64
|
-
Component: React.ComponentType<P>,
|
|
65
|
-
options?: WithAuthProps
|
|
66
|
-
) {
|
|
64
|
+
export function withAuth<P extends object>(Component: React.ComponentType<P>, options?: WithAuthProps) {
|
|
67
65
|
const AuthenticatedComponent: React.FC<P> = (props) => {
|
|
68
|
-
const { user, loading } = useAuth()
|
|
66
|
+
const { user, loading } = useAuth();
|
|
69
67
|
|
|
70
|
-
if (loading) return <Spin
|
|
71
|
-
if (!user) return <Navigate to="/login" replace
|
|
68
|
+
if (loading) return <Spin />;
|
|
69
|
+
if (!user) return <Navigate to="/login" replace />;
|
|
72
70
|
|
|
73
71
|
if (options?.requiredRole && user.role !== options.requiredRole) {
|
|
74
|
-
return <Result status="403" title="无权限访问"
|
|
72
|
+
return <Result status="403" title="无权限访问" />;
|
|
75
73
|
}
|
|
76
74
|
|
|
77
|
-
return <Component {...props}
|
|
78
|
-
}
|
|
75
|
+
return <Component {...props} />;
|
|
76
|
+
};
|
|
79
77
|
|
|
80
|
-
AuthenticatedComponent.displayName = `withAuth(${Component.displayName || Component.name})
|
|
81
|
-
return AuthenticatedComponent
|
|
78
|
+
AuthenticatedComponent.displayName = `withAuth(${Component.displayName || Component.name})`;
|
|
79
|
+
return AuthenticatedComponent;
|
|
82
80
|
}
|
|
83
81
|
|
|
84
82
|
// 使用
|
|
85
|
-
const AdminDashboard = withAuth(Dashboard, { requiredRole: UserRole.Admin })
|
|
83
|
+
const AdminDashboard = withAuth(Dashboard, { requiredRole: UserRole.Admin });
|
|
86
84
|
```
|
|
87
85
|
|
|
88
86
|
## 组件组合模式
|
|
@@ -128,33 +126,33 @@ Card.Footer = ({ children }) => (
|
|
|
128
126
|
```tsx
|
|
129
127
|
// 数据获取 Render Props(复用数据加载逻辑)
|
|
130
128
|
interface DataLoaderProps<T> {
|
|
131
|
-
fetchFn: () => Promise<T
|
|
132
|
-
children: (result: { data: T | null; loading: boolean; error: Error | null }) => React.ReactNode
|
|
129
|
+
fetchFn: () => Promise<T>;
|
|
130
|
+
children: (result: { data: T | null; loading: boolean; error: Error | null }) => React.ReactNode;
|
|
133
131
|
}
|
|
134
132
|
|
|
135
133
|
function DataLoader<T>({ fetchFn, children }: DataLoaderProps<T>) {
|
|
136
|
-
const [data, setData] = useState<T | null>(null)
|
|
137
|
-
const [loading, setLoading] = useState(true)
|
|
138
|
-
const [error, setError] = useState<Error | null>(null)
|
|
134
|
+
const [data, setData] = useState<T | null>(null);
|
|
135
|
+
const [loading, setLoading] = useState(true);
|
|
136
|
+
const [error, setError] = useState<Error | null>(null);
|
|
139
137
|
|
|
140
138
|
useEffect(() => {
|
|
141
139
|
fetchFn()
|
|
142
140
|
.then(setData)
|
|
143
141
|
.catch(setError)
|
|
144
|
-
.finally(() => setLoading(false))
|
|
145
|
-
}, [])
|
|
142
|
+
.finally(() => setLoading(false));
|
|
143
|
+
}, []);
|
|
146
144
|
|
|
147
|
-
return <>{children({ data, loading, error })}
|
|
145
|
+
return <>{children({ data, loading, error })}</>;
|
|
148
146
|
}
|
|
149
147
|
|
|
150
148
|
// 使用
|
|
151
149
|
<DataLoader fetchFn={() => userService.getById(id)}>
|
|
152
150
|
{({ data: user, loading, error }) => {
|
|
153
|
-
if (loading) return <Skeleton active
|
|
154
|
-
if (error) return <Alert type="error" message={error.message}
|
|
155
|
-
return <UserForm user={user}
|
|
151
|
+
if (loading) return <Skeleton active />;
|
|
152
|
+
if (error) return <Alert type="error" message={error.message} />;
|
|
153
|
+
return <UserForm user={user} />;
|
|
156
154
|
}}
|
|
157
|
-
</DataLoader
|
|
155
|
+
</DataLoader>;
|
|
158
156
|
```
|
|
159
157
|
|
|
160
158
|
## Discriminated Union 组件模式
|
|
@@ -164,12 +162,12 @@ function DataLoader<T>({ fetchFn, children }: DataLoaderProps<T>) {
|
|
|
164
162
|
type NotificationCardProps =
|
|
165
163
|
| { type: 'success'; message: string }
|
|
166
164
|
| { type: 'error'; message: string; errorCode?: string }
|
|
167
|
-
| { type: 'confirm'; message: string; onConfirm: () => void; onCancel: () => void }
|
|
165
|
+
| { type: 'confirm'; message: string; onConfirm: () => void; onCancel: () => void };
|
|
168
166
|
|
|
169
167
|
const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
|
170
168
|
switch (props.type) {
|
|
171
169
|
case 'success':
|
|
172
|
-
return <Alert type="success" message={props.message} showIcon
|
|
170
|
+
return <Alert type="success" message={props.message} showIcon />;
|
|
173
171
|
case 'error':
|
|
174
172
|
return (
|
|
175
173
|
<Alert
|
|
@@ -178,18 +176,13 @@ const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
|
|
178
176
|
description={props.errorCode ? `错误码: ${props.errorCode}` : undefined}
|
|
179
177
|
showIcon
|
|
180
178
|
/>
|
|
181
|
-
)
|
|
179
|
+
);
|
|
182
180
|
case 'confirm':
|
|
183
181
|
return (
|
|
184
|
-
<Modal
|
|
185
|
-
open
|
|
186
|
-
title="确认"
|
|
187
|
-
onOk={props.onConfirm}
|
|
188
|
-
onCancel={props.onCancel}
|
|
189
|
-
>
|
|
182
|
+
<Modal open title="确认" onOk={props.onConfirm} onCancel={props.onCancel}>
|
|
190
183
|
<p>{props.message}</p>
|
|
191
184
|
</Modal>
|
|
192
|
-
)
|
|
185
|
+
);
|
|
193
186
|
}
|
|
194
|
-
}
|
|
187
|
+
};
|
|
195
188
|
```
|