| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473 |
- using System;
- using System.Collections.Generic;
- using System.Drawing;
- using System.Linq;
- using System.Windows.Forms;
- namespace DRG.DRGInterfaceClient
- {
- public partial class MainForm : Form
- {
- private TextBox txtEndpoint;
- private Label lblHosp;
- private CheckedListBox clbHospital;
- private Button btnLoadHosp;
- private Button btnSelectAll;
- private Button btnSelectNone;
- private Label lblSelectedCount;
- private Button btnLoadConfig;
- private Button btnSyncNow;
- private Button btnStartSchedule;
- private Button btnStopSchedule;
- private Button btnRegisterTask;
- private Button btnSetScheduledHosp;
- private Button btnOpenLog;
- private Button btnClearLog;
- private GroupBox grpConfig;
- private Dictionary<string, Label> cfgLabelMap;
- private TextBox txtLog;
- private TrayIcon tray;
- /// <summary>勾选列表中最后一个勾选的机构,用于配置预览面板与 session。</summary>
- private HospitalInfoItem _previewHosp;
- private Dictionary<string, object> _session;
- private SyncConfig _syncConfig;
- private SchedulerService _scheduler;
- private bool _exiting;
- private bool _bulkUpdating;
- public MainForm()
- {
- InitializeComponent();
- _scheduler = new SchedulerService(this);
- Logger.OnLog += Logger_OnLog;
- tray = new TrayIcon(this);
- Load += (s, e) =>
- {
- txtEndpoint.Text = Config.Current.Endpoint;
- _ = LoadHospitalsAsync();
- };
- FormClosing += MainForm_FormClosing;
- Disposed += (s, e) => { Logger.OnLog -= Logger_OnLog; tray?.Dispose(); };
- }
- // ===== UI 构建 =====
- private void InitializeComponent()
- {
- Text = "DRG 接口客户端";
- Size = new Size(760, 700);
- StartPosition = FormStartPosition.CenterScreen;
- MinimumSize = new Size(640, 480);
- var lblEp = new Label { Left = 8, Top = 15, Width = 76, Text = "接口地址:" };
- txtEndpoint = new TextBox { Left = 90, Top = 12, Width = 640, Text = "http://127.0.0.1:52773/csp/drg/sysInternalMutiple" };
- lblHosp = new Label { Left = 8, Top = 47, Width = 76, Text = "医疗机构:" };
- clbHospital = new CheckedListBox
- {
- Left = 90,
- Top = 44,
- Width = 360,
- Height = 90,
- CheckOnClick = true,
- DisplayMember = "descripts"
- };
- btnLoadHosp = new Button { Left = 460, Top = 42, Width = 100, Text = "加载机构" };
- btnSelectAll = new Button { Left = 460, Top = 78, Width = 48, Text = "全选" };
- btnSelectNone = new Button { Left = 512, Top = 78, Width = 48, Text = "全否" };
- lblSelectedCount = new Label { Left = 566, Top = 80, Width = 170, Text = "已选 0 家" };
- grpConfig = new GroupBox { Left = 8, Top = 148, Width = 724, Height = 110, Text = "同步配置(选中机构预览)" };
- string[] names = { "syncType", "frequency", "scheduledTime", "lookbackDays", "enabled", "synFlag", "warningFlag", "status" };
- cfgLabelMap = new Dictionary<string, Label>();
- for (int i = 0; i < names.Length; i++)
- {
- int col = i % 4; int row = i / 4;
- var l = new Label
- {
- Left = 12 + col * 180,
- Top = 22 + row * 40,
- Width = 170,
- Height = 32,
- Text = (FieldNames.TryGetValue(names[i], out var fn) ? fn : names[i]) + ": (未加载)",
- BorderStyle = BorderStyle.FixedSingle,
- BackColor = SystemColors.ControlLight
- };
- cfgLabelMap[names[i]] = l;
- grpConfig.Controls.Add(l);
- }
- btnLoadConfig = new Button { Left = 8, Top = 272, Width = 110, Text = "加载配置" };
- btnSyncNow = new Button { Left = 124, Top = 272, Width = 110, Text = "立即执行一次" };
- btnStartSchedule = new Button { Left = 240, Top = 272, Width = 110, Text = "开始调度" };
- btnStopSchedule = new Button { Left = 356, Top = 272, Width = 110, Text = "停止调度" };
- btnRegisterTask = new Button { Left = 472, Top = 272, Width = 120, Text = "注册任务计划" };
- btnSetScheduledHosp = new Button { Left = 598, Top = 272, Width = 134, Text = "设为定时机构" };
- btnOpenLog = new Button { Left = 8, Top = 308, Width = 110, Text = "打开日志" };
- btnClearLog = new Button { Left = 124, Top = 308, Width = 110, Text = "清空日志" };
- txtLog = new TextBox
- {
- Left = 8,
- Top = 344,
- Width = 724,
- Height = 320,
- Multiline = true,
- ReadOnly = true,
- ScrollBars = ScrollBars.Vertical,
- Font = new Font("Consolas", 9)
- };
- Controls.AddRange(new Control[] { lblEp, txtEndpoint, lblHosp, clbHospital, btnLoadHosp,
- btnSelectAll, btnSelectNone, lblSelectedCount, grpConfig, btnLoadConfig, btnSyncNow,
- btnStartSchedule, btnStopSchedule, btnRegisterTask, btnSetScheduledHosp,
- btnOpenLog, btnClearLog, txtLog });
- btnLoadHosp.Click += async (s, e) => await LoadHospitalsAsync();
- btnLoadConfig.Click += async (s, e) => await LoadPreviewConfigAsync(_previewHosp);
- btnSyncNow.Click += async (s, e) => await DoSyncAsync();
- btnStartSchedule.Click += (s, e) => StartSchedule();
- btnStopSchedule.Click += (s, e) => StopSchedule();
- btnRegisterTask.Click += async (s, e) => await RegisterTaskNow();
- btnSetScheduledHosp.Click += (s, e) => SetScheduledHosp();
- btnOpenLog.Click += (s, e) => Logger.OpenLogFile();
- btnClearLog.Click += (s, e) => { Logger.Clear(); txtLog.Clear(); };
- btnSelectAll.Click += (s, e) => SelectAllHospitals(true);
- btnSelectNone.Click += (s, e) => SelectAllHospitals(false);
- clbHospital.ItemCheck += (s, e) => OnHospitalItemCheck();
- }
- // ===== 校验 =====
- private bool EnsureEndpoint()
- {
- string ep = txtEndpoint.Text.Trim();
- if (string.IsNullOrEmpty(ep))
- {
- MessageBox.Show("接口地址不能为空", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
- txtEndpoint.Focus();
- return false;
- }
- Config.Current.Endpoint = ep;
- return true;
- }
- private bool EnsureHospitals(out List<HospitalInfoItem> list)
- {
- list = GetCheckedHospitals();
- if (list.Count == 0)
- {
- MessageBox.Show("请至少勾选一家医疗机构", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
- clbHospital.Focus();
- return false;
- }
- return true;
- }
- // ===== 机构勾选 =====
- private List<HospitalInfoItem> GetCheckedHospitals()
- {
- var list = new List<HospitalInfoItem>();
- for (int i = 0; i < clbHospital.Items.Count; i++)
- if (clbHospital.GetItemChecked(i) && clbHospital.Items[i] is HospitalInfoItem h)
- list.Add(h);
- return list;
- }
- private HospitalInfoItem GetLastCheckedHospital()
- {
- HospitalInfoItem last = null;
- for (int i = 0; i < clbHospital.Items.Count; i++)
- if (clbHospital.GetItemChecked(i) && clbHospital.Items[i] is HospitalInfoItem h)
- last = h;
- return last;
- }
- private void SelectAllHospitals(bool check)
- {
- _bulkUpdating = true;
- for (int i = 0; i < clbHospital.Items.Count; i++)
- clbHospital.SetItemChecked(i, check);
- _bulkUpdating = false;
- RefreshSelectionUI();
- }
- /// <summary>勾选变化后刷新「已选 N 家」与配置预览。ItemCheck 时控件状态尚未提交,故用 BeginInvoke 延后读取。</summary>
- private void OnHospitalItemCheck()
- {
- if (_bulkUpdating) return;
- clbHospital.BeginInvoke(new Action(RefreshSelectionUI));
- }
- private void RefreshSelectionUI()
- {
- UpdateSelectedCount();
- _previewHosp = GetLastCheckedHospital();
- if (_previewHosp != null)
- {
- _session = SessionBuilder.Build(_previewHosp, Config.Current.SessionDefaults);
- _ = LoadPreviewConfigAsync(_previewHosp);
- }
- else
- {
- _session = null;
- _syncConfig = null;
- ClearConfigDisplay();
- }
- }
- private void UpdateSelectedCount()
- {
- if (lblSelectedCount.InvokeRequired) { lblSelectedCount.Invoke(new Action(UpdateSelectedCount)); return; }
- lblSelectedCount.Text = "已选 " + GetCheckedHospitals().Count + " 家";
- }
- // ===== 业务动作 =====
- private async System.Threading.Tasks.Task LoadHospitalsAsync()
- {
- if (!EnsureEndpoint()) return;
- var resp = await DrgApiClient.QueryHospitalInfoAsync("", "",
- SessionBuilder.BuildDefault(Config.Current.SessionDefaults));
- if (!DrgApiClient.IsOk(resp))
- {
- MessageBox.Show("加载机构失败:" + DrgApiClient.GetError(resp), "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
- return;
- }
- var arr = DrgApiClient.GetResultArray(resp);
- clbHospital.Items.Clear();
- if (arr == null || arr.Length == 0)
- {
- MessageBox.Show("未查询到医疗机构", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
- UpdateSelectedCount();
- return;
- }
- var scheduled = Config.Current.ScheduledHospCodes ?? new List<string>();
- _bulkUpdating = true;
- foreach (var o in arr)
- {
- if (o is Dictionary<string, object> d)
- {
- var h = DrgApiClient.ParseHospital(d);
- int idx = clbHospital.Items.Add(h);
- if (scheduled.Contains(h.code, StringComparer.OrdinalIgnoreCase))
- clbHospital.SetItemChecked(idx, true);
- }
- }
- _bulkUpdating = false;
- RefreshSelectionUI();
- }
- /// <summary>加载指定机构的 02010079 配置用于预览面板(不弹窗,仅日志)。</summary>
- private async System.Threading.Tasks.Task LoadPreviewConfigAsync(HospitalInfoItem hosp)
- {
- if (hosp == null) { ClearConfigDisplay(); return; }
- if (!EnsureEndpoint()) return;
- var session = SessionBuilder.Build(hosp, Config.Current.SessionDefaults);
- var resp = await DrgApiClient.GetSyncConfigAsync(hosp.hospCode, session);
- if (!DrgApiClient.IsOk(resp))
- {
- Logger.Error(string.Format("预览配置加载失败(机构 {0}):{1}", hosp.descripts, DrgApiClient.GetError(resp)));
- ClearConfigDisplay();
- return;
- }
- _syncConfig = DrgApiClient.ParseSyncConfig(DrgApiClient.GetResult(resp));
- UpdateConfigDisplay(_syncConfig);
- }
- /// <summary>对勾选的所有机构顺序执行 02010067 同步(每家各自取 02010079 配置)。</summary>
- public async System.Threading.Tasks.Task DoSyncAsync()
- {
- if (InvokeRequired) { Invoke(new Action(() => _ = DoSyncAsync())); return; }
- if (!EnsureEndpoint()) return;
- if (!EnsureHospitals(out var list)) return;
- int ok = 0, fail = 0;
- foreach (var hosp in list)
- {
- var session = SessionBuilder.Build(hosp, Config.Current.SessionDefaults);
- var cfgResp = await DrgApiClient.GetSyncConfigAsync(hosp.hospCode, session);
- if (!DrgApiClient.IsOk(cfgResp))
- {
- Logger.Error(string.Format("机构 {0} 配置加载失败:{1}", hosp.descripts, DrgApiClient.GetError(cfgResp)));
- fail++;
- continue;
- }
- var cfg = DrgApiClient.ParseSyncConfig(DrgApiClient.GetResult(cfgResp));
- var (st, ed) = DrgApiClient.ComputeDateRange(
- cfg.syncType, cfg.lookbackDays, Config.Current.FullSyncStartDays);
- Logger.Info(string.Format("开始同步 机构={0} syncType={1} 区间[{2},{3}]",
- hosp.descripts, cfg.syncType, st, ed));
- var resp = await DrgApiClient.GetInpatientDataAsync(
- hosp.hospCode, st, ed, cfg.synFlag ?? "Y", session);
- if (DrgApiClient.IsOk(resp))
- {
- Logger.Info(string.Format("机构 {0} 同步成功", hosp.descripts));
- ok++;
- }
- else
- {
- Logger.Error(string.Format("机构 {0} 同步失败:{1}", hosp.descripts, DrgApiClient.GetError(resp)));
- fail++;
- }
- }
- Logger.Info(string.Format("批量同步完成:成功 {0} 家,失败 {1} 家(共 {2} 家)", ok, fail, list.Count));
- }
- /// <summary>供 SchedulerService 在整点调用(同步所有勾选机构)。</summary>
- public void TriggerSync() { _ = DoSyncAsync(); }
- private void StartSchedule()
- {
- if (!EnsureHospitals(out var list)) return;
- // 多机构统一采用程序内整点轮询(覆盖所有勾选机构);daily/weekly 外部定时请用「注册任务计划」。
- _scheduler.StartHourly();
- tray?.ShowBalloon("已启动内部整点轮询(覆盖 " + list.Count + " 家机构)");
- }
- private void StopSchedule()
- {
- _scheduler.Stop();
- TaskSchedulerHelper.Delete(Config.Current.TaskName);
- tray?.ShowBalloon("已停止调度");
- }
- private async System.Threading.Tasks.Task RegisterTaskNow()
- {
- if (!EnsureHospitals(out var list)) return;
- // 取每家配置,决定任务频率(任一为 weekly 则 WEEKLY,否则 DAILY),计划时间取首个。
- string freq = "daily";
- string scheduledTime = "";
- foreach (var hosp in list)
- {
- var session = SessionBuilder.Build(hosp, Config.Current.SessionDefaults);
- var cfgResp = await DrgApiClient.GetSyncConfigAsync(hosp.hospCode, session);
- if (!DrgApiClient.IsOk(cfgResp)) continue;
- var cfg = DrgApiClient.ParseSyncConfig(DrgApiClient.GetResult(cfgResp));
- if (cfg.frequency == "weekly") freq = "weekly";
- if (string.IsNullOrEmpty(scheduledTime)) scheduledTime = cfg.scheduledTime;
- }
- string exe = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "DRGInterfaceClient.exe");
- TaskSchedulerHelper.Register(Config.Current.TaskName, exe, freq, scheduledTime);
- MessageBox.Show(string.Format("已注册计划任务({0},覆盖 {1} 家机构):{2}",
- freq == "weekly" ? "每周" : "每日", list.Count, Config.Current.TaskName),
- "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
- }
- private void SetScheduledHosp()
- {
- if (!EnsureHospitals(out var list)) return;
- Config.Current.ScheduledHospCodes = list.ConvertAll(h => h.code);
- Config.Save();
- MessageBox.Show(string.Format("已设为定时机构({0} 家)", list.Count),
- "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
- }
- // ===== 配置展示(中文) =====
- private static readonly Dictionary<string, string> FieldNames = new Dictionary<string, string>
- {
- { "syncType", "同步类型" },
- { "frequency", "调度频率" },
- { "scheduledTime", "计划时间" },
- { "lookbackDays", "回溯天数" },
- { "enabled", "是否启用" },
- { "synFlag", "同步标志" },
- { "warningFlag", "预警标志" },
- { "status", "状态" }
- };
- private void UpdateConfigDisplay(SyncConfig c)
- {
- if (c == null) { ClearConfigDisplay(); return; }
- SetCfg("syncType", c.syncType);
- SetCfg("frequency", c.frequency);
- SetCfg("scheduledTime", c.scheduledTime);
- SetCfg("lookbackDays", c.lookbackDays.ToString());
- SetCfg("enabled", c.enabled ? "1" : "0");
- SetCfg("synFlag", c.synFlag);
- SetCfg("warningFlag", c.warningFlag);
- SetCfg("status", c.status);
- }
- private void ClearConfigDisplay()
- {
- foreach (var k in cfgLabelMap.Keys) SetCfg(k, "");
- }
- private void SetCfg(string key, string val)
- {
- if (!cfgLabelMap.TryGetValue(key, out var l)) return;
- string name = FieldNames.TryGetValue(key, out var n) ? n : key;
- l.Text = string.IsNullOrEmpty(val) ? (name + ": (未加载)") : (name + ": " + TranslateValue(key, val));
- }
- /// <summary>将配置字段的原始值翻译为中文展示。</summary>
- private static string TranslateValue(string key, string raw)
- {
- if (string.IsNullOrEmpty(raw)) return raw;
- switch (key)
- {
- case "syncType":
- if (raw == "full") return "全量";
- if (raw == "incremental") return "增量";
- return raw;
- case "frequency":
- if (raw == "hourly") return "每小时";
- if (raw == "daily") return "每日";
- if (raw == "weekly") return "每周";
- if (raw == "manual") return "手动";
- return raw;
- case "enabled":
- case "synFlag":
- case "warningFlag":
- if (raw == "1" || raw == "Y" || raw.Equals("true", StringComparison.OrdinalIgnoreCase)) return "是";
- if (raw == "0" || raw == "N" || raw.Equals("false", StringComparison.OrdinalIgnoreCase)) return "否";
- return raw;
- case "status":
- if (raw == "1") return "正常";
- if (raw == "0") return "停用";
- return raw;
- default:
- return raw;
- }
- }
- // ===== 托盘 / 关闭 =====
- public void ShowMain()
- {
- if (WindowState == FormWindowState.Minimized) WindowState = FormWindowState.Normal;
- Show();
- BringToFront();
- }
- public void ExitApp()
- {
- _exiting = true;
- _scheduler?.Stop();
- tray?.Dispose();
- Logger.OnLog -= Logger_OnLog;
- Application.Exit();
- }
- private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
- {
- if (!_exiting)
- {
- e.Cancel = true;
- Hide();
- tray?.ShowBalloon("已最小化到托盘");
- }
- }
- private void Logger_OnLog(string line)
- {
- if (txtLog.InvokeRequired) { txtLog.Invoke(new Action<string>(Logger_OnLog), line); return; }
- txtLog.AppendText(line + Environment.NewLine);
- }
- }
- }
|