瀏覽代碼

no message

DFS_Shuo_Chen 1 月之前
父節點
當前提交
44e8c790c5

+ 1 - 0
EasyTemplate.Blazor.Web/Common/TheRegister.cs

@@ -187,6 +187,7 @@ public static class TheRegister
         builder.Services.AddHostedService<HandleDataService>();
 
         builder.Services.AddScoped<NozzleService>();
+        builder.Services.AddScoped<WarningService>();
 
         // builder.Services.AddSingleton<DB>();
 

+ 176 - 0
EasyTemplate.Page/Pages/Warning_day.razor

@@ -0,0 +1,176 @@
+@page "/warning_day"
+@using EasyTemplate.Tool.Entity.App
+@using EasyTemplate.Service
+@inject WarningService WarningService
+@inject NavigationManager Navigation
+
+<PageTitle>预警告统计</PageTitle>
+
+<div class="container-fluid">
+    <div class="card">
+        <div class="card-header">
+            <h5 class="mb-0"><i class="fas fa-chart-line"></i> 预警告统计(35 天累计)</h5>
+        </div>
+        <div class="card-body">
+            @* 筛选条件 *@
+            <div class="row mb-3">
+                <div class="col-md-3">
+                    <label class="form-label">油枪 ID</label>
+                    <AntDesign.InputNumber Placeholder="请输入油枪 ID" @bind-Value="@selectedNozzleId" Style="width: 100%" />
+                </div>
+                <div class="col-md-3">
+                    <label class="form-label">开始日期</label>
+                    <DatePicker @bind-Value="@startDate" Format="yyyy-MM-dd" Style="width: 100%" />
+                </div>
+                <div class="col-md-3">
+                    <label class="form-label">结束日期</label>
+                    <DatePicker @bind-Value="@endDate" Format="yyyy-MM-dd" Style="width: 100%" />
+                </div>
+                <div class="col-md-3 d-flex align-items-end">
+                    <button class="btn btn-primary me-2" @onclick="LoadDataAsync">
+                        <i class="fas fa-search"></i> 查询
+                    </button>
+                    <button class="btn btn-secondary" @onclick="ResetFilter">
+                        <i class="fas fa-redo"></i> 重置
+                    </button>
+                </div>
+            </div>
+
+            @* 统计信息 *@
+            @if (statisticsList.Count > 0)
+            {
+                <div class="alert alert-info">
+                    <i class="fas fa-info-circle"></i> 
+                    共查询到 @statisticsList.Count 条记录,统计范围:@startDate?.ToString("yyyy-MM-dd") 至 @endDate?.ToString("yyyy-MM-dd")
+                </div>
+            }
+
+            @* 数据表格 *@
+            <Spin Spinning="@loading">
+                <Table TItem="WarningStatistics" DataSource="@statisticsList" ScrollY="600px" Bordered="true" Size="TableSize.Small">
+                    <ColumnDefinitions Context="row">
+                        <PropertyColumn TItem="WarningStatistics" TProp="int" Align="ColumnAlign.Center" Property="c=>c.nozzle" Title="油枪 ID" Width="80" />
+                        <PropertyColumn TItem="WarningStatistics" TProp="DateOnly" Align="ColumnAlign.Center" Property="c=>c.statistics_date" Title="统计日期" Width="100">
+                            @{ var statDate = row.statistics_date.ToString("yyyy-MM-dd"); }
+                            @statDate
+                        </PropertyColumn>
+                        <PropertyColumn TItem="WarningStatistics" TProp="int" Align="ColumnAlign.Center" Property="c=>c.daily_total" Title="当天笔数" Width="80" />
+                        <PropertyColumn TItem="WarningStatistics" TProp="int" Align="ColumnAlign.Center" Property="c=>c.daily_overproof" Title="当天超标" Width="80" />
+                        <PropertyColumn TItem="WarningStatistics" TProp="int" Align="ColumnAlign.Center" Title="当天超标率" Width="90">
+                            @{ var dailyRate = row.daily_overproofrate; }
+                            @dailyRate%
+                        </PropertyColumn>
+                        <PropertyColumn TItem="WarningStatistics" TProp="int" Align="ColumnAlign.Center" Property="c=>c.daily_overproof_alert" Title="当天严重超标" Width="90" />
+                        <PropertyColumn TItem="WarningStatistics" TProp="int" Align="ColumnAlign.Center" Title="当天严重超标率" Width="100">
+                            @{ var dailyAlertRate = row.daily_overproofrate_alert; }
+                            @dailyAlertRate%
+                        </PropertyColumn>
+                        <PropertyColumn TItem="WarningStatistics" TProp="int" Align="ColumnAlign.Center" Property="c=>c.daily_overproof_2" Title="当天二级超标" Width="90" />
+                        <PropertyColumn TItem="WarningStatistics" TProp="int" Align="ColumnAlign.Center" Title="当天二级超标率" Width="100">
+                            @{ var dailyRate2 = row.daily_overproofrate_2; }
+                            @dailyRate2%
+                        </PropertyColumn>
+                        
+                        <PropertyColumn TItem="WarningStatistics" TProp="int" Align="ColumnAlign.Center" Property="c=>c.total_count" Title="累计笔数 (35 天)" Width="120">
+                            @{ 
+                                var isAccumulated = row.is_accumulated;
+                                var totalCount = row.total_count;
+                            }
+                            <span class="@(isAccumulated ? "text-success fw-bold" : "text-warning")">
+                                @totalCount
+                                @if (!isAccumulated)
+                                {
+                                    <small class="text-muted">(未满 5 笔)</small>
+                                }
+                            </span>
+                        </PropertyColumn>
+                        <PropertyColumn TItem="WarningStatistics" TProp="int" Align="ColumnAlign.Center" Property="c=>c.accumulated_days" Title="累计天数" Width="80">
+                            @{ var accDays = row.accumulated_days; }
+                            @accDays 天
+                        </PropertyColumn>
+                        <PropertyColumn TItem="WarningStatistics" TProp="int" Align="ColumnAlign.Center" Property="c=>c.total_overproof" Title="累计超标数" Width="80" />
+                        <PropertyColumn TItem="WarningStatistics" TProp="int" Align="ColumnAlign.Center" Title="累计超标率" Width="90">
+                            @{ var totalRate = row.total_overproofrate; }
+                            <span class="fw-bold">@totalRate%</span>
+                        </PropertyColumn>
+                        <PropertyColumn TItem="WarningStatistics" TProp="int" Align="ColumnAlign.Center" Property="c=>c.total_overproof_alert" Title="累计严重超标" Width="90" />
+                        <PropertyColumn TItem="WarningStatistics" TProp="int" Align="ColumnAlign.Center" Title="累计严重超标率" Width="100">
+                            @{ var totalAlertRate = row.total_overproofrate_alert; }
+                            @totalAlertRate%
+                        </PropertyColumn>
+                        <PropertyColumn TItem="WarningStatistics" TProp="int" Align="ColumnAlign.Center" Property="c=>c.total_overproof_2" Title="累计二级超标" Width="90" />
+                        <PropertyColumn TItem="WarningStatistics" TProp="int" Align="ColumnAlign.Center" Title="累计二级超标率" Width="100">
+                            @{ var totalRate2 = row.total_overproofrate_2; }
+                            @totalRate2%
+                        </PropertyColumn>
+                    </ColumnDefinitions>
+                </Table>
+            </Spin>
+        </div>
+    </div>
+</div>
+
+@code {
+    private List<WarningStatistics> statisticsList = new();
+    private bool loading = false;
+    
+    private int? selectedNozzleId;
+    private DateOnly? startDate;
+    private DateOnly? endDate;
+
+    protected override async Task OnInitializedAsync()
+    {
+        // 初始化默认日期范围为最近 35 天
+        endDate = DateOnly.FromDateTime(DateTime.Today);
+        startDate = endDate?.AddDays(-34);
+        
+        await LoadDataAsync();
+    }
+
+    private void ResetFilter()
+    {
+        selectedNozzleId = null;
+        endDate = DateOnly.FromDateTime(DateTime.Today);
+        startDate = endDate?.AddDays(-34);
+    }
+
+    private async Task LoadDataAsync()
+    {
+        if (startDate == null || endDate == null)
+        {
+            return;
+        }
+
+        loading = true;
+        StateHasChanged();
+
+        try
+        {
+            var query = new WarningStatisticsQuery
+            {
+                NozzleId = selectedNozzleId,
+                StartDate = startDate,
+                EndDate = endDate,
+                PageIndex = 1,
+                PageSize = 1000
+            };
+
+            statisticsList = await WarningService.GetStatisticsByFilterAsync(query);
+            
+            // 按日期和油枪排序
+            statisticsList = statisticsList
+                .OrderBy(s => s.nozzle)
+                .ThenByDescending(s => s.statistics_date)
+                .ToList();
+        }
+        catch (Exception ex)
+        {
+            Console.WriteLine($"加载统计数据失败:{ex.Message}");
+        }
+        finally
+        {
+            loading = false;
+            StateHasChanged();
+        }
+    }
+}

