From 0a1d0274072d167562ad37453a1f8c30e60517a2 Mon Sep 17 00:00:00 2001 From: SugarChes Date: Tue, 21 Jul 2026 10:02:11 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=A4=96=E9=83=A8=20?= =?UTF-8?q?WPF=20=E4=B8=BB=E7=95=8C=E9=9D=A2=E6=8E=A5=E7=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 插件库/Lskj.Main/FrmLogin.cs | 4 +- 插件库/Lskj.Main/FrmSubSystem.cs | 4 +- .../Lskj.Main/Hosting/IExternalMainShell.cs | 19 +++++++++ .../Hosting/LegacyApplicationHost.cs | 29 +++++++++++++ 插件库/Lskj.Main/Hosting/README.md | 35 ++++++++++++++++ 插件库/Lskj.Main/Lskj.Main.csproj | 2 + 插件库/Lskj.Main/Model/Manager.cs | 42 +++++++++++++++++-- 插件库/Lskj.Main/Program.cs | 8 ++++ 8 files changed, 136 insertions(+), 7 deletions(-) create mode 100644 插件库/Lskj.Main/Hosting/IExternalMainShell.cs create mode 100644 插件库/Lskj.Main/Hosting/LegacyApplicationHost.cs create mode 100644 插件库/Lskj.Main/Hosting/README.md diff --git a/插件库/Lskj.Main/FrmLogin.cs b/插件库/Lskj.Main/FrmLogin.cs index 6de4ef2..4515a2b 100644 --- a/插件库/Lskj.Main/FrmLogin.cs +++ b/插件库/Lskj.Main/FrmLogin.cs @@ -1284,7 +1284,7 @@ namespace Lskj.Main DialogResult result = MessageUtil.Show(ResourceKeys.IsExit, MessageBoxButtons.YesNo); if (result == DialogResult.Yes) { - Process[] p = Process.GetProcessesByName("Ls_ERP"); + Process[] p = Manager.GetApplicationProcesses(); foreach (Process item in p) { try @@ -1920,4 +1920,4 @@ namespace Lskj.Main isPlaintextPwd = !isPlaintextPwd; } } -} \ No newline at end of file +} diff --git a/插件库/Lskj.Main/FrmSubSystem.cs b/插件库/Lskj.Main/FrmSubSystem.cs index 19c6e15..1e22645 100644 --- a/插件库/Lskj.Main/FrmSubSystem.cs +++ b/插件库/Lskj.Main/FrmSubSystem.cs @@ -546,7 +546,7 @@ namespace Lskj.Main //记录登出日志 LogUtil.WriteDebug("", "退出软件", "软件退出", "系统登录"); - Process[] p = Process.GetProcessesByName("Ls_ERP"); + Process[] p = Manager.GetApplicationProcesses(); foreach (Process item in p) { try @@ -716,4 +716,4 @@ namespace Lskj.Main } #endregion } -} \ No newline at end of file +} diff --git a/插件库/Lskj.Main/Hosting/IExternalMainShell.cs b/插件库/Lskj.Main/Hosting/IExternalMainShell.cs new file mode 100644 index 0000000..53d0ef0 --- /dev/null +++ b/插件库/Lskj.Main/Hosting/IExternalMainShell.cs @@ -0,0 +1,19 @@ +using System.Windows.Forms; + +namespace Lskj.Main.Hosting +{ + /// + /// 旧启动流程与可选新主界面之间的中立扩展点。 + /// 此接口只使用 .NET Framework 4.0/WinForms 基础类型,不引用 WPF 或 DevExpress 25.2。 + /// + public interface IExternalMainShell + { + bool IsOpen { get; } + + DialogResult ShowDialog(); + + void RequestRelogin(); + + void RequestExit(string message); + } +} diff --git a/插件库/Lskj.Main/Hosting/LegacyApplicationHost.cs b/插件库/Lskj.Main/Hosting/LegacyApplicationHost.cs new file mode 100644 index 0000000..febbf40 --- /dev/null +++ b/插件库/Lskj.Main/Hosting/LegacyApplicationHost.cs @@ -0,0 +1,29 @@ +using System; +using Lskj.Main.Model; + +namespace Lskj.Main.Hosting +{ + /// + /// 为独立的新启动程序公开原 Ls_ERP 初始化和登录流程。 + /// + public static class LegacyApplicationHost + { + public static void Run(string[] args, IExternalMainShell externalMainShell) + { + if (externalMainShell == null) + throw new ArgumentNullException("externalMainShell"); + if (Manager.ExternalMainShell != null) + throw new InvalidOperationException("旧程序已经由另一个外部主界面托管。"); + + Manager.ExternalMainShell = externalMainShell; + try + { + Program.RunFromExternalHost(args); + } + finally + { + Manager.ExternalMainShell = null; + } + } + } +} diff --git a/插件库/Lskj.Main/Hosting/README.md b/插件库/Lskj.Main/Hosting/README.md new file mode 100644 index 0000000..0673a43 --- /dev/null +++ b/插件库/Lskj.Main/Hosting/README.md @@ -0,0 +1,35 @@ +# 外部主界面兼容层 + +这个目录只为旧 `.NET Framework 4.0` 启动流程提供一个可选扩展点,不引用 WPF 或 DevExpress 25.2。 + +## 默认路线 + +直接运行 `Ls_ERP.exe` 时,`Manager.ExternalMainShell` 为 `null`: + +```text +Program.Main + -> Manager.StartForm + -> FrmLogin + -> Manager.RunMainForm + -> FrmMain +``` + +这条路线不加载任何 WPF 程序集,并保持 DevExpress WinForms 15.2。 + +## 外部宿主路线 + +`.NET Framework 4.8` 的独立启动程序通过 `LegacyApplicationHost.Run` 注入 `IExternalMainShell`。旧程序仍负责 CEF、全局异常、配置、数据库、登录和子系统选择,只在 `RunMainForm` 位置回调外部主界面。 + +`IExternalMainShell` 当前承担: + +- 同步显示外部主窗口; +- 返回普通关闭或重新登录结果; +- 处理旧插件发起的重新登录; +- 在强制下线时通知外部主窗口退出。 + +## 修改规则 + +- 此层必须继续可以在 .NET Framework 4.0 下编译。 +- 禁止引用 `Lserp.Wpf.*`、WPF 类型或 DevExpress 25.2。 +- 新界面特有实现放在独立 WPF 解决方案的 `Lserp.Wpf.Legacy.WinForms`。 +- 新增兼容能力时优先扩展中立接口,不要在旧工程中直接调用 WPF。 diff --git a/插件库/Lskj.Main/Lskj.Main.csproj b/插件库/Lskj.Main/Lskj.Main.csproj index 1bc8efc..355288c 100644 --- a/插件库/Lskj.Main/Lskj.Main.csproj +++ b/插件库/Lskj.Main/Lskj.Main.csproj @@ -276,6 +276,8 @@ + + diff --git a/插件库/Lskj.Main/Model/Manager.cs b/插件库/Lskj.Main/Model/Manager.cs index de4de7b..f34e07f 100644 --- a/插件库/Lskj.Main/Model/Manager.cs +++ b/插件库/Lskj.Main/Model/Manager.cs @@ -15,6 +15,7 @@ using Lskj.Control; using Lskj.Control.Model; using Lskj.Core; using Lskj.Data; +using Lskj.Main.Hosting; using Lskj.Model; using Lskj.Util; using Lskj.Web.Core.Util; @@ -55,6 +56,10 @@ namespace Lskj.Main.Model public static int isAwaitTime = 10; public static int RefreshTime = 0; /// + /// 由外部启动程序提供的可选主界面。默认为 null,旧 Ls_ERP.exe 仍打开 FrmMain。 + /// + public static IExternalMainShell ExternalMainShell { get; internal set; } + /// /// 默认下载地址 /// private static string filePath = string.Empty; @@ -190,7 +195,12 @@ namespace Lskj.Main.Model if (isStart) { // 直接进入主界面 - RunMainForm(); + result = RunMainForm(); + if (result == DialogResult.Retry) + { + StartForm(); + return; + } if (ERPInfo.Instance.SubMenuCount > 1) { ReStartMain(); @@ -686,6 +696,10 @@ namespace Lskj.Main.Model /// DialogResult. private static DialogResult RunMainForm() { + IExternalMainShell externalMainShell = ExternalMainShell; + if (externalMainShell != null) + return externalMainShell.ShowDialog(); + _frmMain = new FrmMain(); return _frmMain.ShowDialog(); } @@ -909,6 +923,11 @@ namespace Lskj.Main.Model } })); } + else if (ExternalMainShell != null && ExternalMainShell.IsOpen) + { + ExternalMainShell.RequestExit($"当前用户在另一电脑登录\r\nClientIp:{clientip}\r\nMacAdress:{loginMacAdress}\r\n当前系统即将关闭"); + isClose = true; + } else if (_frmMain != null && _frmMain.Visible == true) { _frmMain.Invoke(new Action(() => @@ -953,7 +972,7 @@ namespace Lskj.Main.Model } //记录登出日志 LogUtil.WriteDebug("", "退出软件", "软件退出", "系统登录"); - Process[] p = Process.GetProcessesByName("Ls_ERP"); + Process[] p = GetApplicationProcesses(); foreach (Process item in p) { try @@ -1107,6 +1126,12 @@ namespace Lskj.Main.Model /// public static void ReLogin() { + IExternalMainShell externalMainShell = ExternalMainShell; + if (externalMainShell != null && externalMainShell.IsOpen) + { + externalMainShell.RequestRelogin(); + return; + } if (_frmMain != null) { if (ERPInfo.Instance.WatermarkForm != null) @@ -1121,5 +1146,16 @@ namespace Lskj.Main.Model StartForm(); } + /// + /// 返回当前启动路线需要退出的主进程。 + /// 旧入口保持原有 Ls_ERP 进程名语义,外部宿主只退出当前 WPF 进程。 + /// + internal static Process[] GetApplicationProcesses() + { + return ExternalMainShell == null + ? Process.GetProcessesByName("Ls_ERP") + : new[] { Process.GetCurrentProcess() }; + } + } -} \ No newline at end of file +} diff --git a/插件库/Lskj.Main/Program.cs b/插件库/Lskj.Main/Program.cs index 6a1afc1..9956002 100644 --- a/插件库/Lskj.Main/Program.cs +++ b/插件库/Lskj.Main/Program.cs @@ -92,6 +92,14 @@ namespace Lskj.Main } } + /// + /// 允许 .NET Framework 4.8 外部宿主复用原程序的 CEF、皮肤、异常、注册和登录初始化。 + /// 正常启动 Ls_ERP.exe 仍由 Main 直接进入,不经过此方法。 + /// + internal static void RunFromExternalHost(string[] args) + { + Main(args ?? new string[0]); + } #region 浏览器处理类 /// /// 说明:浏览器处理类