2
0
yourname 5 сар өмнө
parent
commit
19f6e553a1

+ 16 - 0
src/client/index.tsx

@@ -0,0 +1,16 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import { BrowserRouter } from 'react-router-dom';
+import App from './App';
+import { logger } from './utils/logger';
+
+// 初始化日志
+logger.info('CRM前端应用启动');
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+  <React.StrictMode>
+    <BrowserRouter>
+      <App />
+    </BrowserRouter>
+  </React.StrictMode>
+);

+ 3 - 1
src/server/api.ts

@@ -1,4 +1,5 @@
 import { OpenAPIHono } from '@hono/zod-openapi';
+import campaignsRoute from '@/server/api/campaigns/index';
 import departmentsRoute from '@/server/api/departments/index';
 import userRoute from '@/server/api/users/index';
 import contactsRoute from '@/server/api/contacts/index';
@@ -17,6 +18,7 @@ const api = new OpenAPIHono()
   .route('/api/v1/opportunities', opportunitiesRoute)
   .route('/api/v1/tickets', ticketsRoute)
   .route('/api/v1/roles', roleRoute)
-  .route('/api/v1/users', userRoute);
+  .route('/api/v1/users', userRoute)
+  .route('/api/v1/campaigns', campaignsRoute);
 
 export default api;

+ 94 - 0
src/server/api/campaigns/[id]/delete.ts

@@ -0,0 +1,94 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from 'zod';
+import { AppDataSource } from '@/server/data-source';
+import { CampaignService } from '@/server/modules/campaigns/campaign.service';
+import { authMiddleware } from '@/server/middleware/auth';
+import { ErrorSchema } from '@/server/utils/errorHandler';
+import { AuthContext } from '@/server/types/context';
+import { logger } from '@/server/utils/logger';
+
+// 定义路径参数Schema
+const DeleteCampaignParams = z.object({
+  id: z.coerce.number().int().positive().openapi({
+    param: { name: 'id', in: 'path' },
+    example: 1,
+    description: '活动ID'
+  })
+});
+
+// 定义响应Schema
+const DeleteCampaignResponse = z.object({
+  code: z.number().openapi({ example: 200 }),
+  message: z.string().openapi({ example: '活动删除成功' })
+});
+
+// 创建路由定义
+const routeDef = createRoute({
+  method: 'delete',
+  path: '/{id}',
+  middleware: [authMiddleware],
+  request: {
+    params: DeleteCampaignParams
+  },
+  responses: {
+    200: {
+      description: '成功删除活动',
+      content: {
+        'application/json': { schema: DeleteCampaignResponse }
+      }
+    },
+    400: {
+      description: '请求参数错误',
+      content: {
+        'application/json': { schema: ErrorSchema }
+      }
+    },
+    404: {
+      description: '活动不存在',
+      content: {
+        'application/json': { schema: ErrorSchema }
+      }
+    },
+    500: {
+      description: '服务器内部错误',
+      content: {
+        'application/json': { schema: ErrorSchema }
+      }
+    }
+  },
+  tags: ['campaigns']
+});
+
+// 创建路由处理
+const app = new OpenAPIHono<AuthContext>().openapi(routeDef, async (c) => {
+  try {
+    const { id } = c.req.valid('param');
+    
+    const campaignService = new CampaignService(AppDataSource);
+    const result = await campaignService.remove(id);
+    
+    if (!result) {
+      logger.error(`活动不存在: ID=${id}`);
+      return c.json({
+        code: 404,
+        message: '活动不存在'
+      }, 404);
+    }
+    
+    logger.api(`删除活动成功: ID=${id}`);
+    
+    return c.json({
+      code: 200,
+      message: '活动删除成功'
+    }, 200);
+  } catch (error) {
+    logger.error(`删除活动失败: ${error instanceof Error ? error.message : String(error)}`);
+    
+    return c.json({
+      code: 500,
+      message: error instanceof Error ? error.message : '删除活动失败'
+    }, 500);
+  }
+});
+
+export default app;

+ 116 - 0
src/server/api/campaigns/[id]/effectiveness.ts

