diff --git a/yy-admin-master/YY.Admin-Release-win-x64-publish.zip b/yy-admin-master/YY.Admin-Release-win-x64-publish.zip
new file mode 100644
index 0000000..998d676
Binary files /dev/null and b/yy-admin-master/YY.Admin-Release-win-x64-publish.zip differ
diff --git a/yy-admin-master/YY.Admin.Core/SqlSugar/SqlSugarSetup.cs b/yy-admin-master/YY.Admin.Core/SqlSugar/SqlSugarSetup.cs
index 94ab22f..5a9df0c 100644
--- a/yy-admin-master/YY.Admin.Core/SqlSugar/SqlSugarSetup.cs
+++ b/yy-admin-master/YY.Admin.Core/SqlSugar/SqlSugarSetup.cs
@@ -10,6 +10,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
+using System.IO;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Reflection;
@@ -19,7 +20,9 @@ using Yitter.IdGenerator;
using YY.Admin.Core;
using YY.Admin.Core.Extension;
using YY.Admin.Core.Option;
+using YY.Admin.Core.SeedData;
using YY.Admin.Core.Session;
+using YY.Admin.Core.Util;
using DbType = SqlSugar.DbType;
namespace YY.Admin.Core.SqlSugar
@@ -160,6 +163,12 @@ namespace YY.Admin.Core.SqlSugar
config.ConnectionString = CryptogramUtil.Decrypt(config.ConnectionString);
}
+ // SQLite 相对路径默认依赖进程工作目录;快捷方式/从不同目录启动会在别处生成空库,表现为发布后菜单全空
+ if (config.DbType == DbType.Sqlite && !string.IsNullOrWhiteSpace(config.ConnectionString))
+ {
+ config.ConnectionString = ResolveSqliteConnectionToAbsolutePath(config.ConnectionString);
+ }
+
var configureExternalServices = new ConfigureExternalServices
{
EntityNameService = (type, entity) => // 处理表
@@ -250,6 +259,96 @@ namespace YY.Admin.Core.SqlSugar
//if (config.DbSettings.EnableInitView) InitView(dbProvider);
// 初始化种子数据
if (config.SeedSettings.EnableInitSeed) InitSeedData(db, config);
+ // 关闭全量种子时首启可能无菜单数据;补一份基准菜单,避免打包版本左侧空白
+ EnsureBaselineSysMenuSeed(db, config);
+ }
+
+ ///
+ /// 将 SQLite 连接串中的相对 DataSource 解析为基于应用程序基目录的绝对路径。
+ ///
+ private static string ResolveSqliteConnectionToAbsolutePath(string connectionString)
+ {
+ try
+ {
+ var builder = new SqliteConnectionStringBuilder(connectionString);
+ var ds = builder.DataSource;
+ if (string.IsNullOrWhiteSpace(ds))
+ {
+ return connectionString;
+ }
+
+ if (Path.IsPathFullyQualified(ds))
+ {
+ return connectionString;
+ }
+
+ // Program Files 等安装目录对普通用户只读;SQLite 库放到 LocalApplicationData
+ var dataDir = AppWritablePaths.EnsureDirectoryExists(AppWritablePaths.DataDirectory);
+ var fileName = Path.GetFileName(ds.TrimStart('.', '/', '\\'));
+ if (string.IsNullOrWhiteSpace(fileName))
+ {
+ fileName = "Admin.NET.db";
+ }
+
+ builder.DataSource = Path.Combine(dataDir, fileName);
+ return builder.ConnectionString;
+ }
+ catch
+ {
+ return connectionString;
+ }
+ }
+
+ ///
+ /// 未启用 EnableInitSeed 且 sys_menu 为空时,写入 SysMenu 种子,避免发布后界面无功能菜单。
+ ///
+ private static void EnsureBaselineSysMenuSeed(SqlSugarScope db, DbConnectionConfig config)
+ {
+ try
+ {
+ if (config.SeedSettings.EnableInitSeed)
+ {
+ return;
+ }
+
+ if (!string.Equals(config.ConfigId.ToString(), SqlSugarConst.MainConfigId, StringComparison.Ordinal))
+ {
+ return;
+ }
+
+ if (config.DbType != DbType.Sqlite)
+ {
+ return;
+ }
+
+ var dbProvider = db.GetConnectionScope(config.ConfigId);
+ var menuEntityInfo = dbProvider.EntityMaintenance.GetEntityInfo(typeof(SysMenu));
+ if (!dbProvider.DbMaintenance.IsAnyTable(menuEntityInfo.DbTableName, false))
+ {
+ return;
+ }
+
+ var cnt = dbProvider.Queryable().ClearFilter().Count();
+ if (cnt > 0)
+ {
+ return;
+ }
+
+ var seedType = typeof(SysMenuSeedData);
+ var seedData = GetSeedData(seedType)?.ToList();
+ if (seedData == null || seedData.Count == 0)
+ {
+ return;
+ }
+
+ AdjustSeedDataIds(seedData, config);
+ var progress = 0;
+ InsertOrUpdateSeedData(dbProvider, seedType, typeof(SysMenu), seedData, config, ref progress, 1);
+ }
+ catch
+ {
+ // 启动阶段不因兜底种子失败而阻断
+ }
}
///
diff --git a/yy-admin-master/YY.Admin.Core/Util/AppWritablePaths.cs b/yy-admin-master/YY.Admin.Core/Util/AppWritablePaths.cs
new file mode 100644
index 0000000..cd5abca
--- /dev/null
+++ b/yy-admin-master/YY.Admin.Core/Util/AppWritablePaths.cs
@@ -0,0 +1,38 @@
+using System.IO;
+
+namespace YY.Admin.Core.Util;
+
+///
+/// 当前用户可写应用数据目录(避免安装在 Program Files 时无写权限)。
+///
+public static class AppWritablePaths
+{
+ private static readonly string Root = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
+ "YY.Admin");
+
+ /// 应用私有根目录:%LocalAppData%\YY.Admin
+ public static string LocalApplicationRoot => Root;
+
+ /// SQLite 等业务数据库目录。
+ public static string DataDirectory => Path.Combine(Root, "Data");
+
+ /// 用户覆盖的配置、Jeecg 同步状态等。
+ public static string ConfigurationDirectory => Path.Combine(Root, "Configuration");
+
+ /// 按账号划分的本地设置(对应 CommonConst.AppSettingsFilePath 前缀)。
+ public static string AccountSettingsRootDirectory => Path.Combine(Root, "AppSettings");
+
+ ///
+ /// 创建目录(若不存在)并返回路径。
+ ///
+ public static string EnsureDirectoryExists(string directoryPath)
+ {
+ if (!string.IsNullOrWhiteSpace(directoryPath))
+ {
+ Directory.CreateDirectory(directoryPath);
+ }
+
+ return directoryPath;
+ }
+}
diff --git a/yy-admin-master/YY.Admin.Services/Service/Jeecg/JeecgLoginLogReportService.cs b/yy-admin-master/YY.Admin.Services/Service/Jeecg/JeecgLoginLogReportService.cs
index b9b94f3..03f4e24 100644
--- a/yy-admin-master/YY.Admin.Services/Service/Jeecg/JeecgLoginLogReportService.cs
+++ b/yy-admin-master/YY.Admin.Services/Service/Jeecg/JeecgLoginLogReportService.cs
@@ -3,6 +3,7 @@ using System.Net.Http;
using System.IO;
using System.Text;
using System.Text.Json;
+using YY.Admin.Core.Util;
namespace YY.Admin.Services.Service.Jeecg;
@@ -226,7 +227,8 @@ public class JeecgLoginLogReportService : IJeecgLoginLogReportService, IClientLo
private static string GetQueuePath()
{
- return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AppSettings", "offline-scada-log-queue.jsonl");
+ var dir = AppWritablePaths.EnsureDirectoryExists(AppWritablePaths.AccountSettingsRootDirectory);
+ return Path.Combine(dir, "offline-scada-log-queue.jsonl");
}
private static void EnsureQueueDir(string path)
diff --git a/yy-admin-master/YY.Admin.Services/Service/Jeecg/JeecgSyncStateStore.cs b/yy-admin-master/YY.Admin.Services/Service/Jeecg/JeecgSyncStateStore.cs
index abedaf7..6b34859 100644
--- a/yy-admin-master/YY.Admin.Services/Service/Jeecg/JeecgSyncStateStore.cs
+++ b/yy-admin-master/YY.Admin.Services/Service/Jeecg/JeecgSyncStateStore.cs
@@ -1,10 +1,11 @@
using System.IO;
using System.Text.Json;
+using YY.Admin.Core.Util;
namespace YY.Admin.Services.Service.Jeecg
{
///
- /// 读写本地 Jeecg 同步状态文件(与 appsettings 同目录下的 Configuration)
+ /// 读写本地 Jeecg 同步状态文件(用户可写目录,避免 Program Files 无写权限)。
///
public class JeecgSyncStateStore
{
@@ -12,7 +13,7 @@ namespace YY.Admin.Services.Service.Jeecg
public JeecgSyncStateStore()
{
- var dir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configuration");
+ var dir = AppWritablePaths.EnsureDirectoryExists(AppWritablePaths.ConfigurationDirectory);
_filePath = Path.Combine(dir, "jeecg-sync-state.json");
}
diff --git a/yy-admin-master/YY.Admin/App.xaml.cs b/yy-admin-master/YY.Admin/App.xaml.cs
index 2345a90..f6cc0cf 100644
--- a/yy-admin-master/YY.Admin/App.xaml.cs
+++ b/yy-admin-master/YY.Admin/App.xaml.cs
@@ -4,7 +4,9 @@ using Mapster;
using Microsoft.Extensions.Configuration;
using NewLife;
using System.IO;
+using System.Text;
using System.Windows;
+using System.Windows.Threading;
using YY.Admin.Core;
using YY.Admin.EventBus;
using YY.Admin.Filter;
@@ -14,6 +16,7 @@ using YY.Admin.Services.Service.Print;
using YY.Admin.Setup;
using YY.Admin.ViewModels;
using YY.Admin.Views;
+using YY.Admin.Core.Util;
namespace YY.Admin
{
///
@@ -24,32 +27,116 @@ namespace YY.Admin
private IConfiguration? _configuration;
private ILoggerService? _logger;
private readonly SyncModule _syncModule = new();
+
+ public App()
+ {
+ DispatcherUnhandledException += OnDispatcherUnhandledException;
+ AppDomain.CurrentDomain.UnhandledException += OnUnhandledDomainException;
+ }
+
+ private static void TryWriteCrashLog(string headline, Exception ex)
+ {
+ try
+ {
+ var dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "YY.Admin", "logs");
+ Directory.CreateDirectory(dir);
+ var file = Path.Combine(dir, $"crash-{DateTime.Now:yyyyMMdd-HHmmss}.txt");
+ var sb = new StringBuilder();
+ sb.AppendLine(headline);
+ sb.AppendLine(ex.ToString());
+ File.WriteAllText(file, sb.ToString(), Encoding.UTF8);
+ MessageBox.Show($"程序异常已写入日志:\n{file}\n\n{ex.Message}", "智能制造MES工控", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ catch
+ {
+ MessageBox.Show($"{headline}\n{ex}", "智能制造MES工控", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+ private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
+ {
+ TryWriteCrashLog("UI 线程未处理异常", e.Exception);
+ e.Handled = true;
+ Shutdown(1);
+ }
+
+ private void OnUnhandledDomainException(object? sender, UnhandledExceptionEventArgs e)
+ {
+ if (e.ExceptionObject is Exception ex)
+ {
+ TryWriteCrashLog("域未处理异常", ex);
+ }
+
+ if (e.IsTerminating)
+ {
+ Environment.Exit(1);
+ }
+ }
+
protected override Window CreateShell()
{
return Container.Resolve();
}
protected override void OnStartup(StartupEventArgs e)
{
- var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
- // 构建配置
- _configuration = new ConfigurationBuilder()
- .SetBasePath(baseDirectory)
- .AddJsonFile("Configuration/appsettings.json", optional: false, reloadOnChange: true)
- .Build();
- // 全局配置
- TypeAdapterConfig.GlobalSettings.Default
- .IgnoreNullValues(true)
- .NameMatchingStrategy(NameMatchingStrategy.IgnoreCase);
+ try
+ {
+ var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
+ var cfgPath = Path.Combine(baseDirectory, "Configuration", "appsettings.json");
+ if (!File.Exists(cfgPath))
+ {
+ var msg = $"未找到配置文件(请确认与 YY.Admin.exe 同目录存在 Configuration\\appsettings.json):\n{cfgPath}";
+ TryWriteStartupFailure(msg);
+ Shutdown(1);
+ return;
+ }
- // FluentValidation 全局规则级别配置
- ValidatorOptions.Global.DefaultRuleLevelCascadeMode = CascadeMode.Stop;
+ var userCfgPath = Path.Combine(
+ AppWritablePaths.EnsureDirectoryExists(AppWritablePaths.ConfigurationDirectory),
+ "appsettings.json");
- // Mapster 全局配置
- #if DEBUG
- //TypeAdapterConfig.GlobalSettings.RequireExplicitMapping = true;
- #endif
+ // 构建配置:安装目录默认 + 用户目录覆盖(JeecgIntegration 等可由服务器设置写入)
+ _configuration = new ConfigurationBuilder()
+ .SetBasePath(baseDirectory)
+ .AddJsonFile(Path.Combine("Configuration", "appsettings.json"), optional: false, reloadOnChange: true)
+ .AddJsonFile(userCfgPath, optional: true, reloadOnChange: true)
+ .Build();
+ // 全局配置
+ TypeAdapterConfig.GlobalSettings.Default
+ .IgnoreNullValues(true)
+ .NameMatchingStrategy(NameMatchingStrategy.IgnoreCase);
- base.OnStartup(e);
+ // FluentValidation 全局规则级别配置
+ ValidatorOptions.Global.DefaultRuleLevelCascadeMode = CascadeMode.Stop;
+
+ // Mapster 全局配置
+ #if DEBUG
+ //TypeAdapterConfig.GlobalSettings.RequireExplicitMapping = true;
+ #endif
+
+ base.OnStartup(e);
+ }
+ catch (Exception ex)
+ {
+ TryWriteCrashLog("启动阶段异常(配置/框架初始化失败)", ex);
+ Shutdown(1);
+ }
+ }
+
+ private static void TryWriteStartupFailure(string message)
+ {
+ try
+ {
+ var dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "YY.Admin", "logs");
+ Directory.CreateDirectory(dir);
+ var file = Path.Combine(dir, $"startup-{DateTime.Now:yyyyMMdd-HHmmss}.txt");
+ File.WriteAllText(file, message, Encoding.UTF8);
+ MessageBox.Show($"{message}\n\n已记录:{file}", "智能制造MES工控", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ catch
+ {
+ MessageBox.Show(message, "智能制造MES工控", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
}
//注册
protected override void RegisterTypes(IContainerRegistry containerRegistry)
diff --git a/yy-admin-master/YY.Admin/Helper/ServerSettingsStore.cs b/yy-admin-master/YY.Admin/Helper/ServerSettingsStore.cs
index aace8dd..acb7c10 100644
--- a/yy-admin-master/YY.Admin/Helper/ServerSettingsStore.cs
+++ b/yy-admin-master/YY.Admin/Helper/ServerSettingsStore.cs
@@ -1,6 +1,7 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.IO;
+using YY.Admin.Core.Util;
namespace YY.Admin.Helper
{
@@ -11,6 +12,37 @@ namespace YY.Admin.Helper
{
private const string DefaultWebSocketPath = "/websocket/scada-sync";
+ ///
+ /// 安装目录随包发布的默认配置(只读)。
+ ///
+ public static string GetBundledAppSettingsPath()
+ {
+ return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configuration", "appsettings.json");
+ }
+
+ ///
+ /// 用户覆盖配置(可写),仅覆盖 JeecgIntegration 节点时使用。
+ ///
+ public static string GetUserAppSettingsPath()
+ {
+ var dir = AppWritablePaths.EnsureDirectoryExists(AppWritablePaths.ConfigurationDirectory);
+ return Path.Combine(dir, "appsettings.json");
+ }
+
+ ///
+ /// 兼容旧调用:优先返回用于读取的实际路径(存在用户覆盖则用用户文件)。
+ ///
+ public static string GetConfigPath()
+ {
+ var user = GetUserAppSettingsPath();
+ if (File.Exists(user))
+ {
+ return user;
+ }
+
+ return GetBundledAppSettingsPath();
+ }
+
public class ServerSettingsModel
{
public string Ip { get; set; } = "127.0.0.1";
@@ -25,22 +57,35 @@ namespace YY.Admin.Helper
public bool DisconnectConnection { get; set; } = false;
}
- public static string GetConfigPath()
+ private static JObject LoadMergedRoot()
{
- return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configuration", "appsettings.json");
+ var bundledPath = GetBundledAppSettingsPath();
+ if (!File.Exists(bundledPath))
+ {
+ throw new FileNotFoundException("未找到安装目录默认配置文件 appsettings.json", bundledPath);
+ }
+
+ var root = JObject.Parse(File.ReadAllText(bundledPath));
+ var userPath = GetUserAppSettingsPath();
+ if (!File.Exists(userPath))
+ {
+ return root;
+ }
+
+ var userRoot = JObject.Parse(File.ReadAllText(userPath));
+ var userJeecg = userRoot["JeecgIntegration"] as JObject;
+ if (userJeecg != null)
+ {
+ root["JeecgIntegration"] = userJeecg;
+ }
+
+ return root;
}
public static ServerSettingsModel Load()
{
var model = new ServerSettingsModel();
- var path = GetConfigPath();
- if (!File.Exists(path))
- {
- return model;
- }
-
- var content = File.ReadAllText(path);
- var root = JObject.Parse(content);
+ var root = LoadMergedRoot();
var jeecg = root["JeecgIntegration"] as JObject;
if (jeecg == null)
{
@@ -64,14 +109,7 @@ namespace YY.Admin.Helper
public static void Save(ServerSettingsModel model)
{
- var path = GetConfigPath();
- if (!File.Exists(path))
- {
- throw new FileNotFoundException("未找到配置文件 appsettings.json", path);
- }
-
- var content = File.ReadAllText(path);
- var root = JObject.Parse(content);
+ var root = LoadMergedRoot();
var jeecg = root["JeecgIntegration"] as JObject;
if (jeecg == null)
{
@@ -95,7 +133,9 @@ namespace YY.Admin.Helper
jeecg["WebSocketPath"] = webSocketPath;
jeecg["DisconnectConnection"] = model.DisconnectConnection;
- File.WriteAllText(path, root.ToString(Formatting.Indented));
+ var userPath = GetUserAppSettingsPath();
+ var outRoot = new JObject { ["JeecgIntegration"] = jeecg };
+ File.WriteAllText(userPath, outRoot.ToString(Formatting.Indented));
}
public static string BuildDefaultWebSocketUrl(string baseScheme, string ip, int port, string basePath, string webSocketPath = DefaultWebSocketPath)
@@ -106,6 +146,7 @@ namespace YY.Admin.Helper
{
safeBasePath = "/" + safeBasePath;
}
+
var safeWsPath = NormalizeWebSocketPath(webSocketPath);
return $"{safeScheme}://{ip}:{port}{safeBasePath}{safeWsPath}";
}
@@ -117,6 +158,7 @@ namespace YY.Admin.Helper
{
value = "/" + value;
}
+
return value;
}
}
diff --git a/yy-admin-master/YY.Admin/Helper/WebView2UserDataFolder.cs b/yy-admin-master/YY.Admin/Helper/WebView2UserDataFolder.cs
new file mode 100644
index 0000000..0b8a423
--- /dev/null
+++ b/yy-admin-master/YY.Admin/Helper/WebView2UserDataFolder.cs
@@ -0,0 +1,23 @@
+using System.IO;
+using Microsoft.Web.WebView2.Wpf;
+using YY.Admin.Core.Util;
+
+namespace YY.Admin.Helper;
+
+///
+/// WebView2 默认将用户数据目录放在宿主 exe 旁边;安装在 Program Files 时目录只读会导致初始化失败、预览白屏。
+/// 统一到当前用户 LocalAppData 下的可写路径。
+///
+public static class WebView2UserDataFolder
+{
+ ///
+ /// 为控件创建 CreationProperties(须在首次 EnsureCoreWebView2Async 之前赋值)。
+ ///
+ /// 子目录名,避免不同场景争用同一 profile。
+ public static CoreWebView2CreationProperties CreateCreationProperties(string subFolder)
+ {
+ var folder = AppWritablePaths.EnsureDirectoryExists(
+ Path.Combine(AppWritablePaths.LocalApplicationRoot, "WebView2", subFolder));
+ return new CoreWebView2CreationProperties { UserDataFolder = folder };
+ }
+}
diff --git a/yy-admin-master/YY.Admin/Infrastructure/Print/HtmlToPdfRenderer.cs b/yy-admin-master/YY.Admin/Infrastructure/Print/HtmlToPdfRenderer.cs
index d2edf37..25cd11c 100644
--- a/yy-admin-master/YY.Admin/Infrastructure/Print/HtmlToPdfRenderer.cs
+++ b/yy-admin-master/YY.Admin/Infrastructure/Print/HtmlToPdfRenderer.cs
@@ -3,6 +3,7 @@ using System.Text;
using System.Windows;
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.Wpf;
+using YY.Admin.Core.Util;
namespace YY.Admin.Infrastructure.Print;
@@ -42,7 +43,9 @@ public static class HtmlToPdfRenderer
{
File.WriteAllText(tempHtml, html, Encoding.UTF8);
- var env = await CoreWebView2Environment.CreateAsync();
+ var userData = AppWritablePaths.EnsureDirectoryExists(
+ Path.Combine(AppWritablePaths.LocalApplicationRoot, "WebView2", "HtmlToPdf"));
+ var env = await CoreWebView2Environment.CreateAsync(browserExecutableFolder: null, userDataFolder: userData);
var wv = new WebView2();
win = new Window
diff --git a/yy-admin-master/YY.Admin/Module/NavigationExtensions.cs b/yy-admin-master/YY.Admin/Module/NavigationExtensions.cs
index 540358c..121fcbf 100644
--- a/yy-admin-master/YY.Admin/Module/NavigationExtensions.cs
+++ b/yy-admin-master/YY.Admin/Module/NavigationExtensions.cs
@@ -1,6 +1,6 @@
-
using System.Windows;
using System.Windows.Media;
+using Prism.Dialogs;
using YY.Admin.ViewModels.Control;
using YY.Admin.ViewModels.Dialogs;
using YY.Admin.Views;
@@ -20,6 +20,15 @@ using YY.Admin.Views.Print;
namespace YY.Admin
{
+ ///
+ /// Prism DialogService 中 使用的宿主窗口注册名。
+ ///
+ public static class DialogWindowNames
+ {
+ /// 标准边框、可调整大小,且不使用 AllowsTransparency(服务器设置等需改 WindowStyle 的对话框)。
+ public const string ChromeDialogWindow = "ChromeDialogWindow";
+ }
+
public static class NavigationExtensions
{
///
@@ -35,8 +44,9 @@ namespace YY.Admin
containerRegistry.RegisterDialog("ConfirmDialog");
containerRegistry.RegisterDialog("ServerSettingsDialog");
- // 设置对话框样式
+ // 默认透明无边框宿主;需调整 WindowStyle/AllowsTransparency 的对话框改用命名宿主 ChromeDialogWindow
containerRegistry.RegisterDialogWindow();
+ containerRegistry.RegisterDialogWindow(DialogWindowNames.ChromeDialogWindow);
// 注册导航
containerRegistry.RegisterForNavigation("DashboardView");
@@ -103,6 +113,30 @@ namespace YY.Admin
}
public IDialogResult? Result { get; set; }
}
+
+ ///
+ /// 标准窗口边框宿主:从构造起即 AllowsTransparency=false,避免窗口显示后再切换透明属性引发异常。
+ ///
+ public class ChromeDialogWindow : Window, IDialogWindow
+ {
+ public ChromeDialogWindow()
+ {
+ AllowsTransparency = false;
+ Background = SystemColors.WindowBrush;
+ WindowStyle = WindowStyle.SingleBorderWindow;
+ ResizeMode = ResizeMode.CanResizeWithGrip;
+ WindowStartupLocation = WindowStartupLocation.CenterOwner;
+ // 按内容测量客户区高度;勿将 Window.Height 设为与 UserControl 相同数值,
+ // 否则会与标题栏/边框抢高度导致底部按钮被裁切。
+ SizeToContent = SizeToContent.WidthAndHeight;
+ MinWidth = 520;
+ MinHeight = 360;
+ Title = "对话框";
+ }
+
+ public IDialogResult? Result { get; set; }
+ }
+
//public class DialogWindow : Window, IDialogWindow
//{
// public DialogWindow()
diff --git a/yy-admin-master/YY.Admin/ViewModels/AppSettingsViewModel.cs b/yy-admin-master/YY.Admin/ViewModels/AppSettingsViewModel.cs
index 39c097a..1b852b9 100644
--- a/yy-admin-master/YY.Admin/ViewModels/AppSettingsViewModel.cs
+++ b/yy-admin-master/YY.Admin/ViewModels/AppSettingsViewModel.cs
@@ -11,6 +11,7 @@ using YY.Admin.Core.EventBus;
using YY.Admin.Core.Helper;
using YY.Admin.Core.Model;
using YY.Admin.Core.Session;
+using YY.Admin.Core.Util;
using HcSkinType = HandyControl.Data.SkinType;
namespace YY.Admin.ViewModels
@@ -167,7 +168,10 @@ namespace YY.Admin.ViewModels
public static string GetFilePath()
{
string filePathSuffix = string.Format(CommonConst.AppSettingsFilePath, AppSession.CurrentUser!.Account);
- return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, filePathSuffix);
+ var fullPath = Path.Combine(AppWritablePaths.LocalApplicationRoot, filePathSuffix);
+ var dir = Path.GetDirectoryName(fullPath);
+ AppWritablePaths.EnsureDirectoryExists(dir!);
+ return fullPath;
}
///
diff --git a/yy-admin-master/YY.Admin/ViewModels/LoginWindowViewModel.cs b/yy-admin-master/YY.Admin/ViewModels/LoginWindowViewModel.cs
index 2ae9053..7d422ba 100644
--- a/yy-admin-master/YY.Admin/ViewModels/LoginWindowViewModel.cs
+++ b/yy-admin-master/YY.Admin/ViewModels/LoginWindowViewModel.cs
@@ -2,6 +2,8 @@ using System.Windows;
using System.Windows.Media;
using System.Net.Http;
using Microsoft.Extensions.Configuration;
+using Prism.Dialogs;
+using YY.Admin;
using YY.Admin.Core.Helper;
using YY.Admin.Core.Session;
using YY.Admin.FluentValidation;
@@ -291,7 +293,11 @@ namespace YY.Admin.ViewModels
private void OpenServerSettings()
{
- _dialogService.ShowDialog("ServerSettingsDialog", r =>
+ var parameters = new DialogParameters
+ {
+ { KnownDialogParameters.WindowName, DialogWindowNames.ChromeDialogWindow },
+ };
+ _dialogService.ShowDialog("ServerSettingsDialog", parameters, r =>
{
if (r.Result == ButtonResult.OK)
{
diff --git a/yy-admin-master/YY.Admin/ViewModels/MainWindowViewModel.cs b/yy-admin-master/YY.Admin/ViewModels/MainWindowViewModel.cs
index 73c958d..f337fba 100644
--- a/yy-admin-master/YY.Admin/ViewModels/MainWindowViewModel.cs
+++ b/yy-admin-master/YY.Admin/ViewModels/MainWindowViewModel.cs
@@ -1,5 +1,6 @@
using Mapster;
using Microsoft.Extensions.Configuration;
+using Prism.Dialogs;
using Newtonsoft.Json;
using SqlSugar;
using System.Collections.ObjectModel;
@@ -9,6 +10,7 @@ using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
+using YY.Admin;
using YY.Admin.Core;
using YY.Admin.Core.Const;
using YY.Admin.Core.Model;
@@ -16,12 +18,12 @@ using YY.Admin.Core.Services;
using YY.Admin.Core.Session;
using YY.Admin.Core.Util;
using YY.Admin.Event;
+using YY.Admin.Helper;
using YY.Admin.Module;
using YY.Admin.Services.Service.Auth;
using YY.Admin.Services.Service.Jeecg;
using YY.Admin.Services.Service.Menu;
using YY.Admin.ViewModels.Control;
-using YY.Admin.Helper;
namespace YY.Admin.ViewModels
{
@@ -724,7 +726,11 @@ namespace YY.Admin.ViewModels
private void OpenServerSettings()
{
- _dialogService.ShowDialog("ServerSettingsDialog", r =>
+ var parameters = new DialogParameters
+ {
+ { KnownDialogParameters.WindowName, DialogWindowNames.ChromeDialogWindow },
+ };
+ _dialogService.ShowDialog("ServerSettingsDialog", parameters, r =>
{
if (r.Result == ButtonResult.OK)
{
diff --git a/yy-admin-master/YY.Admin/Views/Dialogs/ServerSettingsDialogView.xaml b/yy-admin-master/YY.Admin/Views/Dialogs/ServerSettingsDialogView.xaml
index c2c16f4..2f2bc05 100644
--- a/yy-admin-master/YY.Admin/Views/Dialogs/ServerSettingsDialogView.xaml
+++ b/yy-admin-master/YY.Admin/Views/Dialogs/ServerSettingsDialogView.xaml
@@ -2,13 +2,13 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ctls="clr-namespace:YY.Admin.Core.Controls;assembly=YY.Admin.Core"
- Width="520" Height="420"
- Loaded="UserControl_Loaded">
+ MinWidth="520">
-
+
+
diff --git a/yy-admin-master/YY.Admin/Views/Dialogs/ServerSettingsDialogView.xaml.cs b/yy-admin-master/YY.Admin/Views/Dialogs/ServerSettingsDialogView.xaml.cs
index 481dd67..d5e2b3d 100644
--- a/yy-admin-master/YY.Admin/Views/Dialogs/ServerSettingsDialogView.xaml.cs
+++ b/yy-admin-master/YY.Admin/Views/Dialogs/ServerSettingsDialogView.xaml.cs
@@ -1,5 +1,4 @@
using System.Windows.Controls;
-using System.Windows;
namespace YY.Admin.Views.Dialogs
{
@@ -12,22 +11,5 @@ namespace YY.Admin.Views.Dialogs
{
InitializeComponent();
}
-
- private void UserControl_Loaded(object sender, RoutedEventArgs e)
- {
- var win = Window.GetWindow(this);
- if (win == null) return;
-
- // 必须先关闭 AllowsTransparency,再改 WindowStyle(否则会抛出:
- // 「当 AllowsTransparency 为 true 时,WindowStyle.None 是唯一有效值」)
- win.AllowsTransparency = false;
- win.WindowStyle = WindowStyle.SingleBorderWindow;
- win.ResizeMode = ResizeMode.CanResizeWithGrip;
- win.SizeToContent = SizeToContent.Manual;
- win.MinWidth = 520;
- win.MinHeight = 420;
- if (win.Width < 520) win.Width = 520;
- if (win.Height < 420) win.Height = 420;
- }
}
}
diff --git a/yy-admin-master/YY.Admin/Views/Print/PrintPreviewWindow.xaml.cs b/yy-admin-master/YY.Admin/Views/Print/PrintPreviewWindow.xaml.cs
index cfbe240..3000868 100644
--- a/yy-admin-master/YY.Admin/Views/Print/PrintPreviewWindow.xaml.cs
+++ b/yy-admin-master/YY.Admin/Views/Print/PrintPreviewWindow.xaml.cs
@@ -6,6 +6,7 @@ using System.Text.RegularExpressions;
using System.Windows;
using YY.Admin.Core.Entity;
using YY.Admin.Core.Services;
+using YY.Admin.Helper;
using YY.Admin.Services.Service.Print;
namespace YY.Admin.Views.Print;
@@ -32,6 +33,8 @@ public partial class PrintPreviewWindow : HandyControl.Controls.Window
_printDotService = printDotService;
_initialPrinterName = selectedPrinterName;
+ WebView.CreationProperties = WebView2UserDataFolder.CreateCreationProperties("PrintPreview");
+
TbTemplateName.Text = template.TemplateName ?? "(未命名)";
TbTemplateCode.Text = $"编码:{template.TemplateCode} " +
$"尺寸:{template.PaperWidthMm ?? 210}×{template.PaperHeightMm ?? 297} mm " +
diff --git a/yy-admin-master/YY.Admin/Views/RawMaterialEntry/RawMaterialCardGenerateConfirmWindow.xaml.cs b/yy-admin-master/YY.Admin/Views/RawMaterialEntry/RawMaterialCardGenerateConfirmWindow.xaml.cs
index 5c02d67..bcab0a9 100644
--- a/yy-admin-master/YY.Admin/Views/RawMaterialEntry/RawMaterialCardGenerateConfirmWindow.xaml.cs
+++ b/yy-admin-master/YY.Admin/Views/RawMaterialEntry/RawMaterialCardGenerateConfirmWindow.xaml.cs
@@ -10,6 +10,7 @@ using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls.Primitives;
using YY.Admin.Core.Services;
+using YY.Admin.Helper;
using YY.Admin.ViewModels.RawMaterialEntry;
namespace YY.Admin.Views.RawMaterialEntry;
@@ -101,6 +102,7 @@ public partial class RawMaterialCardGenerateConfirmWindow : HandyControl.Control
Func previewHtmlBuilder)
{
InitializeComponent();
+ PreviewWebView.CreationProperties = WebView2UserDataFolder.CreateCreationProperties("RawMaterialCardConfirm");
_printDotService = printDotService;
_previewHtmlBuilder = previewHtmlBuilder;
HeaderText = $"共 {planItems.Count} 张,左侧展示即将生成的原材料卡片,右侧展示业务关联打印模板预览。";
diff --git a/yy-admin-master/YY.Admin/Views/RawMaterialEntry/RawMaterialEntryOperationView.xaml.cs b/yy-admin-master/YY.Admin/Views/RawMaterialEntry/RawMaterialEntryOperationView.xaml.cs
index c0e2aac..fbfbf0e 100644
--- a/yy-admin-master/YY.Admin/Views/RawMaterialEntry/RawMaterialEntryOperationView.xaml.cs
+++ b/yy-admin-master/YY.Admin/Views/RawMaterialEntry/RawMaterialEntryOperationView.xaml.cs
@@ -2,6 +2,7 @@ using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
+using YY.Admin.Helper;
using YY.Admin.ViewModels.RawMaterialEntry;
namespace YY.Admin.Views.RawMaterialEntry;
@@ -14,6 +15,7 @@ public partial class RawMaterialEntryOperationView : UserControl
public RawMaterialEntryOperationView()
{
InitializeComponent();
+ PrintPreviewWebView.CreationProperties = WebView2UserDataFolder.CreateCreationProperties("RawMaterialEntryPreview");
Loaded += OnLoaded;
DataContextChanged += OnDataContextChanged;
Unloaded += (_, _) => DetachVm();
diff --git a/yy-admin-master/YY.Admin/YY.Admin.csproj b/yy-admin-master/YY.Admin/YY.Admin.csproj
index d74804e..911083c 100644
--- a/yy-admin-master/YY.Admin/YY.Admin.csproj
+++ b/yy-admin-master/YY.Admin/YY.Admin.csproj
@@ -59,6 +59,8 @@
..\YY.Admin.Core\libs\HandyControl.dll
+
+ true
@@ -106,10 +108,12 @@
PreserveNewest
+
+ true
-
+
Configuration\appsettings.json
@@ -117,4 +121,13 @@
+
+
+ <_AppSettingsSource>$(MSBuildProjectDirectory)\..\YY.Admin.Services\Configuration\appsettings.json
+
+
+
+
+
+
diff --git a/yy-admin-master/_installer_output/YY.Admin_Setup_1.1.0_win-x64.exe b/yy-admin-master/_installer_output/YY.Admin_Setup_1.1.0_win-x64.exe
new file mode 100644
index 0000000..dc9280c
Binary files /dev/null and b/yy-admin-master/_installer_output/YY.Admin_Setup_1.1.0_win-x64.exe differ
diff --git a/yy-admin-master/installer/YY.Admin.Setup.iss b/yy-admin-master/installer/YY.Admin.Setup.iss
new file mode 100644
index 0000000..9fa4467
--- /dev/null
+++ b/yy-admin-master/installer/YY.Admin.Setup.iss
@@ -0,0 +1,78 @@
+; Inno Setup 6 安装脚本 — 用法见同目录 build-installer.ps1
+; 需先安装: https://jrsoftware.org/isdl.php
+
+#define MyAppName "智能制造MES工控"
+#define MyAppExeName "YY.Admin.exe"
+#define MyAppVersion "1.1.0"
+#define MyPublisher "星数连科技科技有限公司"
+; 相对本 .iss 文件位置(installer\ 下的上一级为 yy-admin-master)
+#define PublishRoot "..\YY.Admin\bin\Release\net8.0-windows10.0.19041\win-x64\publish"
+
+[Setup]
+AppId={{B7E8F4A2-9C1D-4E6F-8A3B-2D5E9C1F4A7B}
+AppName={#MyAppName}
+AppVersion={#MyAppVersion}
+AppPublisher={#MyPublisher}
+AppPublisherURL=https://www.example.com/
+DefaultDirName={autopf}\{#MyPublisher}\{#MyAppName}
+DefaultGroupName={#MyAppName}
+AllowNoIcons=yes
+OutputDir=..\_installer_output
+OutputBaseFilename=YY.Admin_Setup_{#MyAppVersion}_win-x64
+Compression=lzma2/max
+SolidCompression=yes
+WizardStyle=modern
+PrivilegesRequired=admin
+ArchitecturesAllowed=x64compatible
+ArchitecturesInstallIn64BitMode=x64compatible
+MinVersion=10.0.17763
+DisableProgramGroupPage=no
+DisableDirPage=no
+UninstallDisplayIcon={app}\{#MyAppExeName}
+SetupLogging=yes
+
+; 向导语言:使用 Inno 自带的 Default.isl(英文)。若本机存在 Languages\ChineseSimplified.isl,
+; 可改为: Name: "chinesesimp"; MessagesFile: "compiler:Languages\ChineseSimplified.isl"
+[Languages]
+Name: "english"; MessagesFile: "compiler:Default.isl"
+
+[Tasks]
+Name: "desktopicon"; Description: "在桌面创建快捷方式"; GroupDescription: "附加图标:"; Flags: unchecked
+Name: "installwebview2"; Description: "安装 Microsoft Edge WebView2 运行时(内嵌网页需要;若已安装会自动跳过)"; GroupDescription: "运行环境:"; Flags: checkedonce
+
+[Files]
+; 发布目录完整拷贝(含 Configuration、Updates 等)
+Source: "{#PublishRoot}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
+; WebView2 引导安装包(由 build-installer.ps1 下载到 redist;编译时若无文件可加 Flags skipifsourcedoesntexist)
+Source: "redist\MicrosoftEdgeWebview2Setup.exe"; DestDir: "{tmp}"; Flags: deleteafterinstall skipifsourcedoesntexist
+
+[Icons]
+Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; WorkingDir: "{app}"
+Name: "{group}\卸载 {#MyAppName}"; Filename: "{uninstallexe}"
+Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; WorkingDir: "{app}"; Tasks: desktopicon
+
+[Run]
+Filename: "{tmp}\MicrosoftEdgeWebview2Setup.exe"; Parameters: "/silent /install"; StatusMsg: "正在安装 WebView2 运行时..."; Flags: waituntilterminated; Tasks: installwebview2; Check: ShouldInstallWebView2()
+
+Filename: "{app}\{#MyAppExeName}"; Description: "启动 {#MyAppName}"; Flags: nowait postinstall skipifsilent
+
+[Code]
+
+// 安装包是否携带了 WebView2 引导程序(已释放到临时目录)
+function WebView2BootstrapInTmp: Boolean;
+begin
+ Result := FileExists(ExpandConstant('{tmp}\MicrosoftEdgeWebview2Setup.exe'));
+end;
+
+// Evergreen WebView2 是否存在(注册表中存在版本号则认为已装)
+function WebView2RuntimeInstalled: Boolean;
+var
+ Ver: String;
+begin
+ Result := RegQueryStringValue(HKLM, 'SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}', 'pv', Ver) and (Trim(Ver) <> '');
+end;
+
+function ShouldInstallWebView2: Boolean;
+begin
+ Result := WebView2BootstrapInTmp and (not WebView2RuntimeInstalled);
+end;
diff --git a/yy-admin-master/installer/build-installer.ps1 b/yy-admin-master/installer/build-installer.ps1
new file mode 100644
index 0000000..c1a9d72
--- /dev/null
+++ b/yy-admin-master/installer/build-installer.ps1
@@ -0,0 +1,89 @@
+# Release publish + WebView2 bootstrap download + Inno Setup ISCC
+# Requires Inno Setup 6 (ISCC.exe)
+
+$ErrorActionPreference = 'Stop'
+$Root = Split-Path -Parent $PSScriptRoot
+$Csproj = Join-Path $Root 'YY.Admin\YY.Admin.csproj'
+$PublishRel = 'YY.Admin\bin\Release\net8.0-windows10.0.19041\win-x64\publish'
+$PublishDir = Join-Path $Root $PublishRel
+$RedistDir = Join-Path $PSScriptRoot 'redist'
+$WebView2Exe = Join-Path $RedistDir 'MicrosoftEdgeWebview2Setup.exe'
+$WebView2Url = 'https://go.microsoft.com/fwlink/p/?LinkId=2124703'
+
+Write-Host '>>> dotnet publish (Release, win-x64)...'
+dotnet publish $Csproj -c Release `
+ -p:IncludeNativeLibrariesForSelfExtract=true `
+ -p:EnableCompressionInSingleFile=true `
+ --verbosity minimal
+
+if (-not (Test-Path $PublishDir)) {
+ throw "Publish output not found: $PublishDir"
+}
+
+$cfgPublish = Join-Path $PublishDir 'Configuration\appsettings.json'
+if (-not (Test-Path $cfgPublish)) {
+ throw "Missing Configuration\appsettings.json under publish (ExcludeFromSingleFile required). Path: $cfgPublish"
+}
+
+New-Item -ItemType Directory -Force -Path $RedistDir | Out-Null
+if (-not (Test-Path $WebView2Exe)) {
+ Write-Host '>>> Downloading WebView2 Evergreen bootstrapper...'
+ Invoke-WebRequest -Uri $WebView2Url -OutFile $WebView2Exe -UseBasicParsing
+}
+
+function Find-InnoCompiler {
+ if ($env:ISCC -and (Test-Path -LiteralPath $env:ISCC)) {
+ return $env:ISCC
+ }
+ if ($env:INNO_SETUP_DIR) {
+ $p = Join-Path $env:INNO_SETUP_DIR.TrimEnd('\') 'ISCC.exe'
+ if (Test-Path -LiteralPath $p) {
+ return $p
+ }
+ }
+ $cmd = Get-Command 'iscc.exe' -ErrorAction SilentlyContinue
+ if ($cmd -and $cmd.Source -and (Test-Path -LiteralPath $cmd.Source)) {
+ return $cmd.Source
+ }
+ $candidates = @(
+ 'D:\Inno Setup 6\ISCC.exe'
+ "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe"
+ "${env:ProgramFiles}\Inno Setup 6\ISCC.exe"
+ "${env:LocalAppData}\Programs\Inno Setup 6\ISCC.exe"
+ )
+ foreach ($c in $candidates) {
+ if ($c -and (Test-Path -LiteralPath $c)) {
+ return $c
+ }
+ }
+ return $null
+}
+
+$iscc = Find-InnoCompiler
+
+if (-not $iscc) {
+ throw @'
+ISCC.exe not found.
+
+1) Install Inno Setup 6: https://jrsoftware.org/isdl.php
+2) Or set env ISCC to full path of ISCC.exe, e.g.:
+ set ISCC=C:\Program Files (x86)\Inno Setup 6\ISCC.exe
+3) Or set INNO_SETUP_DIR to the Inno Setup 6 folder.
+
+Then run this script again.
+'@
+}
+
+Write-Host ">>> Using ISCC: $iscc"
+
+$iss = Join-Path $PSScriptRoot 'YY.Admin.Setup.iss'
+Write-Host '>>> Compiling installer (working dir: installer)...'
+Push-Location $PSScriptRoot
+try {
+ & $iscc "`"$iss`""
+} finally {
+ Pop-Location
+}
+
+$outDir = Join-Path $Root '_installer_output'
+Write-Host ">>> Done. Output folder: $outDir"
diff --git a/yy-admin-master/installer/redist/.gitignore b/yy-admin-master/installer/redist/.gitignore
new file mode 100644
index 0000000..c44c892
--- /dev/null
+++ b/yy-admin-master/installer/redist/.gitignore
@@ -0,0 +1,2 @@
+# WebView2 Evergreen 引导程序由 build-installer.ps1 自动下载,不必提交仓库
+MicrosoftEdgeWebview2Setup.exe