2026-05-05 15:31:24 +08:00
|
|
|
|
using Microsoft.Extensions.Configuration;
|
|
|
|
|
|
using Microsoft.Data.Sqlite;
|
|
|
|
|
|
using OfficeOpenXml;
|
|
|
|
|
|
using System;
|
|
|
|
|
|
using System.IO;
|
2026-05-18 15:18:28 +08:00
|
|
|
|
using System.Text.Json;
|
2026-05-05 15:31:24 +08:00
|
|
|
|
using System.Windows;
|
|
|
|
|
|
using TabletTester2025.Data;
|
|
|
|
|
|
using TabletTester2025.Models;
|
|
|
|
|
|
using TabletTester2025.Services;
|
|
|
|
|
|
using TabletTester2025.ViewModels;
|
|
|
|
|
|
|
|
|
|
|
|
namespace TabletTester2025
|
|
|
|
|
|
{
|
|
|
|
|
|
public partial class App : Application
|
|
|
|
|
|
{
|
|
|
|
|
|
public static IPlcService PlcService { get; private set; }
|
|
|
|
|
|
public static PlcConfiguration PlcConfig { get; private set; }
|
|
|
|
|
|
public static PharmaParameters CurrentPharmaParams { get; set; } = new PharmaParameters();
|
2026-05-18 15:18:28 +08:00
|
|
|
|
private static readonly JsonSerializerOptions PharmaJsonOptions = new() { WriteIndented = true };
|
|
|
|
|
|
|
|
|
|
|
|
public static string PharmaParametersFilePath
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
string directory = Path.Combine(
|
|
|
|
|
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
|
|
|
|
|
"CSI-Z420-Tablet-Multi-Function-Tester");
|
|
|
|
|
|
return Path.Combine(directory, "pharma-standard.json");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-05 15:31:24 +08:00
|
|
|
|
|
2026-05-16 17:47:46 +08:00
|
|
|
|
protected override void OnStartup(StartupEventArgs e)
|
2026-05-05 15:31:24 +08:00
|
|
|
|
{
|
|
|
|
|
|
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
|
|
|
|
|
|
base.OnStartup(e);
|
|
|
|
|
|
|
|
|
|
|
|
// 读取配置
|
|
|
|
|
|
var builder = new ConfigurationBuilder()
|
|
|
|
|
|
.SetBasePath(Directory.GetCurrentDirectory())
|
|
|
|
|
|
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
|
|
|
|
|
|
var configuration = builder.Build();
|
|
|
|
|
|
|
2026-05-06 16:41:32 +08:00
|
|
|
|
// 数据库初始化(手动建表)
|
2026-05-05 15:31:24 +08:00
|
|
|
|
var connectionString = configuration.GetConnectionString("DefaultConnection") ?? "Data Source=TabletTests.db";
|
2026-05-06 16:41:32 +08:00
|
|
|
|
using (var connection = new Microsoft.Data.Sqlite.SqliteConnection(connectionString))
|
2026-05-05 15:31:24 +08:00
|
|
|
|
{
|
2026-05-06 16:41:32 +08:00
|
|
|
|
connection.Open();
|
|
|
|
|
|
var cmd = connection.CreateCommand();
|
|
|
|
|
|
cmd.CommandText = @"
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS TestBatches (
|
|
|
|
|
|
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
|
TestTime TEXT NOT NULL,
|
|
|
|
|
|
StationId INTEGER NOT NULL,
|
|
|
|
|
|
SampleName TEXT,
|
|
|
|
|
|
|
|
|
|
|
|
-- 硬度
|
|
|
|
|
|
HardnessAvg REAL,
|
|
|
|
|
|
HardnessRSD REAL,
|
|
|
|
|
|
HardnessMax REAL,
|
|
|
|
|
|
HardnessMin REAL,
|
|
|
|
|
|
HardnessTestCount INTEGER,
|
2026-05-18 14:06:04 +08:00
|
|
|
|
HardnessInternalMin REAL,
|
|
|
|
|
|
HardnessInternalMax REAL,
|
2026-05-06 16:41:32 +08:00
|
|
|
|
|
|
|
|
|
|
-- 脆碎度
|
|
|
|
|
|
FriabilityLoss REAL,
|
|
|
|
|
|
FriabilityTargetRpm REAL,
|
|
|
|
|
|
FriabilityTargetTimeSec INTEGER,
|
2026-05-18 14:06:04 +08:00
|
|
|
|
FriabilityTargetRounds INTEGER,
|
2026-05-06 16:41:32 +08:00
|
|
|
|
FriabilityClockwise INTEGER, -- bool 存储为 0/1
|
|
|
|
|
|
FriabilityRemainingRounds INTEGER,
|
|
|
|
|
|
WeightBefore REAL,
|
|
|
|
|
|
WeightAfter REAL,
|
|
|
|
|
|
|
|
|
|
|
|
-- 崩解
|
|
|
|
|
|
DisintegrationTimeSec REAL,
|
|
|
|
|
|
RemainingTubesAtEnd INTEGER,
|
|
|
|
|
|
DisintegrationTargetFreq REAL,
|
|
|
|
|
|
DisintegrationTemp REAL,
|
2026-05-18 14:06:04 +08:00
|
|
|
|
DisintegrationDosageForm TEXT,
|
|
|
|
|
|
DisintegrationLimitSeconds INTEGER,
|
2026-05-06 16:41:32 +08:00
|
|
|
|
|
|
|
|
|
|
-- 溶出
|
2026-05-16 16:58:57 +08:00
|
|
|
|
DissolutionChannel TEXT,
|
2026-05-06 16:41:32 +08:00
|
|
|
|
DissolutionRate30Min REAL,
|
|
|
|
|
|
DissolutionTargetRpm REAL,
|
|
|
|
|
|
DissolutionRSquared REAL,
|
|
|
|
|
|
DissolutionSampleInterval INTEGER,
|
2026-05-18 09:47:22 +08:00
|
|
|
|
Dissolution1SampleInterval REAL,
|
|
|
|
|
|
Dissolution2SampleInterval REAL,
|
2026-05-06 16:41:32 +08:00
|
|
|
|
DissolutionUpDownFreq REAL,
|
2026-05-05 15:31:24 +08:00
|
|
|
|
|
2026-05-06 16:41:32 +08:00
|
|
|
|
-- 综合
|
|
|
|
|
|
IsQualified INTEGER,
|
|
|
|
|
|
|
|
|
|
|
|
-- 各项目单独合格标志
|
|
|
|
|
|
HardnessPass INTEGER,
|
|
|
|
|
|
FriabilityPass INTEGER,
|
|
|
|
|
|
DisintegrationPass INTEGER,
|
|
|
|
|
|
DissolutionPass INTEGER,
|
|
|
|
|
|
|
|
|
|
|
|
-- 测试类型(用于区分报表)
|
|
|
|
|
|
TestType TEXT
|
|
|
|
|
|
);
|
|
|
|
|
|
";
|
|
|
|
|
|
cmd.ExecuteNonQuery();
|
2026-05-18 14:06:04 +08:00
|
|
|
|
|
|
|
|
|
|
var sampleCmd = connection.CreateCommand();
|
|
|
|
|
|
sampleCmd.CommandText = @"
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS DissolutionSamplePoints (
|
|
|
|
|
|
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
|
TestBatchId INTEGER NOT NULL,
|
|
|
|
|
|
Channel INTEGER NOT NULL,
|
|
|
|
|
|
ScheduledTimeMin REAL NOT NULL,
|
|
|
|
|
|
ActualTimeMin REAL,
|
|
|
|
|
|
Percent REAL,
|
|
|
|
|
|
RecordedAt TEXT,
|
|
|
|
|
|
FOREIGN KEY(TestBatchId) REFERENCES TestBatches(Id) ON DELETE CASCADE
|
|
|
|
|
|
);
|
|
|
|
|
|
";
|
|
|
|
|
|
sampleCmd.ExecuteNonQuery();
|
2026-05-06 16:41:32 +08:00
|
|
|
|
}
|
2026-05-16 16:58:57 +08:00
|
|
|
|
EnsureColumnsExist(connectionString);
|
|
|
|
|
|
|
2026-05-05 15:31:24 +08:00
|
|
|
|
// 绑定药典参数
|
|
|
|
|
|
configuration.GetSection("PharmaStandard").Bind(CurrentPharmaParams);
|
2026-05-18 15:18:28 +08:00
|
|
|
|
LoadLocalPharmaParameters();
|
2026-05-05 15:31:24 +08:00
|
|
|
|
|
|
|
|
|
|
// PLC配置
|
|
|
|
|
|
PlcConfig = configuration.GetSection("Plc").Get<PlcConfiguration>();
|
|
|
|
|
|
if (PlcConfig == null)
|
|
|
|
|
|
throw new InvalidOperationException("PLC配置缺失");
|
|
|
|
|
|
|
2026-05-16 17:47:46 +08:00
|
|
|
|
PlcService = new ModbusTcpPlcService(PlcConfig);
|
2026-05-05 15:31:24 +08:00
|
|
|
|
|
|
|
|
|
|
// 业务服务
|
|
|
|
|
|
var dbService = new DatabaseService(connectionString);
|
|
|
|
|
|
var excelService = new ExcelExportService();
|
|
|
|
|
|
var alarmService = new AlarmService();
|
|
|
|
|
|
|
|
|
|
|
|
// 主窗口
|
|
|
|
|
|
var mainWindow = new MainWindow();
|
|
|
|
|
|
mainWindow.DataContext = new MainViewModel(PlcService, dbService, excelService, alarmService, PlcConfig);
|
|
|
|
|
|
MainWindow = mainWindow;
|
|
|
|
|
|
mainWindow.Show();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 15:18:28 +08:00
|
|
|
|
public static void SaveCurrentPharmaParameters()
|
|
|
|
|
|
{
|
|
|
|
|
|
string path = PharmaParametersFilePath;
|
|
|
|
|
|
string? directory = Path.GetDirectoryName(path);
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(directory))
|
|
|
|
|
|
Directory.CreateDirectory(directory);
|
|
|
|
|
|
|
|
|
|
|
|
File.WriteAllText(path, JsonSerializer.Serialize(CurrentPharmaParams, PharmaJsonOptions));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void LoadLocalPharmaParameters()
|
|
|
|
|
|
{
|
|
|
|
|
|
string path = PharmaParametersFilePath;
|
|
|
|
|
|
if (!File.Exists(path))
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var local = JsonSerializer.Deserialize<PharmaParameters>(File.ReadAllText(path));
|
|
|
|
|
|
if (local != null)
|
|
|
|
|
|
CurrentPharmaParams = local;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
// Keep appsettings defaults if the local parameter file is damaged.
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 15:31:24 +08:00
|
|
|
|
private void EnsureColumnsExist(string connectionString)
|
|
|
|
|
|
{
|
|
|
|
|
|
var requiredColumns = new[]
|
2026-05-06 16:41:32 +08:00
|
|
|
|
{
|
|
|
|
|
|
("TestBatches", "HardnessMax", "REAL", "0"),
|
|
|
|
|
|
("TestBatches", "HardnessMin", "REAL", "0"),
|
|
|
|
|
|
("TestBatches", "HardnessTestCount", "INTEGER", "6"),
|
2026-05-18 14:06:04 +08:00
|
|
|
|
("TestBatches", "HardnessInternalMin", "REAL", "40"),
|
|
|
|
|
|
("TestBatches", "HardnessInternalMax", "REAL", "60"),
|
2026-05-06 16:41:32 +08:00
|
|
|
|
("TestBatches", "FriabilityTargetRpm", "REAL", "25"),
|
|
|
|
|
|
("TestBatches", "FriabilityTargetTimeSec", "INTEGER", "240"),
|
2026-05-18 14:06:04 +08:00
|
|
|
|
("TestBatches", "FriabilityTargetRounds", "INTEGER", "100"),
|
2026-05-06 16:41:32 +08:00
|
|
|
|
("TestBatches", "FriabilityClockwise", "INTEGER", "1"),
|
|
|
|
|
|
("TestBatches", "FriabilityRemainingRounds", "INTEGER", "100"),
|
|
|
|
|
|
("TestBatches", "WeightBefore", "REAL", "0"),
|
|
|
|
|
|
("TestBatches", "WeightAfter", "REAL", "0"),
|
|
|
|
|
|
("TestBatches", "DisintegrationTargetFreq", "REAL", "31"),
|
|
|
|
|
|
("TestBatches", "DisintegrationTemp", "REAL", "37"),
|
2026-05-18 14:06:04 +08:00
|
|
|
|
("TestBatches", "DisintegrationDosageForm", "TEXT", "'普通片'"),
|
|
|
|
|
|
("TestBatches", "DisintegrationLimitSeconds", "INTEGER", "900"),
|
2026-05-16 16:58:57 +08:00
|
|
|
|
("TestBatches", "DissolutionChannel", "TEXT", "''"),
|
2026-05-06 16:41:32 +08:00
|
|
|
|
("TestBatches", "DissolutionTargetRpm", "REAL", "50"),
|
|
|
|
|
|
("TestBatches", "DissolutionRSquared", "REAL", "0"),
|
|
|
|
|
|
("TestBatches", "DissolutionSampleInterval", "INTEGER", "5"),
|
2026-05-18 09:47:22 +08:00
|
|
|
|
("TestBatches", "Dissolution1SampleInterval", "REAL", "5"),
|
|
|
|
|
|
("TestBatches", "Dissolution2SampleInterval", "REAL", "5"),
|
2026-05-06 16:41:32 +08:00
|
|
|
|
("TestBatches", "DissolutionUpDownFreq", "REAL", "32"),
|
|
|
|
|
|
("TestBatches", "HardnessRSD", "REAL", "0"), // 已存在但确保
|
|
|
|
|
|
("TestBatches", "RemainingTubesAtEnd", "INTEGER", "0"),
|
|
|
|
|
|
// 新增合格列
|
|
|
|
|
|
("TestBatches", "HardnessPass", "INTEGER", "0"),
|
|
|
|
|
|
("TestBatches", "FriabilityPass", "INTEGER", "0"),
|
|
|
|
|
|
("TestBatches", "DisintegrationPass", "INTEGER", "0"),
|
|
|
|
|
|
("TestBatches", "DissolutionPass", "INTEGER", "0"),
|
|
|
|
|
|
|
|
|
|
|
|
("TestBatches", "TestType", "TEXT", "")
|
|
|
|
|
|
};
|
2026-05-05 15:31:24 +08:00
|
|
|
|
|
|
|
|
|
|
using var connection = new SqliteConnection(connectionString);
|
|
|
|
|
|
connection.Open();
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var (table, column, colType, defaultValue) in requiredColumns)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 检查列是否存在
|
|
|
|
|
|
var pragmaCmd = connection.CreateCommand();
|
|
|
|
|
|
pragmaCmd.CommandText = $"PRAGMA table_info({table})";
|
|
|
|
|
|
using var reader = pragmaCmd.ExecuteReader();
|
|
|
|
|
|
bool columnExists = false;
|
|
|
|
|
|
while (reader.Read())
|
|
|
|
|
|
{
|
|
|
|
|
|
if (reader.GetString(1) == column)
|
|
|
|
|
|
{
|
|
|
|
|
|
columnExists = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!columnExists)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 添加缺失的列
|
|
|
|
|
|
var alterCmd = connection.CreateCommand();
|
|
|
|
|
|
alterCmd.CommandText = $"ALTER TABLE {table} ADD COLUMN {column} {colType} DEFAULT {defaultValue}";
|
|
|
|
|
|
alterCmd.ExecuteNonQuery();
|
|
|
|
|
|
System.Diagnostics.Debug.WriteLine($"已添加缺失的列: {table}.{column}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-16 16:58:57 +08:00
|
|
|
|
}
|