@@ -0,0 +1,116 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from 'zod';
+import { AppDataSource } from '@/server/data-source';
+import { CampaignService } from '@/server/modules/campaigns/campaign.service';
+import { authMiddleware } from '@/server/middleware/auth';
+import { ErrorSchema } from '@/server/utils/errorHandler';
+import { AuthContext } from '@/server/types/context';
+import { logger } from '@/server/utils/logger';
+
+// 定义路径参数Schema
+const CampaignEffectivenessParams = z.object({
+  id: z.coerce.number().int().positive().openapi({
+    param: { name: 'id', in: 'path' },
+    example: 1,
+    description: '活动ID'
+  })
+});
+
+// 定义效果分析响应Schema
+const CampaignEffectivenessSchema = z.object({
+  participationRate: z.number().openapi({ 
+    example: 0.85, 
+    description: '参与率' 
+  }),
+  conversionRate: z.number().openapi({ 
+    example: 0.25, 
+    description: '转化率' 
+  }),
+  roi: z.number().openapi({ 
+    example: 3.2, 
+    description: '投资回报率' 
+  }),
+  totalParticipants: z.number().openapi({ 
+    example: 850, 
+    description: '总参与人数' 
+  }),
+  totalConversions: z.number().openapi({ 
+    example: 212, 
+    description: '总转化人数' 
+  }),
+  totalRevenue: z.number().openapi({ 
+    example: 16000, 
+    description: '总收益' 
+  })
+});
+
+// 定义响应Schema
+const GetEffectivenessResponse = z.object({
+  code: z.number().openapi({ example: 200 }),
+  message: z.string().openapi({ example: 'success' }),
+  data: CampaignEffectivenessSchema
+});
+
+// 创建路由定义
+const routeDef = createRoute({
+  method: 'get',
+  path: '/{id}/effectiveness',
+  middleware: [authMiddleware],
+  request: {
+    params: CampaignEffectivenessParams
+  },
+  responses: {
+    200: {
+      description: '成功获取活动效果分析',
+      content: {
+        'application/json': { schema: GetEffectivenessResponse }
+      }
+    },
+    400: {
+      description: '请求参数错误',
+      content: {
+        'application/json': { schema: ErrorSchema }
+      }
+    },
+    404: {
+      description: '活动不存在',
+      content: {
+        'application/json': { schema: ErrorSchema }
+      }
+    },
+    500: {
+      description: '服务器内部错误',
+      content: {
+        'application/json': { schema: ErrorSchema }
+      }
+    }
+  },
+  tags: ['campaigns']
+});
+
+// 创建路由处理
+const app = new OpenAPIHono<AuthContext>().openapi(routeDef, async (c) => {
+  try {
+    const { id } = c.req.valid('param');
+    
+    const campaignService = new CampaignService(AppDataSource);
+    const effectiveness = await campaignService.analyzeEffectiveness(id);
+    
+    logger.api(`获取活动效果分析成功: ID=${id}`);
+    
+    return c.json({
+      code: 200,
+      message: 'success',
+      data: effectiveness
+    }, 200);
+  } catch (error) {
+    logger.error(`获取活动效果分析失败: ${error instanceof Error ? error.message : String(error)}`);
+    
+    return c.json({
+      code: 500,
+      message: error instanceof Error ? error.message : '获取活动效果分析失败'
+    }, 500);
+  }
+});
+
+export default app;

+ 97 - 0
src/server/api/campaigns/[id]/get.ts

@@ -0,0 +1,97 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from 'zod';
+import { AppDataSource } from '@/server/data-source';
+import { CampaignService } from '@/server/modules/campaigns/campaign.service';
+import { authMiddleware } from '@/server/middleware/auth';
+import { ErrorSchema } from '@/server/utils/errorHandler';
+import { AuthContext } from '@/server/types/context';
+import { CampaignSchema } from '@/server/modules/campaigns/campaign.entity';
+import { logger } from '@/server/utils/logger';
+
+// 定义路径参数Schema
+const GetCampaignParams = z.object({
+  id: z.coerce.number().int().positive().openapi({
+    param: { name: 'id', in: 'path' },
+    example: 1,
+    description: '活动ID'
+  })
+});
+
+// 定义响应Schema
+const GetCampaignResponse = z.object({
+  code: z.number().openapi({ example: 200 }),
+  message: z.string().openapi({ example: 'success' }),
+  data: CampaignSchema
+});
+
+// 创建路由定义
+const routeDef = createRoute({
+  method: 'get',
+  path: '/{id}',
+  middleware: [authMiddleware],
+  request: {
+    params: GetCampaignParams
+  },
+  responses: {
+    200: {
+      description: '成功获取活动详情',
+      content: {
+        'application/json': { schema: GetCampaignResponse }
+      }
+    },
+    400: {
+      description: '请求参数错误',
+      content: {
+        'application/json': { schema: ErrorSchema }
+      }
+    },
+    404: {
+      description: '活动不存在',
+      content: {
+        'application/json': { schema: ErrorSchema }
+      }
+    },
+    500: {
+      description: '服务器内部错误',
+      content: {
+        'application/json': { schema: ErrorSchema }
+      }
+    }
+  },
+  tags: ['campaigns']
+});
+
+// 创建路由处理
+const app = new OpenAPIHono<AuthContext>().openapi(routeDef, async (c) => {
+  try {
+    const { id } = c.req.valid('param');
+    
+    const campaignService = new CampaignService(AppDataSource);
+    const campaign = await campaignService.findOne(id);
+    
+    if (!campaign) {
+      logger.error(`活动不存在: ID=${id}`);
+      return c.json({
+        code: 404,
+        message: '活动不存在'
+      }, 404);
+    }
+    
+    logger.api(`获取活动详情成功: ID=${id}`);
+    
+    return c.json({
+      code: 200,
+      message: 'success',
+      data: campaign
+    }, 200);
+  } catch (error) {
+    logger.error(`获取活动详情失败: ${error instanceof Error ? error.message : String(error)}`);
+    
+    return c.json({
+      code: 500,
+      message: error instanceof Error ? error.message : '获取活动详情失败'
+    }, 500);
+  }
+});
+
+export default app;

