@jahanxu/trellis 0.4.2 → 0.5.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/dist/configurators/workflow.d.ts.map +1 -1
- package/dist/configurators/workflow.js +58 -1
- package/dist/configurators/workflow.js.map +1 -1
- package/dist/constants/paths.d.ts +17 -0
- package/dist/constants/paths.d.ts.map +1 -1
- package/dist/constants/paths.js +19 -0
- package/dist/constants/paths.js.map +1 -1
- package/dist/templates/claude/commands/trellis/handoff.md +90 -387
- package/dist/templates/claude/commands/trellis/pick-task.md +74 -444
- package/dist/templates/claude/commands/trellis/update-spec.md +19 -3
- package/dist/templates/claude/hooks/inject-subagent-context.py +17 -101
- package/dist/templates/claude/hooks/ralph-loop.py +1 -0
- package/dist/templates/claude/hooks/session-start.py +170 -54
- package/dist/templates/iflow/commands/trellis/handoff.md +148 -0
- package/dist/templates/iflow/commands/trellis/pick-task.md +145 -0
- package/dist/templates/iflow/commands/trellis/update-spec.md +19 -3
- package/dist/templates/iflow/hooks/inject-subagent-context.py +1 -0
- package/dist/templates/iflow/hooks/ralph-loop.py +1 -0
- package/dist/templates/iflow/hooks/session-start.py +171 -0
- package/dist/templates/markdown/index.d.ts +9 -0
- package/dist/templates/markdown/index.d.ts.map +1 -1
- package/dist/templates/markdown/index.js +10 -0
- package/dist/templates/markdown/index.js.map +1 -1
- package/dist/templates/markdown/spec/roles/designer/index.md.txt +57 -0
- package/dist/templates/markdown/spec/roles/designer/mock-data-standards.md.txt +63 -0
- package/dist/templates/markdown/spec/roles/designer/prototype-guidelines.md.txt +49 -0
- package/dist/templates/markdown/spec/roles/frontend-impl/api-integration.md.txt +63 -0
- package/dist/templates/markdown/spec/roles/frontend-impl/index.md.txt +57 -0
- package/dist/templates/markdown/spec/roles/frontend-impl/prototype-to-production.md.txt +57 -0
- package/dist/templates/markdown/spec/roles/pm/index.md.txt +45 -0
- package/dist/templates/markdown/spec/roles/pm/prd-template.md.txt +64 -0
- package/dist/templates/markdown/spec/roles/pm/requirement-checklist.md.txt +43 -0
- package/dist/templates/trellis/index.d.ts +1 -0
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +2 -0
- package/dist/templates/trellis/index.js.map +1 -1
- package/dist/templates/trellis/scripts/add_session.py +3 -2
- package/dist/templates/trellis/scripts/common/cli_adapter.py +4 -4
- package/dist/templates/trellis/scripts/common/developer.py +4 -4
- package/dist/templates/trellis/scripts/common/git_context.py +7 -7
- package/dist/templates/trellis/scripts/common/paths.py +64 -14
- package/dist/templates/trellis/scripts/common/phase.py +2 -2
- package/dist/templates/trellis/scripts/common/registry.py +16 -16
- package/dist/templates/trellis/scripts/common/task_queue.py +10 -10
- package/dist/templates/trellis/scripts/common/task_utils.py +5 -5
- package/dist/templates/trellis/scripts/common/worktree.py +8 -8
- package/dist/templates/trellis/scripts/pool.py +214 -266
- package/dist/templates/trellis/scripts/task.py +3 -116
- package/package.json +3 -3
- package/dist/templates/claude/commands/trellis/before-role-work.md +0 -364
- package/dist/templates/trellis/VERSION +0 -1
- package/dist/templates/trellis/deliverables/README.md +0 -51
- package/dist/templates/trellis/paths.README.md +0 -277
- package/dist/templates/trellis/paths.yaml +0 -41
- package/dist/templates/trellis/pool/implementations.json +0 -5
- package/dist/templates/trellis/pool/prototypes.json +0 -5
- package/dist/templates/trellis/pool/requirements.json +0 -5
- package/dist/templates/trellis/scripts/common/project_paths.py +0 -189
- package/dist/templates/trellis/scripts/handoff_generator.py +0 -380
- package/dist/templates/trellis/spec/roles/designer/index.md +0 -243
- package/dist/templates/trellis/spec/roles/designer/mock-data-standards.md +0 -481
- package/dist/templates/trellis/spec/roles/designer/prototype-guidelines.md +0 -429
- package/dist/templates/trellis/spec/roles/frontend-impl/api-integration.md +0 -565
- package/dist/templates/trellis/spec/roles/frontend-impl/index.md +0 -321
- package/dist/templates/trellis/spec/roles/frontend-impl/state-management.md +0 -599
- package/dist/templates/trellis/spec/roles/pm/index.md +0 -112
- package/dist/templates/trellis/spec/roles/pm/prd-template.md +0 -124
|
@@ -1,599 +0,0 @@
|
|
|
1
|
-
# 状态管理规范
|
|
2
|
-
|
|
3
|
-
> Frontend 角色选择和实现状态管理方案的指南
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 核心原则
|
|
8
|
-
|
|
9
|
-
状态管理应该:
|
|
10
|
-
1. **按需选择** - 根据复杂度选择合适的方案
|
|
11
|
-
2. **类型安全** - 使用 TypeScript 确保状态类型正确
|
|
12
|
-
3. **可预测性** - 状态变化清晰可追踪
|
|
13
|
-
4. **性能优化** - 避免不必要的重渲染
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## 状态分类
|
|
18
|
-
|
|
19
|
-
### 1. 组件本地状态(Local State)
|
|
20
|
-
|
|
21
|
-
**定义**:仅在单个组件内使用的状态
|
|
22
|
-
|
|
23
|
-
**适用场景**:
|
|
24
|
-
- 表单输入值
|
|
25
|
-
- UI 切换状态(展开/折叠)
|
|
26
|
-
- 临时加载状态
|
|
27
|
-
|
|
28
|
-
**技术选择**:React `useState`
|
|
29
|
-
|
|
30
|
-
**示例**:
|
|
31
|
-
```typescript
|
|
32
|
-
const LoginForm = () => {
|
|
33
|
-
const [email, setEmail] = useState('');
|
|
34
|
-
const [password, setPassword] = useState('');
|
|
35
|
-
const [showPassword, setShowPassword] = useState(false);
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<form>
|
|
39
|
-
<input
|
|
40
|
-
type="email"
|
|
41
|
-
value={email}
|
|
42
|
-
onChange={(e) => setEmail(e.target.value)}
|
|
43
|
-
/>
|
|
44
|
-
<input
|
|
45
|
-
type={showPassword ? 'text' : 'password'}
|
|
46
|
-
value={password}
|
|
47
|
-
onChange={(e) => setPassword(e.target.value)}
|
|
48
|
-
/>
|
|
49
|
-
<button onClick={() => setShowPassword(!showPassword)}>
|
|
50
|
-
{showPassword ? '隐藏' : '显示'}密码
|
|
51
|
-
</button>
|
|
52
|
-
</form>
|
|
53
|
-
);
|
|
54
|
-
};
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
---
|
|
58
|
-
|
|
59
|
-
### 2. 跨组件状态(Shared State)
|
|
60
|
-
|
|
61
|
-
**定义**:多个组件共享的状态
|
|
62
|
-
|
|
63
|
-
**适用场景**:
|
|
64
|
-
- 用户认证状态
|
|
65
|
-
- 主题设置(暗色/亮色模式)
|
|
66
|
-
- 语言设置
|
|
67
|
-
|
|
68
|
-
**技术选择**:React Context + `useContext`
|
|
69
|
-
|
|
70
|
-
**示例**:
|
|
71
|
-
```typescript
|
|
72
|
-
// src/contexts/AuthContext.tsx
|
|
73
|
-
import { createContext, useContext, useState, ReactNode } from 'react';
|
|
74
|
-
import type { User } from '@/types/user';
|
|
75
|
-
|
|
76
|
-
interface AuthContextType {
|
|
77
|
-
user: User | null;
|
|
78
|
-
login: (user: User, token: string) => void;
|
|
79
|
-
logout: () => void;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
|
83
|
-
|
|
84
|
-
export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|
85
|
-
const [user, setUser] = useState<User | null>(null);
|
|
86
|
-
|
|
87
|
-
const login = (user: User, token: string) => {
|
|
88
|
-
setUser(user);
|
|
89
|
-
localStorage.setItem('auth_token', token);
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
const logout = () => {
|
|
93
|
-
setUser(null);
|
|
94
|
-
localStorage.removeItem('auth_token');
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
return (
|
|
98
|
-
<AuthContext.Provider value={{ user, login, logout }}>
|
|
99
|
-
{children}
|
|
100
|
-
</AuthContext.Provider>
|
|
101
|
-
);
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
export const useAuth = () => {
|
|
105
|
-
const context = useContext(AuthContext);
|
|
106
|
-
if (!context) {
|
|
107
|
-
throw new Error('useAuth must be used within AuthProvider');
|
|
108
|
-
}
|
|
109
|
-
return context;
|
|
110
|
-
};
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
**使用方式**:
|
|
114
|
-
```typescript
|
|
115
|
-
// App.tsx
|
|
116
|
-
<AuthProvider>
|
|
117
|
-
<App />
|
|
118
|
-
</AuthProvider>
|
|
119
|
-
|
|
120
|
-
// LoginPage.tsx
|
|
121
|
-
import { useAuth } from '@/contexts/AuthContext';
|
|
122
|
-
|
|
123
|
-
const LoginPage = () => {
|
|
124
|
-
const { login } = useAuth();
|
|
125
|
-
|
|
126
|
-
const handleSubmit = async (email: string, password: string) => {
|
|
127
|
-
const { user, token } = await loginAPI(email, password);
|
|
128
|
-
login(user, token);
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
// ...
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
// Header.tsx
|
|
135
|
-
import { useAuth } from '@/contexts/AuthContext';
|
|
136
|
-
|
|
137
|
-
const Header = () => {
|
|
138
|
-
const { user, logout } = useAuth();
|
|
139
|
-
|
|
140
|
-
return (
|
|
141
|
-
<div>
|
|
142
|
-
{user && (
|
|
143
|
-
<>
|
|
144
|
-
<span>{user.name}</span>
|
|
145
|
-
<button onClick={logout}>登出</button>
|
|
146
|
-
</>
|
|
147
|
-
)}
|
|
148
|
-
</div>
|
|
149
|
-
);
|
|
150
|
-
};
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
---
|
|
154
|
-
|
|
155
|
-
### 3. 全局复杂状态(Global Complex State)
|
|
156
|
-
|
|
157
|
-
**定义**:跨多个页面、需要复杂操作的状态
|
|
158
|
-
|
|
159
|
-
**适用场景**:
|
|
160
|
-
- 购物车状态
|
|
161
|
-
- 消息通知队列
|
|
162
|
-
- 应用配置
|
|
163
|
-
|
|
164
|
-
**技术选择**:Zustand(推荐)或 Redux Toolkit
|
|
165
|
-
|
|
166
|
-
---
|
|
167
|
-
|
|
168
|
-
## Zustand 使用指南
|
|
169
|
-
|
|
170
|
-
### 1. 安装
|
|
171
|
-
|
|
172
|
-
```bash
|
|
173
|
-
npm install zustand
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### 2. 创建 Store
|
|
177
|
-
|
|
178
|
-
```typescript
|
|
179
|
-
// src/stores/cartStore.ts
|
|
180
|
-
import { create } from 'zustand';
|
|
181
|
-
import type { Product } from '@/types/product';
|
|
182
|
-
|
|
183
|
-
interface CartItem {
|
|
184
|
-
product: Product;
|
|
185
|
-
quantity: number;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
interface CartStore {
|
|
189
|
-
items: CartItem[];
|
|
190
|
-
addItem: (product: Product, quantity: number) => void;
|
|
191
|
-
removeItem: (productId: string) => void;
|
|
192
|
-
updateQuantity: (productId: string, quantity: number) => void;
|
|
193
|
-
clearCart: () => void;
|
|
194
|
-
total: () => number;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
export const useCartStore = create<CartStore>((set, get) => ({
|
|
198
|
-
items: [],
|
|
199
|
-
|
|
200
|
-
addItem: (product, quantity) => {
|
|
201
|
-
set((state) => {
|
|
202
|
-
const existingItem = state.items.find(
|
|
203
|
-
(item) => item.product.id === product.id
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
if (existingItem) {
|
|
207
|
-
return {
|
|
208
|
-
items: state.items.map((item) =>
|
|
209
|
-
item.product.id === product.id
|
|
210
|
-
? { ...item, quantity: item.quantity + quantity }
|
|
211
|
-
: item
|
|
212
|
-
),
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return {
|
|
217
|
-
items: [...state.items, { product, quantity }],
|
|
218
|
-
};
|
|
219
|
-
});
|
|
220
|
-
},
|
|
221
|
-
|
|
222
|
-
removeItem: (productId) => {
|
|
223
|
-
set((state) => ({
|
|
224
|
-
items: state.items.filter((item) => item.product.id !== productId),
|
|
225
|
-
}));
|
|
226
|
-
},
|
|
227
|
-
|
|
228
|
-
updateQuantity: (productId, quantity) => {
|
|
229
|
-
set((state) => ({
|
|
230
|
-
items: state.items.map((item) =>
|
|
231
|
-
item.product.id === productId ? { ...item, quantity } : item
|
|
232
|
-
),
|
|
233
|
-
}));
|
|
234
|
-
},
|
|
235
|
-
|
|
236
|
-
clearCart: () => {
|
|
237
|
-
set({ items: [] });
|
|
238
|
-
},
|
|
239
|
-
|
|
240
|
-
total: () => {
|
|
241
|
-
const { items } = get();
|
|
242
|
-
return items.reduce(
|
|
243
|
-
(sum, item) => sum + item.product.price * item.quantity,
|
|
244
|
-
0
|
|
245
|
-
);
|
|
246
|
-
},
|
|
247
|
-
}));
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
### 3. 使用 Store
|
|
251
|
-
|
|
252
|
-
```typescript
|
|
253
|
-
// 组件中使用
|
|
254
|
-
import { useCartStore } from '@/stores/cartStore';
|
|
255
|
-
|
|
256
|
-
const CartPage = () => {
|
|
257
|
-
const items = useCartStore((state) => state.items);
|
|
258
|
-
const total = useCartStore((state) => state.total());
|
|
259
|
-
const removeItem = useCartStore((state) => state.removeItem);
|
|
260
|
-
|
|
261
|
-
return (
|
|
262
|
-
<div>
|
|
263
|
-
<h1>购物车</h1>
|
|
264
|
-
{items.map((item) => (
|
|
265
|
-
<div key={item.product.id}>
|
|
266
|
-
<span>{item.product.name}</span>
|
|
267
|
-
<span>数量: {item.quantity}</span>
|
|
268
|
-
<button onClick={() => removeItem(item.product.id)}>删除</button>
|
|
269
|
-
</div>
|
|
270
|
-
))}
|
|
271
|
-
<p>总计: ¥{total}</p>
|
|
272
|
-
</div>
|
|
273
|
-
);
|
|
274
|
-
};
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
### 4. Zustand 持久化
|
|
278
|
-
|
|
279
|
-
```typescript
|
|
280
|
-
import { create } from 'zustand';
|
|
281
|
-
import { persist } from 'zustand/middleware';
|
|
282
|
-
|
|
283
|
-
export const useCartStore = create<CartStore>()(
|
|
284
|
-
persist(
|
|
285
|
-
(set, get) => ({
|
|
286
|
-
items: [],
|
|
287
|
-
addItem: (product, quantity) => {
|
|
288
|
-
// ...
|
|
289
|
-
},
|
|
290
|
-
// ...
|
|
291
|
-
}),
|
|
292
|
-
{
|
|
293
|
-
name: 'cart-storage', // localStorage key
|
|
294
|
-
getStorage: () => localStorage,
|
|
295
|
-
}
|
|
296
|
-
)
|
|
297
|
-
);
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
---
|
|
301
|
-
|
|
302
|
-
## Redux Toolkit 使用指南
|
|
303
|
-
|
|
304
|
-
### 1. 安装
|
|
305
|
-
|
|
306
|
-
```bash
|
|
307
|
-
npm install @reduxjs/toolkit react-redux
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
### 2. 创建 Slice
|
|
311
|
-
|
|
312
|
-
```typescript
|
|
313
|
-
// src/store/slices/cartSlice.ts
|
|
314
|
-
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
|
315
|
-
import type { Product } from '@/types/product';
|
|
316
|
-
|
|
317
|
-
interface CartItem {
|
|
318
|
-
product: Product;
|
|
319
|
-
quantity: number;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
interface CartState {
|
|
323
|
-
items: CartItem[];
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const initialState: CartState = {
|
|
327
|
-
items: [],
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
const cartSlice = createSlice({
|
|
331
|
-
name: 'cart',
|
|
332
|
-
initialState,
|
|
333
|
-
reducers: {
|
|
334
|
-
addItem: (state, action: PayloadAction<{ product: Product; quantity: number }>) => {
|
|
335
|
-
const { product, quantity } = action.payload;
|
|
336
|
-
const existingItem = state.items.find(
|
|
337
|
-
(item) => item.product.id === product.id
|
|
338
|
-
);
|
|
339
|
-
|
|
340
|
-
if (existingItem) {
|
|
341
|
-
existingItem.quantity += quantity;
|
|
342
|
-
} else {
|
|
343
|
-
state.items.push({ product, quantity });
|
|
344
|
-
}
|
|
345
|
-
},
|
|
346
|
-
removeItem: (state, action: PayloadAction<string>) => {
|
|
347
|
-
state.items = state.items.filter(
|
|
348
|
-
(item) => item.product.id !== action.payload
|
|
349
|
-
);
|
|
350
|
-
},
|
|
351
|
-
updateQuantity: (
|
|
352
|
-
state,
|
|
353
|
-
action: PayloadAction<{ productId: string; quantity: number }>
|
|
354
|
-
) => {
|
|
355
|
-
const item = state.items.find(
|
|
356
|
-
(item) => item.product.id === action.payload.productId
|
|
357
|
-
);
|
|
358
|
-
if (item) {
|
|
359
|
-
item.quantity = action.payload.quantity;
|
|
360
|
-
}
|
|
361
|
-
},
|
|
362
|
-
clearCart: (state) => {
|
|
363
|
-
state.items = [];
|
|
364
|
-
},
|
|
365
|
-
},
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
export const { addItem, removeItem, updateQuantity, clearCart } = cartSlice.actions;
|
|
369
|
-
export default cartSlice.reducer;
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
### 3. 配置 Store
|
|
373
|
-
|
|
374
|
-
```typescript
|
|
375
|
-
// src/store/index.ts
|
|
376
|
-
import { configureStore } from '@reduxjs/toolkit';
|
|
377
|
-
import cartReducer from './slices/cartSlice';
|
|
378
|
-
|
|
379
|
-
export const store = configureStore({
|
|
380
|
-
reducer: {
|
|
381
|
-
cart: cartReducer,
|
|
382
|
-
},
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
export type RootState = ReturnType<typeof store.getState>;
|
|
386
|
-
export type AppDispatch = typeof store.dispatch;
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
### 4. 使用 Store
|
|
390
|
-
|
|
391
|
-
```typescript
|
|
392
|
-
// App.tsx
|
|
393
|
-
import { Provider } from 'react-redux';
|
|
394
|
-
import { store } from './store';
|
|
395
|
-
|
|
396
|
-
<Provider store={store}>
|
|
397
|
-
<App />
|
|
398
|
-
</Provider>
|
|
399
|
-
|
|
400
|
-
// 组件中使用
|
|
401
|
-
import { useSelector, useDispatch } from 'react-redux';
|
|
402
|
-
import { addItem, removeItem } from '@/store/slices/cartSlice';
|
|
403
|
-
import type { RootState } from '@/store';
|
|
404
|
-
|
|
405
|
-
const CartPage = () => {
|
|
406
|
-
const items = useSelector((state: RootState) => state.cart.items);
|
|
407
|
-
const dispatch = useDispatch();
|
|
408
|
-
|
|
409
|
-
const handleRemove = (productId: string) => {
|
|
410
|
-
dispatch(removeItem(productId));
|
|
411
|
-
};
|
|
412
|
-
|
|
413
|
-
return (
|
|
414
|
-
// ...
|
|
415
|
-
);
|
|
416
|
-
};
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
---
|
|
420
|
-
|
|
421
|
-
## 服务器状态管理
|
|
422
|
-
|
|
423
|
-
### 使用 React Query(推荐)
|
|
424
|
-
|
|
425
|
-
**定义**:从服务器获取的数据(用户列表、产品详情等)
|
|
426
|
-
|
|
427
|
-
**技术选择**:React Query(TanStack Query)
|
|
428
|
-
|
|
429
|
-
**安装**:
|
|
430
|
-
```bash
|
|
431
|
-
npm install @tanstack/react-query
|
|
432
|
-
```
|
|
433
|
-
|
|
434
|
-
**配置**:
|
|
435
|
-
```typescript
|
|
436
|
-
// src/App.tsx
|
|
437
|
-
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
438
|
-
|
|
439
|
-
const queryClient = new QueryClient({
|
|
440
|
-
defaultOptions: {
|
|
441
|
-
queries: {
|
|
442
|
-
staleTime: 60000, // 1分钟内数据不过期
|
|
443
|
-
refetchOnWindowFocus: false,
|
|
444
|
-
},
|
|
445
|
-
},
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
<QueryClientProvider client={queryClient}>
|
|
449
|
-
<App />
|
|
450
|
-
</QueryClientProvider>
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
**使用示例**:
|
|
454
|
-
```typescript
|
|
455
|
-
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
456
|
-
import { getUsers, deleteUser } from '@/lib/api/user';
|
|
457
|
-
|
|
458
|
-
const UserList = () => {
|
|
459
|
-
const queryClient = useQueryClient();
|
|
460
|
-
|
|
461
|
-
// 查询用户列表
|
|
462
|
-
const { data, isLoading, error } = useQuery({
|
|
463
|
-
queryKey: ['users'],
|
|
464
|
-
queryFn: getUsers,
|
|
465
|
-
});
|
|
466
|
-
|
|
467
|
-
// 删除用户 mutation
|
|
468
|
-
const deleteMutation = useMutation({
|
|
469
|
-
mutationFn: deleteUser,
|
|
470
|
-
onSuccess: () => {
|
|
471
|
-
// 刷新用户列表
|
|
472
|
-
queryClient.invalidateQueries({ queryKey: ['users'] });
|
|
473
|
-
},
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
if (isLoading) return <div>加载中...</div>;
|
|
477
|
-
if (error) return <div>错误: {error.message}</div>;
|
|
478
|
-
|
|
479
|
-
return (
|
|
480
|
-
<div>
|
|
481
|
-
{data?.items.map((user) => (
|
|
482
|
-
<div key={user.id}>
|
|
483
|
-
<span>{user.name}</span>
|
|
484
|
-
<button onClick={() => deleteMutation.mutate(user.id)}>
|
|
485
|
-
删除
|
|
486
|
-
</button>
|
|
487
|
-
</div>
|
|
488
|
-
))}
|
|
489
|
-
</div>
|
|
490
|
-
);
|
|
491
|
-
};
|
|
492
|
-
```
|
|
493
|
-
|
|
494
|
-
---
|
|
495
|
-
|
|
496
|
-
## 状态管理方案选择
|
|
497
|
-
|
|
498
|
-
| 状态类型 | 复杂度 | 推荐方案 | 示例场景 |
|
|
499
|
-
|---------|--------|---------|---------|
|
|
500
|
-
| **本地状态** | 简单 | `useState` | 表单输入、UI切换 |
|
|
501
|
-
| **跨组件状态** | 中等 | Context + `useContext` | 用户认证、主题设置 |
|
|
502
|
-
| **全局复杂状态** | 复杂 | Zustand | 购物车、通知队列 |
|
|
503
|
-
| **超大型应用** | 极复杂 | Redux Toolkit | 企业级应用 |
|
|
504
|
-
| **服务器状态** | - | React Query | API数据缓存 |
|
|
505
|
-
|
|
506
|
-
---
|
|
507
|
-
|
|
508
|
-
## 性能优化
|
|
509
|
-
|
|
510
|
-
### 1. 避免不必要的重渲染
|
|
511
|
-
|
|
512
|
-
**使用 selector 精确订阅**:
|
|
513
|
-
```typescript
|
|
514
|
-
// ❌ 避免:订阅整个 store
|
|
515
|
-
const store = useCartStore();
|
|
516
|
-
|
|
517
|
-
// ✅ 推荐:只订阅需要的状态
|
|
518
|
-
const items = useCartStore((state) => state.items);
|
|
519
|
-
const total = useCartStore((state) => state.total());
|
|
520
|
-
```
|
|
521
|
-
|
|
522
|
-
### 2. 使用 memo 优化组件
|
|
523
|
-
|
|
524
|
-
```typescript
|
|
525
|
-
import { memo } from 'react';
|
|
526
|
-
|
|
527
|
-
const CartItem = memo(({ item, onRemove }: CartItemProps) => {
|
|
528
|
-
return (
|
|
529
|
-
<div>
|
|
530
|
-
<span>{item.product.name}</span>
|
|
531
|
-
<button onClick={() => onRemove(item.product.id)}>删除</button>
|
|
532
|
-
</div>
|
|
533
|
-
);
|
|
534
|
-
});
|
|
535
|
-
```
|
|
536
|
-
|
|
537
|
-
### 3. 使用 useMemo 缓存计算结果
|
|
538
|
-
|
|
539
|
-
```typescript
|
|
540
|
-
const CartSummary = () => {
|
|
541
|
-
const items = useCartStore((state) => state.items);
|
|
542
|
-
|
|
543
|
-
const total = useMemo(() => {
|
|
544
|
-
return items.reduce(
|
|
545
|
-
(sum, item) => sum + item.product.price * item.quantity,
|
|
546
|
-
0
|
|
547
|
-
);
|
|
548
|
-
}, [items]);
|
|
549
|
-
|
|
550
|
-
return <div>总计: ¥{total}</div>;
|
|
551
|
-
};
|
|
552
|
-
```
|
|
553
|
-
|
|
554
|
-
---
|
|
555
|
-
|
|
556
|
-
## 检查清单
|
|
557
|
-
|
|
558
|
-
状态管理实现完成前,确保:
|
|
559
|
-
|
|
560
|
-
- [ ] 本地状态使用 `useState`
|
|
561
|
-
- [ ] 跨组件状态使用 Context 或 Zustand
|
|
562
|
-
- [ ] 服务器状态使用 React Query
|
|
563
|
-
- [ ] 所有状态有类型定义
|
|
564
|
-
- [ ] 避免不必要的重渲染(使用 selector)
|
|
565
|
-
- [ ] 复杂计算使用 `useMemo` 缓存
|
|
566
|
-
- [ ] 状态更新逻辑清晰可追踪
|
|
567
|
-
|
|
568
|
-
---
|
|
569
|
-
|
|
570
|
-
## 常见问题
|
|
571
|
-
|
|
572
|
-
### Q: 什么时候使用 Context,什么时候使用 Zustand?
|
|
573
|
-
A:
|
|
574
|
-
- **Context**:简单跨组件状态(用户认证、主题设置)
|
|
575
|
-
- **Zustand**:复杂状态操作、需要频繁更新(购物车、通知)
|
|
576
|
-
|
|
577
|
-
### Q: Redux 和 Zustand 如何选择?
|
|
578
|
-
A:
|
|
579
|
-
- **Zustand**:中小型应用,API 简单,性能好
|
|
580
|
-
- **Redux**:大型企业应用,需要严格的状态管理规范
|
|
581
|
-
|
|
582
|
-
### Q: 如何处理状态持久化?
|
|
583
|
-
A:
|
|
584
|
-
- **localStorage**:Zustand persist 中间件
|
|
585
|
-
- **sessionStorage**:配置 `getStorage: () => sessionStorage`
|
|
586
|
-
|
|
587
|
-
### Q: React Query 和 Zustand 如何配合?
|
|
588
|
-
A:
|
|
589
|
-
- **React Query**:管理服务器状态(API 数据)
|
|
590
|
-
- **Zustand**:管理客户端状态(购物车、UI设置)
|
|
591
|
-
|
|
592
|
-
---
|
|
593
|
-
|
|
594
|
-
## 相关文档
|
|
595
|
-
|
|
596
|
-
- [API 集成指南](./api-integration.md)
|
|
597
|
-
- [Frontend 工作规范](./index.md)
|
|
598
|
-
- [React Query 官方文档](https://tanstack.com/query/latest)
|
|
599
|
-
- [Zustand 官方文档](https://zustand-demo.pmnd.rs/)
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
# PM (Product Manager) 工作规范
|
|
2
|
-
|
|
3
|
-
> 产品经理在三角色协作中的职责、标准和工作流程
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 角色定位
|
|
8
|
-
|
|
9
|
-
**产品经理(PM)** 是三角色协作流水线的**第一环**:
|
|
10
|
-
- 负责需求分析和产品定义
|
|
11
|
-
- 输出 PRD(Product Requirements Document)和用户故事
|
|
12
|
-
- 产出物进入 `requirements` 池,供 Designer 消费
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## 工作流程
|
|
17
|
-
|
|
18
|
-
### 1. 创建任务
|
|
19
|
-
```bash
|
|
20
|
-
python3 .trellis/scripts/task.py create "功能标题" --slug feature-name
|
|
21
|
-
python3 .trellis/scripts/task.py set-role <task-dir> pm
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
### 2. 编写PRD
|
|
25
|
-
在任务目录创建 `prd.md`,参考模板:`.trellis/spec/roles/pm/prd-template.md`
|
|
26
|
-
|
|
27
|
-
### 3. 完成并交接
|
|
28
|
-
```bash
|
|
29
|
-
# AI会自动生成 HANDOFF.md
|
|
30
|
-
/trellis:handoff
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
产出自动进入 `deliverables/requirements/{task-id}/`
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## PRD标准
|
|
38
|
-
|
|
39
|
-
### 必需章节
|
|
40
|
-
1. **功能概述** - 一句话说明这是什么功能
|
|
41
|
-
2. **用户故事** - As a [角色], I want [功能], So that [价值]
|
|
42
|
-
3. **功能需求** - 详细功能点列表
|
|
43
|
-
4. **验收标准** - 如何判断功能完成
|
|
44
|
-
5. **非功能需求** - 性能、安全、兼容性等
|
|
45
|
-
|
|
46
|
-
### 推荐章节
|
|
47
|
-
- 竞品分析
|
|
48
|
-
- 数据埋点需求
|
|
49
|
-
- 上线计划
|
|
50
|
-
|
|
51
|
-
### 禁止内容
|
|
52
|
-
- ❌ 技术实现细节(由Designer和Frontend决定)
|
|
53
|
-
- ❌ UI设计细节(在PRD中仅描述布局要求,具体设计由Designer完成)
|
|
54
|
-
|
|
55
|
-
---
|
|
56
|
-
|
|
57
|
-
## HANDOFF文档要点
|
|
58
|
-
|
|
59
|
-
AI生成的 `HANDOFF.md` 应包含:
|
|
60
|
-
|
|
61
|
-
### 核心需求总结
|
|
62
|
-
- 2-3段文字提炼PRD核心要点
|
|
63
|
-
- 突出重点功能和优先级
|
|
64
|
-
|
|
65
|
-
### 关键设计要点
|
|
66
|
-
- Designer需要重点关注的UI/交互需求
|
|
67
|
-
- 特殊布局要求
|
|
68
|
-
- 品牌规范要求
|
|
69
|
-
|
|
70
|
-
### 特别注意事项
|
|
71
|
-
- 已知的技术限制(如兼容性要求)
|
|
72
|
-
- 外部依赖(如第三方SDK)
|
|
73
|
-
- 时间节点要求
|
|
74
|
-
|
|
75
|
-
### 相关资源
|
|
76
|
-
- 设计稿链接(如已有)
|
|
77
|
-
- API文档链接
|
|
78
|
-
- 参考资料链接
|
|
79
|
-
|
|
80
|
-
---
|
|
81
|
-
|
|
82
|
-
## 质量检查清单
|
|
83
|
-
|
|
84
|
-
在执行 `/trellis:handoff` 前,确保:
|
|
85
|
-
|
|
86
|
-
- [ ] PRD包含所有必需章节
|
|
87
|
-
- [ ] 用户故事清晰完整
|
|
88
|
-
- [ ] 验收标准可测试
|
|
89
|
-
- [ ] 非功能需求已明确
|
|
90
|
-
- [ ] 相关资源链接有效
|
|
91
|
-
- [ ] 没有遗漏关键业务逻辑
|
|
92
|
-
|
|
93
|
-
---
|
|
94
|
-
|
|
95
|
-
## 常见问题
|
|
96
|
-
|
|
97
|
-
### Q: PRD应该写多详细?
|
|
98
|
-
A: 足够Designer理解要做什么,但不要限制实现方式。描述"什么"和"为什么",不描述"怎么做"。
|
|
99
|
-
|
|
100
|
-
### Q: 需要提供UI设计稿吗?
|
|
101
|
-
A: 如果有现成设计稿,在HANDOFF中提供链接。如果没有,Designer会根据PRD创建原型。
|
|
102
|
-
|
|
103
|
-
### Q: 如何处理需求变更?
|
|
104
|
-
A: 小变更直接更新PRD和HANDOFF;大变更建议创建新任务,标注基于原任务。
|
|
105
|
-
|
|
106
|
-
---
|
|
107
|
-
|
|
108
|
-
## 参考资料
|
|
109
|
-
|
|
110
|
-
- [PRD模板](./prd-template.md)
|
|
111
|
-
- [用户故事编写指南](./user-story-guide.md)
|
|
112
|
-
- [需求检查清单](./requirement-checklist.md)
|