Sfoglia il codice sorgente

feat: 增加系统信息配置功能,支持更改系统图标

许俊杰 1 anno fa
parent
commit
63c4c8afe6

+ 1 - 0
Admin.NET/Admin.NET.Core/SeedData/SysConfigSeedData.cs

@@ -34,6 +34,7 @@ public class SysConfigSeedData : ISqlSugarEntitySeedData<SysConfig>
             new SysConfig{ Id=1300000000231, Name="系统描述", Code="sys_web_viceDesc", Value="站在巨人肩膀上的 .NET 通用权限开发框架", SysFlag=YesNoEnum.Y, Remark="系统描述", OrderNo=130, GroupCode="WebConfig", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
             new SysConfig{ Id=1300000000241, Name="水印内容", Code="sys_web_watermark", Value="Admin.NET", SysFlag=YesNoEnum.Y, Remark="水印内容", OrderNo=140, GroupCode="WebConfig", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
             new SysConfig{ Id=1300000000251, Name="版权说明", Code="sys_web_copyright", Value="Copyright © 2021-present Admin.NET All rights reserved.", SysFlag=YesNoEnum.Y, Remark="版权说明", OrderNo=150, GroupCode="WebConfig", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
+            new SysConfig{ Id=1300000000261, Name="系统图标", Code="sys_web_logo", Value="Upload/Logo/sys_logo.png", SysFlag=YesNoEnum.Y, Remark="系统图标", OrderNo=160, GroupCode="WebConfig", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
     };
     }
 }

+ 2 - 0
Admin.NET/Admin.NET.Core/SeedData/SysMenuSeedData.cs

@@ -163,6 +163,8 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
             new SysMenu{ Id=1310000000424, Pid=1310000000421, Title="增加", Permission="sysOpenAccess:add", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
             new SysMenu{ Id=1310000000425, Pid=1310000000421, Title="删除", Permission="sysOpenAccess:delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
 
+            new SysMenu{ Id=1310000000431, Pid=1310000000301, Title="系统信息配置", Path="/platform/infoSetting", Name="sysInfoSetting", Component="/system/infoSetting/index", Icon="ele-Postcard", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=220 },
+
             new SysMenu{ Id=1310000000501, Pid=0, Title="日志管理", Path="/log", Name="log", Component="Layout", Icon="ele-DocumentCopy", Type=MenuTypeEnum.Dir, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=12000 },
             new SysMenu{ Id=1310000000511, Pid=1310000000501, Title="访问日志", Path="/log/vislog", Name="sysVisLog", Component="/system/log/vislog/index", Icon="ele-Document", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
             new SysMenu{ Id=1310000000512, Pid=1310000000511, Title="查询", Permission="sysVislog:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },

+ 43 - 0
Admin.NET/Admin.NET.Core/Service/Config/Dto/InfoInput.cs

@@ -0,0 +1,43 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core.Service;
+
+/// <summary>
+/// 系统信息保存输入参数
+/// </summary>
+public class InfoSaveInput
+{
+    /// <summary>
+    /// 系统图标(Data URI scheme base64 编码)
+    /// </summary>
+    public string SysLogoBase64 { get; set; }
+
+    /// <summary>
+    /// 系统主标题
+    /// </summary>
+    public string SysTitle { get; set; }
+
+    /// <summary>
+    /// 系统副标题
+    /// </summary>
+    public string SysViceTitle { get; set; }
+
+    /// <summary>
+    /// 系统描述
+    /// </summary>
+    public string SysViceDesc { get; set; }
+
+    /// <summary>
+    /// 水印内容
+    /// </summary>
+    public string SysWatermark { get; set; }
+
+    /// <summary>
+    /// 版权说明
+    /// </summary>
+    public string SysCopyright { get; set; }
+}

+ 96 - 12
Admin.NET/Admin.NET.Core/Service/Config/SysConfigService.cs

@@ -31,6 +31,7 @@ public class SysConfigService : IDynamicApiController, ITransient
     public async Task<SqlSugarPagedList<SysConfig>> Page(PageConfigInput input)
     {
         return await _sysConfigRep.AsQueryable()
+            .Where(u => u.GroupCode != "WebConfig"/*不显示 WebConfig 分组*/)
             .WhereIF(!string.IsNullOrWhiteSpace(input.Name?.Trim()), u => u.Name.Contains(input.Name))
             .WhereIF(!string.IsNullOrWhiteSpace(input.Code?.Trim()), u => u.Code.Contains(input.Code))
             .WhereIF(!string.IsNullOrWhiteSpace(input.GroupCode?.Trim()), u => u.GroupCode.Equals(input.GroupCode))
@@ -155,13 +156,107 @@ public class SysConfigService : IDynamicApiController, ITransient
     }
 
     /// <summary>
+    /// 更新参数配置值
+    /// </summary>
+    /// <param name="code"></param>
+    /// <param name="value"></param>
+    /// <returns></returns>
+    [NonAction]
+    public async Task UpdateConfigValue(string code, string value)
+    {
+        var config = await _sysConfigRep.GetFirstAsync(u => u.Code == code);
+        if (config == null) return;
+
+        config.Value = value;
+        await _sysConfigRep.AsUpdateable(config).ExecuteCommandAsync();
+
+        _sysCacheService.Remove(config.Code);
+    }
+
+    /// <summary>
     /// 获取分组列表 🔖
     /// </summary>
     /// <returns></returns>
     [DisplayName("获取分组列表")]
     public async Task<List<string>> GetGroupList()
     {
-        return await _sysConfigRep.AsQueryable().GroupBy(u => u.GroupCode).Select(u => u.GroupCode).ToListAsync();
+        return await _sysConfigRep.AsQueryable().Where(u => u.GroupCode != "WebConfig"/*不显示 WebConfig 分组*/).GroupBy(u => u.GroupCode).Select(u => u.GroupCode).ToListAsync();
+    }
+
+
+    /// <summary>
+    /// 获取系统信息
+    /// </summary>
+    /// <returns></returns>
+    [SuppressMonitor]
+    [DisplayName("获取系统信息")]
+    [AllowAnonymous]
+    public async Task<dynamic> GetSysInfo()
+    {
+        var sysLogo = await GetConfigValue<string>("sys_web_logo");
+        var sysTitle = await GetConfigValue<string>("sys_web_title");
+        var sysViceTitle = await GetConfigValue<string>("sys_web_viceTitle");
+        var sysViceDesc = await GetConfigValue<string>("sys_web_viceDesc");
+        var sysWatermark = await GetConfigValue<string>("sys_web_watermark");
+        var sysCopyright = await GetConfigValue<string>("sys_web_copyright");
+        return new
+        {
+            SysLogo = sysLogo,
+            SysTitle = sysTitle,
+            SysViceTitle = sysViceTitle,
+            SysViceDesc = sysViceDesc,
+            SysWatermark = sysWatermark,
+            SysCopyright = sysCopyright,
+        };
+    }
+
+    /// <summary>
+    /// 保存系统信息
+    /// </summary>
+    /// <returns></returns>
+    [DisplayName("保存系统信息")]
+    public async Task SaveSysInfo(InfoSaveInput input)
+    {
+        //不为空才保存 SysLogo
+        if (!string.IsNullOrEmpty(input.SysLogoBase64))
+        {
+            //旧图标文件相对路径
+            var oldSysLogoRelativeFilePath = await GetConfigValue<string>("sys_web_logo") ?? "";
+            var oldSysLogoAbsoluteFilePath = Path.Combine(App.WebHostEnvironment.WebRootPath, oldSysLogoRelativeFilePath);
+
+            var groups = Regex.Match(input.SysLogoBase64, @"data:image/(?<type>.+?);base64,(?<data>.+)").Groups;
+            var type = groups["type"].Value;
+            var base64Data = groups["data"].Value;
+            var binData = Convert.FromBase64String(base64Data);
+
+            //本地保存图标路径
+            var path = "Upload/Logo";
+
+            //文件路径
+            var relativeUrl = $"{path}/logo.{type}";
+            var absoluteFilePath = Path.Combine(App.WebHostEnvironment.WebRootPath, path, $"logo.{type}");
+
+            //删除已存在文件
+            if (File.Exists(oldSysLogoAbsoluteFilePath))
+                File.Delete(oldSysLogoAbsoluteFilePath);
+
+            //创建文件夹
+            var absoluteFileDir = Path.GetDirectoryName(absoluteFilePath);
+            if (!Directory.Exists(absoluteFileDir))
+                Directory.CreateDirectory(absoluteFileDir);
+
+            //保存文件
+            await File.WriteAllBytesAsync(absoluteFilePath, binData);
+
+            //保存图标配置
+            await UpdateConfigValue("sys_web_logo", relativeUrl);
+        }
+
+        await UpdateConfigValue("sys_web_title", input.SysTitle);
+        await UpdateConfigValue("sys_web_viceTitle", input.SysViceTitle);
+        await UpdateConfigValue("sys_web_viceDesc", input.SysViceDesc);
+        await UpdateConfigValue("sys_web_watermark", input.SysWatermark);
+        await UpdateConfigValue("sys_web_copyright", input.SysCopyright);
     }
 
     /// <summary>
@@ -187,15 +282,4 @@ public class SysConfigService : IDynamicApiController, ITransient
         _ = int.TryParse(refreshTokenExpireStr, out var refreshTokenExpire);
         return refreshTokenExpire == 0 ? 40 : refreshTokenExpire;
     }
-
-    /// <summary>
-    /// 获取前端配置
-    /// </summary>
-    /// <returns></returns>
-    [AllowAnonymous]
-    [DisplayName("获取前端配置")]
-    public async Task<dynamic> GetWebConfig()
-    {
-        return await _sysConfigRep.AsQueryable().Where(u => u.GroupCode == "WebConfig").Select(u => new { u.Code, u.Value }).ToListAsync();
-    }
 }

+ 7 - 0
Admin.NET/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj

@@ -52,4 +52,11 @@
     <ProjectReference Include="..\Admin.NET.Web.Core\Admin.NET.Web.Core.csproj" />
   </ItemGroup>
 
+
+  <ItemGroup>
+    <Content Update="wwwroot\Upload\Logo\sys_logo.png">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+
 </Project>

BIN
Admin.NET/Admin.NET.Web.Entry/wwwroot/Upload/Logo/sys_logo.png


+ 1 - 1
Web/index.html

@@ -5,7 +5,7 @@
 		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
 		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
 		<link rel="stylesheet" type="text/css" media="print" href="/print-lock.css" />
-		<link rel="icon" href="/favicon.ico" />
+		<link rel="icon" id="favicon" href="data:;base64,=" />
 		<title>Admin.NET</title>
 	</head>
 	<body>

+ 42 - 31
Web/src/App.vue

@@ -85,7 +85,6 @@ onMounted(() => {
 		// 获取缓存中的布局配置
 		if (Local.get('themeConfig')) {
 			var themeConfig = Local.get('themeConfig');
-			setWebConfig(themeConfig);
 			storesThemeConfig.setThemeConfig({ themeConfig: themeConfig });
 			document.documentElement.style.cssText = Local.get('themeConfigStyle');
 		}
@@ -110,37 +109,49 @@ watch(
 	}
 );
 
-// 获取系统配置
-const setWebConfig = async (themeConfig: any) => {
-	var res = await getAPI(SysConfigApi).apiSysConfigWebConfigGet();
-	var webConfig = res.data.result;
-	for (let index = 0; index < webConfig.length; index++) {
-		const element = webConfig[index];
-		if (element.code == 'sys_web_title') {
-			document.title = element.value;
-			themeConfig.globalTitle = element.value;
-		}
-		if (element.code == 'sys_web_watermark') {
-			if (element.value == 'False') {
-				themeConfig.isWatermark = false;
-				Watermark.del();
-			} else {
-				themeConfig.isWatermark = true;
-				themeConfig.watermarkText = element.value;
-				Watermark.set(element.value);
-			}
-		}
-		if (element.code == 'sys_web_viceTitle') {
-			themeConfig.globalViceTitle = element.value;
-		}
-		if (element.code == 'sys_web_viceDesc') {
-			themeConfig.globalViceTitleMsg = element.value;
-		}
-		if (element.code == 'sys_web_copyright') {
-			themeConfig.copyright = element.value;
-		}
-	}
+/** 加载系统信息 */
+const loadSysInfo = () => {
+	getAPI(SysConfigApi)
+		.apiSysConfigSysInfoGet()
+		.then((res) => {
+			if (res.data.type != 'success') return;
+
+			const data = res.data.result;
+
+			themeConfig.value.logoUrl = data.sysLogo;
+			themeConfig.value.globalTitle = data.sysTitle;
+			themeConfig.value.globalViceTitle = data.sysViceTitle;
+			themeConfig.value.globalViceTitleMsg = data.sysViceDesc;
+			// 水印
+			themeConfig.value.watermarkText = data.sysWatermark;
+			// 版权说明
+			themeConfig.value.copyright = data.sysCopyright;
+
+			// 更新 favicon
+			updateFavicon(data.sysLogo);
+
+			// 保存配置
+			Local.remove('themeConfig');
+			Local.set('themeConfig', storesThemeConfig.themeConfig);
+		})
+		.catch(() => {
+			// 置空 Logo 地址
+			themeConfig.value.logoUrl = '';
+			// 保存配置
+			Local.remove('themeConfig');
+			Local.set('themeConfig', storesThemeConfig.themeConfig);
+			return;
+		});
 };
+
+/** 更新 favicon */
+const updateFavicon = (url: string): void => {
+	const favicon = document.getElementById('favicon') as HTMLAnchorElement;
+	favicon!.href = url ? url : 'data:;base64,=';
+};
+
+// 加载系统信息
+loadSysInfo();
 </script>
 
 <style lang="scss">

+ 112 - 28
Web/src/api-services/apis/sys-config-api.ts

@@ -24,6 +24,7 @@ import { AdminResultObject } from '../models';
 import { AdminResultSqlSugarPagedListSysConfig } from '../models';
 import { AdminResultSysConfig } from '../models';
 import { DeleteConfigInput } from '../models';
+import { InfoSaveInput } from '../models';
 import { PageConfigInput } from '../models';
 import { UpdateConfigInput } from '../models';
 /**
@@ -364,13 +365,13 @@ export const SysConfigApiAxiosParamCreator = function (configuration?: Configura
         },
         /**
          * 
-         * @summary 更新参数配置 🔖
-         * @param {UpdateConfigInput} [body] 
+         * @summary 保存系统信息
+         * @param {InfoSaveInput} [body] 
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        apiSysConfigUpdatePost: async (body?: UpdateConfigInput, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
-            const localVarPath = `/api/sysConfig/update`;
+        apiSysConfigSaveSysInfoPost: async (body?: InfoSaveInput, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            const localVarPath = `/api/sysConfig/saveSysInfo`;
             // use dummy base URL string because the URL constructor only accepts absolute URLs.
             const localVarUrlObj = new URL(localVarPath, 'https://example.com');
             let baseOptions;
@@ -412,12 +413,12 @@ export const SysConfigApiAxiosParamCreator = function (configuration?: Configura
         },
         /**
          * 
-         * @summary 获取前端配置
+         * @summary 获取系统信息
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        apiSysConfigWebConfigGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
-            const localVarPath = `/api/sysConfig/webConfig`;
+        apiSysConfigSysInfoGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            const localVarPath = `/api/sysConfig/sysInfo`;
             // use dummy base URL string because the URL constructor only accepts absolute URLs.
             const localVarUrlObj = new URL(localVarPath, 'https://example.com');
             let baseOptions;
@@ -453,6 +454,54 @@ export const SysConfigApiAxiosParamCreator = function (configuration?: Configura
                 options: localVarRequestOptions,
             };
         },
+        /**
+         * 
+         * @summary 更新参数配置 🔖
+         * @param {UpdateConfigInput} [body] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        apiSysConfigUpdatePost: async (body?: UpdateConfigInput, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            const localVarPath = `/api/sysConfig/update`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, 'https://example.com');
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+            const localVarRequestOptions :AxiosRequestConfig = { method: 'POST', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication Bearer required
+            // http bearer authentication required
+            if (configuration && configuration.accessToken) {
+                const accessToken = typeof configuration.accessToken === 'function'
+                    ? await configuration.accessToken()
+                    : await configuration.accessToken;
+                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
+            }
+
+            localVarHeaderParameter['Content-Type'] = 'application/json-patch+json';
+
+            const query = new URLSearchParams(localVarUrlObj.search);
+            for (const key in localVarQueryParameter) {
+                query.set(key, localVarQueryParameter[key]);
+            }
+            for (const key in options.params) {
+                query.set(key, options.params[key]);
+            }
+            localVarUrlObj.search = (new URLSearchParams(query)).toString();
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+            const needsSerialization = (typeof body !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
+            localVarRequestOptions.data =  needsSerialization ? JSON.stringify(body !== undefined ? body : {}) : (body || "");
+
+            return {
+                url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
+                options: localVarRequestOptions,
+            };
+        },
     }
 };
 
@@ -560,13 +609,13 @@ export const SysConfigApiFp = function(configuration?: Configuration) {
         },
         /**
          * 
-         * @summary 更新参数配置 🔖
-         * @param {UpdateConfigInput} [body] 
+         * @summary 保存系统信息
+         * @param {InfoSaveInput} [body] 
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        async apiSysConfigUpdatePost(body?: UpdateConfigInput, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<void>>> {
-            const localVarAxiosArgs = await SysConfigApiAxiosParamCreator(configuration).apiSysConfigUpdatePost(body, options);
+        async apiSysConfigSaveSysInfoPost(body?: InfoSaveInput, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<void>>> {
+            const localVarAxiosArgs = await SysConfigApiAxiosParamCreator(configuration).apiSysConfigSaveSysInfoPost(body, options);
             return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
                 const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
                 return axios.request(axiosRequestArgs);
@@ -574,12 +623,26 @@ export const SysConfigApiFp = function(configuration?: Configuration) {
         },
         /**
          * 
-         * @summary 获取前端配置
+         * @summary 获取系统信息
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        async apiSysConfigWebConfigGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminResultObject>>> {
-            const localVarAxiosArgs = await SysConfigApiAxiosParamCreator(configuration).apiSysConfigWebConfigGet(options);
+        async apiSysConfigSysInfoGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminResultObject>>> {
+            const localVarAxiosArgs = await SysConfigApiAxiosParamCreator(configuration).apiSysConfigSysInfoGet(options);
+            return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
+                const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
+                return axios.request(axiosRequestArgs);
+            };
+        },
+        /**
+         * 
+         * @summary 更新参数配置 🔖
+         * @param {UpdateConfigInput} [body] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async apiSysConfigUpdatePost(body?: UpdateConfigInput, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<void>>> {
+            const localVarAxiosArgs = await SysConfigApiAxiosParamCreator(configuration).apiSysConfigUpdatePost(body, options);
             return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
                 const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
                 return axios.request(axiosRequestArgs);
@@ -664,22 +727,32 @@ export const SysConfigApiFactory = function (configuration?: Configuration, base
         },
         /**
          * 
-         * @summary 更新参数配置 🔖
-         * @param {UpdateConfigInput} [body] 
+         * @summary 保存系统信息
+         * @param {InfoSaveInput} [body] 
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        async apiSysConfigUpdatePost(body?: UpdateConfigInput, options?: AxiosRequestConfig): Promise<AxiosResponse<void>> {
-            return SysConfigApiFp(configuration).apiSysConfigUpdatePost(body, options).then((request) => request(axios, basePath));
+        async apiSysConfigSaveSysInfoPost(body?: InfoSaveInput, options?: AxiosRequestConfig): Promise<AxiosResponse<void>> {
+            return SysConfigApiFp(configuration).apiSysConfigSaveSysInfoPost(body, options).then((request) => request(axios, basePath));
         },
         /**
          * 
-         * @summary 获取前端配置
+         * @summary 获取系统信息
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        async apiSysConfigWebConfigGet(options?: AxiosRequestConfig): Promise<AxiosResponse<AdminResultObject>> {
-            return SysConfigApiFp(configuration).apiSysConfigWebConfigGet(options).then((request) => request(axios, basePath));
+        async apiSysConfigSysInfoGet(options?: AxiosRequestConfig): Promise<AxiosResponse<AdminResultObject>> {
+            return SysConfigApiFp(configuration).apiSysConfigSysInfoGet(options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
+         * @summary 更新参数配置 🔖
+         * @param {UpdateConfigInput} [body] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async apiSysConfigUpdatePost(body?: UpdateConfigInput, options?: AxiosRequestConfig): Promise<AxiosResponse<void>> {
+            return SysConfigApiFp(configuration).apiSysConfigUpdatePost(body, options).then((request) => request(axios, basePath));
         },
     };
 };
@@ -768,23 +841,34 @@ export class SysConfigApi extends BaseAPI {
     }
     /**
      * 
-     * @summary 更新参数配置 🔖
-     * @param {UpdateConfigInput} [body] 
+     * @summary 保存系统信息
+     * @param {InfoSaveInput} [body] 
      * @param {*} [options] Override http request option.
      * @throws {RequiredError}
      * @memberof SysConfigApi
      */
-    public async apiSysConfigUpdatePost(body?: UpdateConfigInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<void>> {
-        return SysConfigApiFp(this.configuration).apiSysConfigUpdatePost(body, options).then((request) => request(this.axios, this.basePath));
+    public async apiSysConfigSaveSysInfoPost(body?: InfoSaveInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<void>> {
+        return SysConfigApiFp(this.configuration).apiSysConfigSaveSysInfoPost(body, options).then((request) => request(this.axios, this.basePath));
+    }
+    /**
+     * 
+     * @summary 获取系统信息
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof SysConfigApi
+     */
+    public async apiSysConfigSysInfoGet(options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminResultObject>> {
+        return SysConfigApiFp(this.configuration).apiSysConfigSysInfoGet(options).then((request) => request(this.axios, this.basePath));
     }
     /**
      * 
-     * @summary 获取前端配置
+     * @summary 更新参数配置 🔖
+     * @param {UpdateConfigInput} [body] 
      * @param {*} [options] Override http request option.
      * @throws {RequiredError}
      * @memberof SysConfigApi
      */
-    public async apiSysConfigWebConfigGet(options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminResultObject>> {
-        return SysConfigApiFp(this.configuration).apiSysConfigWebConfigGet(options).then((request) => request(this.axios, this.basePath));
+    public async apiSysConfigUpdatePost(body?: UpdateConfigInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<void>> {
+        return SysConfigApiFp(this.configuration).apiSysConfigUpdatePost(body, options).then((request) => request(this.axios, this.basePath));
     }
 }

+ 23 - 0
Web/src/api-services/models/add-print-input.ts

@@ -12,6 +12,7 @@
  * Do not edit the class manually.
  */
 
+import { PrintTypeEnum } from './print-type-enum';
 import { StatusEnum } from './status-enum';
  /**
  * 
@@ -102,6 +103,28 @@ export interface AddPrintInput {
     template: string;
 
     /**
+     * @type {PrintTypeEnum}
+     * @memberof AddPrintInput
+     */
+    printType: PrintTypeEnum;
+
+    /**
+     * 客户端服务地址
+     *
+     * @type {string}
+     * @memberof AddPrintInput
+     */
+    clientServiceAddress?: string | null;
+
+    /**
+     * 打印参数
+     *
+     * @type {string}
+     * @memberof AddPrintInput
+     */
+    printParam?: string | null;
+
+    /**
      * 排序
      *
      * @type {number}

+ 2 - 0
Web/src/api-services/models/index.ts

@@ -182,6 +182,7 @@ export * from './icomponent';
 export * from './icontainer';
 export * from './icustom-attribute-provider';
 export * from './isite';
+export * from './info-save-input';
 export * from './int-ptr';
 export * from './jtoken';
 export * from './job-create-type-enum';
@@ -234,6 +235,7 @@ export * from './page-user-input';
 export * from './parameter-attributes';
 export * from './parameter-info';
 export * from './platform-type-enum';
+export * from './print-type-enum';
 export * from './property-attributes';
 export * from './property-info';
 export * from './relation-query-input';

+ 70 - 0
Web/src/api-services/models/info-save-input.ts

@@ -0,0 +1,70 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Admin.NET 通用权限开发平台
+ * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。<br/><u><b><font color='FF0000'> 👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!</font></b></u>
+ *
+ * OpenAPI spec version: 1.0.0
+ * 
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+
+ /**
+ * 系统信息保存输入参数
+ *
+ * @export
+ * @interface InfoSaveInput
+ */
+export interface InfoSaveInput {
+
+    /**
+     * 系统图标(Data URI scheme base64 编码)
+     *
+     * @type {string}
+     * @memberof InfoSaveInput
+     */
+    sysLogoBase64?: string | null;
+
+    /**
+     * 系统主标题
+     *
+     * @type {string}
+     * @memberof InfoSaveInput
+     */
+    sysTitle?: string | null;
+
+    /**
+     * 系统副标题
+     *
+     * @type {string}
+     * @memberof InfoSaveInput
+     */
+    sysViceTitle?: string | null;
+
+    /**
+     * 系统描述
+     *
+     * @type {string}
+     * @memberof InfoSaveInput
+     */
+    sysViceDesc?: string | null;
+
+    /**
+     * 水印内容
+     *
+     * @type {string}
+     * @memberof InfoSaveInput
+     */
+    sysWatermark?: string | null;
+
+    /**
+     * 版权说明
+     *
+     * @type {string}
+     * @memberof InfoSaveInput
+     */
+    sysCopyright?: string | null;
+}

+ 24 - 0
Web/src/api-services/models/print-type-enum.ts

@@ -0,0 +1,24 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Admin.NET 通用权限开发平台
+ * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。<br/><u><b><font color='FF0000'> 👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!</font></b></u>
+ *
+ * OpenAPI spec version: 1.0.0
+ * 
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+
+/**
+ * 打印类型枚举<br />&nbsp;浏览器打印 Browser = 1<br />&nbsp;客户端打印 Client = 2<br />
+ * @export
+ * @enum {string}
+ */
+export enum PrintTypeEnum {
+    NUMBER_1 = 1,
+    NUMBER_2 = 2
+}
+

+ 23 - 0
Web/src/api-services/models/sys-print.ts

@@ -12,6 +12,7 @@
  * Do not edit the class manually.
  */
 
+import { PrintTypeEnum } from './print-type-enum';
 import { StatusEnum } from './status-enum';
  /**
  * 系统打印模板表
@@ -110,6 +111,28 @@ export interface SysPrint {
     template: string;
 
     /**
+     * @type {PrintTypeEnum}
+     * @memberof SysPrint
+     */
+    printType: PrintTypeEnum;
+
+    /**
+     * 客户端服务地址
+     *
+     * @type {string}
+     * @memberof SysPrint
+     */
+    clientServiceAddress?: string | null;
+
+    /**
+     * 打印参数
+     *
+     * @type {string}
+     * @memberof SysPrint
+     */
+    printParam?: string | null;
+
+    /**
      * 排序
      *
      * @type {number}

+ 23 - 0
Web/src/api-services/models/update-print-input.ts

@@ -12,6 +12,7 @@
  * Do not edit the class manually.
  */
 
+import { PrintTypeEnum } from './print-type-enum';
 import { StatusEnum } from './status-enum';
  /**
  * 
@@ -102,6 +103,28 @@ export interface UpdatePrintInput {
     template: string;
 
     /**
+     * @type {PrintTypeEnum}
+     * @memberof UpdatePrintInput
+     */
+    printType: PrintTypeEnum;
+
+    /**
+     * 客户端服务地址
+     *
+     * @type {string}
+     * @memberof UpdatePrintInput
+     */
+    clientServiceAddress?: string | null;
+
+    /**
+     * 打印参数
+     *
+     * @type {string}
+     * @memberof UpdatePrintInput
+     */
+    printParam?: string | null;
+
+    /**
      * 排序
      *
      * @type {number}

+ 2 - 2
Web/src/layout/component/columnsAside.vue

@@ -1,6 +1,6 @@
 <template>
 	<div class="layout-columns-aside">
-		<div class="layout-logo"><img :src="logoMini" class="layout-logo-medium-img" /></div>
+		<div class="layout-logo"><img :src="themeConfig.logoUrl" class="layout-logo-medium-img" /></div>
 		<el-scrollbar>
 			<ul @mouseleave="onColumnsAsideMenuMouseleave()">
 				<li
@@ -44,7 +44,7 @@ import { storeToRefs } from 'pinia';
 import { useRoutesList } from '/@/stores/routesList';
 import { useThemeConfig } from '/@/stores/themeConfig';
 import mittBus from '/@/utils/mitt';
-import logoMini from '/@/assets/logo-mini.svg';
+// import logoMini from '/@/assets/logo-mini.svg';
 
 // 定义变量内容
 const columnsAsideOffsetTopRefs = ref<RefType>([]);

+ 3 - 3
Web/src/layout/logo/index.vue

@@ -1,10 +1,10 @@
 <template>
 	<div class="layout-logo" v-if="setShowLogo" @click="onThemeConfigChange">
-		<img :src="logoMini" class="layout-logo-medium-img" v-if="showLogo" />
+		<img :src="themeConfig.logoUrl" class="layout-logo-medium-img" v-if="showLogo" />
 		<span>{{ themeConfig.globalTitle }}</span>
 	</div>
 	<div class="layout-logo-size" v-else @click="onThemeConfigChange">
-		<img :src="logoMini" class="layout-logo-size-img" />
+		<img :src="themeConfig.logoUrl" class="layout-logo-size-img" />
 	</div>
 </template>
 
@@ -12,7 +12,7 @@
 import { computed } from 'vue';
 import { storeToRefs } from 'pinia';
 import { useThemeConfig } from '/@/stores/themeConfig';
-import logoMini from '/@/assets/logo-mini.svg';
+// import logoMini from '/@/assets/logo-mini.svg';
 
 // 定义变量内容
 const storesThemeConfig = useThemeConfig();

+ 2 - 0
Web/src/stores/themeConfig.ts

@@ -140,6 +140,8 @@ export const useThemeConfig = defineStore('themeConfig', {
 			globalI18n: 'zh-cn',
 			// 默认全局组件大小,可选值"<large|'default'|small>",默认 'large'
 			globalComponentSize: 'small',
+			// 系统 Logo 地址
+			logoUrl: '',
 		},
 	}),
 	actions: {

+ 1 - 0
Web/src/types/pinia.d.ts

@@ -92,5 +92,6 @@ declare interface ThemeConfigState {
 		copyright: string;
 		globalI18n: string;
 		globalComponentSize: string;
+		logoUrl: string; // 系统 Logo 地址
 	};
 }

+ 8 - 2
Web/src/views/login/component/account.vue

@@ -87,11 +87,16 @@ import { sm2 } from 'sm-crypto-v2';
 
 import { accessTokenKey, clearTokens, feature, getAPI } from '/@/utils/axios-utils';
 import { SysAuthApi } from '/@/api-services/api';
+import { useThemeConfig } from '/@/stores/themeConfig';
+import { storeToRefs } from 'pinia';
 
 // 旋转图片滑块组件
-import verifyImg from '/@/assets/logo-mini.svg';
+// import verifyImg from '/@/assets/logo-mini.svg';
 const DragVerifyImgRotate = defineAsyncComponent(() => import('/@/components/dragVerify/dragVerifyImgRotate.vue'));
 
+const storesThemeConfig = useThemeConfig();
+const { themeConfig } = storeToRefs(storesThemeConfig);
+
 const { t } = useI18n();
 const route = useRoute();
 const router = useRouter();
@@ -120,7 +125,8 @@ const state = reactive({
 	},
 	captchaImage: '',
 	rotateVerifyVisible: false,
-	rotateVerifyImg: verifyImg,
+	// rotateVerifyImg: verifyImg,
+	rotateVerifyImg: themeConfig.value.logoUrl,
 	secondVerEnabled: false,
 	captchaEnabled: false,
 	isPassRotate: false,

+ 2 - 2
Web/src/views/login/index.vue

@@ -2,7 +2,7 @@
 	<div class="login-container flex">
 		<div class="login-left flex-margin">
 			<div class="login-left-logo">
-				<img :src="logoMini" />
+				<img :src="getThemeConfig.logoUrl" />
 				<div class="login-left-logo-text">
 					<span>{{ getThemeConfig.globalViceTitle }}</span>
 					<span class="login-left-logo-text-msg">{{ getThemeConfig.globalViceTitleMsg }}</span>
@@ -55,7 +55,7 @@ import { defineAsyncComponent, onMounted, reactive, computed } from 'vue';
 import { storeToRefs } from 'pinia';
 import { useThemeConfig } from '/@/stores/themeConfig';
 import { NextLoading } from '/@/utils/loading';
-import logoMini from '/@/assets/logo-mini.svg';
+// import logoMini from '/@/assets/logo-mini.svg';
 import loginIconTwo from '/@/assets/login-icon-two.svg';
 import loginIconTwo1 from '/@/assets/login-icon-two1.svg';
 import loginIconTwo2 from '/@/assets/login-icon-two2.svg';

+ 143 - 0
Web/src/views/system/infoSetting/index.vue

@@ -0,0 +1,143 @@
+<template>
+	<div>
+		<el-card shadow="hover" v-loading="state.isLoading">
+			<el-descriptions title="系统信息配置" :column="2" :border="true">
+				<el-descriptions-item label="系统图标" :span="2">
+					<el-upload class="avatar-uploader" :showFileList="false" :autoUpload="false" accept=".jpg,.png,.svg" action="" :limit="1" :onChange="handleUploadChange">
+						<img v-if="state.formData.sysLogo" :src="state.formData.sysLogo" class="avatar" />
+						<SvgIcon v-else class="avatar-uploader-icon" name="ele-Plus" :size="28" />
+					</el-upload>
+				</el-descriptions-item>
+				<el-descriptions-item label="系统主标题">
+					<el-input v-model="state.formData.sysTitle" />
+				</el-descriptions-item>
+				<el-descriptions-item label="系统副标题">
+					<el-input v-model="state.formData.sysViceTitle" />
+				</el-descriptions-item>
+				<el-descriptions-item label="系统描述" :span="2">
+					<el-input v-model="state.formData.sysViceDesc" />
+				</el-descriptions-item>
+				<el-descriptions-item label="水印内容" :span="2">
+					<el-input v-model="state.formData.sysWatermark" />
+				</el-descriptions-item>
+				<el-descriptions-item label="版权说明" :span="2">
+					<el-input v-model="state.formData.sysCopyright" />
+				</el-descriptions-item>
+				<template #extra>
+					<el-button type="primary" @click="onSave">保存</el-button>
+				</template>
+			</el-descriptions>
+		</el-card>
+	</div>
+</template>
+<script setup lang="ts" name="sysInfoSetting">
+import { nextTick, reactive } from 'vue';
+import { getAPI } from '/@/utils/axios-utils';
+import { SysConfigApi } from '/@/api-services';
+import { ElMessage } from 'element-plus';
+import { fileToBase64 } from '/@/utils/base64Conver';
+
+const state = reactive({
+	isLoading: false,
+	file: undefined as any,
+	formData: {
+		sysLogoBlob: undefined,
+		sysLogo: '',
+		sysTitle: '',
+		sysViceTitle: '',
+		sysViceDesc: '',
+		sysWatermark: '',
+		sysCopyright: '',
+	},
+});
+
+// 通过onChanne方法获得文件列表
+const handleUploadChange = (file: any) => {
+	state.file = file;
+	// 改变 sysLogo,显示预览
+	state.formData.sysLogo = URL.createObjectURL(state.file.raw);
+};
+
+/** 保存 */
+const onSave = async () => {
+	// 如果有选择图标,则转换为 base64
+	let sysLogoBase64 = '';
+	if (state.file) {
+		sysLogoBase64 = (await fileToBase64(state.file.raw)) as string;
+	}
+
+	try {
+		state.isLoading = true;
+		const res = await getAPI(SysConfigApi).apiSysConfigSaveSysInfoPost({
+			sysLogoBase64: sysLogoBase64,
+			sysTitle: state.formData.sysTitle,
+			sysViceTitle: state.formData.sysViceTitle,
+			sysViceDesc: state.formData.sysViceDesc,
+			sysWatermark: state.formData.sysWatermark,
+			sysCopyright: state.formData.sysCopyright,
+		});
+		if (res.data!.type !== 'success') return;
+
+		// 清空 file 变量
+		state.file = undefined;
+		await loadData();
+		ElMessage.success('保存成功');
+	} finally {
+		nextTick(() => {
+			state.isLoading = false;
+		});
+	}
+};
+
+/** 加载数据 */
+const loadData = async () => {
+	try {
+		state.isLoading = true;
+		const res = await getAPI(SysConfigApi).apiSysConfigSysInfoGet();
+		if (res.data!.type !== 'success') return;
+
+		const result = res.data.result;
+		state.formData = {
+			sysLogoBlob: undefined,
+			sysLogo: result.sysLogo,
+			sysTitle: result.sysTitle,
+			sysViceTitle: result.sysViceTitle,
+			sysViceDesc: result.sysViceDesc,
+			sysWatermark: result.sysWatermark,
+			sysCopyright: result.sysCopyright,
+		};
+	} finally {
+		nextTick(() => {
+			state.isLoading = false;
+		});
+	}
+};
+
+loadData();
+</script>
+<style lang="scss" scoped>
+.avatar-uploader .avatar {
+	width: 100px;
+	height: 100px;
+	display: block;
+}
+
+:deep(.avatar-uploader) .el-upload {
+	border: 1px dashed var(--el-border-color);
+	cursor: pointer;
+	position: relative;
+	overflow: hidden;
+	transition: var(--el-transition-duration-fast);
+}
+
+:deep(.avatar-uploader) .el-upload:hover {
+	border-color: var(--el-color-primary);
+}
+
+.el-icon.avatar-uploader-icon {
+	color: #8c939d;
+	width: 100px;
+	height: 100px;
+	text-align: center;
+}
+</style>