DataServices 现场调试OK

This commit is contained in:
2025-11-26 16:46:48 +08:00
parent 979afae645
commit 010272aa0b
31 changed files with 1866 additions and 267 deletions

View File

@@ -85,7 +85,7 @@ namespace FATrace.App
tabPage3 = new TabPage();
btnLogin = new Button();
txtPassword = new TextBox();
txtUserName = new TextBox();
txtCheckUserName = new TextBox();
label17 = new Label();
label16 = new Label();
label15 = new Label();
@@ -103,6 +103,8 @@ namespace FATrace.App
dtpSearchStartTime = new DateTimePicker();
dataGridView1 = new DataGridView();
label22 = new Label();
txtOpName = new TextBox();
label28 = new Label();
statusStrip1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)splitContainer1).BeginInit();
splitContainer1.Panel1.SuspendLayout();
@@ -369,11 +371,11 @@ namespace FATrace.App
//
// btnWeightPrint
//
btnWeightPrint.Font = new Font("Microsoft YaHei UI", 15.75F, FontStyle.Bold, GraphicsUnit.Point, 134);
btnWeightPrint.Font = new Font("Microsoft YaHei UI", 20F, FontStyle.Bold);
btnWeightPrint.ForeColor = SystemColors.ControlDarkDark;
btnWeightPrint.Location = new Point(741, 119);
btnWeightPrint.Location = new Point(670, 95);
btnWeightPrint.Name = "btnWeightPrint";
btnWeightPrint.Size = new Size(119, 47);
btnWeightPrint.Size = new Size(190, 71);
btnWeightPrint.TabIndex = 9;
btnWeightPrint.Text = "称重打印";
btnWeightPrint.UseVisualStyleBackColor = true;
@@ -735,9 +737,11 @@ namespace FATrace.App
//
// tabPage3
//
tabPage3.Controls.Add(txtOpName);
tabPage3.Controls.Add(label28);
tabPage3.Controls.Add(btnLogin);
tabPage3.Controls.Add(txtPassword);
tabPage3.Controls.Add(txtUserName);
tabPage3.Controls.Add(txtCheckUserName);
tabPage3.Controls.Add(label17);
tabPage3.Controls.Add(label16);
tabPage3.Controls.Add(label15);
@@ -753,7 +757,7 @@ namespace FATrace.App
btnLogin.BackColor = Color.SandyBrown;
btnLogin.Font = new Font("微软雅黑", 22F);
btnLogin.ForeColor = SystemColors.ButtonHighlight;
btnLogin.Location = new Point(329, 387);
btnLogin.Location = new Point(355, 372);
btnLogin.Name = "btnLogin";
btnLogin.Size = new Size(224, 79);
btnLogin.TabIndex = 7;
@@ -764,24 +768,24 @@ namespace FATrace.App
// txtPassword
//
txtPassword.Font = new Font("微软雅黑", 16F);
txtPassword.Location = new Point(365, 296);
txtPassword.Location = new Point(335, 296);
txtPassword.Name = "txtPassword";
txtPassword.Size = new Size(224, 36);
txtPassword.Size = new Size(289, 36);
txtPassword.TabIndex = 6;
//
// txtUserName
// txtCheckUserName
//
txtUserName.Font = new Font("微软雅黑", 16F);
txtUserName.Location = new Point(365, 218);
txtUserName.Name = "txtUserName";
txtUserName.Size = new Size(224, 36);
txtUserName.TabIndex = 5;
txtCheckUserName.Font = new Font("微软雅黑", 16F);
txtCheckUserName.Location = new Point(335, 218);
txtCheckUserName.Name = "txtCheckUserName";
txtCheckUserName.Size = new Size(97, 36);
txtCheckUserName.TabIndex = 5;
//
// label17
//
label17.AutoSize = true;
label17.Font = new Font("微软雅黑", 16F);
label17.Location = new Point(277, 296);
label17.Location = new Point(247, 296);
label17.Name = "label17";
label17.Size = new Size(62, 30);
label17.TabIndex = 4;
@@ -791,17 +795,17 @@ namespace FATrace.App
//
label16.AutoSize = true;
label16.Font = new Font("微软雅黑", 16F);
label16.Location = new Point(277, 219);
label16.Location = new Point(247, 219);
label16.Name = "label16";
label16.Size = new Size(84, 30);
label16.TabIndex = 3;
label16.Text = "用户名:";
label16.Text = "确认者:";
//
// label15
//
label15.AutoSize = true;
label15.Font = new Font("微软雅黑", 26.75F);
label15.Location = new Point(365, 128);
label15.Location = new Point(361, 128);
label15.Name = "label15";
label15.Size = new Size(164, 46);
label15.TabIndex = 2;
@@ -957,6 +961,24 @@ namespace FATrace.App
label22.Text = "历史数据";
label22.TextAlign = ContentAlignment.MiddleCenter;
//
// txtOpName
//
txtOpName.Font = new Font("微软雅黑", 16F);
txtOpName.Location = new Point(527, 218);
txtOpName.Name = "txtOpName";
txtOpName.Size = new Size(97, 36);
txtOpName.TabIndex = 9;
//
// label28
//
label28.AutoSize = true;
label28.Font = new Font("微软雅黑", 16F);
label28.Location = new Point(439, 219);
label28.Name = "label28";
label28.Size = new Size(84, 30);
label28.TabIndex = 8;
label28.Text = "操作者:";
//
// frmMain
//
AutoScaleDimensions = new SizeF(7F, 17F);
@@ -1039,7 +1061,7 @@ namespace FATrace.App
private Label label14;
private Button btnLogin;
private TextBox txtPassword;
private TextBox txtUserName;
private TextBox txtCheckUserName;
private Label label17;
private Label label16;
private Label label15;
@@ -1077,5 +1099,7 @@ namespace FATrace.App
private PictureBox pictureBox2;
private PictureBox pictureBox3;
private PictureBox pictureBox4;
private TextBox txtOpName;
private Label label28;
}
}

View File