+ 176 - 0
src/server/api/campaigns/[id]/participants.ts

@@ -0,0 +1,176 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from 'zod';
+import { AppDataSource } from '@/server/data-source';
+import { CampaignService } from '@/server/modules/campaigns/campaign.service';
+import { authMiddleware } from '@/server/middleware/auth';
+import { ErrorSchema } from '@/server/utils/errorHandler';
+import { AuthContext } from '@/server/types/context';
+import { logger } from '@/server/utils/logger';
+
+// 定义路径参数Schema
+const CampaignParticipantParams = z.object({
+  id: z.coerce.number().int().positive().openapi({
+    param: { name: 'id', in: 'path' },
+    example: 1,
+    description: '活动ID'
+  })
+});
+
+// 定义请求体Schema
+const AddParticipantBody = z.object({
+  userId: z.coerce.number().int().positive().openapi({
+    example: 1001,
+    description: '用户ID'
+  })
+});
+
+// 定义参与人响应Schema
+const ParticipantSchema = z.object({
+  campaignId: z.number().openapi({ example: 1 }),
+  userId: z.number().openapi({ example: 1001 }),
+  joinedAt: z.date().openapi({ example: '2023-06-01T12:00:00Z' })
+});
+
+// 定义响应Schema
+const AddParticipantResponse = z.object({
+  code: z.number().openapi({ example: 200 }),
+  message: z.string().openapi({ example: '添加活动参与人成功' }),
+  data: ParticipantSchema
+});
+
+const GetParticipantsResponse = z.object({
+  code: z.number().openapi({ example: 200 }),
+  message: z.string().openapi({ example: 'success' }),
+  data: z.array(ParticipantSchema)
+});
+
+// 创建添加参与人路由定义
+const addRouteDef = createRoute({
+  method: 'post',
+  path: '/{id}/participants',
+  middleware: [authMiddleware],
+  request: {
+    params: CampaignParticipantParams,
+    body: {
+      content: {
+        'application/json': { schema: AddParticipantBody }
+      }
+    }
+  },
+  responses: {
+    200: {
+      description: '成功添加活动参与人',
+      content: {
+        'application/json': { schema: AddParticipantResponse }
+      }
+    },
+    400: {
+      description: '请求参数错误',
+      content: {
+        'application/json': { schema: ErrorSchema }
+      }
+    },
+    404: {
+      description: '活动不存在',
+      content: {
+        'application/json': { schema: ErrorSchema }
+      }
+    },
+    500: {
+      description: '服务器内部错误',
+      content: {
+        'application/json': { schema: ErrorSchema }
+      }
+    }
+  },
+  tags: ['campaigns']
+});
+
+// 创建获取参与人列表路由定义
+const getRouteDef = createRoute({
+  method: 'get',
+  path: '/{id}/participants',
+  middleware: [authMiddleware],
+  request: {
+    params: CampaignParticipantParams
+  },
+  responses: {
+    200: {
+      description: '成功获取活动参与人列表',
+      content: {
+        'application/json': { schema: GetParticipantsResponse }
+      }
+    },
+    400: {
+      description: '请求参数错误',
+      content: {
+        'application/json': { schema: ErrorSchema }
+      }
+    },
+    404: {
+      description: '活动不存在',
+      content: {
+        'application/json': { schema: ErrorSchema }
+      }
+    },
+    500: {
+      description: '服务器内部错误',
+      content: {
+        'application/json': { schema: ErrorSchema }
+      }
+    }
+  },
+  tags: ['campaigns']
+});
+
+// 创建路由处理
+const app = new OpenAPIHono<AuthContext>()
+  .openapi(addRouteDef, async (c): Promise<Response> => {
+    try {
+      const { id } = c.req.valid('param');
+      const { userId } = c.req.valid('json');
+      
+      const campaignService = new CampaignService(AppDataSource);
+      const participant = await campaignService.addParticipant(id, userId);
+      
+      logger.api(`添加活动参与人成功: 活动ID=${id}, 用户ID=${userId}`);
+      
+      return c.json({
+        code: 200,
+        message: '添加活动参与人成功',
+        data: participant
+      }, 200);
+    } catch (error) {
+      logger.error(`添加活动参与人失败: ${error instanceof Error ? error.message : String(error)}`);
+      
+      return c.json({
+        code: 500,
+        message: error instanceof Error ? error.message : '添加活动参与人失败'
+      }, 500);
+    }
+  })
+  .openapi(getRouteDef, async (c): Promise<Response> => {
+    try {
+      const { id } = c.req.valid('param');
+      
+      const campaignService = new CampaignService(AppDataSource);
+      const participants = await campaignService.getParticipants(id);
+      
+      logger.api(`获取活动参与人列表成功: 活动ID=${id}, 参与人数=${participants.length}`);
+      
+      return c.json({
+        code: 200,
+        message: 'success',
+        data: participants
+      }, 200);
+    } catch (error) {
+      logger.error(`获取活动参与人列表失败: ${error instanceof Error ? error.message : String(error)}`);
+      
+      return c.json({
+        code: 500,
+        message: error instanceof Error ? error.message : '获取活动参与人列表失败'
+      }, 500);
+    }
+  });
+
+export default app;

