import { z } from '@hono/zod-openapi'; import { ErrorSchema } from '@d8d/shared-utils'; // 时间筛选参数Schema export const TimeFilterSchema = z.object({ startDate: z.string().datetime({ offset: true }).optional().openapi({ description: '开始时间 (ISO 8601格式,例如: 2025-01-01T00:00:00Z)', example: '2025-01-01T00:00:00Z' }), endDate: z.string().datetime({ offset: true }).optional().openapi({ description: '结束时间 (ISO 8601格式,例如: 2025-01-31T23:59:59Z)', example: '2025-01-31T23:59:59Z' }), timeRange: z.enum(['today', 'yesterday', 'last7days', 'last30days', 'thisYear', 'lastYear', 'custom']).optional().openapi({ description: '时间范围筛选 (今日、昨日、最近7天、最近30天、今年、去年、自定义)', example: 'today' }), year: z.preprocess( (val) => val === undefined ? undefined : Number(val), z.number().int().min(2000).max(2100) ).optional().openapi({ description: '特定年份统计 (例如: 2024, 2025),提供此参数时将忽略timeRange', example: 2025 }), forceRefresh: z.preprocess( (val) => val === undefined ? undefined : val === 'true' || val === true, z.boolean() ).optional().openapi({ description: '强制刷新,跳过缓存直接读取数据库', example: false }) }).refine((data) => { // 如果提供了timeRange为custom,则必须提供startDate和endDate if (data.timeRange === 'custom') { return !!(data.startDate && data.endDate); } return true; }, { message: '当timeRange为custom时,startDate和endDate必须提供', path: ['timeRange'] }).refine((data) => { // 如果提供了startDate和endDate,确保startDate <= endDate if (data.startDate && data.endDate) { return new Date(data.startDate) <= new Date(data.endDate); } return true; }, { message: 'startDate不能晚于endDate', path: ['startDate'] }); // 数据概览统计响应Schema export const SummaryStatisticsSchema = z.object({ totalSales: z.number().openapi({ description: '总销售额', example: 150000.50 }), totalOrders: z.number().int().openapi({ description: '总订单数', example: 120 }), wechatSales: z.number().openapi({ description: '微信支付总金额', example: 100000.00 }), wechatOrders: z.number().int().openapi({ description: '微信支付订单数', example: 80 }), creditSales: z.number().openapi({ description: '额度支付总金额', example: 50000.50 }), creditOrders: z.number().int().openapi({ description: '额度支付订单数', example: 40 }) }); // 今日数据响应Schema export const TodayStatisticsSchema = z.object({ todaySales: z.number().openapi({ description: '今日销售额', example: 5000.00 }), todayOrders: z.number().int().openapi({ description: '今日订单数', example: 10 }) }); // 统一响应Schema export const SummaryResponseSchema = z.object({ data: SummaryStatisticsSchema, success: z.boolean().openapi({ description: '请求是否成功', example: true }), message: z.string().optional().openapi({ description: '响应消息', example: '请求成功' }) }); export const TodayResponseSchema = z.object({ data: TodayStatisticsSchema, success: z.boolean().openapi({ description: '请求是否成功', example: true }), message: z.string().optional().openapi({ description: '响应消息', example: '请求成功' }) }); // 用户消费统计相关Schema export const PaginationParamsSchema = z.object({ page: z.preprocess( (val) => val === undefined ? 1 : Number(val), z.number().int().positive() ).default(1).openapi({ description: '页码,从1开始', example: 1 }), limit: z.preprocess( (val) => val === undefined ? undefined : Number(val), z.number().int().positive().max(100).optional().default(10) ).openapi({ description: '每页数量,最大100', example: 10 }), sortBy: z.enum(['totalSpent', 'orderCount', 'avgOrderAmount', 'lastOrderDate']).optional().default('totalSpent').openapi({ description: '排序字段', example: 'totalSpent' }), sortOrder: z.enum(['asc', 'desc']).optional().default('desc').openapi({ description: '排序方向', example: 'desc' }), forceRefresh: z.preprocess( (val) => val === undefined ? undefined : val === 'true' || val === true, z.boolean() ).optional().openapi({ description: '强制刷新,跳过缓存直接读取数据库', example: false }) }); export const UserConsumptionItemSchema = z.object({ userId: z.number().int().openapi({ description: '用户ID', example: 12345 }), userName: z.string().optional().openapi({ description: '用户名', example: '张三' }), userPhone: z.string().optional().openapi({ description: '用户手机号', example: '13800138000' }), totalSpent: z.number().openapi({ description: '累计消费金额', example: 15000.50 }), orderCount: z.number().int().openapi({ description: '订单数量', example: 15 }), avgOrderAmount: z.number().openapi({ description: '平均订单金额', example: 1000.03 }), lastOrderDate: z.string().datetime({ offset: true }).optional().openapi({ description: '最后下单时间', example: '2025-12-30T10:30:00Z' }) }); export const UserConsumptionResponseSchema = z.object({ items: z.array(UserConsumptionItemSchema).openapi({ description: '用户消费统计列表' }), pagination: z.object({ page: z.number().int().openapi({ description: '当前页码', example: 1 }), limit: z.number().int().openapi({ description: '每页数量', example: 10 }), total: z.number().int().openapi({ description: '总记录数', example: 100 }), totalPages: z.number().int().openapi({ description: '总页数', example: 10 }) }).openapi({ description: '分页信息' }) }); // 用户消费统计查询参数Schema(组合时间筛选和分页参数) export const UserConsumptionQuerySchema = TimeFilterSchema.merge(PaginationParamsSchema); // 统一响应Schema export const UserConsumptionApiResponseSchema = z.object({ data: UserConsumptionResponseSchema, success: z.boolean().openapi({ description: '请求是否成功', example: true }), message: z.string().optional().openapi({ description: '响应消息', example: '获取用户消费统计成功' }) }); // 今日数据查询参数Schema export const TodayQuerySchema = z.object({ forceRefresh: z.preprocess( (val) => val === undefined ? undefined : val === 'true' || val === true, z.boolean() ).optional().openapi({ description: '强制刷新,跳过缓存直接读取数据库', example: false }) }); // 导出错误Schema export { ErrorSchema };