+ 255 - 0
EasyTemplate.Service/WarningService.cs

@@ -0,0 +1,255 @@
+using EasyTemplate.Tool;
+using EasyTemplate.Tool.Entity;
+using EasyTemplate.Tool.Entity.App;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace EasyTemplate.Service
+{
+    /// <summary>
+    /// 预警告统计服务
+    /// </summary>
+    public class WarningService
+    {
+        private readonly SqlSugarRepository<TPrewarning> _prewarning;
+        private readonly SqlSugarRepository<TRecord> _record;
+
+        public WarningService(SqlSugarRepository<TPrewarning> prewarning, SqlSugarRepository<TRecord> record)
+        {
+            _prewarning = prewarning;
+            _record = record;
+        }
+
+        /// <summary>
+        /// 获取 35 天内所有油枪的统计数据
+        /// </summary>
+        public async Task<List<WarningStatistics>> Get35DaysStatisticsAsync()
+        {
+            var endDate = DateOnly.FromDateTime(DateTime.Today);
+            var startDate = endDate.AddDays(-34); // 包含今天共 35 天
+
+            return await CalculateStatisticsAsync(startDate, endDate);
+        }
+
+        /// <summary>
+        /// 按日期范围获取统计数据
+        /// </summary>
+        public async Task<List<WarningStatistics>> GetStatisticsByDateRangeAsync(DateOnly startDate, DateOnly endDate)
+        {
+            return await CalculateStatisticsAsync(startDate, endDate);
+        }
+
+        /// <summary>
+        /// 按油枪和日期范围筛选统计数据
+        /// </summary>
+        public async Task<List<WarningStatistics>> GetStatisticsByFilterAsync(WarningStatisticsQuery query)
+        {
+            var endDate = query.EndDate ?? DateOnly.FromDateTime(DateTime.Today);
+            var startDate = query.StartDate ?? endDate.AddDays(-34);
+
+            var allStats = await CalculateStatisticsAsync(startDate, endDate);
+
+            // 按油枪筛选
+            if (query.NozzleId.HasValue)
+            {
+                allStats = allStats.Where(s => s.nozzle == query.NozzleId.Value).ToList();
+            }
+
+            return allStats;
+        }
+
+        /// <summary>
+        /// 更新指定日期的统计数据(用于新交易插入后)
+        /// </summary>
+        public async Task UpdateDailyStatisticsAsync(DateOnly date, int nozzleId)
+        {
+            try
+            {
+                Console.WriteLine($"[预警告统计] 开始更新油枪{nozzleId},日期:{date} 的统计数据");
+
+                // 重新计算该油枪在该日期的统计
+                var stats = await CalculateSingleDayStatisticsAsync(date, nozzleId);
+                
+                if (stats != null)
+                {
+                    Console.WriteLine($"[预警告统计] 更新成功 - 总笔数:{stats.daily_total}, 超标:{stats.daily_overproof}, 超标率:{stats.daily_overproofrate}%");
+                }
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"[预警告统计] 更新失败:{ex.Message}");
+            }
+        }
+
+        /// <summary>
+        /// 计算单个日期的统计数据
+        /// </summary>
+        private async Task<WarningStatistics> CalculateSingleDayStatisticsAsync(DateOnly date, int nozzleId)
+        {
+            // 获取当天的预警告记录
+            var prewarning = await _prewarning.AsQueryable()
+                .Where(p => p.date == date && p.nozzle == nozzleId)
+                .FirstAsync();
+
+            if (prewarning == null)
+            {
+                return null;
+            }
+
+            return new WarningStatistics
+            {
+                nozzle = nozzleId,
+                statistics_date = date,
+                daily_total = prewarning.total,
+                daily_overproof = prewarning.overproof,
+                daily_overproofrate = prewarning.overproofrate,
+                daily_overproof_alert = prewarning.overproof_alert,
+                daily_overproofrate_alert = prewarning.overproofrate_alert,
+                daily_overproof_2 = prewarning.overproof_2,
+                daily_overproofrate_2 = prewarning.overproofrate_2,
+                is_accumulated = prewarning.total >= 5,
+                accumulated_days = 1
+            };
+        }
+
+        /// <summary>
+        /// 核心统计计算逻辑
+        /// </summary>
+        private async Task<List<WarningStatistics>> CalculateStatisticsAsync(DateOnly startDate, DateOnly endDate)
+        {
+            Console.WriteLine($"[预警告统计] 开始计算统计,范围:{startDate} 至 {endDate}");
+
+            var results = new List<WarningStatistics>();
+
+            // 获取该日期范围内的所有预警告记录
+            var prewarnings = await _prewarning.AsQueryable()
+                .Where(p => p.date >= startDate && p.date <= endDate)
+                .ToListAsync();
+
+            // 按油枪分组
+            var groupedByNozzle = prewarnings.GroupBy(p => p.nozzle);
+
+            foreach (var nozzleGroup in groupedByNozzle)
+            {
+                var nozzleId = nozzleGroup.Key;
+                var sortedData = nozzleGroup.OrderBy(p => p.date).ToList();
+
+                // 为每一天计算统计数据
+                for (int i = 0; i < sortedData.Count; i++)
+                {
+                    var currentPrewarning = sortedData[i];
+                    var currentDate = currentPrewarning.date;
+
+                    var stats = new WarningStatistics
+                    {
+                        nozzle = nozzleId,
+                        statistics_date = currentDate,
+                        daily_total = currentPrewarning.total,
+                        daily_overproof = currentPrewarning.overproof,
+                        daily_overproofrate = currentPrewarning.overproofrate,
+                        daily_overproof_alert = currentPrewarning.overproof_alert,
+                        daily_overproofrate_alert = currentPrewarning.overproofrate_alert,
+                        daily_overproof_2 = currentPrewarning.overproof_2,
+                        daily_overproofrate_2 = currentPrewarning.overproofrate_2
+                    };
+
+                    // 计算 35 天累计统计
+                    CalculateAccumulatedStatistics(sortedData, i, stats);
+
+                    results.Add(stats);
+                }
+            }
+
+            Console.WriteLine($"[预警告统计] 计算完成,共{results.Count}条记录");
+            return results;
+        }
+
+        /// <summary>
+        /// 计算累计统计数据(核心业务逻辑)
+        /// 规则:从当前日期往前找,直到累计笔数>=5 或达到 35 天上限
+        /// </summary>
+        private void CalculateAccumulatedStatistics(List<TPrewarning> sortedData, int currentIndex, WarningStatistics stats)
+        {
+            int accumulatedTotal = 0;
+            int accumulatedOverproof = 0;
+            int accumulatedOverproofAlert = 0;
+            int accumulatedOverproof2 = 0;
+            int accumulatedDays = 0;
+
+            // 从当前日期往前累加,直到满足条件或达到 35 天
+            for (int i = currentIndex; i >= 0 && (currentIndex - i) < 35; i--)
+            {
+                var day = sortedData[i];
+                accumulatedTotal += day.total;
+                accumulatedOverproof += day.overproof;
+                accumulatedOverproofAlert += day.overproof_alert;
+                accumulatedOverproof2 += day.overproof_2;
+                accumulatedDays++;
+
+                // 如果累计笔数>=5,停止累加
+                if (accumulatedTotal >= 5)
+                {
+                    break;
+                }
+            }
+
+            // 设置累计统计结果
+            stats.total_count = accumulatedTotal;
+            stats.total_overproof = accumulatedOverproof;
+            stats.total_overproof_alert = accumulatedOverproofAlert;
+            stats.total_overproof_2 = accumulatedOverproof2;
+            stats.accumulated_days = accumulatedDays;
+            stats.is_accumulated = accumulatedTotal >= 5;
+
+            // 计算累计超标率(避免除以 0)
+            if (accumulatedTotal > 0)
+            {
+                stats.total_overproofrate = (100 * accumulatedOverproof) / accumulatedTotal;
+                stats.total_overproofrate_alert = (100 * accumulatedOverproofAlert) / accumulatedTotal;
+                stats.total_overproofrate_2 = (100 * accumulatedOverproof2) / accumulatedTotal;
+            }
+            else
+            {
+                stats.total_overproofrate = 0;
+                stats.total_overproofrate_alert = 0;
+                stats.total_overproofrate_2 = 0;
+            }
+        }
+
+        /// <summary>
+        /// 获取所有有数据的油枪 ID 列表
+        /// </summary>
+        public async Task<List<int>> GetAllNozzleIdsAsync()
+        {
+            var nozzles = await _prewarning.AsQueryable()
+                .Select(p => p.nozzle)
+                .Distinct()
+                .ToListAsync();
+
+            return nozzles;
+        }
+
+        /// <summary>
+        /// 获取统计日期范围(最早到最新)
+        /// </summary>
+        public async Task<(DateOnly minDate, DateOnly maxDate)> GetDateRangeAsync()
+        {
+            var dates = await _prewarning.AsQueryable()
+                .Select(p => p.date)
+                .ToListAsync();
+            
+            if (dates == null || dates.Count == 0)
+            {
+                return (new DateOnly(), new DateOnly());
+            }
+            
+            var minDate = dates.Min();
+            var maxDate = dates.Max();
+
+            return (minDate, maxDate);
+        }
+    }
+}