+ 111 - 0
src/server/api/campaigns/[id]/put.ts

@@ -0,0 +1,111 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from 'zod';
+import { AppDataSource } from '@/server/data-source';
+import { CampaignService } from '@/server/modules/campaigns/campaign.service';
+import { authMiddleware } from '@/server/middleware/auth';
+import { ErrorSchema } from '@/server/utils/errorHandler';
+import { AuthContext } from '@/server/types/context';
+import { CampaignSchema } from '@/server/modules/campaigns/campaign.entity';
+import { logger } from '@/server/utils/logger';
+
+// 定义路径参数Schema
+const UpdateCampaignParams = z.object({
+  id: z.coerce.number().int().positive().openapi({
+    param: { name: 'id', in: 'path' },
+    example: 1,
+    description: '活动ID'
+  })
+});
+
+// 定义请求体Schema
+const UpdateCampaignBody = CampaignSchema.omit({
+  id: true,
+  createdAt: true,
+  updatedAt: true,
+  isDeleted: true
+}).partial();
+
+// 定义响应Schema
+const UpdateCampaignResponse = z.object({
+  code: z.number().openapi({ example: 200 }),
+  message: z.string().openapi({ example: '活动更新成功' }),
+  data: CampaignSchema
+});
+
+// 创建路由定义
+const routeDef = createRoute({
+  method: 'put',
+  path: '/{id}',
+  middleware: [authMiddleware],
+  request: {
+    params: UpdateCampaignParams,
+    body: {
+      content: {
+        'application/json': { schema: UpdateCampaignBody }
+      }
+    }
+  },
+  responses: {
+    200: {
+      description: '成功更新活动',
+      content: {
+        'application/json': { schema: UpdateCampaignResponse }
+      }
+    },
+    400: {
+      description: '请求参数错误',
+      content: {
+        'application/json': { schema: ErrorSchema }
+      }
+    },
+    404: {
+      description: '活动不存在',
+      content: {
+        'application/json': { schema: ErrorSchema }
+      }
+    },
+    500: {
+      description: '服务器内部错误',
+      content: {
+        'application/json': { schema: ErrorSchema }
+      }
+    }
+  },
+  tags: ['campaigns']
+});
+
+// 创建路由处理
+const app = new OpenAPIHono<AuthContext>().openapi(routeDef, async (c) => {
+  try {
+    const { id } = c.req.valid('param');
+    const body = c.req.valid('json');
+    
+    const campaignService = new CampaignService(AppDataSource);
+    const updatedCampaign = await campaignService.update(id, body as any);
+    
+    if (!updatedCampaign) {
+      logger.error(`活动不存在: ID=${id}`);
+      return c.json({
+        code: 404,
+        message: '活动不存在'
+      }, 404);
+    }
+    
+    logger.api(`更新活动成功: ID=${id}`);
+    
+    return c.json({
+      code: 200,
+      message: '活动更新成功',
+      data: updatedCampaign
+    }, 200);
+  } catch (error) {
+    logger.error(`更新活动失败: ${error instanceof Error ? error.message : String(error)}`);
+    
+    return c.json({
+      code: 500,
+      message: error instanceof Error ? error.message : '更新活动失败'
+    }, 500);
+  }
+});
+
+export default app;

+ 120 - 0
src/server/api/campaigns/[id]/status.ts

