@deepstorm/cli 0.11.0 → 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 +839 -585
- 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/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 +129 -100
- package/dist/skills/sweep-explore/references/explore-flow-template.md +9 -9
- 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 +38 -15
- 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 +4 -3
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
```typescript
|
|
6
6
|
// vitest.config.ts
|
|
7
|
-
import { defineConfig } from 'vitest/config'
|
|
8
|
-
import vue from '@vitejs/plugin-vue'
|
|
7
|
+
import { defineConfig } from 'vitest/config';
|
|
8
|
+
import vue from '@vitejs/plugin-vue';
|
|
9
9
|
|
|
10
10
|
export default defineConfig({
|
|
11
11
|
plugins: [vue()],
|
|
@@ -26,12 +26,12 @@ export default defineConfig({
|
|
|
26
26
|
},
|
|
27
27
|
},
|
|
28
28
|
},
|
|
29
|
-
})
|
|
29
|
+
});
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
```typescript
|
|
33
33
|
// src/test/setup.ts
|
|
34
|
-
import '@testing-library/jest-dom'
|
|
34
|
+
import '@testing-library/jest-dom';
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
## 通用原则
|
|
@@ -44,18 +44,18 @@ import '@testing-library/jest-dom'
|
|
|
44
44
|
## 组件测试(Vue Test Utils)
|
|
45
45
|
|
|
46
46
|
```typescript
|
|
47
|
-
import { mount } from '@vue/test-utils'
|
|
48
|
-
import { describe, it, expect } from 'vitest'
|
|
47
|
+
import { mount } from '@vue/test-utils';
|
|
48
|
+
import { describe, it, expect } from 'vitest';
|
|
49
49
|
|
|
50
50
|
// ✅ 正确:通过文本寻找元素
|
|
51
|
-
const wrapper = mount(MyComponent)
|
|
52
|
-
expect(wrapper.text()).toContain('提交')
|
|
51
|
+
const wrapper = mount(MyComponent);
|
|
52
|
+
expect(wrapper.text()).toContain('提交');
|
|
53
53
|
|
|
54
54
|
// ✅ 正确:模拟用户交互
|
|
55
|
-
await wrapper.find('button').trigger('click')
|
|
55
|
+
await wrapper.find('button').trigger('click');
|
|
56
56
|
|
|
57
57
|
// ✅ 正确:通过 findComponent 查找子组件
|
|
58
|
-
expect(wrapper.findComponent({ name: 'AButton' }).exists()).toBe(true)
|
|
58
|
+
expect(wrapper.findComponent({ name: 'AButton' }).exists()).toBe(true);
|
|
59
59
|
|
|
60
60
|
// ❌ 避免:通过 CSS 类名选择
|
|
61
61
|
// wrapper.find('.btn-primary') // 禁止
|
|
@@ -64,19 +64,19 @@ expect(wrapper.findComponent({ name: 'AButton' }).exists()).toBe(true)
|
|
|
64
64
|
## Hook / 组合式函数测试
|
|
65
65
|
|
|
66
66
|
```typescript
|
|
67
|
-
import { describe, it, expect } from 'vitest'
|
|
67
|
+
import { describe, it, expect } from 'vitest';
|
|
68
68
|
|
|
69
69
|
describe('useCounter', () => {
|
|
70
70
|
it('should increment count', () => {
|
|
71
|
-
const { result } = renderHook(() => useCounter())
|
|
71
|
+
const { result } = renderHook(() => useCounter());
|
|
72
72
|
|
|
73
73
|
act(() => {
|
|
74
|
-
result.current.increment()
|
|
75
|
-
})
|
|
74
|
+
result.current.increment();
|
|
75
|
+
});
|
|
76
76
|
|
|
77
|
-
expect(result.current.count).toBe(1)
|
|
78
|
-
})
|
|
79
|
-
})
|
|
77
|
+
expect(result.current.count).toBe(1);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
## 测试文件命名与位置
|
|
@@ -88,22 +88,22 @@ describe('useCounter', () => {
|
|
|
88
88
|
## Mock 策略
|
|
89
89
|
|
|
90
90
|
```typescript
|
|
91
|
-
import { vi } from 'vitest'
|
|
91
|
+
import { vi } from 'vitest';
|
|
92
92
|
|
|
93
93
|
// Mock fetch
|
|
94
|
-
global.fetch = vi.fn()
|
|
94
|
+
global.fetch = vi.fn();
|
|
95
95
|
|
|
96
96
|
// Mock 模块
|
|
97
97
|
vi.mock('ant-design-vue', async () => {
|
|
98
|
-
const actual = await vi.importActual('ant-design-vue')
|
|
98
|
+
const actual = await vi.importActual('ant-design-vue');
|
|
99
99
|
return {
|
|
100
100
|
...actual,
|
|
101
101
|
message: {
|
|
102
102
|
success: vi.fn(),
|
|
103
103
|
error: vi.fn(),
|
|
104
104
|
},
|
|
105
|
-
}
|
|
106
|
-
})
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
107
|
|
|
108
108
|
// Mock 组合式函数
|
|
109
109
|
vi.mock('@/composables/useUsers', () => ({
|
|
@@ -113,7 +113,7 @@ vi.mock('@/composables/useUsers', () => ({
|
|
|
113
113
|
error: null,
|
|
114
114
|
refresh: vi.fn(),
|
|
115
115
|
}),
|
|
116
|
-
}))
|
|
116
|
+
}));
|
|
117
117
|
```
|
|
118
118
|
|
|
119
119
|
## 异步测试
|
|
@@ -124,39 +124,39 @@ it('should load and display users', async () => {
|
|
|
124
124
|
const mockUsers = [
|
|
125
125
|
{ id: '1', username: 'alice', email: 'alice@example.com', role: 'ADMIN' },
|
|
126
126
|
{ id: '2', username: 'bob', email: 'bob@example.com', role: 'EDITOR' },
|
|
127
|
-
]
|
|
127
|
+
];
|
|
128
128
|
|
|
129
129
|
// 模拟 API 响应
|
|
130
130
|
const mockFetch = vi.fn().mockResolvedValue({
|
|
131
131
|
ok: true,
|
|
132
132
|
json: () => Promise.resolve({ content: mockUsers, totalElements: 2 }),
|
|
133
|
-
})
|
|
134
|
-
global.fetch = mockFetch
|
|
133
|
+
});
|
|
134
|
+
global.fetch = mockFetch;
|
|
135
135
|
|
|
136
136
|
// Act
|
|
137
|
-
const wrapper = mount(UserList)
|
|
137
|
+
const wrapper = mount(UserList);
|
|
138
138
|
|
|
139
139
|
// Assert — 等待异步渲染完成
|
|
140
|
-
await nextTick()
|
|
141
|
-
expect(wrapper.text()).toContain('alice')
|
|
142
|
-
})
|
|
140
|
+
await nextTick();
|
|
141
|
+
expect(wrapper.text()).toContain('alice');
|
|
142
|
+
});
|
|
143
143
|
|
|
144
144
|
// @testing-library/vue 方案(自动等待)
|
|
145
|
-
import { render, screen } from '@testing-library/vue'
|
|
145
|
+
import { render, screen } from '@testing-library/vue';
|
|
146
146
|
|
|
147
147
|
it('should display users with testing-library', async () => {
|
|
148
|
-
render(UserList)
|
|
149
|
-
expect(await screen.findByText('alice')).toBeInTheDocument()
|
|
150
|
-
})
|
|
148
|
+
render(UserList);
|
|
149
|
+
expect(await screen.findByText('alice')).toBeInTheDocument();
|
|
150
|
+
});
|
|
151
151
|
```
|
|
152
152
|
|
|
153
153
|
## Pinia 测试
|
|
154
154
|
|
|
155
155
|
```typescript
|
|
156
|
-
import { mount } from '@vue/test-utils'
|
|
157
|
-
import { createTestingPinia } from '@pinia/testing'
|
|
158
|
-
import { describe, it, expect, vi } from 'vitest'
|
|
159
|
-
import UserList from '@/views/UserList.vue'
|
|
156
|
+
import { mount } from '@vue/test-utils';
|
|
157
|
+
import { createTestingPinia } from '@pinia/testing';
|
|
158
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
159
|
+
import UserList from '@/views/UserList.vue';
|
|
160
160
|
|
|
161
161
|
describe('UserList with Pinia', () => {
|
|
162
162
|
it('renders loading state', () => {
|
|
@@ -171,11 +171,11 @@ describe('UserList with Pinia', () => {
|
|
|
171
171
|
}),
|
|
172
172
|
],
|
|
173
173
|
},
|
|
174
|
-
})
|
|
174
|
+
});
|
|
175
175
|
|
|
176
|
-
expect(wrapper.findComponent({ name: 'ASpin' }).exists()).toBe(true)
|
|
177
|
-
})
|
|
178
|
-
})
|
|
176
|
+
expect(wrapper.findComponent({ name: 'ASpin' }).exists()).toBe(true);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
179
|
```
|
|
180
180
|
|
|
181
181
|
## 覆盖率要求
|
|
@@ -42,19 +42,19 @@ interface Dict {
|
|
|
42
42
|
[key: string]: string;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
const dict: Dict = { a:
|
|
45
|
+
const dict: Dict = { a: '1' };
|
|
46
46
|
|
|
47
47
|
// ❌ 错误 — dict["b"] 可能是 undefined
|
|
48
|
-
console.log(dict[
|
|
48
|
+
console.log(dict['b'].toString());
|
|
49
49
|
|
|
50
50
|
// ✅ 正确
|
|
51
|
-
const b = dict[
|
|
51
|
+
const b = dict['b'];
|
|
52
52
|
if (b) {
|
|
53
53
|
console.log(b.toString());
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
// 或用可选链
|
|
57
|
-
console.log(dict[
|
|
57
|
+
console.log(dict['b']?.toString());
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
### noImplicitOverride
|
|
@@ -63,15 +63,21 @@ console.log(dict["b"]?.toString());
|
|
|
63
63
|
|
|
64
64
|
```typescript
|
|
65
65
|
class Base {
|
|
66
|
-
save(): void {
|
|
66
|
+
save(): void {
|
|
67
|
+
/* ... */
|
|
68
|
+
}
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
class Derived extends Base {
|
|
70
72
|
// ❌ 错误 — 缺少 override
|
|
71
|
-
save(): void {
|
|
73
|
+
save(): void {
|
|
74
|
+
/* ... */
|
|
75
|
+
}
|
|
72
76
|
|
|
73
77
|
// ✅ 正确
|
|
74
|
-
override save(): void {
|
|
78
|
+
override save(): void {
|
|
79
|
+
/* ... */
|
|
80
|
+
}
|
|
75
81
|
}
|
|
76
82
|
```
|
|
77
83
|
|
|
@@ -80,14 +86,14 @@ class Derived extends Base {
|
|
|
80
86
|
### 数组查找
|
|
81
87
|
|
|
82
88
|
```typescript
|
|
83
|
-
const items: string[] = [
|
|
89
|
+
const items: string[] = ['a', 'b', 'c'];
|
|
84
90
|
|
|
85
91
|
// ❌ 错误 — find 返回 T | undefined
|
|
86
|
-
const item = items.find(x => x ===
|
|
92
|
+
const item = items.find((x) => x === 'd');
|
|
87
93
|
console.log(item.length);
|
|
88
94
|
|
|
89
95
|
// ✅ 正确
|
|
90
|
-
const item = items.find(x => x ===
|
|
96
|
+
const item = items.find((x) => x === 'd');
|
|
91
97
|
if (item) {
|
|
92
98
|
console.log(item.length);
|
|
93
99
|
}
|
|
@@ -3,40 +3,40 @@
|
|
|
3
3
|
## 完整数据表格(搜索 + 分页 + 操作列)
|
|
4
4
|
|
|
5
5
|
```tsx
|
|
6
|
-
import { useState, useEffect } from 'react'
|
|
7
|
-
import { Table, Button, Space, Input, Tag, Modal, message } from 'antd'
|
|
8
|
-
import { SearchOutlined, PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons'
|
|
9
|
-
import type { ColumnsType, TablePaginationConfig } from 'antd/es/table'
|
|
10
|
-
import type { User, PageResponse } from '../types/user'
|
|
11
|
-
import { userService } from '../services/userService'
|
|
6
|
+
import { useState, useEffect } from 'react';
|
|
7
|
+
import { Table, Button, Space, Input, Tag, Modal, message } from 'antd';
|
|
8
|
+
import { SearchOutlined, PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
|
9
|
+
import type { ColumnsType, TablePaginationConfig } from 'antd/es/table';
|
|
10
|
+
import type { User, PageResponse } from '../types/user';
|
|
11
|
+
import { userService } from '../services/userService';
|
|
12
12
|
|
|
13
13
|
const UserManagement: React.FC = () => {
|
|
14
|
-
const [users, setUsers] = useState<User[]>([])
|
|
15
|
-
const [loading, setLoading] = useState(false)
|
|
16
|
-
const [pagination, setPagination] = useState({ current: 1, pageSize: 20, total: 0 })
|
|
17
|
-
const [search, setSearch] = useState('')
|
|
14
|
+
const [users, setUsers] = useState<User[]>([]);
|
|
15
|
+
const [loading, setLoading] = useState(false);
|
|
16
|
+
const [pagination, setPagination] = useState({ current: 1, pageSize: 20, total: 0 });
|
|
17
|
+
const [search, setSearch] = useState('');
|
|
18
18
|
|
|
19
19
|
const fetchUsers = async (page: number, size: number, keyword?: string) => {
|
|
20
|
-
setLoading(true)
|
|
20
|
+
setLoading(true);
|
|
21
21
|
try {
|
|
22
|
-
const data: PageResponse<User> = await userService.list({ page, size, search: keyword })
|
|
23
|
-
setUsers(data.content)
|
|
24
|
-
setPagination(prev => ({ ...prev, current: data.number, total: data.totalElements }))
|
|
22
|
+
const data: PageResponse<User> = await userService.list({ page, size, search: keyword });
|
|
23
|
+
setUsers(data.content);
|
|
24
|
+
setPagination((prev) => ({ ...prev, current: data.number, total: data.totalElements }));
|
|
25
25
|
} catch {
|
|
26
|
-
message.error('加载用户列表失败')
|
|
26
|
+
message.error('加载用户列表失败');
|
|
27
27
|
} finally {
|
|
28
|
-
setLoading(false)
|
|
28
|
+
setLoading(false);
|
|
29
29
|
}
|
|
30
|
-
}
|
|
30
|
+
};
|
|
31
31
|
|
|
32
32
|
useEffect(() => {
|
|
33
|
-
fetchUsers(pagination.current, pagination.pageSize, search)
|
|
34
|
-
}, [pagination.current, pagination.pageSize])
|
|
33
|
+
fetchUsers(pagination.current, pagination.pageSize, search);
|
|
34
|
+
}, [pagination.current, pagination.pageSize]);
|
|
35
35
|
|
|
36
36
|
const handleSearch = (value: string) => {
|
|
37
|
-
setSearch(value)
|
|
38
|
-
setPagination(prev => ({ ...prev, current: 1 }))
|
|
39
|
-
}
|
|
37
|
+
setSearch(value);
|
|
38
|
+
setPagination((prev) => ({ ...prev, current: 1 }));
|
|
39
|
+
};
|
|
40
40
|
|
|
41
41
|
const handleDelete = (id: string) => {
|
|
42
42
|
Modal.confirm({
|
|
@@ -45,12 +45,12 @@ const UserManagement: React.FC = () => {
|
|
|
45
45
|
okText: '删除',
|
|
46
46
|
okType: 'danger',
|
|
47
47
|
onOk: async () => {
|
|
48
|
-
await userService.delete(id)
|
|
49
|
-
message.success('删除成功')
|
|
50
|
-
fetchUsers(pagination.current, pagination.pageSize, search)
|
|
48
|
+
await userService.delete(id);
|
|
49
|
+
message.success('删除成功');
|
|
50
|
+
fetchUsers(pagination.current, pagination.pageSize, search);
|
|
51
51
|
},
|
|
52
|
-
})
|
|
53
|
-
}
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
54
|
|
|
55
55
|
const columns: ColumnsType<User> = [
|
|
56
56
|
{ title: '用户名', dataIndex: 'username', key: 'username' },
|
|
@@ -60,39 +60,36 @@ const UserManagement: React.FC = () => {
|
|
|
60
60
|
dataIndex: 'role',
|
|
61
61
|
key: 'role',
|
|
62
62
|
render: (role: string) => {
|
|
63
|
-
const color = role === 'ADMIN' ? 'red' : role === 'EDITOR' ? 'blue' : 'default'
|
|
64
|
-
return <Tag color={color}>{role}</Tag
|
|
63
|
+
const color = role === 'ADMIN' ? 'red' : role === 'EDITOR' ? 'blue' : 'default';
|
|
64
|
+
return <Tag color={color}>{role}</Tag>;
|
|
65
65
|
},
|
|
66
66
|
},
|
|
67
67
|
{
|
|
68
68
|
title: '状态',
|
|
69
69
|
dataIndex: 'active',
|
|
70
70
|
key: 'active',
|
|
71
|
-
render: (active: boolean) =>
|
|
72
|
-
<Tag color={active ? 'green' : 'default'}>{active ? '活跃' : '停用'}</Tag>
|
|
73
|
-
),
|
|
71
|
+
render: (active: boolean) => <Tag color={active ? 'green' : 'default'}>{active ? '活跃' : '停用'}</Tag>,
|
|
74
72
|
},
|
|
75
73
|
{
|
|
76
74
|
title: '操作',
|
|
77
75
|
key: 'action',
|
|
78
76
|
render: (_, record) => (
|
|
79
77
|
<Space>
|
|
80
|
-
<Button type="link" icon={<EditOutlined />} onClick={() => handleEdit(record)}
|
|
81
|
-
|
|
78
|
+
<Button type="link" icon={<EditOutlined />} onClick={() => handleEdit(record)}>
|
|
79
|
+
编辑
|
|
80
|
+
</Button>
|
|
81
|
+
<Button type="link" danger icon={<DeleteOutlined />} onClick={() => handleDelete(record.id)}>
|
|
82
|
+
删除
|
|
83
|
+
</Button>
|
|
82
84
|
</Space>
|
|
83
85
|
),
|
|
84
86
|
},
|
|
85
|
-
]
|
|
87
|
+
];
|
|
86
88
|
|
|
87
89
|
return (
|
|
88
90
|
<div>
|
|
89
91
|
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between' }}>
|
|
90
|
-
<Input.Search
|
|
91
|
-
placeholder="搜索用户名/邮箱"
|
|
92
|
-
allowClear
|
|
93
|
-
onSearch={handleSearch}
|
|
94
|
-
style={{ width: 300 }}
|
|
95
|
-
/>
|
|
92
|
+
<Input.Search placeholder="搜索用户名/邮箱" allowClear onSearch={handleSearch} style={{ width: 300 }} />
|
|
96
93
|
<Button type="primary" icon={<PlusOutlined />} onClick={handleCreate}>
|
|
97
94
|
新增用户
|
|
98
95
|
</Button>
|
|
@@ -109,32 +106,32 @@ const UserManagement: React.FC = () => {
|
|
|
109
106
|
showTotal: (total) => `共 ${total} 条`,
|
|
110
107
|
}}
|
|
111
108
|
onChange={(pag: TablePaginationConfig) => {
|
|
112
|
-
setPagination(prev => ({
|
|
109
|
+
setPagination((prev) => ({
|
|
113
110
|
...prev,
|
|
114
111
|
current: pag.current ?? 1,
|
|
115
112
|
pageSize: pag.pageSize ?? 20,
|
|
116
|
-
}))
|
|
113
|
+
}));
|
|
117
114
|
}}
|
|
118
115
|
/>
|
|
119
116
|
</div>
|
|
120
|
-
)
|
|
121
|
-
}
|
|
117
|
+
);
|
|
118
|
+
};
|
|
122
119
|
```
|
|
123
120
|
|
|
124
121
|
## 表单弹窗(创建/编辑)
|
|
125
122
|
|
|
126
123
|
```tsx
|
|
127
124
|
interface UserFormModalProps {
|
|
128
|
-
open: boolean
|
|
129
|
-
editingUser?: User | null
|
|
130
|
-
onClose: () => void
|
|
131
|
-
onSuccess: () => void
|
|
125
|
+
open: boolean;
|
|
126
|
+
editingUser?: User | null;
|
|
127
|
+
onClose: () => void;
|
|
128
|
+
onSuccess: () => void;
|
|
132
129
|
}
|
|
133
130
|
|
|
134
131
|
const UserFormModal: React.FC<UserFormModalProps> = ({ open, editingUser, onClose, onSuccess }) => {
|
|
135
|
-
const [form] = Form.useForm()
|
|
136
|
-
const [saving, setSaving] = useState(false)
|
|
137
|
-
const isEditing = !!editingUser
|
|
132
|
+
const [form] = Form.useForm();
|
|
133
|
+
const [saving, setSaving] = useState(false);
|
|
134
|
+
const isEditing = !!editingUser;
|
|
138
135
|
|
|
139
136
|
useEffect(() => {
|
|
140
137
|
if (open) {
|
|
@@ -143,35 +140,35 @@ const UserFormModal: React.FC<UserFormModalProps> = ({ open, editingUser, onClos
|
|
|
143
140
|
displayName: editingUser.displayName,
|
|
144
141
|
email: editingUser.email,
|
|
145
142
|
role: editingUser.role,
|
|
146
|
-
})
|
|
143
|
+
});
|
|
147
144
|
} else {
|
|
148
|
-
form.resetFields()
|
|
145
|
+
form.resetFields();
|
|
149
146
|
}
|
|
150
147
|
}
|
|
151
|
-
}, [open, editingUser, form])
|
|
148
|
+
}, [open, editingUser, form]);
|
|
152
149
|
|
|
153
150
|
const handleSubmit = async () => {
|
|
154
151
|
try {
|
|
155
|
-
const values = await form.validateFields()
|
|
156
|
-
setSaving(true)
|
|
152
|
+
const values = await form.validateFields();
|
|
153
|
+
setSaving(true);
|
|
157
154
|
if (isEditing) {
|
|
158
|
-
await userService.update(editingUser!.id, values)
|
|
159
|
-
message.success('用户信息已更新')
|
|
155
|
+
await userService.update(editingUser!.id, values);
|
|
156
|
+
message.success('用户信息已更新');
|
|
160
157
|
} else {
|
|
161
|
-
await userService.create(values)
|
|
162
|
-
message.success('用户创建成功')
|
|
158
|
+
await userService.create(values);
|
|
159
|
+
message.success('用户创建成功');
|
|
163
160
|
}
|
|
164
|
-
onSuccess()
|
|
165
|
-
onClose()
|
|
161
|
+
onSuccess();
|
|
162
|
+
onClose();
|
|
166
163
|
} catch (err) {
|
|
167
164
|
// Form validateFields 校验失败会自动聚焦到错误字段,不需要额外处理
|
|
168
165
|
if (err instanceof Error) {
|
|
169
|
-
message.error(err.message)
|
|
166
|
+
message.error(err.message);
|
|
170
167
|
}
|
|
171
168
|
} finally {
|
|
172
|
-
setSaving(false)
|
|
169
|
+
setSaving(false);
|
|
173
170
|
}
|
|
174
|
-
}
|
|
171
|
+
};
|
|
175
172
|
|
|
176
173
|
return (
|
|
177
174
|
<Modal
|
|
@@ -183,16 +180,8 @@ const UserFormModal: React.FC<UserFormModalProps> = ({ open, editingUser, onClos
|
|
|
183
180
|
destroyOnClose
|
|
184
181
|
okText={isEditing ? '保存' : '创建'}
|
|
185
182
|
>
|
|
186
|
-
<Form
|
|
187
|
-
|
|
188
|
-
layout="vertical"
|
|
189
|
-
autoComplete="off"
|
|
190
|
-
>
|
|
191
|
-
<Form.Item
|
|
192
|
-
name="displayName"
|
|
193
|
-
label="显示名称"
|
|
194
|
-
rules={[{ required: true, message: '请输入显示名称' }]}
|
|
195
|
-
>
|
|
183
|
+
<Form form={form} layout="vertical" autoComplete="off">
|
|
184
|
+
<Form.Item name="displayName" label="显示名称" rules={[{ required: true, message: '请输入显示名称' }]}>
|
|
196
185
|
<Input placeholder="请输入显示名称" />
|
|
197
186
|
</Form.Item>
|
|
198
187
|
|
|
@@ -207,11 +196,7 @@ const UserFormModal: React.FC<UserFormModalProps> = ({ open, editingUser, onClos
|
|
|
207
196
|
<Input placeholder="请输入邮箱" />
|
|
208
197
|
</Form.Item>
|
|
209
198
|
|
|
210
|
-
<Form.Item
|
|
211
|
-
name="role"
|
|
212
|
-
label="角色"
|
|
213
|
-
rules={[{ required: true, message: '请选择角色' }]}
|
|
214
|
-
>
|
|
199
|
+
<Form.Item name="role" label="角色" rules={[{ required: true, message: '请选择角色' }]}>
|
|
215
200
|
<Select placeholder="请选择角色">
|
|
216
201
|
<Select.Option value="ADMIN">管理员</Select.Option>
|
|
217
202
|
<Select.Option value="EDITOR">编辑</Select.Option>
|
|
@@ -220,15 +205,15 @@ const UserFormModal: React.FC<UserFormModalProps> = ({ open, editingUser, onClos
|
|
|
220
205
|
</Form.Item>
|
|
221
206
|
</Form>
|
|
222
207
|
</Modal>
|
|
223
|
-
)
|
|
224
|
-
}
|
|
208
|
+
);
|
|
209
|
+
};
|
|
225
210
|
```
|
|
226
211
|
|
|
227
212
|
## 响应式布局
|
|
228
213
|
|
|
229
214
|
```tsx
|
|
230
|
-
import { Row, Col, Card, Statistic } from 'antd'
|
|
231
|
-
import { UserOutlined, FileTextOutlined, AuditOutlined } from '@ant-design/icons'
|
|
215
|
+
import { Row, Col, Card, Statistic } from 'antd';
|
|
216
|
+
import { UserOutlined, FileTextOutlined, AuditOutlined } from '@ant-design/icons';
|
|
232
217
|
|
|
233
218
|
const Dashboard: React.FC = () => {
|
|
234
219
|
return (
|
|
@@ -252,6 +237,6 @@ const Dashboard: React.FC = () => {
|
|
|
252
237
|
</Col>
|
|
253
238
|
</Row>
|
|
254
239
|
</div>
|
|
255
|
-
)
|
|
256
|
-
}
|
|
240
|
+
);
|
|
241
|
+
};
|
|
257
242
|
```
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
## 常用组件规范
|
|
11
11
|
|
|
12
12
|
### Button(按钮)
|
|
13
|
+
|
|
13
14
|
- 主要操作使用 `type="primary"`,次要操作默认
|
|
14
15
|
- 危险操作使用 `danger` 属性
|
|
15
16
|
- 异步操作必须设置 `loading` 状态防重复提交
|
|
@@ -24,6 +25,7 @@
|
|
|
24
25
|
```
|
|
25
26
|
|
|
26
27
|
### Table(表格)
|
|
28
|
+
|
|
27
29
|
- 始终指定 `rowKey` 为唯一标识字段(如 `id`)
|
|
28
30
|
- 列表格使用 `pagination` 属性控制分页
|
|
29
31
|
- 长列表开启 `virtual` 虚拟滚动(>= 1000 行)
|
|
@@ -44,14 +46,15 @@
|
|
|
44
46
|
showTotal: (total) => `共 ${total} 条`,
|
|
45
47
|
}}
|
|
46
48
|
onChange={(pagination) => {
|
|
47
|
-
setPage(pagination.current)
|
|
48
|
-
setPageSize(pagination.pageSize)
|
|
49
|
+
setPage(pagination.current);
|
|
50
|
+
setPageSize(pagination.pageSize);
|
|
49
51
|
}}
|
|
50
52
|
locale={{ emptyText: <Empty description="暂无数据" /> }}
|
|
51
53
|
/>
|
|
52
54
|
```
|
|
53
55
|
|
|
54
56
|
### Form(表单)
|
|
57
|
+
|
|
55
58
|
- 使用 `layout="vertical"` 默认垂直布局
|
|
56
59
|
- 所有校验规则集中在 `rules` 中定义,避免使用自定义 `validator`(除非复杂业务)
|
|
57
60
|
- `Form.Item` 的 `name` 必须与提交数据结构的字段名一致
|
|
@@ -59,12 +62,7 @@
|
|
|
59
62
|
- 全局校验信息通过 `ConfigProvider` 的 `form.validateMessages` 统一配置
|
|
60
63
|
|
|
61
64
|
```tsx
|
|
62
|
-
<Form
|
|
63
|
-
form={form}
|
|
64
|
-
layout="vertical"
|
|
65
|
-
onFinish={handleSubmit}
|
|
66
|
-
autoComplete="off"
|
|
67
|
-
>
|
|
65
|
+
<Form form={form} layout="vertical" onFinish={handleSubmit} autoComplete="off">
|
|
68
66
|
<Form.Item
|
|
69
67
|
name="email"
|
|
70
68
|
label="邮箱"
|
|
@@ -79,6 +77,7 @@
|
|
|
79
77
|
```
|
|
80
78
|
|
|
81
79
|
### Modal(弹窗)
|
|
80
|
+
|
|
82
81
|
- 表单弹窗用 `destroyOnClose` 确保关闭后重置状态
|
|
83
82
|
- `afterClose` 用于关闭后的清理回调
|
|
84
83
|
- 确认弹窗用 `Modal.confirm()` 快捷方法
|
|
@@ -94,7 +93,7 @@
|
|
|
94
93
|
afterClose={() => form.resetFields()}
|
|
95
94
|
>
|
|
96
95
|
<UserForm form={form} user={editingUser} />
|
|
97
|
-
</Modal
|
|
96
|
+
</Modal>;
|
|
98
97
|
|
|
99
98
|
// 确认弹窗
|
|
100
99
|
Modal.confirm({
|
|
@@ -103,29 +102,34 @@ Modal.confirm({
|
|
|
103
102
|
okText: '确认删除',
|
|
104
103
|
okType: 'danger',
|
|
105
104
|
onOk: () => deleteUser(id),
|
|
106
|
-
})
|
|
105
|
+
});
|
|
107
106
|
```
|
|
108
107
|
|
|
109
108
|
### Select(选择器)
|
|
109
|
+
|
|
110
110
|
- 选项少时直接写 `<Select.Option>`;选项多时使用 `options` 数组属性
|
|
111
111
|
- 远程搜索使用 `showSearch` + `onSearch` + `filterOption` 自定义
|
|
112
112
|
- 多选用 `mode="multiple"`
|
|
113
113
|
|
|
114
114
|
### DatePicker(日期选择器)
|
|
115
|
+
|
|
115
116
|
- 使用 `format` 指定显示格式
|
|
116
117
|
- 表单中配合 `Form.Item` 使用,值类型为 `dayjs` 对象
|
|
117
118
|
- 范围选择用 `DatePicker.RangePicker`
|
|
118
119
|
|
|
119
120
|
### Space(间距)
|
|
121
|
+
|
|
120
122
|
- 组件间间距优先使用 `<Space>` 包裹,避免手动 `margin`
|
|
121
123
|
- 默认间距为 `size="small"`(8px),可根据上下文调整
|
|
122
124
|
|
|
123
125
|
### message / notification(消息提示)
|
|
126
|
+
|
|
124
127
|
- 操作反馈使用 `message.success()` / `message.error()` 轻量提示
|
|
125
128
|
- 重要通知使用 `notification.open()` / `notification.info()`
|
|
126
129
|
- 不要在组件中手动创建 message 实例 — 使用静态方法即可
|
|
127
130
|
|
|
128
131
|
### Spin(加载中)
|
|
132
|
+
|
|
129
133
|
- 页面级加载使用 `Spin` 包裹内容区域
|
|
130
134
|
- 组件级加载使用 Spin 组件的 `spinning` 属性
|
|
131
135
|
- 全局加载状态使用 `Spin` + Suspense(React.lazy 场景)
|