feat(product): 实现产品需求管理模块(后端 + 前端)
后端:Product Module(CRUD)、Requirement Module(CRUD + 状态机流转)、PrismaService 前端:产品列表页、产品详情页(含需求池 Tab)、Zustand Store、API 封装 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
63
apps/web/stores/useProductStore.ts
Normal file
63
apps/web/stores/useProductStore.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
'use client';
|
||||
|
||||
import { create } from 'zustand';
|
||||
import { Product } from '@ftb/shared';
|
||||
import { api } from '@/lib/api';
|
||||
|
||||
interface ProductWithCount extends Product {
|
||||
_count?: { requirements: number; projects: number };
|
||||
}
|
||||
|
||||
interface ProductState {
|
||||
products: ProductWithCount[];
|
||||
currentProduct: (Product & { requirements?: any[]; versions?: any[] }) | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
fetchProducts: () => Promise<void>;
|
||||
fetchProduct: (id: string) => Promise<void>;
|
||||
createProduct: (data: { name: string; description?: string }) => Promise<void>;
|
||||
updateProduct: (id: string, data: { name?: string; description?: string }) => Promise<void>;
|
||||
deleteProduct: (id: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export const useProductStore = create<ProductState>((set, get) => ({
|
||||
products: [],
|
||||
currentProduct: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
|
||||
fetchProducts: async () => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const products = await api.get<ProductWithCount[]>('/products');
|
||||
set({ products, loading: false });
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
fetchProduct: async (id) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const product = await api.get<Product>(`/products/${id}`);
|
||||
set({ currentProduct: product, loading: false });
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
createProduct: async (data) => {
|
||||
await api.post('/products', data);
|
||||
await get().fetchProducts();
|
||||
},
|
||||
|
||||
updateProduct: async (id, data) => {
|
||||
await api.patch(`/products/${id}`, data);
|
||||
await get().fetchProducts();
|
||||
},
|
||||
|
||||
deleteProduct: async (id) => {
|
||||
await api.delete(`/products/${id}`);
|
||||
set({ products: get().products.filter((p) => p.id !== id) });
|
||||
},
|
||||
}));
|
||||
66
apps/web/stores/useRequirementStore.ts
Normal file
66
apps/web/stores/useRequirementStore.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
'use client';
|
||||
|
||||
import { create } from 'zustand';
|
||||
import { Requirement, RequirementStatus } from '@ftb/shared';
|
||||
import { api } from '@/lib/api';
|
||||
|
||||
interface RequirementWithCreator extends Requirement {
|
||||
creator?: { id: string; name: string };
|
||||
}
|
||||
|
||||
interface RequirementState {
|
||||
requirements: RequirementWithCreator[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
statusFilter: RequirementStatus | null;
|
||||
fetchRequirements: (productId: string, status?: RequirementStatus) => Promise<void>;
|
||||
createRequirement: (productId: string, data: { title: string; description?: string; priority?: number; creatorId: string }) => Promise<void>;
|
||||
updateRequirement: (productId: string, id: string, data: { title?: string; description?: string; priority?: number }) => Promise<void>;
|
||||
updateStatus: (productId: string, id: string, status: RequirementStatus) => Promise<void>;
|
||||
deleteRequirement: (productId: string, id: string) => Promise<void>;
|
||||
setStatusFilter: (status: RequirementStatus | null) => void;
|
||||
}
|
||||
|
||||
export const useRequirementStore = create<RequirementState>((set, get) => ({
|
||||
requirements: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
statusFilter: null,
|
||||
|
||||
fetchRequirements: async (productId, status) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const query = status ? `?status=${status}` : '';
|
||||
const requirements = await api.get<RequirementWithCreator[]>(
|
||||
`/products/${productId}/requirements${query}`,
|
||||
);
|
||||
set({ requirements, loading: false });
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
createRequirement: async (productId, data) => {
|
||||
await api.post(`/products/${productId}/requirements`, data);
|
||||
await get().fetchRequirements(productId, get().statusFilter || undefined);
|
||||
},
|
||||
|
||||
updateRequirement: async (productId, id, data) => {
|
||||
await api.patch(`/products/${productId}/requirements/${id}`, data);
|
||||
await get().fetchRequirements(productId, get().statusFilter || undefined);
|
||||
},
|
||||
|
||||
updateStatus: async (productId, id, status) => {
|
||||
await api.patch(`/products/${productId}/requirements/${id}/status`, { status });
|
||||
await get().fetchRequirements(productId, get().statusFilter || undefined);
|
||||
},
|
||||
|
||||
deleteRequirement: async (productId, id) => {
|
||||
await api.delete(`/products/${productId}/requirements/${id}`);
|
||||
set({ requirements: get().requirements.filter((r) => r.id !== id) });
|
||||
},
|
||||
|
||||
setStatusFilter: (status) => {
|
||||
set({ statusFilter: status });
|
||||
},
|
||||
}));
|
||||
Reference in New Issue
Block a user