包含 Turborepo 配置、Next.js 前端骨架、NestJS 后端骨架、Prisma Schema、 共享类型包、Docker Compose 和环境变量模板。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
899 lines
19 KiB
Markdown
899 lines
19 KiB
Markdown
# FTB 智能项目管理系统 — 初始化实现计划
|
||
|
||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||
|
||
**Goal:** 初始化 monorepo 项目代码结构,包含前后端骨架、数据库 Schema、共享类型包,使项目可以运行并进入功能开发阶段。
|
||
|
||
**Architecture:** Turborepo monorepo,apps/web (Next.js 14 App Router) + apps/server (NestJS),packages/shared 共享类型。PostgreSQL + Prisma ORM,pnpm workspace 管理依赖。
|
||
|
||
**Tech Stack:** Next.js 14, Shadcn/ui, Tailwind CSS, Zustand, NestJS, Prisma, PostgreSQL, TypeScript
|
||
|
||
---
|
||
|
||
## Task 1: 初始化 Monorepo 根目录
|
||
|
||
**Files:**
|
||
- Create: `package.json`
|
||
- Create: `pnpm-workspace.yaml`
|
||
- Create: `turbo.json`
|
||
- Create: `.gitignore`
|
||
- Create: `.nvmrc`
|
||
|
||
- [ ] **Step 1: 初始化 git 仓库**
|
||
|
||
```bash
|
||
git init
|
||
```
|
||
|
||
- [ ] **Step 2: 创建根 package.json**
|
||
|
||
```json
|
||
{
|
||
"name": "ftb-project-management",
|
||
"private": true,
|
||
"scripts": {
|
||
"dev": "turbo dev",
|
||
"build": "turbo build",
|
||
"lint": "turbo lint",
|
||
"type-check": "turbo type-check",
|
||
"test": "turbo test",
|
||
"db:migrate": "pnpm --filter=server db:migrate",
|
||
"db:seed": "pnpm --filter=server db:seed",
|
||
"db:studio": "pnpm --filter=server db:studio"
|
||
},
|
||
"devDependencies": {
|
||
"turbo": "^2.0.0",
|
||
"typescript": "^5.5.0"
|
||
},
|
||
"packageManager": "pnpm@9.4.0"
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 3: 创建 pnpm-workspace.yaml**
|
||
|
||
```yaml
|
||
packages:
|
||
- "apps/*"
|
||
- "packages/*"
|
||
```
|
||
|
||
- [ ] **Step 4: 创建 turbo.json**
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://turbo.build/schema.json",
|
||
"tasks": {
|
||
"dev": {
|
||
"cache": false,
|
||
"persistent": true
|
||
},
|
||
"build": {
|
||
"dependsOn": ["^build"],
|
||
"outputs": [".next/**", "dist/**"]
|
||
},
|
||
"lint": {
|
||
"dependsOn": ["^build"]
|
||
},
|
||
"type-check": {
|
||
"dependsOn": ["^build"]
|
||
},
|
||
"test": {
|
||
"dependsOn": ["^build"]
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 5: 创建 .gitignore**
|
||
|
||
```
|
||
node_modules/
|
||
dist/
|
||
.next/
|
||
.env
|
||
.env.local
|
||
.env.production
|
||
*.log
|
||
.turbo/
|
||
coverage/
|
||
.DS_Store
|
||
```
|
||
|
||
- [ ] **Step 6: 创建 .nvmrc**
|
||
|
||
```
|
||
20
|
||
```
|
||
|
||
- [ ] **Step 7: 安装根依赖并验证**
|
||
|
||
```bash
|
||
pnpm install
|
||
```
|
||
|
||
- [ ] **Step 8: Commit**
|
||
|
||
```bash
|
||
git add package.json pnpm-workspace.yaml turbo.json .gitignore .nvmrc
|
||
git commit -m "chore: 初始化 monorepo 根目录配置"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 2: 初始化 packages/shared 共享类型包
|
||
|
||
**Files:**
|
||
- Create: `packages/shared/package.json`
|
||
- Create: `packages/shared/tsconfig.json`
|
||
- Create: `packages/shared/src/index.ts`
|
||
- Create: `packages/shared/src/enums.ts`
|
||
- Create: `packages/shared/src/types.ts`
|
||
|
||
- [ ] **Step 1: 创建 packages/shared/package.json**
|
||
|
||
```json
|
||
{
|
||
"name": "@ftb/shared",
|
||
"version": "0.0.1",
|
||
"private": true,
|
||
"main": "./src/index.ts",
|
||
"types": "./src/index.ts",
|
||
"scripts": {
|
||
"type-check": "tsc --noEmit",
|
||
"lint": "eslint src/"
|
||
},
|
||
"devDependencies": {
|
||
"typescript": "^5.5.0"
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: 创建 packages/shared/tsconfig.json**
|
||
|
||
```json
|
||
{
|
||
"compilerOptions": {
|
||
"target": "ES2020",
|
||
"module": "ESNext",
|
||
"moduleResolution": "bundler",
|
||
"strict": true,
|
||
"esModuleInterop": true,
|
||
"skipLibCheck": true,
|
||
"declaration": true,
|
||
"outDir": "./dist"
|
||
},
|
||
"include": ["src"]
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 3: 创建 packages/shared/src/enums.ts**
|
||
|
||
```typescript
|
||
export enum TaskStatus {
|
||
TODO = 'todo',
|
||
IN_PROGRESS = 'in_progress',
|
||
IN_REVIEW = 'in_review',
|
||
DONE = 'done',
|
||
CLOSED = 'closed',
|
||
}
|
||
|
||
export enum ProjectRole {
|
||
OWNER = 'owner',
|
||
ADMIN = 'admin',
|
||
MEMBER = 'member',
|
||
VIEWER = 'viewer',
|
||
}
|
||
|
||
export enum RequirementStatus {
|
||
DRAFT = 'draft',
|
||
REVIEWING = 'reviewing',
|
||
APPROVED = 'approved',
|
||
REJECTED = 'rejected',
|
||
DELIVERED = 'delivered',
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 4: 创建 packages/shared/src/types.ts**
|
||
|
||
```typescript
|
||
import { TaskStatus, ProjectRole, RequirementStatus } from './enums';
|
||
|
||
export interface Product {
|
||
id: string;
|
||
name: string;
|
||
description: string;
|
||
createdAt: Date;
|
||
updatedAt: Date;
|
||
}
|
||
|
||
export interface Project {
|
||
id: string;
|
||
productId: string;
|
||
name: string;
|
||
description: string;
|
||
createdAt: Date;
|
||
updatedAt: Date;
|
||
}
|
||
|
||
export interface Task {
|
||
id: string;
|
||
projectId: string;
|
||
sprintId: string | null;
|
||
versionId: string | null;
|
||
parentId: string | null;
|
||
title: string;
|
||
description: string;
|
||
status: TaskStatus;
|
||
assigneeId: string | null;
|
||
creatorId: string;
|
||
priority: number;
|
||
startDate: Date | null;
|
||
dueDate: Date | null;
|
||
createdAt: Date;
|
||
updatedAt: Date;
|
||
}
|
||
|
||
export interface Requirement {
|
||
id: string;
|
||
productId: string;
|
||
title: string;
|
||
description: string;
|
||
status: RequirementStatus;
|
||
priority: number;
|
||
creatorId: string;
|
||
createdAt: Date;
|
||
updatedAt: Date;
|
||
}
|
||
|
||
export interface Version {
|
||
id: string;
|
||
productId: string;
|
||
name: string;
|
||
description: string;
|
||
releaseDate: Date | null;
|
||
createdAt: Date;
|
||
updatedAt: Date;
|
||
}
|
||
|
||
export interface Sprint {
|
||
id: string;
|
||
projectId: string;
|
||
name: string;
|
||
startDate: Date;
|
||
endDate: Date;
|
||
createdAt: Date;
|
||
updatedAt: Date;
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 5: 创建 packages/shared/src/index.ts**
|
||
|
||
```typescript
|
||
export * from './enums';
|
||
export * from './types';
|
||
```
|
||
|
||
- [ ] **Step 6: Commit**
|
||
|
||
```bash
|
||
git add packages/shared/
|
||
git commit -m "feat(shared): 初始化共享类型包,定义核心枚举和实体类型"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 3: 初始化 NestJS 后端 (apps/server)
|
||
|
||
**Files:**
|
||
- Create: `apps/server/package.json`
|
||
- Create: `apps/server/tsconfig.json`
|
||
- Create: `apps/server/nest-cli.json`
|
||
- Create: `apps/server/src/main.ts`
|
||
- Create: `apps/server/src/app.module.ts`
|
||
|
||
- [ ] **Step 1: 创建 apps/server/package.json**
|
||
|
||
```json
|
||
{
|
||
"name": "server",
|
||
"version": "0.0.1",
|
||
"private": true,
|
||
"scripts": {
|
||
"dev": "nest start --watch",
|
||
"build": "nest build",
|
||
"start": "nest start",
|
||
"start:prod": "node dist/main",
|
||
"lint": "eslint \"src/**/*.ts\"",
|
||
"type-check": "tsc --noEmit",
|
||
"test": "jest",
|
||
"test:watch": "jest --watch",
|
||
"db:migrate": "prisma migrate dev",
|
||
"db:seed": "ts-node prisma/seed.ts",
|
||
"db:studio": "prisma studio"
|
||
},
|
||
"dependencies": {
|
||
"@nestjs/common": "^10.0.0",
|
||
"@nestjs/core": "^10.0.0",
|
||
"@nestjs/platform-express": "^10.0.0",
|
||
"@prisma/client": "^5.15.0",
|
||
"reflect-metadata": "^0.2.0",
|
||
"rxjs": "^7.8.0",
|
||
"@ftb/shared": "workspace:*"
|
||
},
|
||
"devDependencies": {
|
||
"@nestjs/cli": "^10.0.0",
|
||
"@nestjs/testing": "^10.0.0",
|
||
"@types/express": "^4.17.0",
|
||
"@types/jest": "^29.5.0",
|
||
"@types/node": "^20.0.0",
|
||
"jest": "^29.7.0",
|
||
"prisma": "^5.15.0",
|
||
"ts-jest": "^29.1.0",
|
||
"ts-node": "^10.9.0",
|
||
"typescript": "^5.5.0"
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: 创建 apps/server/tsconfig.json**
|
||
|
||
```json
|
||
{
|
||
"compilerOptions": {
|
||
"module": "commonjs",
|
||
"declaration": true,
|
||
"emitDecoratorMetadata": true,
|
||
"experimentalDecorators": true,
|
||
"target": "ES2021",
|
||
"sourceMap": true,
|
||
"outDir": "./dist",
|
||
"baseUrl": "./",
|
||
"strict": true,
|
||
"skipLibCheck": true,
|
||
"paths": { "@/*": ["src/*"] }
|
||
},
|
||
"include": ["src"]
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 3: 创建 apps/server/nest-cli.json**
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://json.schemastore.org/nest-cli",
|
||
"collection": "@nestjs/schematics",
|
||
"sourceRoot": "src",
|
||
"compilerOptions": { "deleteOutDir": true }
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 4: 创建 apps/server/src/app.module.ts**
|
||
|
||
```typescript
|
||
import { Module } from '@nestjs/common';
|
||
|
||
@Module({
|
||
imports: [],
|
||
controllers: [],
|
||
providers: [],
|
||
})
|
||
export class AppModule {}
|
||
```
|
||
|
||
- [ ] **Step 5: 创建 apps/server/src/main.ts**
|
||
|
||
```typescript
|
||
import { NestFactory } from '@nestjs/core';
|
||
import { ValidationPipe } from '@nestjs/common';
|
||
import { AppModule } from './app.module';
|
||
|
||
async function bootstrap() {
|
||
const app = await NestFactory.create(AppModule);
|
||
app.setGlobalPrefix('api/v1');
|
||
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
|
||
app.enableCors();
|
||
await app.listen(3001);
|
||
}
|
||
bootstrap();
|
||
```
|
||
|
||
- [ ] **Step 6: Commit**
|
||
|
||
```bash
|
||
git add apps/server/
|
||
git commit -m "feat(server): 初始化 NestJS 后端骨架"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 4: 配置 Prisma Schema
|
||
|
||
**Files:**
|
||
- Create: `apps/server/prisma/schema.prisma`
|
||
|
||
- [ ] **Step 1: 创建 Prisma Schema 文件**
|
||
|
||
```prisma
|
||
generator client {
|
||
provider = "prisma-client-js"
|
||
}
|
||
|
||
datasource db {
|
||
provider = "postgresql"
|
||
url = env("DATABASE_URL")
|
||
}
|
||
|
||
model User {
|
||
id String @id @default(cuid())
|
||
email String @unique
|
||
name String
|
||
avatar String?
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
createdTasks Task[] @relation("TaskCreator")
|
||
assignedTasks Task[] @relation("TaskAssignee")
|
||
projectMembers ProjectMember[]
|
||
requirements Requirement[]
|
||
watchedTasks TaskWatcher[]
|
||
|
||
@@map("users")
|
||
}
|
||
|
||
model Product {
|
||
id String @id @default(cuid())
|
||
name String
|
||
description String @default("")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
projects Project[]
|
||
requirements Requirement[]
|
||
versions Version[]
|
||
|
||
@@map("products")
|
||
}
|
||
|
||
model Project {
|
||
id String @id @default(cuid())
|
||
productId String @map("product_id")
|
||
name String
|
||
description String @default("")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
product Product @relation(fields: [productId], references: [id])
|
||
sprints Sprint[]
|
||
tasks Task[]
|
||
members ProjectMember[]
|
||
|
||
@@map("projects")
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: 继续添加剩余模型到 schema.prisma**
|
||
|
||
在上面的文件末尾追加:
|
||
|
||
```prisma
|
||
model Sprint {
|
||
id String @id @default(cuid())
|
||
projectId String @map("project_id")
|
||
name String
|
||
startDate DateTime @map("start_date")
|
||
endDate DateTime @map("end_date")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
project Project @relation(fields: [projectId], references: [id])
|
||
tasks Task[]
|
||
|
||
@@map("sprints")
|
||
}
|
||
|
||
model Version {
|
||
id String @id @default(cuid())
|
||
productId String @map("product_id")
|
||
name String
|
||
description String @default("")
|
||
releaseDate DateTime? @map("release_date")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
product Product @relation(fields: [productId], references: [id])
|
||
tasks Task[]
|
||
|
||
@@map("versions")
|
||
}
|
||
|
||
model Requirement {
|
||
id String @id @default(cuid())
|
||
productId String @map("product_id")
|
||
title String
|
||
description String @default("")
|
||
status String @default("draft")
|
||
priority Int @default(0)
|
||
creatorId String @map("creator_id")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
product Product @relation(fields: [productId], references: [id])
|
||
creator User @relation(fields: [creatorId], references: [id])
|
||
|
||
@@map("requirements")
|
||
}
|
||
|
||
model Task {
|
||
id String @id @default(cuid())
|
||
projectId String @map("project_id")
|
||
sprintId String? @map("sprint_id")
|
||
versionId String? @map("version_id")
|
||
parentId String? @map("parent_id")
|
||
title String
|
||
description String @default("")
|
||
status String @default("todo")
|
||
priority Int @default(0)
|
||
assigneeId String? @map("assignee_id")
|
||
creatorId String @map("creator_id")
|
||
startDate DateTime? @map("start_date")
|
||
dueDate DateTime? @map("due_date")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
project Project @relation(fields: [projectId], references: [id])
|
||
sprint Sprint? @relation(fields: [sprintId], references: [id])
|
||
version Version? @relation(fields: [versionId], references: [id])
|
||
parent Task? @relation("TaskChildren", fields: [parentId], references: [id])
|
||
children Task[] @relation("TaskChildren")
|
||
assignee User? @relation("TaskAssignee", fields: [assigneeId], references: [id])
|
||
creator User @relation("TaskCreator", fields: [creatorId], references: [id])
|
||
watchers TaskWatcher[]
|
||
comments Comment[]
|
||
|
||
@@map("tasks")
|
||
}
|
||
|
||
model TaskWatcher {
|
||
id String @id @default(cuid())
|
||
taskId String @map("task_id")
|
||
userId String @map("user_id")
|
||
|
||
task Task @relation(fields: [taskId], references: [id])
|
||
user User @relation(fields: [userId], references: [id])
|
||
|
||
@@unique([taskId, userId])
|
||
@@map("task_watchers")
|
||
}
|
||
|
||
model Comment {
|
||
id String @id @default(cuid())
|
||
taskId String @map("task_id")
|
||
authorId String @map("author_id")
|
||
content String
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
task Task @relation(fields: [taskId], references: [id])
|
||
|
||
@@map("comments")
|
||
}
|
||
|
||
model ProjectMember {
|
||
id String @id @default(cuid())
|
||
projectId String @map("project_id")
|
||
userId String @map("user_id")
|
||
role String @default("member")
|
||
|
||
project Project @relation(fields: [projectId], references: [id])
|
||
user User @relation(fields: [userId], references: [id])
|
||
|
||
@@unique([projectId, userId])
|
||
@@map("project_members")
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
git add apps/server/prisma/
|
||
git commit -m "feat(server): 添加 Prisma Schema,定义全部核心数据模型"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 5: 初始化 Next.js 前端 (apps/web)
|
||
|
||
**Files:**
|
||
- Create: `apps/web/package.json`
|
||
- Create: `apps/web/tsconfig.json`
|
||
- Create: `apps/web/next.config.js`
|
||
- Create: `apps/web/tailwind.config.ts`
|
||
- Create: `apps/web/postcss.config.js`
|
||
- Create: `apps/web/app/layout.tsx`
|
||
- Create: `apps/web/app/page.tsx`
|
||
- Create: `apps/web/app/globals.css`
|
||
|
||
- [ ] **Step 1: 创建 apps/web/package.json**
|
||
|
||
```json
|
||
{
|
||
"name": "web",
|
||
"version": "0.0.1",
|
||
"private": true,
|
||
"scripts": {
|
||
"dev": "next dev --port 3000",
|
||
"build": "next build",
|
||
"start": "next start",
|
||
"lint": "next lint",
|
||
"type-check": "tsc --noEmit"
|
||
},
|
||
"dependencies": {
|
||
"next": "^14.2.0",
|
||
"react": "^18.3.0",
|
||
"react-dom": "^18.3.0",
|
||
"zustand": "^4.5.0",
|
||
"@ftb/shared": "workspace:*"
|
||
},
|
||
"devDependencies": {
|
||
"@types/node": "^20.0.0",
|
||
"@types/react": "^18.3.0",
|
||
"@types/react-dom": "^18.3.0",
|
||
"autoprefixer": "^10.4.0",
|
||
"postcss": "^8.4.0",
|
||
"tailwindcss": "^3.4.0",
|
||
"typescript": "^5.5.0"
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: 创建 apps/web/tsconfig.json**
|
||
|
||
```json
|
||
{
|
||
"compilerOptions": {
|
||
"target": "ES2017",
|
||
"lib": ["dom", "dom.iterable", "esnext"],
|
||
"allowJs": true,
|
||
"skipLibCheck": true,
|
||
"strict": true,
|
||
"noEmit": true,
|
||
"esModuleInterop": true,
|
||
"module": "esnext",
|
||
"moduleResolution": "bundler",
|
||
"resolveJsonModule": true,
|
||
"isolatedModules": true,
|
||
"jsx": "preserve",
|
||
"incremental": true,
|
||
"plugins": [{ "name": "next" }],
|
||
"paths": { "@/*": ["./*"] }
|
||
},
|
||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||
"exclude": ["node_modules"]
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 3: 创建 apps/web/next.config.js**
|
||
|
||
```javascript
|
||
/** @type {import('next').NextConfig} */
|
||
const nextConfig = {
|
||
transpilePackages: ['@ftb/shared'],
|
||
};
|
||
|
||
module.exports = nextConfig;
|
||
```
|
||
|
||
- [ ] **Step 4: 创建 apps/web/tailwind.config.ts**
|
||
|
||
```typescript
|
||
import type { Config } from 'tailwindcss';
|
||
|
||
const config: Config = {
|
||
content: ['./app/**/*.{ts,tsx}', './components/**/*.{ts,tsx}'],
|
||
theme: { extend: {} },
|
||
plugins: [],
|
||
};
|
||
|
||
export default config;
|
||
```
|
||
|
||
- [ ] **Step 5: 创建 apps/web/postcss.config.js**
|
||
|
||
```javascript
|
||
module.exports = {
|
||
plugins: {
|
||
tailwindcss: {},
|
||
autoprefixer: {},
|
||
},
|
||
};
|
||
```
|
||
|
||
- [ ] **Step 6: 创建 apps/web/app/globals.css**
|
||
|
||
```css
|
||
@tailwind base;
|
||
@tailwind components;
|
||
@tailwind utilities;
|
||
```
|
||
|
||
- [ ] **Step 7: 创建 apps/web/app/layout.tsx**
|
||
|
||
```tsx
|
||
import './globals.css';
|
||
import type { Metadata } from 'next';
|
||
|
||
export const metadata: Metadata = {
|
||
title: 'FTB 项目管理',
|
||
description: '智能项目管理系统',
|
||
};
|
||
|
||
export default function RootLayout({
|
||
children,
|
||
}: {
|
||
children: React.ReactNode;
|
||
}) {
|
||
return (
|
||
<html lang="zh-CN">
|
||
<body>{children}</body>
|
||
</html>
|
||
);
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 8: 创建 apps/web/app/page.tsx**
|
||
|
||
```tsx
|
||
export default function HomePage() {
|
||
return (
|
||
<main className="flex min-h-screen items-center justify-center">
|
||
<h1 className="text-3xl font-bold">FTB 项目管理系统</h1>
|
||
</main>
|
||
);
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 9: 创建前端页面目录占位**
|
||
|
||
```bash
|
||
mkdir -p apps/web/app/workspace
|
||
mkdir -p apps/web/app/products/[id]/requirements
|
||
mkdir -p apps/web/app/products/[id]/versions
|
||
mkdir -p apps/web/app/projects/[id]/board
|
||
mkdir -p apps/web/app/projects/[id]/gantt
|
||
mkdir -p apps/web/app/projects/[id]/sprints
|
||
mkdir -p apps/web/app/projects/[id]/settings
|
||
mkdir -p apps/web/app/admin/members
|
||
mkdir -p apps/web/components/ui
|
||
mkdir -p apps/web/components/board
|
||
mkdir -p apps/web/components/gantt
|
||
mkdir -p apps/web/components/shared
|
||
mkdir -p apps/web/hooks
|
||
mkdir -p apps/web/stores
|
||
mkdir -p apps/web/lib
|
||
```
|
||
|
||
- [ ] **Step 10: Commit**
|
||
|
||
```bash
|
||
git add apps/web/
|
||
git commit -m "feat(web): 初始化 Next.js 前端骨架,配置 Tailwind CSS"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 6: Docker + 环境配置
|
||
|
||
**Files:**
|
||
- Create: `docker-compose.yml`
|
||
- Create: `.env.example`
|
||
- Create: `apps/server/.env.example`
|
||
|
||
- [ ] **Step 1: 创建 docker-compose.yml**
|
||
|
||
```yaml
|
||
version: '3.8'
|
||
|
||
services:
|
||
postgres:
|
||
image: postgres:16-alpine
|
||
ports:
|
||
- '5432:5432'
|
||
environment:
|
||
POSTGRES_USER: postgres
|
||
POSTGRES_PASSWORD: postgres
|
||
POSTGRES_DB: ftb_pm
|
||
volumes:
|
||
- postgres_data:/var/lib/postgresql/data
|
||
|
||
redis:
|
||
image: redis:7-alpine
|
||
ports:
|
||
- '6379:6379'
|
||
|
||
volumes:
|
||
postgres_data:
|
||
```
|
||
|
||
- [ ] **Step 2: 创建 .env.example**
|
||
|
||
```bash
|
||
# 数据库
|
||
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/ftb_pm
|
||
|
||
# 认证
|
||
NEXTAUTH_SECRET=change-me-to-random-secret
|
||
NEXTAUTH_URL=http://localhost:3000
|
||
|
||
# AI
|
||
ANTHROPIC_API_KEY=sk-ant-xxx
|
||
|
||
# Redis
|
||
REDIS_URL=redis://localhost:6379
|
||
|
||
# 邮件通知(可选)
|
||
SMTP_HOST=smtp.example.com
|
||
SMTP_PORT=465
|
||
SMTP_USER=noreply@example.com
|
||
SMTP_PASS=your-smtp-password
|
||
```
|
||
|
||
- [ ] **Step 3: 创建 apps/server/.env.example**
|
||
|
||
```bash
|
||
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/ftb_pm
|
||
ANTHROPIC_API_KEY=sk-ant-xxx
|
||
REDIS_URL=redis://localhost:6379
|
||
```
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
git add docker-compose.yml .env.example apps/server/.env.example
|
||
git commit -m "chore: 添加 Docker Compose 和环境变量示例配置"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 7: 验证整体项目结构
|
||
|
||
- [ ] **Step 1: 安装全部依赖**
|
||
|
||
```bash
|
||
pnpm install
|
||
```
|
||
|
||
- [ ] **Step 2: 启动数据库**
|
||
|
||
```bash
|
||
docker-compose up -d
|
||
```
|
||
|
||
- [ ] **Step 3: 运行 Prisma 迁移**
|
||
|
||
```bash
|
||
cd apps/server && pnpm db:migrate
|
||
```
|
||
|
||
- [ ] **Step 4: 验证后端启动**
|
||
|
||
```bash
|
||
pnpm dev --filter=server
|
||
```
|
||
|
||
预期:控制台输出 `Nest application successfully started`,监听 3001 端口。
|
||
|
||
- [ ] **Step 5: 验证前端启动**
|
||
|
||
```bash
|
||
pnpm dev --filter=web
|
||
```
|
||
|
||
预期:控制台输出 `ready started server on 0.0.0.0:3000`,浏览器打开看到 "FTB 项目管理系统"。
|
||
|
||
- [ ] **Step 6: 类型检查通过**
|
||
|
||
```bash
|
||
pnpm type-check
|
||
```
|
||
|
||
预期:无错误输出。
|