@@ -182,9 +182,14 @@ namespace FATrace.App
}
/// <summary>
/// 当前用户名
/// 当前确认者用户名
/// </summary>
public string CurrentOperationNo { get; set; } = string.Empty;
public string CurrentCheckUserNo { get; set; } = string.Empty;
/// <summary>
/// 当前操作者用户名
/// </summary>
public string CurrentOpUserNo { get; set; } = string.Empty;
public string CurrentOperationNoUserLevel { get; set; } = string.Empty;
@@ -696,13 +701,14 @@ namespace FATrace.App
Batch = CurSelectedRawProInput.Batch,
InBagCode = Code,
BoxCode = Code + ",A",
OpUser = CurrentOperationNo,
CheckUser = CurrentOperationNo,
OpUser = CurrentOpUserNo,
CheckUser = CurrentCheckUserNo,
RawCode = CurSelectedRawProInput.RawCode,
ShelfLife = CurSelectedRawProInput.ShelfLife,
RawName = CurSelectedRawProInput.RawName,
RemainWeight = NewRemainWeight,
Weight = CurWeight,
DeliveryDate= DateTime.Now.ToString("yyyy-MM-dd"),
WeightTime = DateTime.Now,
StockWeight = CurSelectedRawProInput.Weight,
}).ExecuteAffrows();
@@ -714,7 +720,7 @@ namespace FATrace.App
txtRemainWeight.Text = CurSelectedRawProInput.RemainWeight.ToString();
//称重检查如果剩余重量为0进行提示当剩余的重量小于称重的重量进行提示
if (CurSelectedRawProInput.RemainWeight < CurWeight / 1000)
if (CurSelectedRawProInput.RemainWeight < CurWeight / 1000.0)
{
//剩余的重量小于称重的重量,进行提示,强制进行清零
Task.Run(() =>
@@ -928,9 +934,14 @@ namespace FATrace.App
{
try
{
if (string.IsNullOrEmpty(txtUserName.Text))
if (string.IsNullOrEmpty(txtCheckUserName.Text))
{
MessageBox.Show("请输入用户名称", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
MessageBox.Show("请输入确认者用户名称", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
if (string.IsNullOrEmpty(txtOpName.Text))
{
MessageBox.Show("请输入操作者用户名称", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
if (string.IsNullOrEmpty(txtPassword.Text))
@@ -939,17 +950,17 @@ namespace FATrace.App
return;
}
var ListUser = FSqlContext.FDb.Select<TbUser>().Where(a => a.UserName == txtUserName.Text.Trim()).ToList();
var ListUser = FSqlContext.FDb.Select<TbUser>().Where(a => a.UserName == txtCheckUserName.Text.Trim()).ToList();
if (ListUser != null && ListUser.Count() > 0)
{
if (ListUser.FirstOrDefault().Password == txtPassword.Text.Trim())
{
MessageBox.Show("登录成功!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
Main_PopUserNameEvent(txtPassword.Text.Trim(), ListUser.FirstOrDefault().AccessLevel);
Main_PopUserNameEvent(txtCheckUserName.Text.Trim(), txtOpName.Text.Trim(), ListUser.FirstOrDefault().AccessLevel);
this.TabControlMain.SelectedIndex = 0;
txtUserName.Text = "";
txtCheckUserName.Text = "";
txtPassword.Text = "";
//PopUserNameEvent(txtUserName.Text.Trim());
//this.Close();
@@ -977,15 +988,17 @@ namespace FATrace.App
/// 用户登录的事件发布方法
/// </summary>
/// <param name="UserName"></param>
private void Main_PopUserNameEvent(string UserName, string UserLevel)
private void Main_PopUserNameEvent(string CheckUserName, string OpUserName, string UserLevel)
{
try
{
CurrentOperationNo = UserName;
CurrentCheckUserNo = CheckUserName;
CurrentOpUserNo = OpUserName;
CurrentOperationNoUserLevel = UserLevel;
Invoke(new Action(() =>
{
tslCurrentUser.Text = "当前用户:" + CurrentOperationNo;
tslCurrentUser.Text = "确认者用户:" + CurrentCheckUserNo;
tslCurrentUser.BackColor = Color.Green;
tslCurrentUser.ForeColor = Color.White;
}));
@@ -1006,7 +1019,7 @@ namespace FATrace.App
private void btnSysConfig_Click(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(CurrentOperationNo))
if (!string.IsNullOrEmpty(CurrentCheckUserNo))
{
}
@@ -1048,7 +1061,7 @@ namespace FATrace.App
private void btnMain_Click(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(CurrentOperationNo))
if (!string.IsNullOrEmpty(CurrentCheckUserNo))
{
TabControlMain.SelectedIndex = 0;
}

View File

@@ -0,0 +1,51 @@
using FreeSql.DataAnnotations;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FATrace.Model
{
/// <summary>
/// 报警 HistoryAlarm
/// </summary>
[Table(Name = "HistoryAlarm")]
public class HistoryAlarm
{
/// <summary>
/// 主键
/// </summary>
[Column(IsPrimary = true, IsIdentity = true)]
public long Id { get; set; }
/// <summary>
/// 报警消息分类
/// </summary>
[Column(Name = "Category", IsNullable = false, StringLength = 20)]
public string? Category { get; set; }
/// <summary>
/// 报警消息
/// </summary>
[Column(Name = "Message", IsNullable = false, StringLength = 150)]
public string? Message { get; set; }
///// <summary>
///// 报警时长
///// 秒
///// </summary>
//[Column(Name = "Duration")]
//public long Duration { get; set; }
[Column(Name = "CreateTime")]
public DateTime CreateTime { get; set; }
/// <summary>
/// ///////////////////////////////////////////导航属性///////////////////////////////////////////////////////
/// </summary>
//public Guid? AlarmAddressId { get; set; }
//public AlarmAddress AlarmAddress { get; set; }
}
}

View File

@@ -49,6 +49,13 @@ namespace FATrace.Model
[Column(Name = "Batch", IsNullable = false, StringLength = 50)]
public string? Batch { get; set; }
/// <summary>
/// 配料日期 当天日期
/// 年,月,日
/// </summary>
[Column(Name = "DeliveryDate", IsNullable = false, StringLength = 30)]
public string? DeliveryDate { get; set; }
/// <summary>
/// 保质期 年
/// </summary>

View File

@@ -8,6 +8,7 @@
<add key="PLCIP" value="127.0.1.1" />
<add key="PLCPort" value="5000" />
<add key="PLCScan" value="600" />
<add key="RawProUseCsvPath" value="D:\TestData" />
<!-- Keyence 布尔量地址:用于 WeightPhotoEnable 信号(默认 M100 -->
<add key="Addr_WeightPhotoEnable" value="M100" />
</appSettings>

View File

@@ -1,20 +1,21 @@
using AutoMapper;
using DryIoc;
using FATrace.Com;
using FATrace.WPLApp.MapperProfile;
using FATrace.WPLApp.RegionAdapter;
using FATrace.WPLApp.Services;
using FATrace.WPLApp.ViewModels;
using FATrace.WPLApp.Views;
using FreeSql;
using Prism.DryIoc;
using Prism.Ioc;
using Prism.Regions;
using System.Windows;
using DryIoc;
using AutoMapper;
using FreeSql;
using FATrace.WPLApp.Views;
using FATrace.WPLApp.ViewModels;
using FATrace.WPLApp.RegionAdapter;
using Syncfusion.UI.Xaml.NavigationDrawer;
using System.Windows.Threading;
using System.Diagnostics;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using FATrace.Com;
using System.Windows;
using System.Windows.Threading;
namespace FATrace.WPLApp
{
@@ -189,7 +190,7 @@ namespace FATrace.WPLApp
// 基础服务注册
containerRegistry.RegisterSingleton<ILogService, LogService>();
containerRegistry.RegisterSingleton<IMapperProvider, MapperProvidere>();
containerRegistry.RegisterSingleton<IMapperProvider, MapperConfig>();
containerRegistry.Register(typeof(IMapper), GetMapper);
//containerRegistry.RegisterSingleton<AlarmService>();
@@ -255,11 +256,16 @@ namespace FATrace.WPLApp
//containerRegistry.RegisterForNavigation<ProFlowView, ProFlowViewModel>();
//containerRegistry.RegisterForNavigation<ReportView, ReportViewModel>();
//containerRegistry.RegisterForNavigation<HelpManualView, HelpManualViewModel>();
//containerRegistry.RegisterForNavigation<HistoryAlarmView, HistoryAlarmViewModel>();
containerRegistry.RegisterForNavigation<HistoryAlarmView, HistoryAlarmViewModel>();
// 原料使用查询页
containerRegistry.RegisterForNavigation<RawProUseView, RawProUseViewModel>();
// 原料入库查询页
containerRegistry.RegisterForNavigation<RawProInputView, RawProInputViewModel>();
// 用户登录
containerRegistry.RegisterForNavigation<LoginView, LoginViewModel>("LoginView");
containerRegistry.RegisterForNavigation<HistoryAlarmView, HistoryAlarmViewModel>();
// 日志查看
containerRegistry.RegisterForNavigation<LogView, LogViewModel>("LogView");
//containerRegistry.RegisterForNavigation<RealTimeChartView, RealTimeChartViewModel>();
//containerRegistry.RegisterForNavigation<UserManageView, UserManageViewModel>();

View File

@@ -0,0 +1,45 @@
using CsvHelper.Configuration;
using FATrace.WPLApp.ModelDto;
namespace FATrace.WPLApp.CsvModel
{
/// <summary>
/// RawProUserCsvDto 的 CSV 列映射,固定输出顺序并设置日期格式。
/// </summary>
public class RawProUserCsvDtoMap : ClassMap<RawProUserCsvDto>
{
public RawProUserCsvDtoMap()
{
//Map(x => x.RawCode).Index(0).Name(nameof(RawProUserCsvDto.RawCode));
//Map(x => x.RawName).Index(1).Name(nameof(RawProUserCsvDto.RawName));
//Map(x => x.InBagCode).Index(2).Name(nameof(RawProUserCsvDto.InBagCode));
//Map(x => x.BoxCode).Index(3).Name(nameof(RawProUserCsvDto.BoxCode));
//Map(x => x.Batch).Index(4).Name(nameof(RawProUserCsvDto.Batch));
//Map(x => x.ShelfLife).Index(5).Name(nameof(RawProUserCsvDto.ShelfLife));
//Map(x => x.Weight).Index(6).Name(nameof(RawProUserCsvDto.Weight));
//Map(x => x.DeliveryDate).Index(7).Name(nameof(RawProUserCsvDto.DeliveryDate));
//Map(x => x.RemainWeight).Index(8).Name(nameof(RawProUserCsvDto.RemainWeight));
//Map(x => x.StockWeight).Index(9).Name(nameof(RawProUserCsvDto.StockWeight));
//Map(x => x.WeightTime).Index(10).Name(nameof(RawProUserCsvDto.WeightTime)).TypeConverterOption.Format("yyyy-MM-dd HH:mm:ss");
//Map(x => x.OpUser).Index(11).Name(nameof(RawProUserCsvDto.OpUser));
//Map(x => x.CheckUser).Index(12).Name(nameof(RawProUserCsvDto.CheckUser));
//Map(x => x.OutTime).Index(13).Name(nameof(RawProUserCsvDto.OutTime)).TypeConverterOption.Format("yyyy-MM-dd HH:mm:ss");
Map(x => x.RawCode).Index(0).Name("原料编号");
Map(x => x.RawName).Index(1).Name("原料名称");
Map(x => x.InBagCode).Index(2).Name("内袋二维码");
Map(x => x.BoxCode).Index(3).Name("外箱二维码");
Map(x => x.Batch).Index(4).Name("批号");
Map(x => x.ShelfLife).Index(5).Name("保质期");
Map(x => x.Weight).Index(6).Name("称重重量(g)");
Map(x => x.DeliveryDate).Index(7).Name("配料日期");
Map(x => x.RemainWeight).Index(8).Name("剩余重量(Kg)");
Map(x => x.StockWeight).Index(9).Name("入库总重量(Kg)");
Map(x => x.WeightTime).Index(10).Name("称重时间").TypeConverterOption.Format("yyyy-MM-dd HH:mm:ss");
Map(x => x.OpUser).Index(11).Name("操作者");
Map(x => x.CheckUser).Index(12).Name("确认者");
Map(x => x.OutTime).Index(13).Name("外箱扫码时间").TypeConverterOption.Format("yyyy-MM-dd HH:mm:ss");
}
}
}

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
@@ -58,7 +58,6 @@
<ItemGroup>
<Folder Include="PrismEvent\" />
<Folder Include="MapperProfile\" />
<Folder Include="EnvSupport\" />
</ItemGroup>
@@ -68,4 +67,10 @@
</None>
</ItemGroup>
<ItemGroup>
<None Update="Report\UserManual.md">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,35 @@
using AutoMapper;
using FATrace.WPLApp.Services;
using Prism.Ioc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FATrace.WPLApp.MapperProfile
{
public class MapperConfig : IMapperProvider
{
private readonly MapperConfiguration _Configuration;
public MapperConfig(IContainerProvider container)
{
_Configuration = new MapperConfiguration(configure =>
{
//var assemblys = AppDomain.CurrentDomain.GetAssemblies();
//configure.AddMaps(assemblys);
configure.ConstructServicesUsing(container.Resolve);
//扫描profile文件
configure.AddMaps(AppDomain.CurrentDomain.GetAssemblies());
});
}
public IMapper GetMapper()
{
return _Configuration.CreateMapper();
}
}
}

View File

@@ -0,0 +1,20 @@
using AutoMapper;
using FATrace.Model;
using FATrace.WPLApp.ModelDto;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FATrace.WPLApp.MapperProfile
{
public class RawProUseProfile : Profile
{
public RawProUseProfile()
{
CreateMap<RawProUse, RawProUserCsvDto>().ReverseMap();
CreateMap<RawProUse, RawProUseDto>().ReverseMap();
}
}
}

View File

@@ -39,6 +39,11 @@ namespace FATrace.WPLApp.ModelDto
/// </summary>
public string? Batch { get; set; }
/// <summary>
/// 配料日期
/// </summary>
public string? DeliveryDate { get; set; }
/// <summary>
/// 保质期 年
/// </summary>

View File

@@ -0,0 +1,144 @@
# FATrace 用户说明书WPLApp
版本v1.0 适用模块FATrace.WPLApp桌面应用
## 1. 简介
FATrace 是一套用于食品添加剂生产过程追溯与运行监控的桌面应用。WPLApp 负责生产端的日常使用,包括:
- 实时运行信息查看与统计总览Dashboard
- 历史报警查询History Alarm
- 用户登录与状态显示
- PLC 通信状态指示与当前终端信息(底部状态栏)
本说明书面向一线操作员、现场工程、以及系统管理员,帮助快速上手并定位常见问题。
## 2. 安装与启动
- 安装:由管理员完成程序分发与安装(通常包含可执行文件与依赖项)。
- 运行:双击启动桌面应用,进入主界面。
- 字体资源:应用内置图标字体,界面会自动加载,无需额外配置。
- 数据库:应用使用 FreeSql 进行数据访问。数据库连接等参数由管理员预配置。
如启动失败、页面空白或频繁报错,请联系系统管理员检查运行环境与数据库连接配置。
## 3. 登录与账号
- 打开左侧菜单“用户登录”进入登录页。
- 输入“用户名”和“密码”,点击“登录”或直接按回车键。
- 登录成功后自动导航到 Dashboard底部状态栏将显示“当前用户已登录”。
注意:当前版本按用户表中明文密码进行校验,请妥善保管账号信息。若忘记密码,请联系管理员重置。
## 4. 主界面与导航
应用主界面由顶部、内容区域、底部状态栏组成。常用页面:
- Dashboard统计与实时信息
- 历史报警History Alarm
- 原料相关查询(如原料使用、原料入库等,若已在系统中启用)
通过左侧菜单切换页面。页面内容区支持自动刷新与数据联动,无需手动刷新按钮。
## 5. Dashboard统计与实时信息
Dashboard 提供生产运行的概览信息,包括:
- 统计卡片按“日、月、年、累计”汇总原料使用重量RawProUse
- 实时运行信息:系统拦截并呈现关键运行日志与事件(如扫码、喷码、过程提示等)。
- PLC 连接状态:以指示灯颜色直观显示当前 PLC 通信状态。
- 预留区域:用于后续扩展显示(如更多曲线、指标或提示)。
刷新机制:当产生关键业务事件(例如扫码处理)时,系统自动发布事件触发 Dashboard 刷新,无需手动点击。
使用建议:
- 关注统计卡片的趋势变化,快速判断当日与周期产量情况。
- 实时信息区域用于排查当班异常,如出现错误或报警文字,可结合“历史报警”页进一步定位。
## 6. 历史报警History Alarm
用于查询系统运行过程中记录的历史报警。功能特性:
- 支持按关键字、类别、时间范围进行过滤查询。
- 结果列表分页显示,便于快速定位。
- 报警内容来自系统在关键点输出的报警信息,出现后会自动写入数据库。
使用步骤:
1. 打开“历史报警”页。
2. 选择查询条件(时间范围、类别、关键字等)。
3. 点击查询,查看结果列表。
排查建议:
- 若报警持续出现,先检查 PLC 连接与设备状态,再根据报警文本采取现场处理措施。
- 如怀疑数据库未记录,关注是否出现数据库连接问题或权限异常。
## 7. 底部状态栏FootView
底部状态栏持续显示系统运行关键信息:
- PLC 通信指示灯:绿色表示连接正常;红色表示断开或异常。
- PLC 终端信息:显示配置的 IP:Port便于对照设备。
- 当前用户与登录状态:显示当前登录用户及是否已登录。
当 PLC 状态改变时,这里会同步更新,便于第一时间发现通信问题。
## 8. 常见操作流程
- 开机与登录:启动应用 → 进入“用户登录” → 登录成功 → 自动跳转至 Dashboard。
- 查看产线运行:在 Dashboard 观察统计卡片与实时信息,注意异常提示。
- 报警定位与复盘:在“历史报警”中按时间段和关键字查询,结合现场记录进行处理与复盘。
## 9. 常见问题与排查
- 无法登录:
- 确认用户名、密码输入正确;
- 若提示“用户不存在”,请联系管理员检查用户表;
- 若提示数据库错误,联系管理员检查数据库连接与网络。
- Dashboard 统计为空:
- 确认数据库中是否存在有效的原料使用数据;
- 等待系统产生新数据或触发刷新事件;
- 检查时间范围与系统时间是否正确。
- 实时信息无更新:
- 确认产线是否有新的扫码或业务事件;
- 若日志区长时间无输出,可能产线空闲或通信异常。
- PLC 指示红色:
- 检查 PLC 电源、网线、IP 配置;
- 确认与应用配置一致IP、端口
- 联系电气与系统工程师排查网络或驱动问题。
- 历史报警无记录:
- 近期是否确有报警触发;
- 检查数据库是否可写;
- 关注应用日志,必要时联系管理员。
## 10. 使用技巧
- 登录页支持“回车键”快捷登录。
- 关注底部状态栏的用户与 PLC 状态,能快速判断系统是否处于可用状态。
- 建议按班次定期查看“历史报警”,形成闭环处理与优化记录。
## 11. 安全与权限
- 账号仅限本人使用,请勿外借与分享。
- 退出离岗前可在“用户登录”页切换用户(如需要可联系管理员提供退出功能)。
- 如需更细粒度权限(基于岗位/等级),请与管理员沟通后续版本规划。
## 12. 版本与更新
- 本说明书适配当前应用版本的已上线功能。新增功能或 UI 调整将另行更新说明。
- 如需升级或安装补丁,请联系系统管理员统一安排。
## 13. 反馈与支持
- 使用中如遇到界面异常、统计不准、报警缺失等问题:
- 记录问题发生时间、操作步骤、界面提示;
- 截图或拍照(含底部状态栏与异常信息);
- 联系系统管理员或运维支持。
— 结束 —

View File

@@ -10,12 +10,15 @@ using CsvHelper;
using CsvHelper.Configuration;
using FATrace.WPLApp.ModelDto;
using Prism.Mvvm;
using FATrace.WPLApp.CsvModel;
using FATrace.Com;
namespace FATrace.WPLApp.Services
{
/// <summary>
/// CSV服务
///导出CSV文件
///读取CSV 文件
/// </summary>
public class CsvServices:BindableBase
{
@@ -26,74 +29,121 @@ namespace FATrace.WPLApp.Services
{
LogService = logService;
FreeSql = freeSql;
RawProUseCsvPath = GetConfiguredOutputDirectory();
}
/// <summary>
/// 导出单条记录为 CSV 文件,文件名即记录的 InBagCode自动追加 .csv 扩展名)。
/// 导出Csv目录
/// </summary>
/// <param name="item">要导出的数据项,类型为 RawProUserCsvDto</param>
/// <param name="outputDirectory">导出目录,不存在将自动创建</param>
/// <param name="overwrite">若目标文件已存在,是否允许覆盖</param>
/// <returns>导出后的完整文件路径</returns>
/// <exception cref="ArgumentNullException">item 或 outputDirectory 为 null</exception>
/// <exception cref="ArgumentException">InBagCode 为空或仅空白</exception>
/// <exception cref="IOException">文件已存在且不允许覆盖</exception>
public string ExportSingle(RawProUserCsvDto item, string outputDirectory, bool overwrite = false)
private string RawProUseCsvPath { get; set; }
/// <summary>
/// 从 App.config 读取导出目录key: RawProUseCsvPath
/// </summary>
private string GetConfiguredOutputDirectory()
{
if (item is null) throw new ArgumentNullException(nameof(item));
if (outputDirectory is null) throw new ArgumentNullException(nameof(outputDirectory));
var dir = ConfigHelper.GetValue("RawProUseCsvPath");
// 支持环境变量(如 %USERPROFILE%
dir = Environment.ExpandEnvironmentVariables(dir ?? string.Empty).Trim();
if (string.IsNullOrWhiteSpace(dir))
throw new InvalidOperationException("配置项 RawProUseCsvPath 为空");
return dir;
}
var inBagCode = (item.InBagCode ?? string.Empty).Trim();
if (string.IsNullOrWhiteSpace(inBagCode))
throw new ArgumentException("InBagCode 不能为空", nameof(item.InBagCode));
// 目录确保存在
Directory.CreateDirectory(outputDirectory);
var safeName = SanitizeFileName(inBagCode);
var filePath = Path.Combine(outputDirectory, safeName + ".csv");
if (File.Exists(filePath) && !overwrite)
/// <summary>
/// 导出单条记录为 CSV 文件,文件名为记录的 InBagCode自动追加 .csv 扩展名)。
/// 使用 App.config 中的 RawProUseCsvPath 作为导出目录,目录不存在会自动创建。
/// 失败时记录错误日志并返回空字符串。
/// </summary>
/// <param name="item">要导出的数据项,类型为 RawProUserCsvDtoInBagCode 不能为空)</param>
/// <param name="overwrite">若目标文件已存在,是否允许覆盖</param>
/// <returns>成功:返回导出后的完整文件路径;失败:返回空字符串</returns>
public string ExportSingle(RawProUserCsvDto item, bool overwrite = false)
{
try
{
throw new IOException($"文件已存在且不允许覆盖: {filePath}");
if (item is null) throw new ArgumentNullException(nameof(item));
// 线程安全:使用局部缓存目录变量,必要时从配置读取一次并回写字段。
var outputDir = RawProUseCsvPath;
if (string.IsNullOrWhiteSpace(outputDir))
{
outputDir = GetConfiguredOutputDirectory();
RawProUseCsvPath = outputDir;
}
var inBagCode = (item.InBagCode ?? string.Empty).Trim();
if (string.IsNullOrWhiteSpace(inBagCode))
throw new ArgumentException("InBagCode 不能为空", nameof(item.InBagCode));
// 目录确保存在
Directory.CreateDirectory(outputDir);
var safeName = SanitizeFileName(inBagCode);
var filePath = Path.Combine(outputDir, safeName + ".csv");
if (File.Exists(filePath) && !overwrite)
{
throw new IOException($"文件已存在且不允许覆盖: {filePath}");
}
// 使用 UTF8 BOM便于 Excel 正确识别中文
using var writer = new StreamWriter(filePath, false, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true));
using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);
// 固定列顺序映射,并设置日期格式
csv.Context.RegisterClassMap<RawProUserCsvDtoMap>();
csv.WriteHeader<RawProUserCsvDto>();
csv.NextRecord();
csv.WriteRecord(item);
csv.NextRecord();
return filePath;
}
catch (Exception ex)
{
var code = item?.InBagCode ?? string.Empty;
var outputDir = RawProUseCsvPath;
LogService.Error($"导出CSV失败目录={outputDir}, InBagCode={code}", ex);
return string.Empty;
}
// 使用 UTF8 BOM便于 Excel 正确识别中文
using var writer = new StreamWriter(filePath, false, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true));
using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);
// 固定列顺序映射,并设置日期格式
csv.Context.RegisterClassMap<RawProUserCsvDtoMap>();
csv.WriteHeader<RawProUserCsvDto>();
csv.NextRecord();
csv.WriteRecord(item);
csv.NextRecord();
return filePath;
}
/// <summary>
/// 批量导出:把每一条记录分别导出为以各自 InBagCode 命名的 CSV 文件
/// 异步导出单条记录(包装线程池执行)
/// </summary>
/// <param name="items">数据集合</param>
/// <param name="outputDirectory">导出目录,不存在将自动创建</param>
/// <param name="overwrite">若目标文件已存在,是否允许覆盖</param>
/// <returns>成功导出的文件完整路径集合</returns>
public IEnumerable<string> ExportMany(IEnumerable<RawProUserCsvDto> items, string outputDirectory, bool overwrite = false)
public Task<string> ExportSingleAsync(RawProUserCsvDto item, bool overwrite = false)
=> Task.Run(() => ExportSingle(item, overwrite));
/// <summary>
/// 批量导出:将集合中的每一条记录分别导出为以 InBagCode 命名的 CSV 文件。
/// 成功项返回其路径,失败项返回空字符串(并已记录日志)。
/// </summary>
public IEnumerable<string> ExportMany(IEnumerable<RawProUserCsvDto> items, bool overwrite = false)
{
if (items is null) throw new ArgumentNullException(nameof(items));
var results = new List<string>();
foreach (var item in items)
foreach (var it in items)
{
// 逐条导出,沿用相同规则
var path = ExportSingle(item, outputDirectory, overwrite);
results.Add(path);
results.Add(ExportSingle(it, overwrite));
}
return results;
}
/// <summary>
/// 异步批量导出(并行度受限于线程池;如需更精细控制,可改为并行批处理)。
/// </summary>
public async Task<List<string>> ExportManyAsync(IEnumerable<RawProUserCsvDto> items, bool overwrite = false)
{
if (items is null) throw new ArgumentNullException(nameof(items));
var list = items.ToList();
var tasks = list.Select(it => Task.Run(() => ExportSingle(it, overwrite)));
var paths = await Task.WhenAll(tasks);
return paths.ToList();
}
/// <summary>
/// 清理文件名中的非法字符。
/// </summary>
@@ -108,44 +158,6 @@ namespace FATrace.WPLApp.Services
return sb.ToString();
}
/// <summary>
/// RawProUserCsvDto 的 CSV 列映射,固定输出顺序并设置日期格式。
/// </summary>
private sealed class RawProUserCsvDtoMap : ClassMap<RawProUserCsvDto>
{
public RawProUserCsvDtoMap()
{
//Map(x => x.RawCode).Index(0).Name(nameof(RawProUserCsvDto.RawCode));
//Map(x => x.RawName).Index(1).Name(nameof(RawProUserCsvDto.RawName));
//Map(x => x.InBagCode).Index(2).Name(nameof(RawProUserCsvDto.InBagCode));
//Map(x => x.BoxCode).Index(3).Name(nameof(RawProUserCsvDto.BoxCode));
//Map(x => x.Batch).Index(4).Name(nameof(RawProUserCsvDto.Batch));
//Map(x => x.ShelfLife).Index(5).Name(nameof(RawProUserCsvDto.ShelfLife));
//Map(x => x.Weight).Index(6).Name(nameof(RawProUserCsvDto.Weight));
//Map(x => x.DeliveryDate).Index(7).Name(nameof(RawProUserCsvDto.DeliveryDate));
//Map(x => x.RemainWeight).Index(8).Name(nameof(RawProUserCsvDto.RemainWeight));
//Map(x => x.StockWeight).Index(9).Name(nameof(RawProUserCsvDto.StockWeight));
//Map(x => x.WeightTime).Index(10).Name(nameof(RawProUserCsvDto.WeightTime)).TypeConverterOption.Format("yyyy-MM-dd HH:mm:ss");
//Map(x => x.OpUser).Index(11).Name(nameof(RawProUserCsvDto.OpUser));
//Map(x => x.CheckUser).Index(12).Name(nameof(RawProUserCsvDto.CheckUser));
//Map(x => x.OutTime).Index(13).Name(nameof(RawProUserCsvDto.OutTime)).TypeConverterOption.Format("yyyy-MM-dd HH:mm:ss");
Map(x => x.RawCode).Index(0).Name("原料编号");
Map(x => x.RawName).Index(1).Name("原料名称");
Map(x => x.InBagCode).Index(2).Name("内袋二维码");
Map(x => x.BoxCode).Index(3).Name("外箱二维码");
Map(x => x.Batch).Index(4).Name("批号");
Map(x => x.ShelfLife).Index(5).Name("保质期");
Map(x => x.Weight).Index(6).Name("称重重量(g)");
Map(x => x.DeliveryDate).Index(7).Name("配料日期");
Map(x => x.RemainWeight).Index(8).Name("剩余重量(Kg)");
Map(x => x.StockWeight).Index(9).Name("入库总重量(Kg)");
Map(x => x.WeightTime).Index(10).Name("称重时间").TypeConverterOption.Format("yyyy-MM-dd HH:mm:ss");
Map(x => x.OpUser).Index(11).Name("操作者");
Map(x => x.CheckUser).Index(12).Name("确认者");
Map(x => x.OutTime).Index(13).Name("出库时间").TypeConverterOption.Format("yyyy-MM-dd HH:mm:ss");
}
}
}
}

View File

@@ -1,13 +1,15 @@
using AutoMapper;
using FATrace.Model;
using FATrace.WPLApp.Events;
using FATrace.WPLApp.ModelDto;
using FATrace.WPLApp.Models;
using HslCommunication;
using HslCommunication.Profinet.Keyence;
using Prism.Events;
using Prism.Mvvm;
using System.Collections.Concurrent;
using System.Windows;
using ComConfigHelper = FATrace.Com.ConfigHelper;
using Prism.Events;
using FATrace.WPLApp.Events;
namespace FATrace.WPLApp.Services
{
@@ -17,13 +19,14 @@ namespace FATrace.WPLApp.Services
/// </summary>
public class DataServices : BindableBase, IDisposable
{
public DataServices(ILogService logService, IFreeSql freeSql, IEventAggregator eventAggregator, SysRunService sysRunService)
public DataServices(ILogService logService, IFreeSql freeSql, IEventAggregator eventAggregator, IMapper mapper, SysRunService sysRunService, CsvServices csvServices)
{
LogService = logService;
FreeSql = freeSql;
EventAggregator = eventAggregator;
Mapper = mapper;
SysRunService = sysRunService;
CsvServices = csvServices;
LineSglModel = new LineSglModel();
LineSglModel.WeightScanCodeHandle += LineSglModel_WeightScanCodeHandle;
LineSglModel.BoxSprayCodeReqHandle += LineSglModel_BoxSprayCodeReqHandle;
@@ -32,6 +35,10 @@ namespace FATrace.WPLApp.Services
PLCLinkInitial();
StartPlcScan();
//var DD= RevData("DYG05030013,20250923,802,3,01,0001,");
//var dd1= RevData("DYG05030013,251111,10193,6,01,3");
//var dd2 = RevData("DYG05030013,251111,2116,6,01,3");
}
@@ -53,9 +60,13 @@ namespace FATrace.WPLApp.Services
if (ReqData.Count > 0)
{
var CodeItem = ReqData.FirstOrDefault();
KeyencePlcMcNet.Write("D1150", RevData(CodeItem!.Code + ",A"));
Console.WriteLine($"外箱喷码:{CodeItem.Code}-发送OK");
var BoxSprayCodeSource = CodeItem!.Code + ",A";
var BoxSprayCodeRev = RevData(BoxSprayCodeSource);
KeyencePlcMcNet.Write("D1150", BoxSprayCodeRev);
Console.WriteLine($"外箱喷码:{BoxSprayCodeSource}-发送OK");
//这里需要删除数据吗?还是等扫码后删除,会不会过来新的箱子
@@ -65,6 +76,7 @@ namespace FATrace.WPLApp.Services
//不存在请求的数据,报错
KeyencePlcMcNet.Write("D1102", (Int16)1);
Console.WriteLine($"外箱喷码:不存在请求的数据");
AddAlarm("外箱喷码", "不存在请求的数据");
}
@@ -72,10 +84,9 @@ namespace FATrace.WPLApp.Services
{
await Task.Delay(1000).ConfigureAwait(false);
KeyencePlcMcNet!.Write("D1102", (Int16)0);
//KeyencePlcMcNet.Write("D1150", new Int16[30]);
});
}
/// <summary>
@@ -89,17 +100,20 @@ namespace FATrace.WPLApp.Services
//首先复位PLC信号
KeyencePlcMcNet!.Write("D1200", (Int16)0);
string BoxScanCode = string.Empty;
BoxScanCode = this.BoxScanCode;
if (!string.IsNullOrEmpty(BoxScanCode))
{
//var IsExist = FreeSql.Select<RawProUse>().Where(a => a.BoxCode == BoxScanCode);
var IsExist = FreeSql.Select<RawProUse>().Where(a => a.BoxCode!.Contains(BoxScanCode));// 内包和外包一样的条码测试用 ???????????????
var IsExist = FreeSql.Select<RawProUse>().Where(a => a.BoxCode == BoxScanCode);
//var IsExist = FreeSql.Select<RawProUse>().Where(a => a.BoxCode!.Contains(BoxScanCode));// 内包和外包一样的条码测试用 ???????????????
if (IsExist.Count() > 0)
{
//存在条码数据 OK返回PLC结果
KeyencePlcMcNet!.Write("D1210", (Int16)1);
Console.WriteLine($"外箱扫描码:{BoxScanCode}-存在找到");
//// 加入队列
// 加入队列
//EnqueueMessage(WeightScanCode);
LogService.Info($"外箱扫描码:{BoxScanCode}");
@@ -109,28 +123,40 @@ namespace FATrace.WPLApp.Services
.ExecuteAffrows();
if (DeleteResult > 0)
{
Console.WriteLine($"外箱扫描码:{WeightScanCode}-删除临时队列数据成功");
LogService.Info($"外箱扫描码:{WeightScanCode}-删除临时队列数据成功");
Console.WriteLine($"外箱扫描码:{BoxScanCode}-删除临时队列数据成功");
LogService.Info($"外箱扫描码:{BoxScanCode}-删除临时队列数据成功");
}
else
{
Console.WriteLine($"外箱扫描码:{WeightScanCode}-删除临时队列数据失败");
LogService.Info($"外箱扫描码:{WeightScanCode}-删除临时队列数据失败");
Console.WriteLine($"外箱扫描码:{BoxScanCode}-删除临时队列数据失败");
LogService.Info($"外箱扫描码:{BoxScanCode}-删除临时队列数据失败");
AddAlarm("外箱扫描", $"{BoxScanCode}-删除临时队列数据失败");
}
var Result = FreeSql.Update<RawProUse>()
var UpdatedResult = FreeSql.Update<RawProUse>()
.Set(p => p.OutTime, DateTime.Now)
.Where(p => p.BoxCode == BoxScanCode + ",A")//外箱二维码就是外箱扫描码
.ExecuteAffrows();
if (Result > 0)
.Where(p => p.BoxCode == BoxScanCode)//外箱二维码就是外箱扫描码
.ExecuteUpdated();
if (UpdatedResult.Count() > 0)
{
Console.WriteLine($"外箱扫描码:{WeightScanCode}更新时间成功");
LogService.Info($"外箱扫描码:{WeightScanCode}更新时间成功");
var Data = CsvServices.ExportSingle(Mapper.Map<RawProUserCsvDto>(UpdatedResult.First()), true);
if (Data != null && !string.IsNullOrEmpty(Data))
{
Console.WriteLine($"外箱扫描码:{BoxScanCode} CSV文件生成");
}
else
{
Console.WriteLine($"外箱扫描码:{BoxScanCode} CSV文件失败");
}
Console.WriteLine($"外箱扫描码:{BoxScanCode}更新时间成功");
LogService.Info($"外箱扫描码:{BoxScanCode}更新时间成功");
}
else
{
Console.WriteLine($"外箱扫描码:{WeightScanCode}更新时间失败");
LogService.Warn($"外箱扫描码:{WeightScanCode}更新时间失败");
Console.WriteLine($"外箱扫描码:{BoxScanCode}更新时间失败");
LogService.Warn($"外箱扫描码:{BoxScanCode}更新时间失败");
AddAlarm("外箱扫描", $"{BoxScanCode}更新时间失败");
}
}
else
@@ -138,14 +164,16 @@ namespace FATrace.WPLApp.Services
//不存在条码数据 NG返回PLC结果报警
KeyencePlcMcNet!.Write("D1210", (Int16)2);
Console.WriteLine($"外箱扫描码:{WeightScanCode}-未发现外箱扫码数据在数据库中");
LogService.Warn($"外箱扫描码:{WeightScanCode}-未发现外箱扫码数据在数据库中");
Console.WriteLine($"外箱扫描码:{BoxScanCode}-未发现外箱扫码数据在数据库中");
LogService.Warn($"外箱扫描码:{BoxScanCode}-未发现外箱扫码数据在数据库中");
AddAlarm("外箱扫描", $"{BoxScanCode}-未发现外箱扫码数据在数据库中");
}
}
else
{
Console.WriteLine($"外箱扫描码为空");
LogService.Warn("外箱扫描码为空");
AddAlarm("外箱扫描", "外箱扫描码为空");
}
Task.Run(async () =>
@@ -155,7 +183,10 @@ namespace FATrace.WPLApp.Services
KeyencePlcMcNet.Write("D1250", new Int16[30]);
});
try { EventAggregator?.GetEvent<DashboardRefreshEvent>()?.Publish(true); } catch { }
//try { EventAggregator?.GetEvent<DashboardRefreshEvent>()?.Publish(true); } catch { }
}
/// <summary>
@@ -200,6 +231,7 @@ namespace FATrace.WPLApp.Services
{
Console.WriteLine($"称重扫描码:{WeightScanCode}更新时间失败");
LogService.Warn($"称重扫描码:{WeightScanCode}更新时间失败");
AddAlarm("称重扫描", $"{WeightScanCode}更新时间失败");
}
}
else
@@ -209,6 +241,7 @@ namespace FATrace.WPLApp.Services
Console.WriteLine($"称重扫描码:{WeightScanCode}-未发现内包扫码数据在数据库中");
LogService.Warn($"称重扫描码:{WeightScanCode}-未发现内包扫码数据在数据库中");
AddAlarm("称重扫描", $"{WeightScanCode}-未发现内包扫码数据在数据库中");
}
Task.Run(async () =>
@@ -225,6 +258,7 @@ namespace FATrace.WPLApp.Services
{
Console.WriteLine($"称重扫描码为空");
LogService.Warn("称重扫描码为空");
AddAlarm("称重扫描", "称重扫描码为空");
}
}
@@ -232,7 +266,9 @@ namespace FATrace.WPLApp.Services
public ILogService LogService { get; }
public IFreeSql FreeSql { get; }
public IEventAggregator EventAggregator { get; }
public IMapper Mapper { get; }
public SysRunService SysRunService { get; }
public CsvServices CsvServices { get; }
private bool _plcConnected;
public bool PlcConnected
@@ -281,27 +317,6 @@ namespace FATrace.WPLApp.Services
/// </summary>
private readonly ConcurrentQueue<string> _messageQueue = new();
/// <summary>
/// 入队:将字符串加入 FIFO 队列(先进先出)。允许空字符串,不允许 null。
/// </summary>
/// <param name="message">要加入队列的字符串</param>
/// <exception cref="ArgumentNullException">当 message 为 null 时抛出</exception>
public void EnqueueMessage(string message)
{
if (message is null) throw new ArgumentNullException(nameof(message));
_messageQueue.Enqueue(message);
}
/// <summary>
/// 出队:尝试从 FIFO 队列取出一个字符串。
/// </summary>
/// <param name="message">输出参数:若成功则为取出的字符串;若失败(队列为空)则为 default</param>
/// <returns>是否成功取出</returns>
public bool TryDequeueMessage(out string message)
{
return _messageQueue.TryDequeue(out message);
}
/// <summary>
/// 产线信号模型
/// </summary>
@@ -327,7 +342,6 @@ namespace FATrace.WPLApp.Services
KeyencePlcMcNet.Port = Port;
KeyencePlcMcNet.ConnectClose();
KeyencePlcMcNet.ConnectTimeOut = 3000; // 连接3秒超时
OperateResult connect = KeyencePlcMcNet.ConnectServer();
if (connect.IsSuccess)//初始连接状态的显示判断
@@ -443,7 +457,7 @@ namespace FATrace.WPLApp.Services
OperateResultWeightScanCode = KeyencePlcMcNet!.ReadString("D1050", 20);
if (OperateResultWeightScanCode.IsSuccess)
{
WeightScanCode = RevData(OperateResultWeightScanCode.Content).Replace("\r", "").Replace("\n", "");
WeightScanCode = RevData(OperateResultWeightScanCode.Content).Replace("\r", "").Replace("\n", "").Trim();
}
OperateResultWeightScanSgl = KeyencePlcMcNet!.ReadInt16("D1000");
if (OperateResultWeightScanSgl.IsSuccess)
@@ -462,7 +476,8 @@ namespace FATrace.WPLApp.Services
OperateResultBoxScanCode = KeyencePlcMcNet!.ReadString("D1250", 20);
if (OperateResultBoxScanCode.IsSuccess)
{
BoxScanCode = RevData(OperateResultBoxScanCode.Content).Replace("\r", "").Replace("\n", "");
BoxScanCode = RevData(OperateResultBoxScanCode.Content).Replace("\r", "").Replace("\n", "").Trim();
//BoxScanCode = OperateResultBoxScanCode.Content.Replace("\r", "").Replace("\n", "");
}
OperateResultBoxScanSgl = KeyencePlcMcNet!.ReadInt16("D1200");
if (OperateResultBoxScanSgl.IsSuccess)
@@ -558,7 +573,7 @@ namespace FATrace.WPLApp.Services
/// <summary>
/// 把字符串对调
/// </summary>
private string RevData(string Code)
public string RevData(string Code)
{
// 需求PLC 字符串按“字”(2字节)进行高低字节对调,导致 ABCDEF -> BADCFE
// 纠正方式:按两个字符一组交换顺序,奇数长度保留最后一个字符
@@ -578,6 +593,8 @@ namespace FATrace.WPLApp.Services
else
{
// 奇数长度,最后一个字符原样保留
//sb.Append((char)0x0);
sb.Append('\0');
sb.Append(src[i]);
}
}
@@ -585,5 +602,25 @@ namespace FATrace.WPLApp.Services
return sb.ToString();
}
/// <summary>
/// 写入历史报警
/// </summary>
private void AddAlarm(string category, string message)
{
try
{
FreeSql.Insert(new HistoryAlarm
{
Category = category,
Message = message,
CreateTime = DateTime.Now
}).ExecuteAffrows();
}
catch (Exception ex)
{
LogService.Error($"写入历史报警失败: {ex.Message}");
}
}
}
}

View File

@@ -1,10 +1,5 @@
using FATrace.WPLApp.ModelDto;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FATrace.WPLApp.Services
{

View File

@@ -0,0 +1,73 @@
using System.Windows;
using System.Windows.Controls;
namespace FATrace.WPLApp.Utils
{
/// <summary>
/// 允许将 PasswordBox 的 Password 与 ViewModel 绑定
/// 用法:
/// <PasswordBox utils:PasswordBoxAssistant.BindPassword="True"
/// utils:PasswordBoxAssistant.BoundPassword="{Binding Password, Mode=TwoWay}" />
/// </summary>
public static class PasswordBoxAssistant
{
public static readonly DependencyProperty BoundPasswordProperty =
DependencyProperty.RegisterAttached("BoundPassword", typeof(string), typeof(PasswordBoxAssistant), new PropertyMetadata(string.Empty, OnBoundPasswordChanged));
public static readonly DependencyProperty BindPasswordProperty =
DependencyProperty.RegisterAttached("BindPassword", typeof(bool), typeof(PasswordBoxAssistant), new PropertyMetadata(false, OnBindPasswordChanged));
private static readonly DependencyProperty IsUpdatingProperty =
DependencyProperty.RegisterAttached("IsUpdating", typeof(bool), typeof(PasswordBoxAssistant));
public static string GetBoundPassword(DependencyObject dp) => (string)dp.GetValue(BoundPasswordProperty);
public static void SetBoundPassword(DependencyObject dp, string value) => dp.SetValue(BoundPasswordProperty, value);
public static bool GetBindPassword(DependencyObject dp) => (bool)dp.GetValue(BindPasswordProperty);
public static void SetBindPassword(DependencyObject dp, bool value) => dp.SetValue(BindPasswordProperty, value);
private static bool GetIsUpdating(DependencyObject dp) => (bool)dp.GetValue(IsUpdatingProperty);
private static void SetIsUpdating(DependencyObject dp, bool value) => dp.SetValue(IsUpdatingProperty, value);
private static void OnBoundPasswordChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
if (dp is PasswordBox passwordBox)
{
passwordBox.PasswordChanged -= HandlePasswordChanged;
if (!(bool)GetIsUpdating(passwordBox))
{
passwordBox.Password = e.NewValue?.ToString() ?? string.Empty;
}
passwordBox.PasswordChanged += HandlePasswordChanged;
}
}
private static void OnBindPasswordChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
if (dp is PasswordBox passwordBox)
{
bool wasBound = (bool)(e.OldValue ?? false);
bool needBind = (bool)(e.NewValue ?? false);
if (wasBound)
{
passwordBox.PasswordChanged -= HandlePasswordChanged;
}
if (needBind)
{
passwordBox.PasswordChanged += HandlePasswordChanged;
}
}
}
private static void HandlePasswordChanged(object sender, RoutedEventArgs e)
{
if (sender is PasswordBox passwordBox)
{
SetIsUpdating(passwordBox, true);
SetBoundPassword(passwordBox, passwordBox.Password);
SetIsUpdating(passwordBox, false);
}
}
}
}

View File

@@ -119,30 +119,6 @@ namespace FATrace.WPLApp.ViewModels
public DelegateCommand ClearLogsCommand { get; }
#endregion
private void StartLogTimer()
{
if (_logTimer != null) return;
_logTimer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(500)
};
_logTimer.Tick += (s, e) =>
{
// 从 DataServices 的队列中取出消息并展示
int drain = 0;
while (_data.TryDequeueMessage(out var msg))
{
if (!string.IsNullOrWhiteSpace(msg))
{
AppendLiveMessage(msg);
drain++;
if (drain >= 50) break; // 防止一次处理过多
}
}
};
_logTimer.Start();
}
private void AppendLiveMessage(string message)
{
if (string.IsNullOrWhiteSpace(message)) return;

View File

@@ -0,0 +1,32 @@
using AutoMapper;
using FATrace.WPLApp.Core;
using Prism.Services.Dialogs;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FATrace.WPLApp.ViewModels
{
public class DialogHelpManualViewModel : DialogViewModel
{
/// <summary>
/// 窗口打开时的传递的参数
/// </summary>
/// <param name="parameters"></param>
public override void OnDialogOpened(IDialogParameters parameters)
{
//CurMeterName = parameters.GetValue<string>("Name");
////RefreshChartSelectedData();
//var Data = FreeSql.Select<ConfigLimit>().Where(a => a.MeterName == CurMeterName).ToList();
//ConfigLimitDtoItems = new ObservableCollection<ConfigLimitDto>(Mapper.Map<List<ConfigLimitDto>>(Data));
}
}
}

View File

@@ -0,0 +1,229 @@
using FATrace.WPLApp.Core;
using Prism.Commands;
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using FreeSql;
using FATrace.Model;
using FATrace.WPLApp.Services;
using System.Windows;
using System.Linq;
namespace FATrace.WPLApp.ViewModels
{
/// <summary>
/// 历史报警查询 VM
/// 支持按时间范围/类别/消息模糊查询,分页展示。
/// </summary>
public class HistoryAlarmViewModel : NavigationViewModel
{
private readonly IFreeSql _fsql;
private readonly ILogService _log;
public HistoryAlarmViewModel(IFreeSql fsql, ILogService log)
{
_fsql = fsql;
_log = log;
Items = new ObservableCollection<HistoryAlarm>();
CategoryList = new ObservableCollection<string>();
// 默认时间范围为今日
StartTime = DateTime.Today;
EndTime = DateTime.Today.AddDays(1).AddSeconds(-1);
SearchCommand = new DelegateCommand(async () => await SearchAsync(), () => !IsBusy)
.ObservesProperty(() => IsBusy);
ClearCommand = new DelegateCommand(ClearFilters, () => !IsBusy)
.ObservesProperty(() => IsBusy);
FirstPageCommand = new DelegateCommand(async () => { if (PageIndex == 1) return; PageIndex = 1; await SearchAsync(); }, () => !IsBusy && PageIndex > 1)
.ObservesProperty(() => IsBusy).ObservesProperty(() => PageIndex);
PrevPageCommand = new DelegateCommand(async () => { if (PageIndex <= 1) return; PageIndex -= 1; await SearchAsync(); }, () => !IsBusy && PageIndex > 1)
.ObservesProperty(() => IsBusy).ObservesProperty(() => PageIndex);
NextPageCommand = new DelegateCommand(async () => { if (PageIndex >= TotalPages) return; PageIndex += 1; await SearchAsync(); }, () => !IsBusy && PageIndex < TotalPages)
.ObservesProperty(() => IsBusy).ObservesProperty(() => PageIndex).ObservesProperty(() => TotalPages);
LastPageCommand = new DelegateCommand(async () => { if (TotalPages <= 0 || PageIndex == TotalPages) return; PageIndex = TotalPages; await SearchAsync(); }, () => !IsBusy && PageIndex < TotalPages)
.ObservesProperty(() => IsBusy).ObservesProperty(() => PageIndex).ObservesProperty(() => TotalPages);
}
#region
public string? Category { get => _category; set { _category = value; RaisePropertyChanged(); } }
private string? _category;
public ObservableCollection<string> CategoryList { get; }
public string? Keyword { get => _keyword; set { _keyword = value; RaisePropertyChanged(); } }
private string? _keyword;
public DateTime? StartTime { get => _startTime; set { _startTime = value; RaisePropertyChanged(); } }
private DateTime? _startTime;
public DateTime? EndTime { get => _endTime; set { _endTime = value; RaisePropertyChanged(); } }
private DateTime? _endTime;
#endregion
#region //
public ObservableCollection<HistoryAlarm> Items { get; }
private bool _isBusy;
public bool IsBusy { get => _isBusy; set { _isBusy = value; RaisePropertyChanged(); } }
private int _totalCount;
public int TotalCount { get => _totalCount; set { _totalCount = value; RaisePropertyChanged(); } }
private int _pageIndex = 1;
public int PageIndex { get => _pageIndex; set { _pageIndex = value < 1 ? 1 : value; RaisePropertyChanged(); } }
private int _pageSize = 20;
public int PageSize
{
get => _pageSize;
set
{
var newSize = value <= 0 ? 20 : value;
if (_pageSize != newSize)
{
_pageSize = newSize;
RaisePropertyChanged();
PageIndex = 1;
if (!IsBusy) _ = SearchAsync();
}
}
}
private int _totalPages;
public int TotalPages { get => _totalPages; set { _totalPages = value; RaisePropertyChanged(); } }
#endregion
#region
public DelegateCommand SearchCommand { get; }
public DelegateCommand ClearCommand { get; }
public DelegateCommand FirstPageCommand { get; }
public DelegateCommand PrevPageCommand { get; }
public DelegateCommand NextPageCommand { get; }
public DelegateCommand LastPageCommand { get; }
#endregion
private void ClearFilters()
{
Category = "全部";
Keyword = string.Empty;
StartTime = DateTime.Today;
EndTime = DateTime.Today.AddDays(1).AddSeconds(-1);
}
private async Task SearchAsync()
{
if (IsBusy) return;
try
{
if (StartTime.HasValue && EndTime.HasValue && StartTime > EndTime)
{
MessageBox.Show("开始时间不能大于结束时间", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
IsBusy = true;
_log.Info("HistoryAlarm 查询开始");
var data = await Task.Run(() =>
{
var q = _fsql.Select<HistoryAlarm>();
DateTime? start = StartTime;
DateTime? end = EndTime;
if (start.HasValue) q = q.Where(a => a.CreateTime >= start.Value);
if (end.HasValue)
{
var endInclusive = end.Value;
if (endInclusive.TimeOfDay == TimeSpan.Zero) endInclusive = endInclusive.Date.AddDays(1).AddTicks(-1);
q = q.Where(a => a.CreateTime <= endInclusive);
}
if (!string.IsNullOrWhiteSpace(Category) && Category != "全部") q = q.Where(a => a.Category == Category);
if (!string.IsNullOrWhiteSpace(Keyword)) q = q.Where(a => a.Message!.Contains(Keyword));
q = q.OrderByDescending(a => a.CreateTime);
var size = PageSize <= 0 ? 20 : PageSize;
var page = PageIndex < 1 ? 1 : PageIndex;
var list = q.Count(out var total)
.Page(page, size)
.ToList();
var pages = total <= 0 || size <= 0 ? 0 : (int)Math.Ceiling(total * 1.0 / size);
if (pages > 0 && page > pages)
{
page = pages;
list = q.Page(page, size).ToList();
}
return (items: list, total: (int)total, normalizedPage: page, totalPages: pages);
});
Application.Current.Dispatcher.Invoke(() =>
{
Items.Clear();
foreach (var it in data.items) Items.Add(it);
TotalCount = data.total;
TotalPages = data.totalPages;
PageIndex = data.normalizedPage == 0 ? 1 : data.normalizedPage;
});
_log.Info($"HistoryAlarm 查询完成,记录数: {TotalCount}");
}
catch (Exception ex)
{
_log.Error($"HistoryAlarm 查询失败: {ex}");
MessageBox.Show($"查询失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally
{
IsBusy = false;
}
}
public override async void OnNavigatedTo(Prism.Regions.NavigationContext navigationContext)
{
await LoadCategoriesAsync();
await SearchAsync();
}
private async Task LoadCategoriesAsync()
{
try
{
var list = await Task.Run(() =>
{
// 直接在数据库端去重,避免将大量重复数据拉回内存
var cateList = _fsql.Select<HistoryAlarm>()
.Where(a => a.Category != null && a.Category != "")
.Distinct()
.ToList(a => a.Category);
return (cateList ?? new System.Collections.Generic.List<string>())
.Where(s => !string.IsNullOrWhiteSpace(s))
.OrderBy(s => s)
.ToList();
});
Application.Current.Dispatcher.Invoke(() =>
{
CategoryList.Clear();
CategoryList.Add("全部");
foreach (var s in list) CategoryList.Add(s);
if (string.IsNullOrWhiteSpace(Category)) Category = "全部";
});
}
catch (Exception ex)
{
_log.Warn($"加载报警类别失败: {ex.Message}");
Application.Current.Dispatcher.Invoke(() =>
{
if (CategoryList.Count == 0) CategoryList.Add("全部");
if (string.IsNullOrWhiteSpace(Category)) Category = "全部";
});
}
}
}
}

View File

@@ -0,0 +1,178 @@
using FATrace.WPLApp.Core;
using Prism.Commands;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Threading;
namespace FATrace.WPLApp.ViewModels
{
public class LogViewModel : NavigationViewModel
{
private readonly DispatcherTimer _timer;
public LogViewModel()
{
Levels = new ObservableCollection<string>(new[] { "ALL", "DEBUG", "INFO", "WARN", "ERROR", "FATAL" });
_selectedDate = DateTime.Today;
_levelFilter = Levels.First();
_maxLines = 2000;
RefreshCommand = new DelegateCommand(async () => await LoadAsync(), () => !IsBusy).ObservesProperty(() => IsBusy);
OpenFolderCommand = new DelegateCommand(OpenFolder);
_timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) };
_timer.Tick += async (_, __) =>
{
if (AutoRefresh && !IsBusy)
await LoadAsync();
};
_timer.Start();
// 初始加载当日日志
_ = LoadAsync();
}
public ObservableCollection<string> Levels { get; }
private ObservableCollection<LogEntry> _items = new();
public ObservableCollection<LogEntry> Items { get => _items; set { _items = value; RaisePropertyChanged(); } }
private DateTime? _selectedDate;
public DateTime? SelectedDate { get => _selectedDate; set { _selectedDate = value; RaisePropertyChanged(); _ = LoadAsync(); } }
private string _levelFilter;
public string LevelFilter { get => _levelFilter; set { _levelFilter = value; RaisePropertyChanged(); _ = LoadAsync(); } }
private string _keyword;
public string Keyword { get => _keyword; set { _keyword = value; RaisePropertyChanged(); _ = LoadAsync(); } }
private bool _autoRefresh;
public bool AutoRefresh { get => _autoRefresh; set { _autoRefresh = value; RaisePropertyChanged(); } }
private int _maxLines;
public int MaxLines { get => _maxLines; set { _maxLines = value; RaisePropertyChanged(); } }
private bool _isBusy;
public bool IsBusy { get => _isBusy; set { _isBusy = value; RaisePropertyChanged(); } }
public DelegateCommand RefreshCommand { get; }
public DelegateCommand OpenFolderCommand { get; }
private string GetLogDirectory()
{
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs");
}
private string GetLogFilePath(DateTime? date)
{
var dir = GetLogDirectory();
var dt = date ?? DateTime.Today;
var file = dt.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture) + ".log";
return Path.Combine(dir, file);
}
private static bool PassLevel(string levelFilter, string level)
{
if (string.Equals(levelFilter, "ALL", StringComparison.OrdinalIgnoreCase)) return true;
return string.Equals(level, levelFilter, StringComparison.OrdinalIgnoreCase);
}
private async Task LoadAsync()
{
if (IsBusy) return;
IsBusy = true;
try
{
var path = GetLogFilePath(SelectedDate);
var list = await Task.Run(() => ReadLogFile(path, MaxLines));
var filtered = list.Where(x => PassLevel(LevelFilter, x.Level)
&& (string.IsNullOrWhiteSpace(Keyword) || (x.Message?.IndexOf(Keyword, StringComparison.OrdinalIgnoreCase) >= 0)
|| (x.Logger?.IndexOf(Keyword, StringComparison.OrdinalIgnoreCase) >= 0)));
Items = new ObservableCollection<LogEntry>(filtered);
}
catch
{
Items = new ObservableCollection<LogEntry>();
}
finally
{
IsBusy = false;
}
}
private static IReadOnlyList<LogEntry> ReadLogFile(string path, int maxLines)
{
var result = new LinkedList<LogEntry>();
if (!File.Exists(path)) return result.ToList();
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using var sr = new StreamReader(fs, Encoding.UTF8);
string? line;
while ((line = sr.ReadLine()) != null)
{
var entry = ParseLine(line);
result.AddLast(entry);
if (result.Count > maxLines) result.RemoveFirst();
}
return result.ToList();
}
private static LogEntry ParseLine(string line)
{
// layout: longdate|LEVEL|logger|threadid|message[|exception]
var parts = line.Split('|');
var entry = new LogEntry();
if (parts.Length >= 4)
{
entry.Time = parts[0];
entry.Level = parts[1];
entry.Logger = parts[2];
entry.ThreadId = parts[3];
if (parts.Length >= 5)
{
entry.Message = string.Join("|", parts.Skip(4));
}
}
else
{
entry.Message = line;
}
return entry;
}
private void OpenFolder()
{
var dir = GetLogDirectory();
Directory.CreateDirectory(dir);
try
{
Process.Start(new ProcessStartInfo
{
FileName = dir,
UseShellExecute = true
});
}
catch { }
}
}
public class LogEntry
{
public string Time { get; set; }
public string Level { get; set; }
public string Logger { get; set; }
public string ThreadId { get; set; }
public string Message { get; set; }
}
}

View File

@@ -0,0 +1,96 @@
using FATrace.Model;
using FATrace.WPLApp.Core;
using FATrace.WPLApp.Services;
using FreeSql;
using Prism.Commands;
using Prism.Regions;
using System;
using System.Security;
using System.Threading.Tasks;
using System.Windows;
namespace FATrace.WPLApp.ViewModels
{
public class LoginViewModel : NavigationViewModel
{
private readonly IFreeSql _fsql;
private readonly ILogService _log;
private readonly SysRunService _sys;
private readonly IRegionManager _regionManager;
public LoginViewModel(IFreeSql fsql, ILogService log, SysRunService sysRunService, IRegionManager regionManager)
{
_fsql = fsql;
_log = log;
_sys = sysRunService;
_regionManager = regionManager;
LoginCommand = new DelegateCommand(async () => await DoLoginAsync(), () => !IsBusy)
.ObservesProperty(() => IsBusy);
}
private string _userName;
public string UserName { get => _userName; set { _userName = value; RaisePropertyChanged(); } }
private string _password;
public string Password { get => _password; set { _password = value; RaisePropertyChanged(); } }
private bool _isBusy;
public bool IsBusy { get => _isBusy; set { _isBusy = value; RaisePropertyChanged(); } }
public DelegateCommand LoginCommand { get; }
private async Task DoLoginAsync()
{
if (IsBusy) return;
try
{
if (string.IsNullOrWhiteSpace(UserName) || string.IsNullOrWhiteSpace(Password))
{
MessageBox.Show("请输入用户名和密码", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
IsBusy = true;
var user = await Task.Run(() =>
{
return _fsql.Select<TbUser>()
.Where(a => a.UserName == UserName)
.First();
});
if (user == null)
{
MessageBox.Show("用户不存在", "登录失败", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
// 明文密码对比(与现有模型字段一致)。如后续改为哈希,可在此替换校验逻辑。
if (!string.Equals(user.Password, Password))
{
MessageBox.Show("密码错误", "登录失败", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
_sys.CurUser = user.UserName;
_log.Info($"用户登录成功: {user.UserName}");
UserName="";
Password="";
try
{
_regionManager.Regions["ContentRegion"].RequestNavigate("DashBoardView");
}
catch { }
}
catch (Exception ex)
{
_log.Error($"登录异常: {ex}");
MessageBox.Show($"登录异常: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally
{
IsBusy = false;
}
}
}
}

View File

@@ -90,12 +90,12 @@ namespace FATrace.WPLApp.ViewModels
});
break;
case "报表数据":
this.regionManager.Regions["ContentRegion"].RequestNavigate("ReportView");
//case "报表数据":
// this.regionManager.Regions["ContentRegion"].RequestNavigate("ReportView");
//this.regionManager.Regions["ContentRegion"].Activate(viewB);
break;
//break;
case "原料使用查询":
this.regionManager.Regions["ContentRegion"].RequestNavigate("RawProUseView");
break;
@@ -105,15 +105,17 @@ namespace FATrace.WPLApp.ViewModels
case "历史报警":
this.regionManager.Regions["ContentRegion"].RequestNavigate("HistoryAlarmView");
//this.regionManager.Regions["ContentRegion"].Activate(viewB);
break;
case "日志信息":
this.regionManager.Regions["ContentRegion"].RequestNavigate("LogView");
break;
case "用户登录":
this.regionManager.Regions["ContentRegion"].RequestNavigate("UserView");
this.regionManager.Regions["ContentRegion"].RequestNavigate("LoginView");
//this.regionManager.Regions["ContentRegion"].Activate(Shift);
break;
case "使用手册":
this.regionManager.Regions["ContentRegion"].RequestNavigate("HelpManualView");
//this.regionManager.Regions["ContentRegion"].RequestNavigate("HelpManualView");
//弹窗
break;

View File

@@ -365,56 +365,56 @@ namespace FATrace.WPLApp.ViewModels
/// </summary>
private void ExportToCsv()
{
if (IsBusy) return;
if (Items.Count == 0)
{
System.Windows.MessageBox.Show("无可导出的数据", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
//if (IsBusy) return;
//if (Items.Count == 0)
//{
// System.Windows.MessageBox.Show("无可导出的数据", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
// return;
//}
try
{
IsBusy = true;
//try
//{
// IsBusy = true;
var dr = "D:\\迅雷下载";
// 转换为 RawProUserCsvDto
var all = Items.Select(it => new RawProUserCsvDto
{
RawCode = it.RawCode,
RawName = it.RawName,
InBagCode = it.InBagCode,
BoxCode = it.BoxCode,
Batch = it.Batch,
ShelfLife = it.ShelfLife,
Weight = it.Weight,
DeliveryDate = it.WeightTime.ToString("yyyyMMdd"),
RemainWeight = it.RemainWeight,
StockWeight = it.StockWeight,
WeightTime = it.WeightTime,
OpUser = it.OpUser,
CheckUser = it.CheckUser,
OutTime = it.OutTime
}).ToList();
// var dr = "D:\\迅雷下载";
// // 转换为 RawProUserCsvDto
// var all = Items.Select(it => new RawProUserCsvDto
// {
// RawCode = it.RawCode,
// RawName = it.RawName,
// InBagCode = it.InBagCode,
// BoxCode = it.BoxCode,
// Batch = it.Batch,
// ShelfLife = it.ShelfLife,
// Weight = it.Weight,
// DeliveryDate = it.WeightTime.ToString("yyyyMMdd"),
// RemainWeight = it.RemainWeight,
// StockWeight = it.StockWeight,
// WeightTime = it.WeightTime,
// OpUser = it.OpUser,
// CheckUser = it.CheckUser,
// OutTime = it.OutTime
// }).ToList();
// 过滤 InBagCode 为空的记录,避免导出失败
var valid = all.Where(x => !string.IsNullOrWhiteSpace(x.InBagCode)).ToList();
int skipped = all.Count - valid.Count;
// // 过滤 InBagCode 为空的记录,避免导出失败
// var valid = all.Where(x => !string.IsNullOrWhiteSpace(x.InBagCode)).ToList();
// int skipped = all.Count - valid.Count;
var svc = csvServices;
var paths = svc.ExportMany(valid, dr, overwrite: true);
// var svc = csvServices;
// var paths = svc.ExportMany(valid, dr, overwrite: true);
_log.Info($"RawProUse CSV 导出完成: 目录={dr}, 成功={valid.Count}, 跳过={skipped}");
System.Windows.MessageBox.Show($"导出完成:成功 {valid.Count} 条,跳过 {skipped} 条。\n目录{dr}", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
_log.Error($"RawProUse CSV 导出失败: {ex}");
System.Windows.MessageBox.Show($"导出失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally
{
IsBusy = false;
}
// _log.Info($"RawProUse CSV 导出完成: 目录={dr}, 成功={valid.Count}, 跳过={skipped}");
// System.Windows.MessageBox.Show($"导出完成:成功 {valid.Count} 条,跳过 {skipped} 条。\n目录{dr}", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
//}
//catch (Exception ex)
//{
// _log.Error($"RawProUse CSV 导出失败: {ex}");
// System.Windows.MessageBox.Show($"导出失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
//}
//finally
//{
// IsBusy = false;
//}
}
/// <summary>

View File

@@ -0,0 +1,12 @@
<UserControl x:Class="FATrace.WPLApp.Views.HelpManualView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:FATrace.WPLApp.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace FATrace.WPLApp.Views
{
/// <summary>
/// HelpManualView.xaml 的交互逻辑
/// </summary>
public partial class HelpManualView : UserControl
{
public HelpManualView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,225 @@
<UserControl
x:Class="FATrace.WPLApp.Views.HistoryAlarmView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism="http://prismlibrary.com/"
d:DesignHeight="720"
d:DesignWidth="1280"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d">
<Grid Margin="10">
<Grid.Resources>
<FontFamily x:Key="IconSegoe">Segoe MDL2 Assets</FontFamily>
<Style x:Key="PrimaryButton" TargetType="Button">
<Setter Property="Background" Value="#4A90E2" />
<Setter Property="Foreground" Value="White" />
<Setter Property="BorderBrush" Value="#4A90E2" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Padding" Value="12,6" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Height" Value="32" />
</Style>
<Style x:Key="SecondaryButton" TargetType="Button">
<Setter Property="Background" Value="#F0F4F8" />
<Setter Property="Foreground" Value="#334E68" />
<Setter Property="BorderBrush" Value="#CBD5E0" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Padding" Value="12,6" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Height" Value="32" />
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- 查询条件 -->
<Border
Grid.Row="0"
Padding="10"
Background="White"
BorderBrush="#E6ECF2"
BorderThickness="1"
CornerRadius="4">
<Border.Effect>
<DropShadowEffect
BlurRadius="8"
Opacity="0.15"
ShadowDepth="1" />
</Border.Effect>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel
Grid.Column="0"
Margin="0,0,10,0"
Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="类别:" />
<ComboBox
Width="180"
ItemsSource="{Binding CategoryList}"
SelectedItem="{Binding Category, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
<StackPanel
Grid.Column="1"
Margin="0,0,10,0"
Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="关键字:" />
<TextBox Width="220" Text="{Binding Keyword, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
<StackPanel
Grid.Column="2"
Margin="0,0,10,0"
Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="开始:" />
<DatePicker Width="160" SelectedDate="{Binding StartTime, Mode=TwoWay}" />
</StackPanel>
<StackPanel
Grid.Column="3"
Margin="0,0,10,0"
Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="结束:" />
<DatePicker Width="160" SelectedDate="{Binding EndTime, Mode=TwoWay}" />
</StackPanel>
<StackPanel
Grid.Column="4"
HorizontalAlignment="Right"
Orientation="Horizontal">
<Button
Width="100"
Margin="0,0,8,0"
Command="{Binding SearchCommand}"
Style="{StaticResource PrimaryButton}">
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<TextBlock
Margin="0,0,6,0"
FontFamily="{StaticResource IconSegoe}"
Text="&#xE721;" />
<TextBlock Text="查询" />
</StackPanel>
</Button>
<Button
Width="100"
Command="{Binding ClearCommand}"
Style="{StaticResource SecondaryButton}">
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<TextBlock
Margin="0,0,6,0"
FontFamily="{StaticResource IconSegoe}"
Text="&#xE711;" />
<TextBlock Text="清空" />
</StackPanel>
</Button>
</StackPanel>
</Grid>
</Border>
<!-- 列表 -->
<DataGrid
Grid.Row="1"
Margin="0,10,0,10"
AlternatingRowBackground="#F7FAFC"
AlternationCount="2"
AutoGenerateColumns="False"
Background="White"
BorderBrush="#E6ECF2"
BorderThickness="1"
CanUserAddRows="False"
FontSize="16"
GridLinesVisibility="Horizontal"
IsReadOnly="True"
ItemsSource="{Binding Items}">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="FontSize" Value="18" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.Columns>
<DataGridTextColumn
Width="160"
Binding="{Binding Category}"
Header="类别" />
<DataGridTextColumn
Width="*"
Binding="{Binding Message}"
Header="消息" />
<DataGridTextColumn
Width="180"
Binding="{Binding CreateTime, StringFormat=yyyy-MM-dd HH:mm:ss}"
Header="时间" />
</DataGrid.Columns>
</DataGrid>
<!-- 底部状态/分页 -->
<StatusBar Grid.Row="2">
<StatusBarItem>
<TextBlock Text="总数:" />
</StatusBarItem>
<StatusBarItem>
<TextBlock Text="{Binding TotalCount}" />
</StatusBarItem>
<StatusBarItem>
<Separator Width="20" />
</StatusBarItem>
<StatusBarItem>
<TextBlock Text="页码:" />
</StatusBarItem>
<StatusBarItem>
<TextBlock Text="{Binding PageIndex}" />
</StatusBarItem>
<StatusBarItem>
<TextBlock Text="/" />
</StatusBarItem>
<StatusBarItem>
<TextBlock Text="{Binding TotalPages}" />
</StatusBarItem>
<StatusBarItem>
<Button
Width="40"
Margin="5,0"
Command="{Binding FirstPageCommand}"
Content="|&lt;" />
</StatusBarItem>
<StatusBarItem>
<Button
Width="40"
Margin="5,0"
Command="{Binding PrevPageCommand}"
Content="&lt;" />
</StatusBarItem>
<StatusBarItem>
<Button
Width="40"
Margin="5,0"
Command="{Binding NextPageCommand}"
Content="&gt;" />
</StatusBarItem>
<StatusBarItem>
<Button
Width="40"
Margin="5,0"
Command="{Binding LastPageCommand}"
Content="&gt;|" />
</StatusBarItem>
</StatusBar>
</Grid>
</UserControl>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace FATrace.WPLApp.Views
{
/// <summary>
/// HistoryAlarmView.xaml 的交互逻辑
/// </summary>
public partial class HistoryAlarmView : UserControl
{
public HistoryAlarmView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,130 @@
<UserControl
x:Class="FATrace.WPLApp.Views.LogView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism="http://prismlibrary.com/"
d:DesignHeight="720"
d:DesignWidth="1280"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d">
<Grid Background="#F5F7FA">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Toolbar -->
<Border
Padding="10"
Background="White"
BorderBrush="#E6ECF2"
BorderThickness="0,0,0,1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="200" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel
Margin="0,0,16,0"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="日期:" />
<DatePicker SelectedDate="{Binding SelectedDate}" />
</StackPanel>
<StackPanel
Grid.Column="1"
Margin="0,0,16,0"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="级别:" />
<ComboBox
Width="110"
ItemsSource="{Binding Levels}"
SelectedItem="{Binding LevelFilter}" />
</StackPanel>
<TextBox
Grid.Column="2"
Height="28"
Margin="0,0,16,0"
VerticalContentAlignment="Center"
Text="{Binding Keyword, UpdateSourceTrigger=PropertyChanged}" />
<CheckBox
Grid.Column="3"
Margin="0,0,16,0"
VerticalAlignment="Center"
Content="自动刷新"
IsChecked="{Binding AutoRefresh}" />
<StackPanel
Grid.Column="4"
Margin="0,0,16,0"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="最大行数:" />
<TextBox Width="80" Text="{Binding MaxLines, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
<StackPanel
Grid.Column="5"
HorizontalAlignment="Left"
Orientation="Horizontal">
<Button
Margin="0,0,8,0"
Command="{Binding RefreshCommand}"
Content="刷新" />
<Button Command="{Binding OpenFolderCommand}" Content="打开日志文件夹" />
</StackPanel>
</Grid>
</Border>
<!-- Log list -->
<Border
Grid.Row="1"
Margin="10"
Background="White"
BorderBrush="#E6ECF2"
BorderThickness="1">
<DataGrid
AutoGenerateColumns="False"
CanUserAddRows="False"
GridLinesVisibility="None"
HeadersVisibility="Column"
IsReadOnly="True"
ItemsSource="{Binding Items}">
<DataGrid.Columns>
<DataGridTextColumn
Width="180"
Binding="{Binding Time}"
Header="时间" />
<DataGridTextColumn
Width="100"
Binding="{Binding Level}"
Header="级别" />
<DataGridTextColumn
Width="200"
Binding="{Binding Logger}"
Header="记录器" />
<DataGridTextColumn
Width="80"
Binding="{Binding ThreadId}"
Header="线程" />
<DataGridTextColumn
Width="*"
Binding="{Binding Message}"
Header="内容" />
</DataGrid.Columns>
</DataGrid>
</Border>
</Grid>
</UserControl>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace FATrace.WPLApp.Views
{
/// <summary>
/// LogView.xaml 的交互逻辑
/// </summary>
public partial class LogView : UserControl
{
public LogView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,134 @@
<UserControl
x:Class="FATrace.WPLApp.Views.LoginView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism="http://prismlibrary.com/"
xmlns:utils="clr-namespace:FATrace.WPLApp.Utils"
d:DesignHeight="720"
d:DesignWidth="1280"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d">
<Grid Background="#F5F7FA">
<Grid.Resources>
<BooleanToVisibilityConverter x:Key="Bool2Vis" />
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border
Width="420"
Padding="28"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="White"
BorderBrush="#E6ECF2"
BorderThickness="1"
CornerRadius="12">
<Border.Effect>
<DropShadowEffect
BlurRadius="12"
Opacity="0.2"
ShadowDepth="2" />
</Border.Effect>
<StackPanel>
<!-- 标题 -->
<StackPanel
Margin="0,6,0,18"
HorizontalAlignment="Center"
Orientation="Horizontal">
<TextBlock
FontFamily="/Assets/Fonts/#iconfont"
FontSize="26"
Foreground="#4A90E2"
Text="&#xec82;" />
<TextBlock
FontSize="24"
FontWeight="Bold"
Text=" 用户登录" />
</StackPanel>
<!-- 用户名 -->
<StackPanel Margin="0,6">
<TextBlock Foreground="#6B7C93" Text="用户名" />
<Border
Height="40"
Margin="0,6,0,0"
Background="#F7F9FC"
CornerRadius="6">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="36" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Margin="8,0,0,0"
VerticalAlignment="Center"
FontFamily="/Assets/Fonts/#iconfont"
Foreground="#A0AEC0"
Text="&#xec23;" />
<TextBox
Grid.Column="1"
Padding="8,0"
VerticalContentAlignment="Center"
Background="Transparent"
BorderThickness="0"
Text="{Binding UserName, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Border>
</StackPanel>
<!-- 密码 -->
<StackPanel Margin="0,6,0,12">
<TextBlock Foreground="#6B7C93" Text="密码" />
<Border
Height="40"
Margin="0,6,0,0"
Background="#F7F9FC"
CornerRadius="6">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="36" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Margin="8,0,0,0"
VerticalAlignment="Center"
FontFamily="/Assets/Fonts/#iconfont"
Foreground="#A0AEC0"
Text="&#xec25;" />
<PasswordBox
Grid.Column="1"
Padding="8,0"
VerticalContentAlignment="Center"
Background="Transparent"
BorderThickness="0"
utils:PasswordBoxAssistant.BindPassword="True"
utils:PasswordBoxAssistant.BoundPassword="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Border>
</StackPanel>
<!-- 登录按钮 -->
<Button
Height="42"
Margin="0,8,0,0"
Background="#4A90E2"
BorderBrush="#4A90E2"
Command="{Binding LoginCommand}"
Content="登录"
Cursor="Hand"
FontSize="16"
Foreground="White" />
</StackPanel>
</Border>
<!-- 忙碌遮罩 -->
<!--<Grid Background="#80FFFFFF" Visibility="{Binding IsBusy, Converter={StaticResource Bool2Vis}}" />-->
</Grid>
</UserControl>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace FATrace.WPLApp.Views
{
/// <summary>
/// LoginView.xaml 的交互逻辑
/// </summary>
public partial class LoginView : UserControl
{
public LoginView()
{
InitializeComponent();
}
}
}