index.ts 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. import { z } from '@hono/zod-openapi';
  2. import { ErrorSchema } from '@d8d/shared-utils';
  3. // 时间筛选参数Schema
  4. export const TimeFilterSchema = z.object({
  5. startDate: z.string().datetime({ offset: true }).optional().openapi({
  6. description: '开始时间 (ISO 8601格式,例如: 2025-01-01T00:00:00Z)',
  7. example: '2025-01-01T00:00:00Z'
  8. }),
  9. endDate: z.string().datetime({ offset: true }).optional().openapi({
  10. description: '结束时间 (ISO 8601格式,例如: 2025-01-31T23:59:59Z)',
  11. example: '2025-01-31T23:59:59Z'
  12. }),
  13. timeRange: z.enum(['today', 'yesterday', 'last7days', 'last30days', 'thisYear', 'lastYear', 'custom']).optional().openapi({
  14. description: '时间范围筛选 (今日、昨日、最近7天、最近30天、今年、去年、自定义)',
  15. example: 'today'
  16. }),
  17. year: z.preprocess(
  18. (val) => val === undefined ? undefined : Number(val),
  19. z.number().int().min(2000).max(2100)
  20. ).optional().openapi({
  21. description: '特定年份统计 (例如: 2024, 2025),提供此参数时将忽略timeRange',
  22. example: 2025
  23. }),
  24. forceRefresh: z.preprocess(
  25. (val) => val === undefined ? undefined : val === 'true' || val === true,
  26. z.boolean()
  27. ).optional().openapi({
  28. description: '强制刷新,跳过缓存直接读取数据库',
  29. example: false
  30. })
  31. }).refine((data) => {
  32. // 如果提供了timeRange为custom,则必须提供startDate和endDate
  33. if (data.timeRange === 'custom') {
  34. return !!(data.startDate && data.endDate);
  35. }
  36. return true;
  37. }, {
  38. message: '当timeRange为custom时,startDate和endDate必须提供',
  39. path: ['timeRange']
  40. }).refine((data) => {
  41. // 如果提供了startDate和endDate,确保startDate <= endDate
  42. if (data.startDate && data.endDate) {
  43. return new Date(data.startDate) <= new Date(data.endDate);
  44. }
  45. return true;
  46. }, {
  47. message: 'startDate不能晚于endDate',
  48. path: ['startDate']
  49. });
  50. // 数据概览统计响应Schema
  51. export const SummaryStatisticsSchema = z.object({
  52. totalSales: z.number().openapi({
  53. description: '总销售额',
  54. example: 150000.50
  55. }),
  56. totalOrders: z.number().int().openapi({
  57. description: '总订单数',
  58. example: 120
  59. }),
  60. wechatSales: z.number().openapi({
  61. description: '微信支付总金额',
  62. example: 100000.00
  63. }),
  64. wechatOrders: z.number().int().openapi({
  65. description: '微信支付订单数',
  66. example: 80
  67. }),
  68. creditSales: z.number().openapi({
  69. description: '额度支付总金额',
  70. example: 50000.50
  71. }),
  72. creditOrders: z.number().int().openapi({
  73. description: '额度支付订单数',
  74. example: 40
  75. })
  76. });
  77. // 今日数据响应Schema
  78. export const TodayStatisticsSchema = z.object({
  79. todaySales: z.number().openapi({
  80. description: '今日销售额',
  81. example: 5000.00
  82. }),
  83. todayOrders: z.number().int().openapi({
  84. description: '今日订单数',
  85. example: 10
  86. })
  87. });
  88. // 统一响应Schema
  89. export const SummaryResponseSchema = z.object({
  90. data: SummaryStatisticsSchema,
  91. success: z.boolean().openapi({
  92. description: '请求是否成功',
  93. example: true
  94. }),
  95. message: z.string().optional().openapi({
  96. description: '响应消息',
  97. example: '请求成功'
  98. })
  99. });
  100. export const TodayResponseSchema = z.object({
  101. data: TodayStatisticsSchema,
  102. success: z.boolean().openapi({
  103. description: '请求是否成功',
  104. example: true
  105. }),
  106. message: z.string().optional().openapi({
  107. description: '响应消息',
  108. example: '请求成功'
  109. })
  110. });
  111. // 用户消费统计相关Schema
  112. export const PaginationParamsSchema = z.object({
  113. page: z.preprocess(
  114. (val) => val === undefined ? 1 : Number(val),
  115. z.number().int().positive()
  116. ).default(1).openapi({
  117. description: '页码,从1开始',
  118. example: 1
  119. }),
  120. limit: z.preprocess(
  121. (val) => val === undefined ? undefined : Number(val),
  122. z.number().int().positive().max(100).optional().default(10)
  123. ).openapi({
  124. description: '每页数量,最大100',
  125. example: 10
  126. }),
  127. sortBy: z.enum(['totalSpent', 'orderCount', 'avgOrderAmount', 'lastOrderDate']).optional().default('totalSpent').openapi({
  128. description: '排序字段',
  129. example: 'totalSpent'
  130. }),
  131. sortOrder: z.enum(['asc', 'desc']).optional().default('desc').openapi({
  132. description: '排序方向',
  133. example: 'desc'
  134. }),
  135. forceRefresh: z.preprocess(
  136. (val) => val === undefined ? undefined : val === 'true' || val === true,
  137. z.boolean()
  138. ).optional().openapi({
  139. description: '强制刷新,跳过缓存直接读取数据库',
  140. example: false
  141. })
  142. });
  143. export const UserConsumptionItemSchema = z.object({
  144. userId: z.number().int().openapi({
  145. description: '用户ID',
  146. example: 12345
  147. }),
  148. userName: z.string().optional().openapi({
  149. description: '用户名',
  150. example: '张三'
  151. }),
  152. userPhone: z.string().optional().openapi({
  153. description: '用户手机号',
  154. example: '13800138000'
  155. }),
  156. totalSpent: z.number().openapi({
  157. description: '累计消费金额',
  158. example: 15000.50
  159. }),
  160. orderCount: z.number().int().openapi({
  161. description: '订单数量',
  162. example: 15
  163. }),
  164. avgOrderAmount: z.number().openapi({
  165. description: '平均订单金额',
  166. example: 1000.03
  167. }),
  168. lastOrderDate: z.string().datetime({ offset: true }).optional().openapi({
  169. description: '最后下单时间',
  170. example: '2025-12-30T10:30:00Z'
  171. })
  172. });
  173. export const UserConsumptionResponseSchema = z.object({
  174. items: z.array(UserConsumptionItemSchema).openapi({
  175. description: '用户消费统计列表'
  176. }),
  177. pagination: z.object({
  178. page: z.number().int().openapi({
  179. description: '当前页码',
  180. example: 1
  181. }),
  182. limit: z.number().int().openapi({
  183. description: '每页数量',
  184. example: 10
  185. }),
  186. total: z.number().int().openapi({
  187. description: '总记录数',
  188. example: 100
  189. }),
  190. totalPages: z.number().int().openapi({
  191. description: '总页数',
  192. example: 10
  193. })
  194. }).openapi({
  195. description: '分页信息'
  196. })
  197. });
  198. // 用户消费统计查询参数Schema(组合时间筛选和分页参数)
  199. export const UserConsumptionQuerySchema = TimeFilterSchema.merge(PaginationParamsSchema);
  200. // 统一响应Schema
  201. export const UserConsumptionApiResponseSchema = z.object({
  202. data: UserConsumptionResponseSchema,
  203. success: z.boolean().openapi({
  204. description: '请求是否成功',
  205. example: true
  206. }),
  207. message: z.string().optional().openapi({
  208. description: '响应消息',
  209. example: '获取用户消费统计成功'
  210. })
  211. });
  212. // 今日数据查询参数Schema
  213. export const TodayQuerySchema = z.object({
  214. forceRefresh: z.preprocess(
  215. (val) => val === undefined ? undefined : val === 'true' || val === true,
  216. z.boolean()
  217. ).optional().openapi({
  218. description: '强制刷新,跳过缓存直接读取数据库',
  219. example: false
  220. })
  221. });
  222. // 导出错误Schema
  223. export { ErrorSchema };