@@ -0,0 +1,120 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from 'zod';
+import { AppDataSource } from '@/server/data-source';
+import { CampaignService, CampaignStatus } from '@/server/modules/campaigns/campaign.service';
+import { authMiddleware } from '@/server/middleware/auth';
+import { ErrorSchema } from '@/server/utils/errorHandler';
+import { AuthContext } from '@/server/types/context';
+import { CampaignSchema } from '@/server/modules/campaigns/campaign.entity';
+import { logger } from '@/server/utils/logger';
+
+// 定义路径参数Schema
+const UpdateStatusParams = z.object({
+  id: z.coerce.number().int().positive().openapi({
+    param: { name: 'id', in: 'path' },
+    example: 1,
+    description: '活动ID'
+  })
+});
+
+// 定义请求体Schema
+const UpdateStatusBody = z.object({
+  status: z.coerce.number().int().openapi({
+    example: 1,
+    description: '活动状态: 0-未开始, 1-进行中, 2-已结束, 3-已取消'
+  })
+});
+
+// 定义响应Schema
+const UpdateStatusResponse = z.object({
+  code: z.number().openapi({ example: 200 }),
+  message: z.string().openapi({ example: '活动状态更新成功' }),
+  data: CampaignSchema
+});
+
+// 创建路由定义
+const routeDef = createRoute({
+  method: 'patch',
+  path: '/{id}/status',
+  middleware: [authMiddleware],
+  request: {
+    params: UpdateStatusParams,
+    body: {
+      content: {
+        'application/json': { schema: UpdateStatusBody }
+      }
+    }
+  },
+  responses: {
+    200: {
+      description: '成功更新活动状态',
+      content: {
+        'application/json': { schema: UpdateStatusResponse }
+      }
+    },
+    400: {
+      description: '请求参数错误',
+      content: {
+        'application/json': { schema: ErrorSchema }
+      }
+    },
+    404: {
+      description: '活动不存在',
+      content: {
+        'application/json': { schema: ErrorSchema }
+      }
+    },
+    500: {
+      description: '服务器内部错误',
+      content: {
+        'application/json': { schema: ErrorSchema }
+      }
+    }
+  },
+  tags: ['campaigns']
+});
+
+// 创建路由处理
+const app = new OpenAPIHono<AuthContext>().openapi(routeDef, async (c) => {
+  try {
+    const { id } = c.req.valid('param');
+    const { status } = c.req.valid('json');
+    
+    // 验证状态值是否有效
+    if (!Object.values(CampaignStatus).includes(status)) {
+      logger.error(`无效的活动状态: ${status}`);
+      return c.json({
+        code: 400,
+        message: '无效的活动状态'
+      }, 400);
+    }
+    
+    const campaignService = new CampaignService(AppDataSource);
+    const updatedCampaign = await campaignService.updateStatus(id, status);
+    
+    if (!updatedCampaign) {
+      logger.error(`活动不存在: ID=${id}`);
+      return c.json({
+        code: 404,
+        message: '活动不存在'
+      }, 404);
+    }
+    
+    logger.api(`更新活动状态成功: ID=${id}, 新状态=${status}`);
+    
+    return c.json({
+      code: 200,
+      message: '活动状态更新成功',
+      data: updatedCampaign
+    }, 200);
+  } catch (error) {
+    logger.error(`更新活动状态失败: ${error instanceof Error ? error.message : String(error)}`);
+    
+    return c.json({
+      code: 500,
+      message: error instanceof Error ? error.message : '更新活动状态失败'
+    }, 500);
+  }
+});
+
+export default app;

+ 119 - 0
src/server/api/campaigns/get.ts

@@ -0,0 +1,119 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from 'zod';
+import { AppDataSource } from '@/server/data-source';
+import { CampaignService } from '@/server/modules/campaigns/campaign.service';
+import { authMiddleware } from '@/server/middleware/auth';
+import { ErrorSchema } from '@/server/utils/errorHandler';
+import { AuthContext } from '@/server/types/context';
+import { CampaignSchema } from '@/server/modules/campaigns/campaign.entity';
+import { logger } from '@/server/utils/logger';
+
+// 定义查询参数Schema
+const GetCampaignsQuery = z.object({
+  page: z.coerce.number().int().positive().optional().openapi({
+    example: 1,
+    description: '页码'
+  }),
+  pageSize: z.coerce.number().int().positive().optional().openapi({
+    example: 10,
+    description: '每页数量'
+  }),
+  status: z.coerce.number().int().optional().openapi({
+    example: 1,
+    description: '活动状态: 0-未开始, 1-进行中, 2-已结束, 3-已取消'
+  }),
+  keyword: z.string().optional().openapi({
+    example: '促销',
+    description: '搜索关键词'
+  }),
+  startDate: z.string().optional().openapi({
+    example: '2023-01-01',
+    description: '开始日期'
+  }),
+  endDate: z.string().optional().openapi({
+    example: '2023-12-31',
+    description: '结束日期'
+  })
+});
+
+// 定义响应Schema
+const CampaignListResponse = z.object({
+  code: z.number().openapi({ example: 200 }),
+  message: z.string().openapi({ example: 'success' }),
+  data: z.array(CampaignSchema),
+  pagination: z.object({
+    total: z.number().openapi({ example: 100 }),
+    current: z.number().openapi({ example: 1 }),
+    pageSize: z.number().openapi({ example: 10 })
+  })
+});
+
+// 创建路由定义
+const routeDef = createRoute({
+  method: 'get',
+  path: '/',
+  middleware: [authMiddleware],
+  request: {
+    query: GetCampaignsQuery
+  },
+  responses: {
+    200: {
+      description: '成功获取活动列表',
+      content: {
+        'application/json': { schema: CampaignListResponse }
+      }
+    },
+    400: {
+      description: '请求参数错误',
+      content: {
+        'application/json': { schema: ErrorSchema }
+      }
+    },
+    500: {
+      description: '服务器内部错误',
+      content: {
+        'application/json': { schema: ErrorSchema }
+      }
+    }
+  },
+  tags: ['campaigns']
+});
+
+// 创建路由处理
+const app = new OpenAPIHono<AuthContext>().openapi(routeDef, async (c): Promise<Response> => {
+  try {
+    const query = c.req.valid('query');
+    
+    // 转换日期格式
+    const params = {
+      ...query,
+      startDate: query.startDate ? new Date(query.startDate) : undefined,
+      endDate: query.endDate ? new Date(query.endDate) : undefined
+    };
+    
+    const campaignService = new CampaignService(AppDataSource);
+    const { data, total } = await campaignService.findAll(params);
+    
+    logger.api(`获取活动列表成功: 共${total}条记录`);
+    
+    return c.json({
+      code: 200,
+      message: 'success',
+      data,
+      pagination: {
+        total,
+        current: query.page || 1,
+        pageSize: query.pageSize || 10
+      }
+    }, 200);
+  } catch (error) {
+    logger.error(`获取活动列表失败: ${error instanceof Error ? error.message : String(error)}`);
+    
+    return c.json({
+      code: 500,
+      message: error instanceof Error ? error.message : '获取活动列表失败'
+    }, 500);
+  }
+});
+
+export default app;

