diff --git a/FATrace.App/frmMain.Designer.cs b/FATrace.App/frmMain.Designer.cs
index 777ad06..e8f78df 100644
--- a/FATrace.App/frmMain.Designer.cs
+++ b/FATrace.App/frmMain.Designer.cs
@@ -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;
}
}
\ No newline at end of file
diff --git a/FATrace.App/frmMain.cs b/FATrace.App/frmMain.cs
index 883c273..eec39f9 100644
--- a/FATrace.App/frmMain.cs
+++ b/FATrace.App/frmMain.cs
@@ -182,9 +182,14 @@ namespace FATrace.App
}
///
- /// 当前用户名
+ /// 当前确认者用户名
///
- public string CurrentOperationNo { get; set; } = string.Empty;
+ public string CurrentCheckUserNo { get; set; } = string.Empty;
+
+ ///
+ /// 当前操作者用户名
+ ///
+ 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().Where(a => a.UserName == txtUserName.Text.Trim()).ToList();
+ var ListUser = FSqlContext.FDb.Select().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
/// 用户登录的事件发布方法
///
///
- 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;
}
diff --git a/FATrace.Model/HistoryAlarm.cs b/FATrace.Model/HistoryAlarm.cs
new file mode 100644
index 0000000..a042f54
--- /dev/null
+++ b/FATrace.Model/HistoryAlarm.cs
@@ -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
+{
+ ///
+ /// 报警 HistoryAlarm
+ ///
+ [Table(Name = "HistoryAlarm")]
+ public class HistoryAlarm
+ {
+ ///
+ /// 主键
+ ///
+ [Column(IsPrimary = true, IsIdentity = true)]
+ public long Id { get; set; }
+
+ ///
+ /// 报警消息分类
+ ///
+ [Column(Name = "Category", IsNullable = false, StringLength = 20)]
+ public string? Category { get; set; }
+
+ ///
+ /// 报警消息
+ ///
+ [Column(Name = "Message", IsNullable = false, StringLength = 150)]
+ public string? Message { get; set; }
+
+ /////
+ ///// 报警时长
+ ///// 秒
+ /////
+ //[Column(Name = "Duration")]
+ //public long Duration { get; set; }
+
+ [Column(Name = "CreateTime")]
+ public DateTime CreateTime { get; set; }
+
+ ///
+ /// ///////////////////////////////////////////导航属性///////////////////////////////////////////////////////
+ ///
+
+ //public Guid? AlarmAddressId { get; set; }
+ //public AlarmAddress AlarmAddress { get; set; }
+ }
+}
diff --git a/FATrace.Model/RawProUse.cs b/FATrace.Model/RawProUse.cs
index f5a6bc7..3a9cc86 100644
--- a/FATrace.Model/RawProUse.cs
+++ b/FATrace.Model/RawProUse.cs
@@ -49,6 +49,13 @@ namespace FATrace.Model
[Column(Name = "Batch", IsNullable = false, StringLength = 50)]
public string? Batch { get; set; }
+ ///
+ /// 配料日期 当天日期
+ /// 年,月,日
+ ///
+ [Column(Name = "DeliveryDate", IsNullable = false, StringLength = 30)]
+ public string? DeliveryDate { get; set; }
+
///
/// 保质期 年
///
diff --git a/FATrace.WPLApp/App.config b/FATrace.WPLApp/App.config
index 55a0ac9..79bdc4f 100644
--- a/FATrace.WPLApp/App.config
+++ b/FATrace.WPLApp/App.config
@@ -8,6 +8,7 @@
+
diff --git a/FATrace.WPLApp/App.xaml.cs b/FATrace.WPLApp/App.xaml.cs
index 2b07f00..e961975 100644
--- a/FATrace.WPLApp/App.xaml.cs
+++ b/FATrace.WPLApp/App.xaml.cs
@@ -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();
- containerRegistry.RegisterSingleton();
+ containerRegistry.RegisterSingleton();
containerRegistry.Register(typeof(IMapper), GetMapper);
//containerRegistry.RegisterSingleton();
@@ -255,12 +256,17 @@ namespace FATrace.WPLApp
//containerRegistry.RegisterForNavigation();
//containerRegistry.RegisterForNavigation();
//containerRegistry.RegisterForNavigation();
- //containerRegistry.RegisterForNavigation();
+ containerRegistry.RegisterForNavigation();
// 原料使用查询页
containerRegistry.RegisterForNavigation();
// 原料入库查询页
containerRegistry.RegisterForNavigation();
-
+ // 用户登录
+ containerRegistry.RegisterForNavigation("LoginView");
+ containerRegistry.RegisterForNavigation();
+ // 日志查看
+ containerRegistry.RegisterForNavigation("LogView");
+
//containerRegistry.RegisterForNavigation();
//containerRegistry.RegisterForNavigation();
//containerRegistry.RegisterForNavigation();
diff --git a/FATrace.WPLApp/CsvModel/RawProUserCsvDtoMap.cs b/FATrace.WPLApp/CsvModel/RawProUserCsvDtoMap.cs
new file mode 100644
index 0000000..b25bff9
--- /dev/null
+++ b/FATrace.WPLApp/CsvModel/RawProUserCsvDtoMap.cs
@@ -0,0 +1,45 @@
+using CsvHelper.Configuration;
+using FATrace.WPLApp.ModelDto;
+
+namespace FATrace.WPLApp.CsvModel
+{
+ ///
+ /// RawProUserCsvDto 的 CSV 列映射,固定输出顺序并设置日期格式。
+ ///
+ public class RawProUserCsvDtoMap : ClassMap
+ {
+ 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");
+
+ }
+ }
+}
diff --git a/FATrace.WPLApp/FATrace.WPLApp.csproj b/FATrace.WPLApp/FATrace.WPLApp.csproj
index bc81944..393e1c5 100644
--- a/FATrace.WPLApp/FATrace.WPLApp.csproj
+++ b/FATrace.WPLApp/FATrace.WPLApp.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -58,7 +58,6 @@
-
@@ -68,4 +67,10 @@
+
+
+ PreserveNewest
+
+
+
diff --git a/FATrace.WPLApp/MapperProfile/MapperConfig.cs b/FATrace.WPLApp/MapperProfile/MapperConfig.cs
new file mode 100644
index 0000000..2bccd3d
--- /dev/null
+++ b/FATrace.WPLApp/MapperProfile/MapperConfig.cs
@@ -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();
+ }
+ }
+}
diff --git a/FATrace.WPLApp/MapperProfile/RawProUseProfile.cs b/FATrace.WPLApp/MapperProfile/RawProUseProfile.cs
new file mode 100644
index 0000000..db95254
--- /dev/null
+++ b/FATrace.WPLApp/MapperProfile/RawProUseProfile.cs
@@ -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().ReverseMap();
+ CreateMap().ReverseMap();
+ }
+ }
+}
diff --git a/FATrace.WPLApp/ModelDto/RawProUseDto.cs b/FATrace.WPLApp/ModelDto/RawProUseDto.cs
index a0fdd2d..b3eba06 100644
--- a/FATrace.WPLApp/ModelDto/RawProUseDto.cs
+++ b/FATrace.WPLApp/ModelDto/RawProUseDto.cs
@@ -39,6 +39,11 @@ namespace FATrace.WPLApp.ModelDto
///
public string? Batch { get; set; }
+ ///
+ /// 配料日期
+ ///
+ public string? DeliveryDate { get; set; }
+
///
/// 保质期 年
///
diff --git a/FATrace.WPLApp/Report/UserManual.md b/FATrace.WPLApp/Report/UserManual.md
new file mode 100644
index 0000000..7763d7e
--- /dev/null
+++ b/FATrace.WPLApp/Report/UserManual.md
@@ -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. 反馈与支持
+
+- 使用中如遇到界面异常、统计不准、报警缺失等问题:
+ - 记录问题发生时间、操作步骤、界面提示;
+ - 截图或拍照(含底部状态栏与异常信息);
+ - 联系系统管理员或运维支持。
+
+— 结束 —
diff --git a/FATrace.WPLApp/Services/CsvServices.cs b/FATrace.WPLApp/Services/CsvServices.cs
index 40f344d..5a81bb6 100644
--- a/FATrace.WPLApp/Services/CsvServices.cs
+++ b/FATrace.WPLApp/Services/CsvServices.cs
@@ -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
{
///
/// CSV服务
///导出CSV文件
+ ///读取CSV 文件
///
public class CsvServices:BindableBase
{
@@ -26,74 +29,121 @@ namespace FATrace.WPLApp.Services
{
LogService = logService;
FreeSql = freeSql;
+
+ RawProUseCsvPath = GetConfiguredOutputDirectory();
}
///
- /// 导出单条记录为 CSV 文件,文件名即记录的 InBagCode(自动追加 .csv 扩展名)。
+ /// 导出Csv目录
///
- /// 要导出的数据项,类型为 RawProUserCsvDto
- /// 导出目录,不存在将自动创建
- /// 若目标文件已存在,是否允许覆盖
- /// 导出后的完整文件路径
- /// item 或 outputDirectory 为 null
- /// InBagCode 为空或仅空白
- /// 文件已存在且不允许覆盖
- public string ExportSingle(RawProUserCsvDto item, string outputDirectory, bool overwrite = false)
+ private string RawProUseCsvPath { get; set; }
+
+ ///
+ /// 从 App.config 读取导出目录(key: RawProUseCsvPath)。
+ ///
+ 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)
+ ///
+ /// 导出单条记录为 CSV 文件,文件名为记录的 InBagCode(自动追加 .csv 扩展名)。
+ /// 使用 App.config 中的 RawProUseCsvPath 作为导出目录,目录不存在会自动创建。
+ /// 失败时记录错误日志并返回空字符串。
+ ///
+ /// 要导出的数据项,类型为 RawProUserCsvDto(InBagCode 不能为空)
+ /// 若目标文件已存在,是否允许覆盖
+ /// 成功:返回导出后的完整文件路径;失败:返回空字符串
+ 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();
+
+ csv.WriteHeader();
+ 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();
-
- csv.WriteHeader();
- csv.NextRecord();
- csv.WriteRecord(item);
- csv.NextRecord();
-
- return filePath;
}
///
- /// 批量导出:把每一条记录分别导出为以各自 InBagCode 命名的 CSV 文件。
+ /// 异步导出单条记录(包装线程池执行)。
///
- /// 数据集合
- /// 导出目录,不存在将自动创建
- /// 若目标文件已存在,是否允许覆盖
- /// 成功导出的文件完整路径集合
- public IEnumerable ExportMany(IEnumerable items, string outputDirectory, bool overwrite = false)
+ public Task ExportSingleAsync(RawProUserCsvDto item, bool overwrite = false)
+ => Task.Run(() => ExportSingle(item, overwrite));
+
+ ///
+ /// 批量导出:将集合中的每一条记录分别导出为以 InBagCode 命名的 CSV 文件。
+ /// 成功项返回其路径,失败项返回空字符串(并已记录日志)。
+ ///
+ public IEnumerable ExportMany(IEnumerable items, bool overwrite = false)
{
if (items is null) throw new ArgumentNullException(nameof(items));
-
var results = new List();
- 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;
}
+ ///
+ /// 异步批量导出(并行度受限于线程池;如需更精细控制,可改为并行批处理)。
+ ///
+ public async Task> ExportManyAsync(IEnumerable 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();
+ }
+
///
/// 清理文件名中的非法字符。
///
@@ -108,44 +158,6 @@ namespace FATrace.WPLApp.Services
return sb.ToString();
}
- ///
- /// RawProUserCsvDto 的 CSV 列映射,固定输出顺序并设置日期格式。
- ///
- private sealed class RawProUserCsvDtoMap : ClassMap
- {
- 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");
-
- }
- }
+
}
}
diff --git a/FATrace.WPLApp/Services/DataServices.cs b/FATrace.WPLApp/Services/DataServices.cs
index f2fbc3a..c269f05 100644
--- a/FATrace.WPLApp/Services/DataServices.cs
+++ b/FATrace.WPLApp/Services/DataServices.cs
@@ -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
///
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]);
});
-
}
///
@@ -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().Where(a => a.BoxCode == BoxScanCode);
- var IsExist = FreeSql.Select().Where(a => a.BoxCode!.Contains(BoxScanCode));// 内包和外包一样的条码测试用 ???????????????
+ var IsExist = FreeSql.Select().Where(a => a.BoxCode == BoxScanCode);
+ //var IsExist = FreeSql.Select().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()
+ var UpdatedResult = FreeSql.Update()
.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(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()?.Publish(true); } catch { }
+
+ //try { EventAggregator?.GetEvent()?.Publish(true); } catch { }
+
+
}
///
@@ -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
///
private readonly ConcurrentQueue _messageQueue = new();
- ///
- /// 入队:将字符串加入 FIFO 队列(先进先出)。允许空字符串,不允许 null。
- ///
- /// 要加入队列的字符串
- /// 当 message 为 null 时抛出
- public void EnqueueMessage(string message)
- {
- if (message is null) throw new ArgumentNullException(nameof(message));
- _messageQueue.Enqueue(message);
- }
-
- ///
- /// 出队:尝试从 FIFO 队列取出一个字符串。
- ///
- /// 输出参数:若成功则为取出的字符串;若失败(队列为空)则为 default
- /// 是否成功取出
- public bool TryDequeueMessage(out string message)
- {
- return _messageQueue.TryDequeue(out message);
- }
-
///
/// 产线信号模型
///
@@ -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
///
/// 把字符串对调
///
- 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();
}
+ ///
+ /// 写入历史报警
+ ///
+ 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}");
+ }
+ }
+
}
}
diff --git a/FATrace.WPLApp/Services/NavigationServices.cs b/FATrace.WPLApp/Services/NavigationServices.cs
index 58eafdb..ba35e6c 100644
--- a/FATrace.WPLApp/Services/NavigationServices.cs
+++ b/FATrace.WPLApp/Services/NavigationServices.cs
@@ -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
{
diff --git a/FATrace.WPLApp/Utils/PasswordBoxAssistant.cs b/FATrace.WPLApp/Utils/PasswordBoxAssistant.cs
new file mode 100644
index 0000000..86e2aec
--- /dev/null
+++ b/FATrace.WPLApp/Utils/PasswordBoxAssistant.cs
@@ -0,0 +1,73 @@
+using System.Windows;
+using System.Windows.Controls;
+
+namespace FATrace.WPLApp.Utils
+{
+ ///
+ /// 允许将 PasswordBox 的 Password 与 ViewModel 绑定
+ /// 用法:
+ ///
+ ///
+ 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);
+ }
+ }
+ }
+}
diff --git a/FATrace.WPLApp/ViewModels/DashboardViewModel.cs b/FATrace.WPLApp/ViewModels/DashboardViewModel.cs
index e8dc2ff..618cb21 100644
--- a/FATrace.WPLApp/ViewModels/DashboardViewModel.cs
+++ b/FATrace.WPLApp/ViewModels/DashboardViewModel.cs
@@ -118,31 +118,7 @@ namespace FATrace.WPLApp.ViewModels
public DelegateCommand RefreshCommand { get; }
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;
diff --git a/FATrace.WPLApp/ViewModels/DialogHelpManualViewModel.cs b/FATrace.WPLApp/ViewModels/DialogHelpManualViewModel.cs
new file mode 100644
index 0000000..2a748a7
--- /dev/null
+++ b/FATrace.WPLApp/ViewModels/DialogHelpManualViewModel.cs
@@ -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
+ {
+
+ ///
+ /// 窗口打开时的传递的参数
+ ///
+ ///
+ public override void OnDialogOpened(IDialogParameters parameters)
+ {
+ //CurMeterName = parameters.GetValue("Name");
+ ////RefreshChartSelectedData();
+
+ //var Data = FreeSql.Select().Where(a => a.MeterName == CurMeterName).ToList();
+
+ //ConfigLimitDtoItems = new ObservableCollection(Mapper.Map>(Data));
+
+ }
+
+ }
+}
diff --git a/FATrace.WPLApp/ViewModels/HistoryAlarmViewModel.cs b/FATrace.WPLApp/ViewModels/HistoryAlarmViewModel.cs
new file mode 100644
index 0000000..fc947c1
--- /dev/null
+++ b/FATrace.WPLApp/ViewModels/HistoryAlarmViewModel.cs
@@ -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
+{
+ ///
+ /// 历史报警查询 VM
+ /// 支持按时间范围/类别/消息模糊查询,分页展示。
+ ///
+ 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();
+ CategoryList = new ObservableCollection();
+
+ // 默认时间范围为今日
+ 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 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 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();
+
+ 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()
+ .Where(a => a.Category != null && a.Category != "")
+ .Distinct()
+ .ToList(a => a.Category);
+ return (cateList ?? new System.Collections.Generic.List())
+ .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 = "全部";
+ });
+ }
+ }
+ }
+}
diff --git a/FATrace.WPLApp/ViewModels/LogViewModel.cs b/FATrace.WPLApp/ViewModels/LogViewModel.cs
new file mode 100644
index 0000000..709917f
--- /dev/null
+++ b/FATrace.WPLApp/ViewModels/LogViewModel.cs
@@ -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(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 Levels { get; }
+
+ private ObservableCollection _items = new();
+ public ObservableCollection 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(filtered);
+ }
+ catch
+ {
+ Items = new ObservableCollection();
+ }
+ finally
+ {
+ IsBusy = false;
+ }
+ }
+
+ private static IReadOnlyList ReadLogFile(string path, int maxLines)
+ {
+ var result = new LinkedList();
+ 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; }
+ }
+}
diff --git a/FATrace.WPLApp/ViewModels/LoginViewModel.cs b/FATrace.WPLApp/ViewModels/LoginViewModel.cs
new file mode 100644
index 0000000..43489d3
--- /dev/null
+++ b/FATrace.WPLApp/ViewModels/LoginViewModel.cs
@@ -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()
+ .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;
+ }
+ }
+ }
+}
diff --git a/FATrace.WPLApp/ViewModels/MainViewModel.cs b/FATrace.WPLApp/ViewModels/MainViewModel.cs
index b419dd2..a8a9057 100644
--- a/FATrace.WPLApp/ViewModels/MainViewModel.cs
+++ b/FATrace.WPLApp/ViewModels/MainViewModel.cs
@@ -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;
diff --git a/FATrace.WPLApp/ViewModels/RawProUseViewModel.cs b/FATrace.WPLApp/ViewModels/RawProUseViewModel.cs
index 24efc44..c61fde2 100644
--- a/FATrace.WPLApp/ViewModels/RawProUseViewModel.cs
+++ b/FATrace.WPLApp/ViewModels/RawProUseViewModel.cs
@@ -365,56 +365,56 @@ namespace FATrace.WPLApp.ViewModels
///
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;
+ //}
}
///
diff --git a/FATrace.WPLApp/Views/HelpManualView.xaml b/FATrace.WPLApp/Views/HelpManualView.xaml
new file mode 100644
index 0000000..f5be18d
--- /dev/null
+++ b/FATrace.WPLApp/Views/HelpManualView.xaml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/FATrace.WPLApp/Views/HelpManualView.xaml.cs b/FATrace.WPLApp/Views/HelpManualView.xaml.cs
new file mode 100644
index 0000000..fceb016
--- /dev/null
+++ b/FATrace.WPLApp/Views/HelpManualView.xaml.cs
@@ -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
+{
+ ///
+ /// HelpManualView.xaml 的交互逻辑
+ ///
+ public partial class HelpManualView : UserControl
+ {
+ public HelpManualView()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/FATrace.WPLApp/Views/HistoryAlarmView.xaml b/FATrace.WPLApp/Views/HistoryAlarmView.xaml
new file mode 100644
index 0000000..54158d5
--- /dev/null
+++ b/FATrace.WPLApp/Views/HistoryAlarmView.xaml
@@ -0,0 +1,225 @@
+
+
+
+ Segoe MDL2 Assets
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FATrace.WPLApp/Views/HistoryAlarmView.xaml.cs b/FATrace.WPLApp/Views/HistoryAlarmView.xaml.cs
new file mode 100644
index 0000000..cece8aa
--- /dev/null
+++ b/FATrace.WPLApp/Views/HistoryAlarmView.xaml.cs
@@ -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
+{
+ ///
+ /// HistoryAlarmView.xaml 的交互逻辑
+ ///
+ public partial class HistoryAlarmView : UserControl
+ {
+ public HistoryAlarmView()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/FATrace.WPLApp/Views/LogView.xaml b/FATrace.WPLApp/Views/LogView.xaml
new file mode 100644
index 0000000..8e4e93d
--- /dev/null
+++ b/FATrace.WPLApp/Views/LogView.xaml
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FATrace.WPLApp/Views/LogView.xaml.cs b/FATrace.WPLApp/Views/LogView.xaml.cs
new file mode 100644
index 0000000..d3aaee8
--- /dev/null
+++ b/FATrace.WPLApp/Views/LogView.xaml.cs
@@ -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
+{
+ ///
+ /// LogView.xaml 的交互逻辑
+ ///
+ public partial class LogView : UserControl
+ {
+ public LogView()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/FATrace.WPLApp/Views/LoginView.xaml b/FATrace.WPLApp/Views/LoginView.xaml
new file mode 100644
index 0000000..ba85865
--- /dev/null
+++ b/FATrace.WPLApp/Views/LoginView.xaml
@@ -0,0 +1,134 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FATrace.WPLApp/Views/LoginView.xaml.cs b/FATrace.WPLApp/Views/LoginView.xaml.cs
new file mode 100644
index 0000000..bd510dc
--- /dev/null
+++ b/FATrace.WPLApp/Views/LoginView.xaml.cs
@@ -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
+{
+ ///
+ /// LoginView.xaml 的交互逻辑
+ ///
+ public partial class LoginView : UserControl
+ {
+ public LoginView()
+ {
+ InitializeComponent();
+ }
+ }
+}