+ 135 - 0
EasyTemplate.Tool/Entity/App/WarningStatistics.cs

@@ -0,0 +1,135 @@
+using SqlSugar;
+using System;
+
+namespace EasyTemplate.Tool.Entity.App
+{
+    /// <summary>
+    /// 油枪预警告统计数据(35 天累计)
+    /// </summary>
+    [SugarTable("v_warning_statistics", "预警告统计视图")]
+    public class WarningStatistics
+    {
+        /// <summary>
+        /// 油枪 ID
+        /// </summary>
+        [SugarColumn(IsPrimaryKey = true)]
+        public int nozzle { get; set; }
+
+        /// <summary>
+        /// 统计日期
+        /// </summary>
+        [SugarColumn(IsPrimaryKey = true)]
+        public DateOnly statistics_date { get; set; }
+
+        /// <summary>
+        /// 累计加油笔数(最近 35 天,笔数>=5 的累计)
+        /// </summary>
+        public int total_count { get; set; }
+
+        /// <summary>
+        /// 累计超标数
+        /// </summary>
+        public int total_overproof { get; set; }
+
+        /// <summary>
+        /// 累计超标率(%)
+        /// </summary>
+        public int total_overproofrate { get; set; }
+
+        /// <summary>
+        /// 累计严重超标数(overproof_alert)
+        /// </summary>
+        public int total_overproof_alert { get; set; }
+
+        /// <summary>
+        /// 累计严重超标率(%)
+        /// </summary>
+        public int total_overproofrate_alert { get; set; }
+
+        /// <summary>
+        /// 累计二级超标数(overproof_2)
+        /// </summary>
+        public int total_overproof_2 { get; set; }
+
+        /// <summary>
+        /// 累计二级超标率(%)
+        /// </summary>
+        public int total_overproofrate_2 { get; set; }
+
+        /// <summary>
+        /// 当天加油笔数
+        /// </summary>
+        public int daily_total { get; set; }
+
+        /// <summary>
+        /// 当天超标数
+        /// </summary>
+        public int daily_overproof { get; set; }
+
+        /// <summary>
+        /// 当天超标率(%)
+        /// </summary>
+        public int daily_overproofrate { get; set; }
+
+        /// <summary>
+        /// 当天严重超标数
+        /// </summary>
+        public int daily_overproof_alert { get; set; }
+
+        /// <summary>
+        /// 当天严重超标率(%)
+        /// </summary>
+        public int daily_overproofrate_alert { get; set; }
+
+        /// <summary>
+        /// 当天二级超标数
+        /// </summary>
+        public int daily_overproof_2 { get; set; }
+
+        /// <summary>
+        /// 当天二级超标率(%)
+        /// </summary>
+        public int daily_overproofrate_2 { get; set; }
+
+        /// <summary>
+        /// 是否满足累计条件(最近连续天数总笔数>=5)
+        /// </summary>
+        public bool is_accumulated { get; set; }
+
+        /// <summary>
+        /// 累计天数
+        /// </summary>
+        public int accumulated_days { get; set; }
+    }
+
+    /// <summary>
+    /// 预警告统计查询参数
+    /// </summary>
+    public class WarningStatisticsQuery
+    {
+        /// <summary>
+        /// 油枪 ID(可选)
+        /// </summary>
+        public int? NozzleId { get; set; }
+
+        /// <summary>
+        /// 开始日期(可选)
+        /// </summary>
+        public DateOnly? StartDate { get; set; }
+
+        /// <summary>
+        /// 结束日期(可选)
+        /// </summary>
+        public DateOnly? EndDate { get; set; }
+
+        /// <summary>
+        /// 页码
+        /// </summary>
+        public int PageIndex { get; set; } = 1;
+
+        /// <summary>
+        /// 每页数量
+        /// </summary>
+        public int PageSize { get; set; } = 20;
+    }
+}

