using Microsoft.Extensions.Configuration; using Microsoft.Data.Sqlite; using OfficeOpenXml; using System; using System.IO; using System.Text.Json; 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(); 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"); } } protected override void OnStartup(StartupEventArgs e) { 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(); // 数据库初始化(手动建表) var connectionString = configuration.GetConnectionString("DefaultConnection") ?? "Data Source=TabletTests.db"; using (var connection = new Microsoft.Data.Sqlite.SqliteConnection(connectionString)) { 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, HardnessInternalMin REAL, HardnessInternalMax REAL, -- 脆碎度 FriabilityLoss REAL, FriabilityTargetRpm REAL, FriabilityTargetTimeSec INTEGER, FriabilityTargetRounds INTEGER, FriabilityClockwise INTEGER, -- bool 存储为 0/1 FriabilityRemainingRounds INTEGER, WeightBefore REAL, WeightAfter REAL, -- 崩解 DisintegrationTimeSec REAL, RemainingTubesAtEnd INTEGER, DisintegrationTargetFreq REAL, DisintegrationTemp REAL, DisintegrationDosageForm TEXT, DisintegrationLimitSeconds INTEGER, -- 溶出 DissolutionChannel TEXT, DissolutionRate30Min REAL, DissolutionTargetRpm REAL, DissolutionRSquared REAL, DissolutionSampleInterval INTEGER, Dissolution1SampleInterval REAL, Dissolution2SampleInterval REAL, DissolutionUpDownFreq REAL, -- 综合 IsQualified INTEGER, -- 各项目单独合格标志 HardnessPass INTEGER, FriabilityPass INTEGER, DisintegrationPass INTEGER, DissolutionPass INTEGER, -- 测试类型(用于区分报表) TestType TEXT ); "; cmd.ExecuteNonQuery(); 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(); } EnsureColumnsExist(connectionString); // 绑定药典参数 configuration.GetSection("PharmaStandard").Bind(CurrentPharmaParams); LoadLocalPharmaParameters(); // PLC配置 PlcConfig = configuration.GetSection("Plc").Get(); if (PlcConfig == null) throw new InvalidOperationException("PLC配置缺失"); PlcService = new ModbusTcpPlcService(PlcConfig); // 业务服务 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(); } 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(File.ReadAllText(path)); if (local != null) CurrentPharmaParams = local; } catch { // Keep appsettings defaults if the local parameter file is damaged. } } private void EnsureColumnsExist(string connectionString) { var requiredColumns = new[] { ("TestBatches", "HardnessMax", "REAL", "0"), ("TestBatches", "HardnessMin", "REAL", "0"), ("TestBatches", "HardnessTestCount", "INTEGER", "6"), ("TestBatches", "HardnessInternalMin", "REAL", "40"), ("TestBatches", "HardnessInternalMax", "REAL", "60"), ("TestBatches", "FriabilityTargetRpm", "REAL", "25"), ("TestBatches", "FriabilityTargetTimeSec", "INTEGER", "240"), ("TestBatches", "FriabilityTargetRounds", "INTEGER", "100"), ("TestBatches", "FriabilityClockwise", "INTEGER", "1"), ("TestBatches", "FriabilityRemainingRounds", "INTEGER", "100"), ("TestBatches", "WeightBefore", "REAL", "0"), ("TestBatches", "WeightAfter", "REAL", "0"), ("TestBatches", "DisintegrationTargetFreq", "REAL", "31"), ("TestBatches", "DisintegrationTemp", "REAL", "37"), ("TestBatches", "DisintegrationDosageForm", "TEXT", "'普通片'"), ("TestBatches", "DisintegrationLimitSeconds", "INTEGER", "900"), ("TestBatches", "DissolutionChannel", "TEXT", "''"), ("TestBatches", "DissolutionTargetRpm", "REAL", "50"), ("TestBatches", "DissolutionRSquared", "REAL", "0"), ("TestBatches", "DissolutionSampleInterval", "INTEGER", "5"), ("TestBatches", "Dissolution1SampleInterval", "REAL", "5"), ("TestBatches", "Dissolution2SampleInterval", "REAL", "5"), ("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", "") }; 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}"); } } } } }