+ 22 - 0
src/server/api/campaigns/index.ts

@@ -0,0 +1,22 @@
+import { OpenAPIHono } from '@hono/zod-openapi';
+import listRoute from './get';
+import createRoute from './post';
+import getByIdRoute from './[id]/get';
+import updateRoute from './[id]/put';
+import deleteRoute from './[id]/delete';
+import statusRoute from './[id]/status';
+import participantsRoute from './[id]/participants';
+import effectivenessRoute from './[id]/effectiveness';
+import { AuthContext } from '@/server/types/context';
+
+const app = new OpenAPIHono<AuthContext>()
+  .route('/', listRoute)
+  .route('/', createRoute)
+  .route('/', getByIdRoute)
+  .route('/', updateRoute)
+  .route('/', deleteRoute)
+  .route('/', statusRoute)
+  .route('/', participantsRoute)
+  .route('/', effectivenessRoute);
+
+export default app;

+ 87 - 0
src/server/api/campaigns/post.ts

@@ -0,0 +1,87 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from 'zod';
+import { AppDataSource } from '@/server/data-source';
+import { CampaignService } from '@/server/modules/campaigns/campaign.service';
+import { authMiddleware } from '@/server/middleware/auth';
+import { ErrorSchema } from '@/server/utils/errorHandler';
+import { AuthContext } from '@/server/types/context';
+import { CampaignSchema } from '@/server/modules/campaigns/campaign.entity';
+import { logger } from '@/server/utils/logger';
+
+// 定义请求体Schema
+const CreateCampaignBody = CampaignSchema.omit({
+  id: true,
+  createdAt: true,
+  updatedAt: true,
+  isDeleted: true
+});
+
+// 定义响应Schema
+const CreateCampaignResponse = z.object({
+  code: z.number().openapi({ example: 200 }),
+  message: z.string().openapi({ example: '活动创建成功' }),
+  data: CampaignSchema
+});
+
+// 创建路由定义
+const routeDef = createRoute({
+  method: 'post',
+  path: '/',
+  middleware: [authMiddleware],
+  request: {
+    body: {
+      content: {
+        'application/json': { schema: CreateCampaignBody }
+      }
+    }
+  },
+  responses: {
+    200: {
+      description: '成功创建活动',
+      content: {
+        'application/json': { schema: CreateCampaignResponse }
+      }
+    },
+    400: {
+      description: '请求参数错误',
+      content: {
+        'application/json': { schema: ErrorSchema }
+      }
+    },
+    500: {
+      description: '服务器内部错误',
+      content: {
+        'application/json': { schema: ErrorSchema }
+      }
+    }
+  },
+  tags: ['campaigns']
+});
+
+// 创建路由处理
+const app = new OpenAPIHono<AuthContext>().openapi(routeDef, async (c) => {
+  try {
+    const body = c.req.valid('json');
+    
+    const campaignService = new CampaignService(AppDataSource);
+    // 添加类型断言以匹配服务方法的参数要求
+    const newCampaign = await campaignService.create(body as unknown as z.infer<typeof CampaignSchema>);
+    
+    logger.api(`创建活动成功: ID=${newCampaign.id}, 名称=${newCampaign.name}`);
+    
+    return c.json({
+      code: 200,
+      message: '活动创建成功',
+      data: newCampaign
+    }, 200);
+  } catch (error) {
+    logger.error(`创建活动失败: ${error instanceof Error ? error.message : String(error)}`);
+    
+    return c.json({
+      code: 500,
+      message: error instanceof Error ? error.message : '创建活动失败'
+    }, 500);
+  }
+});
+
+export default app;

+ 304 - 0
src/server/modules/campaigns/campaign.service.ts

