ReportService.cs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. using Serilog;
  2. using YZWater.Core.Models;
  3. namespace YZWater.Core.Services;
  4. /// <summary>
  5. /// 报表服务 - 生成日报/月报
  6. /// </summary>
  7. public static class ReportService
  8. {
  9. /// <summary>
  10. /// 生成日报(HTML 格式)
  11. /// </summary>
  12. public static async Task<string> GenerateDailyReportAsync(DateTime date)
  13. {
  14. try
  15. {
  16. var start = date.Date;
  17. var end = start.AddDays(1);
  18. // 查询流量数据
  19. var flowRecords = await DatabaseService.Db.Queryable<FlowRecord>()
  20. .Where(r => r.RecordTime >= start && r.RecordTime < end)
  21. .OrderBy(r => r.RecordTime)
  22. .ToListAsync();
  23. // 查询报警数据
  24. var alarmRecords = await DatabaseService.Db.Queryable<AlarmRecord>()
  25. .Where(r => r.AlarmTime >= start && r.AlarmTime < end)
  26. .OrderBy(r => r.AlarmTime)
  27. .ToListAsync();
  28. // 计算统计
  29. var avgInflow = flowRecords.Count > 0 ? flowRecords.Average(r => r.InflowRate) : 0;
  30. var avgOutflow = flowRecords.Count > 0 ? flowRecords.Average(r => r.OutflowRate) : 0;
  31. var maxInflow = flowRecords.Count > 0 ? flowRecords.Max(r => r.InflowRate) : 0;
  32. var maxOutflow = flowRecords.Count > 0 ? flowRecords.Max(r => r.OutflowRate) : 0;
  33. var totalInflow = flowRecords.Count > 0 ? flowRecords.Last().TotalInflow : 0;
  34. var totalOutflow = flowRecords.Count > 0 ? flowRecords.Last().TotalOutflow : 0;
  35. var config = ConfigService.GetConfig();
  36. var companyName = config?.CompanyName ?? "污水处理厂";
  37. var html = $@"<!DOCTYPE html>
  38. <html>
  39. <head>
  40. <meta charset='utf-8'>
  41. <title>日报 - {date:yyyy-MM-dd}</title>
  42. <style>
  43. body {{ font-family: 'Microsoft YaHei', sans-serif; margin: 20px; }}
  44. h1 {{ color: #1976D2; border-bottom: 2px solid #1976D2; padding-bottom: 8px; }}
  45. h2 {{ color: #424242; margin-top: 24px; }}
  46. table {{ border-collapse: collapse; width: 100%; margin: 12px 0; }}
  47. th, td {{ border: 1px solid #ddd; padding: 8px 12px; text-align: left; }}
  48. th {{ background: #f5f5f5; }}
  49. .summary {{ background: #E3F2FD; padding: 16px; border-radius: 8px; margin: 16px 0; }}
  50. .alarm {{ color: #E53935; }}
  51. .footer {{ color: #999; font-size: 12px; margin-top: 32px; border-top: 1px solid #eee; padding-top: 8px; }}
  52. </style>
  53. </head>
  54. <body>
  55. <h1>{companyName} - 日报</h1>
  56. <p>日期: {date:yyyy年MM月dd日}</p>
  57. <h2>流量统计</h2>
  58. <div class='summary'>
  59. <p><b>累计进水量:</b> {totalInflow:F1} m³ | <b>累计出水量:</b> {totalOutflow:F1} m³</p>
  60. <p><b>平均进水流量:</b> {avgInflow:F1} m³/h | <b>平均出水流量:</b> {avgOutflow:F1} m³/h</p>
  61. <p><b>最大进水流量:</b> {maxInflow:F1} m³/h | <b>最大出水流量:</b> {maxOutflow:F1} m³/h</p>
  62. <p><b>记录数:</b> {flowRecords.Count} 条</p>
  63. </div>
  64. <h2>报警统计</h2>
  65. <div class='summary'>
  66. <p><b>报警总数:</b> {alarmRecords.Count} 条</p>
  67. <p><b>未确认:</b> {alarmRecords.Count(r => !r.IsConfirmed)} 条</p>
  68. <p><b>已确认:</b> {alarmRecords.Count(r => r.IsConfirmed)} 条</p>
  69. </div>
  70. {(alarmRecords.Count > 0 ? $@"
  71. <h2>报警详情</h2>
  72. <table>
  73. <tr><th>时间</th><th>类型</th><th>内容</th><th>级别</th><th>状态</th></tr>
  74. {string.Join("", alarmRecords.Take(50).Select(a => $@"
  75. <tr>
  76. <td>{a.AlarmTime:HH:mm:ss}</td>
  77. <td>{a.AlarmType}</td>
  78. <td>{a.AlarmMessage}</td>
  79. <td>{a.AlarmLevel}</td>
  80. <td>{(a.IsConfirmed ? "已确认" : "<span class='alarm'>未确认</span>")}</td>
  81. </tr>"))}
  82. </table>" : "")}
  83. <div class='footer'>
  84. <p>报表生成时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss} | YZWater 污水处理监控系统</p>
  85. </div>
  86. </body>
  87. </html>";
  88. // 保存文件
  89. var reportsDir = "Reports";
  90. Directory.CreateDirectory(reportsDir);
  91. var filePath = Path.Combine(reportsDir, $"日报_{date:yyyyMMdd}.html");
  92. await File.WriteAllTextAsync(filePath, html);
  93. Log.Information("日报已生成: {Path}", filePath);
  94. AuditService.Log("系统", "Report", $"生成日报: {filePath}");
  95. return filePath;
  96. }
  97. catch (Exception ex)
  98. {
  99. Log.Error(ex, "生成日报失败");
  100. return string.Empty;
  101. }
  102. }
  103. /// <summary>
  104. /// 生成月报
  105. /// </summary>
  106. public static async Task<string> GenerateMonthlyReportAsync(int year, int month)
  107. {
  108. try
  109. {
  110. var start = new DateTime(year, month, 1);
  111. var end = start.AddMonths(1);
  112. var flowRecords = await DatabaseService.Db.Queryable<FlowRecord>()
  113. .Where(r => r.RecordTime >= start && r.RecordTime < end)
  114. .ToListAsync();
  115. var alarmRecords = await DatabaseService.Db.Queryable<AlarmRecord>()
  116. .Where(r => r.AlarmTime >= start && r.AlarmTime < end)
  117. .ToListAsync();
  118. var config = ConfigService.GetConfig();
  119. var companyName = config?.CompanyName ?? "污水处理厂";
  120. var avgInflow = flowRecords.Count > 0 ? flowRecords.Average(r => r.InflowRate) : 0;
  121. var avgOutflow = flowRecords.Count > 0 ? flowRecords.Average(r => r.OutflowRate) : 0;
  122. var totalInflow = flowRecords.Count > 0 ? flowRecords.Last().TotalInflow : 0;
  123. var totalOutflow = flowRecords.Count > 0 ? flowRecords.Last().TotalOutflow : 0;
  124. // 按日统计
  125. var dailyStats = flowRecords
  126. .GroupBy(r => r.RecordTime.Date)
  127. .Select(g => new
  128. {
  129. Date = g.Key,
  130. AvgInflow = g.Average(r => r.InflowRate),
  131. AvgOutflow = g.Average(r => r.OutflowRate),
  132. Count = g.Count()
  133. })
  134. .OrderBy(d => d.Date)
  135. .ToList();
  136. var html = $@"<!DOCTYPE html>
  137. <html>
  138. <head>
  139. <meta charset='utf-8'>
  140. <title>月报 - {year}年{month}月</title>
  141. <style>
  142. body {{ font-family: 'Microsoft YaHei', sans-serif; margin: 20px; }}
  143. h1 {{ color: #1976D2; border-bottom: 2px solid #1976D2; padding-bottom: 8px; }}
  144. h2 {{ color: #424242; margin-top: 24px; }}
  145. table {{ border-collapse: collapse; width: 100%; margin: 12px 0; }}
  146. th, td {{ border: 1px solid #ddd; padding: 8px 12px; text-align: left; }}
  147. th {{ background: #f5f5f5; }}
  148. .summary {{ background: #E3F2FD; padding: 16px; border-radius: 8px; margin: 16px 0; }}
  149. .footer {{ color: #999; font-size: 12px; margin-top: 32px; border-top: 1px solid #eee; padding-top: 8px; }}
  150. </style>
  151. </head>
  152. <body>
  153. <h1>{companyName} - 月报</h1>
  154. <p>期间: {year}年{month}月</p>
  155. <h2>汇总统计</h2>
  156. <div class='summary'>
  157. <p><b>累计进水量:</b> {totalInflow:F1} m³ | <b>累计出水量:</b> {totalOutflow:F1} m³</p>
  158. <p><b>平均进水流量:</b> {avgInflow:F1} m³/h | <b>平均出水流量:</b> {avgOutflow:F1} m³/h</p>
  159. <p><b>报警总数:</b> {alarmRecords.Count} 条</p>
  160. <p><b>有效记录天数:</b> {dailyStats.Count} 天</p>
  161. </div>
  162. <h2>每日统计</h2>
  163. <table>
  164. <tr><th>日期</th><th>平均进水 (m³/h)</th><th>平均出水 (m³/h)</th><th>记录数</th></tr>
  165. {string.Join("", dailyStats.Select(d => $@"
  166. <tr>
  167. <td>{d.Date:MM-dd}</td>
  168. <td>{d.AvgInflow:F1}</td>
  169. <td>{d.AvgOutflow:F1}</td>
  170. <td>{d.Count}</td>
  171. </tr>"))}
  172. </table>
  173. <div class='footer'>
  174. <p>报表生成时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss} | YZWater 污水处理监控系统</p>
  175. </div>
  176. </body>
  177. </html>";
  178. var reportsDir = "Reports";
  179. Directory.CreateDirectory(reportsDir);
  180. var filePath = Path.Combine(reportsDir, $"月报_{year}{month:D2}.html");
  181. await File.WriteAllTextAsync(filePath, html);
  182. Log.Information("月报已生成: {Path}", filePath);
  183. AuditService.Log("系统", "Report", $"生成月报: {filePath}");
  184. return filePath;
  185. }
  186. catch (Exception ex)
  187. {
  188. Log.Error(ex, "生成月报失败");
  189. return string.Empty;
  190. }
  191. }
  192. }