+ 1 - 0
EasyTemplate.Tool/Entity/System/SystemMenu.cs

@@ -99,6 +99,7 @@ public class SystemMenuSeedData : ISeedData<SystemMenu>
             new SystemMenu() { Id = 20, ParentId = 0, Path="/setting/boardsetting", Name="主板配置", Key="setting.board", Icon="snippets", Sort=2, Enabled=true, CreateTime = DateTime.Now },
             new SystemMenu() { Id = 21, ParentId = 0, Path="/setting/nozzlesetting", Name="油枪配置", Key="setting.nozzle", Icon="snippets", Sort=2, Enabled=true, CreateTime = DateTime.Now },
             new SystemMenu() { Id = 22, ParentId = 0, Path="/setting/unifiedsetting", Name="整合配置", Key="setting.unified", Icon="snippets", Sort=2, Enabled=true, CreateTime = DateTime.Now },
+            new SystemMenu() { Id = 23, ParentId = 0, Path="/warning_day", Name="35天预报警", Key="warning_day", Icon="snippets", Sort=2, Enabled=true, CreateTime = DateTime.Now },
 
         ];
 }

+ 2 - 4
文档/油气回收在线监控重构PRS文档.docx

@@ -36,8 +36,8 @@
  Introduction 介绍
  Purpose 产品目的 
 