@@ -0,0 +1,304 @@
+import { DataSource, Repository } from 'typeorm';
+import { Campaign } from './campaign.entity';
+import { logger } from '@/server/utils/logger';
+import { z } from 'zod';
+import { CampaignSchema } from './campaign.entity';
+
+// 定义查询参数接口
+interface CampaignQueryParams {
+  page?: number;
+  pageSize?: number;
+  status?: number;
+  keyword?: string;
+  startDate?: Date;
+  endDate?: Date;
+}
+
+// 定义创建和更新活动的接口
+type CreateCampaignDto = z.infer<typeof CampaignSchema>;
+type UpdateCampaignDto = Partial<CreateCampaignDto>;
+
+// 定义活动状态枚举
+export enum CampaignStatus {
+  NOT_STARTED = 0,
+  IN_PROGRESS = 1,
+  ENDED = 2,
+  CANCELLED = 3
+}
+
+// 定义活动参与人接口
+interface CampaignParticipant {
+  campaignId: number;
+  userId: number;
+  joinedAt: Date;
+}
+
+// 定义活动效果分析结果接口
+interface CampaignEffectiveness {
+  participationRate: number;
+  conversionRate: number;
+  roi: number;
+  totalParticipants: number;
+  totalConversions: number;
+  totalRevenue: number;
+}
+
+export class CampaignService {
+  private campaignRepository: Repository<Campaign>;
+  
+  constructor(private dataSource: DataSource) {
+    this.campaignRepository = this.dataSource.getRepository(Campaign);
+  }
+  
+  /**
+   * 获取活动列表(分页)
+   */
+  async findAll(params: CampaignQueryParams): Promise<{ data: Campaign[], total: number }> {
+    const { page = 1, pageSize = 10, status, keyword, startDate, endDate } = params;
+    
+    const query = this.campaignRepository.createQueryBuilder('campaign')
+      .where('campaign.isDeleted = 0');
+    
+    // 条件筛选
+    if (status !== undefined) {
+      query.andWhere('campaign.status = :status', { status });
+    }
+    
+    if (keyword) {
+      query.andWhere('(campaign.name LIKE :keyword OR campaign.description LIKE :keyword)', { 
+        keyword: `%${keyword}%` 
+      });
+    }
+    
+    if (startDate) {
+      query.andWhere('campaign.startDate >= :startDate', { startDate });
+    }
+    
+    if (endDate) {
+      query.andWhere('campaign.endDate <= :endDate', { endDate });
+    }
+    
+    // 分页
+    query.skip((page - 1) * pageSize)
+         .take(pageSize)
+         .orderBy('campaign.createdAt', 'DESC');
+    
+    const [data, total] = await query.getManyAndCount();
+    
+    logger.api(`获取活动列表: 第${page}页, 共${total}条`);
+    return { data, total };
+  }
+  
+  /**
+   * 获取活动详情
+   */
+  async findOne(id: number): Promise<Campaign | null> {
+    const campaign = await this.campaignRepository.findOne({
+      where: { id, isDeleted: 0 }
+    });
+    
+    if (!campaign) {
+      logger.error(`活动不存在: ID=${id}`);
+      return null;
+    }
+    
+    logger.api(`获取活动详情: ID=${id}`);
+    return campaign;
+  }
+  
+  /**
+   * 创建活动
+   */
+  async create(createCampaignDto: CreateCampaignDto): Promise<Campaign> {
+    // 验证数据
+    const validatedData = CampaignSchema.parse(createCampaignDto);
+    
+    const campaign = this.campaignRepository.create({
+      ...validatedData,
+      createdAt: new Date(),
+      updatedAt: new Date(),
+      isDeleted: 0
+    });
+    
+    const result = await this.campaignRepository.save(campaign);
+    logger.api(`创建活动: ID=${result.id}, 名称=${result.name}`);
+    return result;
+  }
+  
+  /**
+   * 更新活动
+   */
+  async update(id: number, updateCampaignDto: UpdateCampaignDto): Promise<Campaign | null> {
+    const campaign = await this.findOne(id);
+    if (!campaign) {
+      return null;
+    }
+    
+    // 验证数据
+    const validatedData = CampaignSchema.partial().parse(updateCampaignDto);
+    
+    Object.assign(campaign, validatedData);
+    campaign.updatedAt = new Date();
+    
+    const result = await this.campaignRepository.save(campaign);
+    logger.api(`更新活动: ID=${id}, 名称=${result.name}`);
+    return result;
+  }
+  
+  /**
+   * 删除活动(软删除)
+   */
+  async remove(id: number): Promise<boolean> {
+    const campaign = await this.findOne(id);
+    if (!campaign) {
+      return false;
+    }
+    
+    campaign.isDeleted = 1;
+    campaign.updatedAt = new Date();
+    
+    await this.campaignRepository.save(campaign);
+    logger.api(`删除活动: ID=${id}`);
+    return true;
+  }
+  
+  /**
+   * 更新活动状态
+   */
+  async updateStatus(id: number, status: CampaignStatus): Promise<Campaign | null> {
+    const campaign = await this.findOne(id);
+    if (!campaign) {
+      return null;
+    }
+    
+    // 状态变更验证
+    if (!Object.values(CampaignStatus).includes(status)) {
+      logger.error(`无效的活动状态: ${status}`);
+      throw new Error('无效的活动状态');
+    }
+    
+    // 业务逻辑验证:已结束的活动不能再改为进行中
+    if (campaign.status === CampaignStatus.ENDED && status === CampaignStatus.IN_PROGRESS) {
+      logger.error(`活动状态变更失败: 已结束的活动不能再改为进行中, ID=${id}`);
+      throw new Error('已结束的活动不能再改为进行中');
+    }
+    
+    campaign.status = status;
+    campaign.updatedAt = new Date();
+    
+    // 如果活动状态改为已结束,更新结束日期
+    if (status === CampaignStatus.ENDED && !campaign.endDate) {
+      campaign.endDate = new Date();
+    }
+    
+    const result = await this.campaignRepository.save(campaign);
+    logger.api(`更新活动状态: ID=${id}, 状态=${status}`);
+    return result;
+  }
+  
+  /**
+   * 添加活动参与人
+   */
+  async addParticipant(campaignId: number, userId: number): Promise<CampaignParticipant> {
+    const campaign = await this.findOne(campaignId);
+    if (!campaign) {
+      throw new Error('活动不存在');
+    }
+    
+    // 检查活动状态
+    if (campaign.status === CampaignStatus.ENDED || campaign.status === CampaignStatus.CANCELLED) {
+      throw new Error('无法添加参与人到已结束或已取消的活动');
+    }
+    
+    // 检查用户是否已参与
+    // 使用类型断言处理类型问题
+    const participantRepository = this.dataSource.getRepository('campaign_participant') as unknown as Repository<CampaignParticipant>;
+    const existingParticipant = await participantRepository.findOne({
+      where: { campaignId, userId }
+    });
+    
+    if (existingParticipant) {
+      throw new Error('用户已参与该活动');
+    }
+    
+    // 添加参与人
+    const participant = participantRepository.create({
+      campaignId,
+      userId,
+      joinedAt: new Date()
+    });
+    
+    const result = await participantRepository.save(participant);
+    logger.api(`添加活动参与人: 活动ID=${campaignId}, 用户ID=${userId}`);
+    return result;
+  }
+  
+  /**
+   * 获取活动参与人列表
+   */
+  async getParticipants(campaignId: number): Promise<CampaignParticipant[]> {
+    const campaign = await this.findOne(campaignId);
+    if (!campaign) {
+      throw new Error('活动不存在');
+    }
+    
+    // 使用类型断言处理类型问题
+    const participantRepository = this.dataSource.getRepository('campaign_participant') as unknown as Repository<CampaignParticipant>;
+    const participants = await participantRepository.find({
+      where: { campaignId }
+    });
+    
+    logger.api(`获取活动参与人列表: 活动ID=${campaignId}, 参与人数=${participants.length}`);
+    return participants;
+  }
+  
+  /**
+   * 分析活动效果
+   */
+  async analyzeEffectiveness(campaignId: number): Promise<CampaignEffectiveness> {
+    const campaign = await this.findOne(campaignId);
+    if (!campaign) {
+      throw new Error('活动不存在');
+    }
+    
+    // 获取参与人数
+    const participantRepository = this.dataSource.getRepository('campaign_participant');
+    const totalParticipants = await participantRepository.count({
+      where: { campaignId }
+    });
+    
+    // 获取转化人数(假设有一个conversions表记录转化数据)
+    const conversionRepository = this.dataSource.getRepository('campaign_conversion');
+    const totalConversions = await conversionRepository.count({
+      where: { campaignId }
+    });
+    
+    // 获取总收入(假设每个转化有一个revenue字段)
+    const conversionSum = await conversionRepository
+      .createQueryBuilder('conversion')
+      .select('SUM(conversion.revenue)', 'totalRevenue')
+      .where('conversion.campaignId = :campaignId', { campaignId })
+      .getRawOne();
+    
+    const totalRevenue = conversionSum.totalRevenue || 0;
+    
+    // 计算参与率、转化率和ROI
+    const participationRate = totalParticipants > 0 ? (totalParticipants / 100) : 0; // 假设目标受众为100人,实际应从活动设置中获取
+    const conversionRate = totalParticipants > 0 ? (totalConversions / totalParticipants) : 0;
+    // 处理budget可能为null的情况
+    const budget = campaign.budget || 0;
+    const roi = budget > 0 ? (totalRevenue - budget) / budget : 0;
+    
+    const effectiveness: CampaignEffectiveness = {
+      participationRate,
+      conversionRate,
+      roi,
+      totalParticipants,
+      totalConversions,
+      totalRevenue
+    };
+    
+    logger.api(`分析活动效果: 活动ID=${campaignId}, ROI=${roi}`);
+    return effectiveness;
+  }
+}