-文档的撰写基于《油气回收在线监控重构模块设计》、《科迈捷产品分析》、《油气回收在线监控重构方案与科迈捷系统功能对比》,文档目标用户是研发团队,团队将基于此来形成开发计划并进行开始迭代开发和实现规范要求。
-此设计文档将关注油气回收在线监控系统的重构设计,实现油机气液比和环境数据的监控、预报警、查询、上传,实现远程升级、远程获取日志等功能,提高问题响应和处理速度,减少售后上站维护成本
+文档的撰写基于《油气回收在线监控重构模块设计》、《科迈捷产品分析》、《油气回收在线监控重构方案与科迈捷系统功能对比》,文档目标用户是研发团队,团队将基于此来形成开发计划并进行开始迭代开发和实现规范要求。
+此设计文档将关注油气回收在线监控系统的重构设计,实现油机气液比和环境数据的监控、预报警、查询、上传,通过实现远程升级、远程获取日志等功能来减少售后上站维护成本,通过预报警推送功能提高问题响应和处理速度,同时兼容国产化操作系统等
  Scope 范围
 此文档将详细分解各市场需求,规范业务流程,以及部分交互细节。即对产品的功能要求、开发要求、兼容性要求、性能要求、扩展要求、外观要求、发布要求、支持要求、其它要求等做出了定义。
  Definitions and Abbreviations 定义和缩写
@@ -113,8 +113,6 @@ Comments
   根据各省市环保协议将数据上传至其数据平台,并将上传的最新状态显示在主界面上。
   例图:
   
- 加油机配置和列表状态显示
-  点击新增加油机,加油机名称,IP地址,备注。加油机列表,区分加油机列表的状态,正常连接为绿色在线。心跳异常则为红色状态。
   
  状态图示说明
   显示油枪与传感器不同状态下的颜色图示。