diff --git a/build/version.props b/build/version.props index 88fe86b92..f1d53f884 100644 --- a/build/version.props +++ b/build/version.props @@ -2,7 +2,7 @@ 8 0 - 9 + 10 $(VersionMajor).$(VersionMinor).$(VersionPatch) diff --git a/src/Util.Core/Helpers/FileWatcher.cs b/src/Util.Core/Helpers/FileWatcher.cs index 05e341cd6..7db14331f 100644 --- a/src/Util.Core/Helpers/FileWatcher.cs +++ b/src/Util.Core/Helpers/FileWatcher.cs @@ -8,13 +8,34 @@ public class FileWatcher : IDisposable { /// 文件系统监视器 /// private readonly FileSystemWatcher _watcher; + /// + /// 防抖间隔 + /// + private int _debounceInterval; + /// + /// 文件监视事件过滤器 + /// + private readonly FileWatcherEventFilter _fileWatcherEventFilter; /// /// 初始化文件监视器 /// public FileWatcher() { _watcher = new FileSystemWatcher(); - _watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; + _watcher.NotifyFilter = NotifyFilters.DirectoryName | NotifyFilters.FileName | NotifyFilters.LastWrite + | NotifyFilters.CreationTime | NotifyFilters.LastAccess + | NotifyFilters.Attributes | NotifyFilters.Size | NotifyFilters.Security; + _debounceInterval = 200; + _fileWatcherEventFilter = new FileWatcherEventFilter(); + } + + /// + /// 设置防抖间隔,默认值: 200 毫秒 + /// + /// 防抖间隔, 单位: 毫秒 + public FileWatcher Debounce( int interval ) { + _debounceInterval = interval; + return this; } /// @@ -52,7 +73,8 @@ public FileWatcher Filter( string filter ) { /// 文件创建监听事件处理器 public FileWatcher OnCreated( Action action ) { _watcher.Created += ( source, e ) => { - action( source, e ); + if ( _fileWatcherEventFilter.IsValid( e.FullPath, _debounceInterval ) ) + action( source, e ); }; return this; } @@ -63,7 +85,8 @@ public FileWatcher OnCreated( Action action ) { /// 文件创建监听事件处理器 public FileWatcher OnCreatedAsync( Func action ) { _watcher.Created += async ( source, e ) => { - await action( source, e ); + if ( _fileWatcherEventFilter.IsValid( e.FullPath, _debounceInterval ) ) + await action( source, e ); }; return this; } @@ -74,7 +97,8 @@ public FileWatcher OnCreatedAsync( Func actio /// 文件变更监听事件处理器 public FileWatcher OnChanged( Action action ) { _watcher.Changed += ( source, e ) => { - action( source, e ); + if ( _fileWatcherEventFilter.IsValid( e.FullPath, _debounceInterval ) ) + action( source, e ); }; return this; } @@ -85,7 +109,8 @@ public FileWatcher OnChanged( Action action ) { /// 文件变更监听事件处理器 public FileWatcher OnChangedAsync( Func action ) { _watcher.Changed += async ( source, e ) => { - await action( source, e ); + if ( _fileWatcherEventFilter.IsValid( e.FullPath, _debounceInterval ) ) + await action( source, e ); }; return this; } @@ -96,7 +121,8 @@ public FileWatcher OnChangedAsync( Func actio /// 文件删除监听事件处理器 public FileWatcher OnDeleted( Action action ) { _watcher.Deleted += ( source, e ) => { - action( source, e ); + if ( _fileWatcherEventFilter.IsValid( e.FullPath, _debounceInterval ) ) + action( source, e ); }; return this; } @@ -107,7 +133,8 @@ public FileWatcher OnDeleted( Action action ) { /// 文件删除监听事件处理器 public FileWatcher OnDeletedAsync( Func action ) { _watcher.Deleted += async ( source, e ) => { - await action( source, e ); + if ( _fileWatcherEventFilter.IsValid( e.FullPath, _debounceInterval ) ) + await action( source, e ); }; return this; } @@ -118,7 +145,8 @@ public FileWatcher OnDeletedAsync( Func actio /// 文件重命名监听事件处理器 public FileWatcher OnRenamed( Action action ) { _watcher.Renamed += ( source, e ) => { - action( source, e ); + if ( _fileWatcherEventFilter.IsValid( e.FullPath, _debounceInterval ) ) + action( source, e ); }; return this; } @@ -129,7 +157,8 @@ public FileWatcher OnRenamed( Action action ) { /// 文件重命名监听事件处理器 public FileWatcher OnRenamedAsync( Func action ) { _watcher.Renamed += async ( source, e ) => { - await action( source, e ); + if ( _fileWatcherEventFilter.IsValid( e.FullPath, _debounceInterval ) ) + await action( source, e ); }; return this; } @@ -200,4 +229,52 @@ public FileWatcher Stop() { public void Dispose() { _watcher?.Dispose(); } +} + +/// +/// 文件监视事件过滤器 +/// +internal class FileWatcherEventFilter { + /// + /// 最后一个文件 + /// + private WatchFile _file; + + /// + /// 监视事件是否有效 + /// + /// 文件路径 + /// 防抖间隔 + internal bool IsValid( string path, int debounceInterval ) { + if ( _file != null && path == _file.Path && DateTime.Now - _file.Time < TimeSpan.FromMilliseconds( debounceInterval ) ) { + _file = new WatchFile( path ); + return false; + } + _file = new WatchFile( path ); + return true; + } +} + +/// +/// 监视文件 +/// +internal class WatchFile { + /// + /// 初始化监视文件 + /// + /// 文件路径 + public WatchFile( string path ) { + Path = path; + Time = DateTime.Now; + } + + /// + /// 文件路径 + /// + public string Path { get; } + + /// + /// 处理时间 + /// + public DateTime Time { get; } } \ No newline at end of file diff --git a/src/Util.Logging.Serilog.Exceptionless/03-Util.Logging.Serilog.Exceptionless.csproj b/src/Util.Logging.Serilog.Exceptionless/03-Util.Logging.Serilog.Exceptionless.csproj index fe6c9a944..5b4bd2c19 100644 --- a/src/Util.Logging.Serilog.Exceptionless/03-Util.Logging.Serilog.Exceptionless.csproj +++ b/src/Util.Logging.Serilog.Exceptionless/03-Util.Logging.Serilog.Exceptionless.csproj @@ -27,8 +27,8 @@ - - + + diff --git a/src/Util.Ui.NgZorro/AppBuilderExtensions.cs b/src/Util.Ui.NgZorro/AppBuilderExtensions.cs index db1bf264d..d2aebb284 100644 --- a/src/Util.Ui.NgZorro/AppBuilderExtensions.cs +++ b/src/Util.Ui.NgZorro/AppBuilderExtensions.cs @@ -27,7 +27,9 @@ public static IAppBuilder AddNgZorro( this IAppBuilder builder, Action { var options = new NgZorroOptions(); setupAction?.Invoke( options ); - services.AddRazorPages().AddRazorRuntimeCompilation().AddConventions(); + services.AddRazorPages( razorPageOptions => { + razorPageOptions.RootDirectory = options.RazorRootDirectory; + } ).AddRazorRuntimeCompilation().AddConventions(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); @@ -53,16 +55,8 @@ private static void ConfigSpaStaticFiles( IServiceCollection services, NgZorroOp /// 配置Razor /// private static void ConfigRazorOptions( IServiceCollection services, NgZorroOptions options ) { - void Action( RazorOptions t ) { - t.IsGenerateHtml = options.IsGenerateHtml; - t.GenerateHtmlBasePath = options.GenerateHtmlBasePath; - t.GenerateHtmlFolder = options.GenerateHtmlFolder; - t.GenerateHtmlSuffix = options.GenerateHtmlSuffix; - t.EnableWatchRazor = options.EnableWatchRazor; - t.StartInitDelay = options.StartInitDelay; - t.HtmlRenderDelayOnRazorChange = options.HtmlRenderDelayOnRazorChange; - t.EnablePreheat = options.EnablePreheat; - t.EnableOverrideHtml = options.EnableOverrideHtml; + void Action( RazorOptions razorOptions ) { + options.MapTo( razorOptions ); } services.Configure( (Action)Action ); } diff --git a/src/Util.Ui.NgZorro/Components/Descriptions/DescriptionItemTagHelper.cs b/src/Util.Ui.NgZorro/Components/Descriptions/DescriptionItemTagHelper.cs index 86ce56fe8..20ac889c2 100644 --- a/src/Util.Ui.NgZorro/Components/Descriptions/DescriptionItemTagHelper.cs +++ b/src/Util.Ui.NgZorro/Components/Descriptions/DescriptionItemTagHelper.cs @@ -1,7 +1,6 @@ using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; using Util.Ui.Angular.TagHelpers; -using Util.Ui.Configs; using Util.Ui.NgZorro.Components.Descriptions.Renders; using Util.Ui.NgZorro.Components.Display.Helpers; using Util.Ui.Renders; diff --git a/src/Util.Ui.NgZorro/Components/Links/Renders/ARender.cs b/src/Util.Ui.NgZorro/Components/Links/Renders/ARender.cs index 5f5867775..f1c32121b 100644 --- a/src/Util.Ui.NgZorro/Components/Links/Renders/ARender.cs +++ b/src/Util.Ui.NgZorro/Components/Links/Renders/ARender.cs @@ -1,8 +1,9 @@ using Util.Ui.Builders; +using Util.Ui.NgZorro.Components.Forms.Configs; using Util.Ui.NgZorro.Components.Links.Builders; using Util.Ui.Renders; -namespace Util.Ui.NgZorro.Components.Links.Renders; +namespace Util.Ui.NgZorro.Components.Links.Renders; /// /// 链接渲染器 @@ -25,11 +26,31 @@ public ARender( Config config ) { /// 获取标签生成器 /// protected override TagBuilder GetTagBuilder() { + if ( IsHide() ) + return new EmptyTagBuilder(); var builder = new ABuilder( _config ); builder.Config(); return builder; } + /// + /// 是否隐藏标签 + /// + private bool IsHide() { + var isSearch = _config.GetValue( UiConst.IsSearch ); + if ( isSearch != true ) + return false; + var formShareConfig = GetFormShareConfig(); + return formShareConfig.GetConditionCount() <= formShareConfig.SearchFormShowNumber; + } + + /// + /// 获取表单共享配置 + /// + public FormShareConfig GetFormShareConfig() { + return _config.GetValueFromItems() ?? new FormShareConfig(); + } + /// public override IHtmlContent Clone() { return new ARender( _config.Copy() ); diff --git a/src/Util.Ui.NgZorro/Components/Tables/Builders/TableBodyRowBuilder.cs b/src/Util.Ui.NgZorro/Components/Tables/Builders/TableBodyRowBuilder.cs index b3d3c515c..73441ba5f 100644 --- a/src/Util.Ui.NgZorro/Components/Tables/Builders/TableBodyRowBuilder.cs +++ b/src/Util.Ui.NgZorro/Components/Tables/Builders/TableBodyRowBuilder.cs @@ -55,6 +55,7 @@ public override void Config() { base.Config(); ConfigTableExtend(); ConfigEdit(); + ConfigTableRowCheckedClass(); ConfigContent(); } @@ -88,6 +89,16 @@ protected void ConfigEdit() { Attribute( "(dblclick)", $"{EditId}.dblClickEdit(row.id)", append: true ); } + /// + /// 配置勾选样式 + /// + protected void ConfigTableRowCheckedClass() { + var options = NgZorroOptionsService.GetOptions(); + if ( options.EnableTableRowCheckedClass == false ) + return; + Attribute( "[class.table-row-checked]", $"{TableShareConfig.TableExtendId}.isChecked(row)" ); + } + /// /// 配置内容 /// diff --git a/src/Util.Ui.NgZorro/Components/Tables/Builders/TableHeadRowBuilder.cs b/src/Util.Ui.NgZorro/Components/Tables/Builders/TableHeadRowBuilder.cs index bb5adc0cc..99d0edc96 100644 --- a/src/Util.Ui.NgZorro/Components/Tables/Builders/TableHeadRowBuilder.cs +++ b/src/Util.Ui.NgZorro/Components/Tables/Builders/TableHeadRowBuilder.cs @@ -1,5 +1,4 @@ using Util.Ui.Angular.Extensions; -using Util.Ui.Configs; using Util.Ui.Extensions; using Util.Ui.NgZorro.Components.Tables.Configs; using Util.Ui.NgZorro.Components.Tables.Helpers; diff --git a/src/Util.Ui.NgZorro/Controllers/GenerateHtmlController.cs b/src/Util.Ui.NgZorro/Controllers/GenerateHtmlController.cs new file mode 100644 index 000000000..906559337 --- /dev/null +++ b/src/Util.Ui.NgZorro/Controllers/GenerateHtmlController.cs @@ -0,0 +1,28 @@ +using Util.Ui.Razor; + +namespace Util.Ui.NgZorro.Controllers; + +/// +/// Razor生成Html控制器 +/// +[ApiController] +[Route( "api/html" )] +public class GenerateHtmlController : ControllerBase { + /// + /// 生成所有Razor页面的Html + /// + [HttpGet] + public async Task GenerateAsync() { + var message = new StringBuilder(); + var result = await HtmlGenerator.GenerateAsync(); + message.AppendLine( "======================= 欢迎使用 Util 应用框架 - 开始生成全部Razor页面html =======================" ); + message.AppendLine(); + message.AppendLine(); + foreach ( var path in result ) + message.AppendLine( path ); + message.AppendLine(); + message.AppendLine(); + message.Append( "======================================== html文件生成完成 ========================================" ); + return message.ToString(); + } +} \ No newline at end of file diff --git a/src/Util.Ui.NgZorro/NgZorroOptions.cs b/src/Util.Ui.NgZorro/NgZorroOptions.cs index c5d8fdd20..e5931111b 100644 --- a/src/Util.Ui.NgZorro/NgZorroOptions.cs +++ b/src/Util.Ui.NgZorro/NgZorroOptions.cs @@ -1,11 +1,12 @@ using Util.Ui.NgZorro.Enums; +using Util.Ui.Razor; namespace Util.Ui.NgZorro; /// /// NgZorro配置 /// -public class NgZorroOptions { +public class NgZorroOptions : RazorOptions { /// /// 初始化NgZorro配置 /// @@ -18,34 +19,6 @@ public NgZorroOptions() { } } - /// - /// Spa静态文件根路径,默认值: ClientApp - /// - public string RootPath { get; set; } = "ClientApp"; - /// - /// 是否自动生成html,默认值: false - /// - internal bool IsGenerateHtml { get; set; } - /// - /// Razor生成Html页面的基路径,默认值: /ClientApp/src/app - /// - public string GenerateHtmlBasePath { get; set; } = "/ClientApp/src/app"; - /// - /// Razor生成Html文件的目录名称,默认值:html - /// - public string GenerateHtmlFolder { get; set; } = "html"; - /// - /// Razor生成Html页面的文件后缀,默认值:component.html - /// - public string GenerateHtmlSuffix { get; set; } = "component.html"; - /// - /// Razor监听服务启动初始化的延迟时间,单位: 毫秒, 默认值:1000, 注意: 需要等待Web服务启动完成才能开始初始化 - /// - public int StartInitDelay { get; set; } = 1000; - /// - /// 修改Razor页面生成Html文件的延迟时间,单位: 毫秒, 默认值:100 ,注意: 延迟太短可能导致生成异常 - /// - public int HtmlRenderDelayOnRazorChange { get; set; } = 100; /// /// 是否启用默认项文本,默认值: true /// @@ -63,17 +36,9 @@ public NgZorroOptions() { /// public bool EnableAllowClear { get; set; } = true; /// - /// 是否启用Razor监视服务,默认值: true - /// - public bool EnableWatchRazor { get; set; } = true; - /// - /// 启动Razor监视服务时是否预热,默认值: true - /// - public bool EnablePreheat { get; set; } = true; - /// - /// Razor生成是否覆盖已存在的html文件,默认值: true + /// 是否启用勾选表格行复选框时添加样式类 table-row-checked ,默认值: false /// - public bool EnableOverrideHtml { get; set; } = true; + public bool EnableTableRowCheckedClass { get; set; } /// /// 获取表格布尔列内容操作 /// diff --git a/src/Util.Ui.NgZorro/WebApplicationExtensions.cs b/src/Util.Ui.NgZorro/WebApplicationExtensions.cs index 00f19571d..ff6a14e06 100644 --- a/src/Util.Ui.NgZorro/WebApplicationExtensions.cs +++ b/src/Util.Ui.NgZorro/WebApplicationExtensions.cs @@ -20,11 +20,10 @@ public static class WebApplicationExtensions { public static WebApplication UseNgZorro( this WebApplication app ) { app.CheckNull( nameof( app ) ); AddEndpoints( app ); - if ( app.Environment.IsDevelopment() == false ) - return app; app.UseAngular( spa => { spa.Options.SourcePath = SourcePath; - spa.UseAngularCliServer( "start" ); + if ( app.Environment.IsDevelopment() ) + spa.UseAngularCliServer( "start" ); } ); return app; } @@ -55,8 +54,12 @@ public static WebApplication UseNgZorro( this WebApplication app, Action { + if ( app.Environment.IsDevelopment() ) + action( spa ); + }); return app; } @@ -89,11 +92,10 @@ public static WebApplication UseNgZorro( this WebApplication app, int port, bool private static WebApplication UseSpa( WebApplication app, string developmentServerBaseUri ) { app.CheckNull( nameof( app ) ); AddEndpoints( app ); - if ( app.Environment.IsDevelopment() == false ) - return app; app.UseSpa( spa => { spa.Options.SourcePath = SourcePath; - spa.UseProxyToSpaDevelopmentServer( developmentServerBaseUri ); + if ( app.Environment.IsDevelopment() ) + spa.UseProxyToSpaDevelopmentServer( developmentServerBaseUri ); } ); return app; } @@ -104,12 +106,11 @@ private static WebApplication UseSpa( WebApplication app, string developmentServ private static WebApplication UseCustomSpa( WebApplication app, int port ) { app.CheckNull( nameof( app ) ); AddEndpoints( app ); - if ( app.Environment.IsDevelopment() == false ) - return app; app.UseAngular( spa => { spa.Options.SourcePath = SourcePath; spa.Options.DevServerPort = port; - spa.UseAngularCliServer( "start" ); + if ( app.Environment.IsDevelopment() ) + spa.UseAngularCliServer( "start" ); } ); return app; } diff --git a/src/Util.Ui/Razor/GenerateHtmlFilter.cs b/src/Util.Ui/Razor/GenerateHtmlFilter.cs index 8d9704078..72c0a1f4c 100644 --- a/src/Util.Ui/Razor/GenerateHtmlFilter.cs +++ b/src/Util.Ui/Razor/GenerateHtmlFilter.cs @@ -1,7 +1,6 @@ using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using System.Globalization; using Util.Helpers; using File = System.IO.File; @@ -60,12 +59,28 @@ protected virtual ILogger GetLogger( PageHandlerSelectedCont private string CreatePath( PageHandlerSelectedContext context, RazorOptions options ) { var attribute = context?.ActionDescriptor?.ModelTypeInfo?.GetCustomAttribute(); if ( attribute == null ) - return GetPath( context?.ActionDescriptor.ViewEnginePath, options.GenerateHtmlBasePath, options.GenerateHtmlFolder, options.GenerateHtmlSuffix ); + return GetPath( context?.ActionDescriptor.ViewEnginePath, options ); if ( attribute.Ignore ) return string.Empty; return string.IsNullOrWhiteSpace( attribute.Path ) ? string.Empty : attribute.Path; } + /// + /// 获取Html文件路径 + /// + /// 路径 + /// 配置 + public static string GetPath( string path, RazorOptions options ) { + if ( string.IsNullOrWhiteSpace( path ) ) + return string.Empty; + if ( options.GenerateHtmlVersion == "v1" ) + return GetPath( path, options.GenerateHtmlBasePath, options.GenerateHtmlFolder, options.GenerateHtmlSuffix ); + if ( path.Contains( "/" ) == false ) + return Util.Helpers.Url.JoinPath( options.RazorRootDirectory, options.GenerateHtmlFolder, $"{path}.html" ); + var lastIndex = path.LastIndexOf( "/", StringComparison.Ordinal ); + return Util.Helpers.Url.JoinPath( options.RazorRootDirectory, path.Substring( 0, lastIndex ), options.GenerateHtmlFolder, $"{path.Substring( lastIndex + 1 )}.html" ); + } + /// /// 获取Html文件路径 /// diff --git a/src/Util.Ui/Razor/HtmlGenerator.cs b/src/Util.Ui/Razor/HtmlGenerator.cs index e82e35cab..de0695da3 100644 --- a/src/Util.Ui/Razor/HtmlGenerator.cs +++ b/src/Util.Ui/Razor/HtmlGenerator.cs @@ -6,33 +6,6 @@ namespace Util.Ui.Razor; /// Html生成器 /// public static class HtmlGenerator { - /// - /// 生成Html - /// - /// 视图路径 - /// 是否生成Html,默认值: true - /// 取消令牌 - public static async Task GenerateAsync( string path, bool isGenerateHtml = true, CancellationToken cancellationToken = default ) { - EnableGenerateHtml( isGenerateHtml ); - var requestPath = Url.JoinPath( GetHost(), "view", path.RemoveStart( "/Pages" ).RemoveEnd( ".cshtml" ) ); - return await Web.Client.Get( requestPath ).GetResultAsync( cancellationToken ); - } - - /// - /// 启用Html自动生成 - /// - private static void EnableGenerateHtml( bool isGenerateHtml = true ) { - var options = Ioc.Create>(); - options.Value.IsGenerateHtml = isGenerateHtml; - } - - /// - /// 获取请求主机 - /// - private static string GetHost() { - return $"{Web.Request.Scheme}://{Web.Request.Host}"; - } - /// /// 生成Html /// @@ -48,6 +21,14 @@ private static string GetHost() { return result.Distinct().ToList(); } + /// + /// 启用Html自动生成 + /// + private static void EnableGenerateHtml( bool isGenerateHtml = true ) { + var options = Ioc.Create>(); + options.Value.IsGenerateHtml = isGenerateHtml; + } + /// /// 获取页面操作描述符列表 /// @@ -55,4 +36,11 @@ private static List GetPageActionDescriptors() { var provider = Ioc.Create(); return provider.ActionDescriptors.Items.OfType().ToList(); } + + /// + /// 获取请求主机 + /// + private static string GetHost() { + return $"{Web.Request.Scheme}://{Web.Request.Host}"; + } } \ No newline at end of file diff --git a/src/Util.Ui/Razor/Internal/PartViewPathFinder.cs b/src/Util.Ui/Razor/Internal/PartViewPathFinder.cs index ade465a8c..73ba55610 100644 --- a/src/Util.Ui/Razor/Internal/PartViewPathFinder.cs +++ b/src/Util.Ui/Razor/Internal/PartViewPathFinder.cs @@ -12,13 +12,19 @@ public class PartViewPathFinder : IPartViewPathFinder { /// 视图引擎 /// private readonly ICompositeViewEngine _viewEngine; + /// + /// Razor配置 + /// + private readonly RazorOptions _options; /// /// 初始化分部视图路径查找器 /// /// 视图引擎 - public PartViewPathFinder( ICompositeViewEngine viewEngine ) { + /// Razor配置 + public PartViewPathFinder( ICompositeViewEngine viewEngine, IOptions options ) { _viewEngine = viewEngine ?? throw new ArgumentNullException( nameof( viewEngine ) ); + _options = options.Value; } /// @@ -38,7 +44,7 @@ public string Find( string viewPath, string partViewName ) { /// 获取路由值 /// private string GetRouteValue( string viewPath ) { - return viewPath.RemoveStart( "/Pages" ).RemoveEnd( ".cshtml" ); + return viewPath.RemoveStart( _options.RazorRootDirectory ).RemoveEnd( ".cshtml" ); } /// diff --git a/src/Util.Ui/Razor/Internal/RazorViewContainer.cs b/src/Util.Ui/Razor/Internal/RazorViewContainer.cs index 92d946129..032eaa1e1 100644 --- a/src/Util.Ui/Razor/Internal/RazorViewContainer.cs +++ b/src/Util.Ui/Razor/Internal/RazorViewContainer.cs @@ -63,7 +63,7 @@ public List GetRandomPaths() { public RazorView FindView( string path ) { if ( path.IsEmpty() ) return null; - return _views.TryGetValue( path, out var view ) ? view : null; + return _views.TryGetValue( path.SafeString().ToLower(), out var view ) ? view : null; } /// @@ -73,7 +73,7 @@ public RazorView FindView( string path ) { public void AddView( RazorView view ) { if ( view == null ) return; - _views.TryAdd( view.Path, view ); + _views.TryAdd( view.Path.SafeString().ToLower(), view ); } /// diff --git a/src/Util.Ui/Razor/RazorOptions.cs b/src/Util.Ui/Razor/RazorOptions.cs index 2ca4395df..c8e86f41b 100644 --- a/src/Util.Ui/Razor/RazorOptions.cs +++ b/src/Util.Ui/Razor/RazorOptions.cs @@ -4,6 +4,18 @@ /// Razor配置 /// public class RazorOptions { + /// + /// Razor文件根路径,默认值: /ClientApp + /// + public string RazorRootDirectory { get; set; } = "/ClientApp"; + /// + /// Spa静态文件根路径,默认值: ClientApp + /// + public string RootPath { get; set; } = "ClientApp"; + /// + /// Razor页面生成html文件路径的版本,用于兼容传统使用方式,可选值: v1,v2, 如果需要退回到传统生成路径,传入 v1 + /// + public string GenerateHtmlVersion { get; set; } /// /// 是否在Razor页面运行时自动生成html文件 /// diff --git a/src/Util.Ui/Razor/RazorWatchService.cs b/src/Util.Ui/Razor/RazorWatchService.cs index 719b7886d..d77a23b58 100644 --- a/src/Util.Ui/Razor/RazorWatchService.cs +++ b/src/Util.Ui/Razor/RazorWatchService.cs @@ -158,9 +158,9 @@ protected void EnableOverrideHtml( bool isOverrideHtml = true ) { /// html是否存在 /// protected bool Exists( List htmlPaths, string razorPath ) { - if ( string.Equals( razorPath, "/Pages/Error.cshtml", StringComparison.OrdinalIgnoreCase ) ) + if ( string.Equals( razorPath, $"{_options.RazorRootDirectory}/Error.cshtml", StringComparison.OrdinalIgnoreCase ) ) return true; - var path = GenerateHtmlFilter.GetPath( razorPath.RemoveStart( "/Pages" ).RemoveEnd( ".cshtml" ) ); + var path = GenerateHtmlFilter.GetPath( razorPath.RemoveStart( _options.RazorRootDirectory ).RemoveEnd( ".cshtml" ), _options ); return htmlPaths.Any( t => t.Replace( "\\", "/" ).EndsWith( path, StringComparison.OrdinalIgnoreCase ) ); } @@ -172,7 +172,7 @@ protected bool Exists( List htmlPaths, string razorPath ) { /// 是否写入日志 public async Task Request( string path, CancellationToken cancellationToken = default, bool isWrite = true ) { await Task.Delay( _options.HtmlRenderDelayOnRazorChange, cancellationToken ); - var requestPath = Url.JoinPath( GetApplicationUrl(), "view", path.RemoveStart( "/Pages" ).RemoveEnd( ".cshtml" ) ); + var requestPath = Url.JoinPath( GetApplicationUrl(), "view", path.RemoveStart( _options.RazorRootDirectory ).RemoveEnd( ".cshtml" ) ); WriteLog( $"发送请求: {requestPath}", isWrite ); var response = await _client.GetAsync( requestPath, cancellationToken ); if ( response.IsSuccessStatusCode ) @@ -216,7 +216,7 @@ protected async Task Preheat( CancellationToken cancellationToken ) { /// protected virtual Task StartWatch( CancellationToken cancellationToken ) { WriteLog( "开始监听..." ); - var path = GetProjectPath( "Pages" ); + var path = GetProjectPath( _options.RazorRootDirectory.RemoveStart( "/" ) ); _watcher.Path( path ) .Filter( "*.cshtml" ) .OnChangedAsync( async ( _, e ) => await GenerateAsync( e.FullPath, cancellationToken ) ) diff --git a/src/Util.Ui/Sources/Spa/Proxying/SpaProxyingExtensions.cs b/src/Util.Ui/Sources/Spa/Proxying/SpaProxyingExtensions.cs index 281b2036d..9b29e846f 100644 --- a/src/Util.Ui/Sources/Spa/Proxying/SpaProxyingExtensions.cs +++ b/src/Util.Ui/Sources/Spa/Proxying/SpaProxyingExtensions.cs @@ -69,6 +69,10 @@ public static void UseProxyToSpaDevelopmentServer( await next(); return; } + if ( context.Request.Path.Value != null && context.Request.Path.Value.StartsWith( "/api/" ) ) { + await next(); + return; + } await SpaProxy.PerformProxyRequest( context, neverTimeOutHttpClient, baseUriTaskFactory(), applicationStoppingToken, proxy404s: true ); diff --git a/src/Util.Ui/Sources/Spa/SpaDefaultPageMiddleware.cs b/src/Util.Ui/Sources/Spa/SpaDefaultPageMiddleware.cs index be3d1de23..32ba3f035 100644 --- a/src/Util.Ui/Sources/Spa/SpaDefaultPageMiddleware.cs +++ b/src/Util.Ui/Sources/Spa/SpaDefaultPageMiddleware.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.Extensions.Hosting; using Util.Ui.Sources.Spa.StaticFiles; namespace Util.Ui.Sources.Spa; @@ -34,18 +33,5 @@ public static void Attach(ISpaBuilder spaBuilder) app.UseSpaStaticFilesInternal( options.DefaultPageStaticFileOptions ?? new StaticFileOptions(), allowFallbackOnServingWebRootFiles: true); - - // If the default file didn't get served as a static file (usually because it was not - // present on disk), the SPA is definitely not going to work. - //app.Use( async (context, next) => - //{ - // // If we have an Endpoint, then this is a deferred match - just noop. - // if (context.GetEndpoint() != null) - // { - // await next(context); - // } - // await Task.Delay(100); - // await next( context ); - //} ); } } diff --git a/test/Util.Ui.NgAlain.Tests/Startup.cs b/test/Util.Ui.NgAlain.Tests/Startup.cs index e3502122e..f57e45a4d 100644 --- a/test/Util.Ui.NgAlain.Tests/Startup.cs +++ b/test/Util.Ui.NgAlain.Tests/Startup.cs @@ -12,13 +12,14 @@ public class Startup { /// /// public void ConfigureHost( IHostBuilder hostBuilder ) { - hostBuilder.ConfigureDefaults( null ).AddUtil(); - } + hostBuilder.ConfigureDefaults( null ).AddUtil(); + Util.Helpers.Environment.IsTest = true; + } /// /// ÷ /// public void ConfigureServices( IServiceCollection services ) { - services.AddLogging( logBuilder => logBuilder.AddXunitOutput() ); - } + services.AddLogging( logBuilder => logBuilder.AddXunitOutput() ); + } } \ No newline at end of file diff --git a/test/Util.Ui.NgZorro.Tests/Links/ATagHelperTest.cs b/test/Util.Ui.NgZorro.Tests/Links/ATagHelperTest.cs index d061ca8c3..726d980d8 100644 --- a/test/Util.Ui.NgZorro.Tests/Links/ATagHelperTest.cs +++ b/test/Util.Ui.NgZorro.Tests/Links/ATagHelperTest.cs @@ -2,6 +2,7 @@ using Util.Ui.Angular.Configs; using Util.Ui.Configs; using Util.Ui.Enums; +using Util.Ui.NgZorro.Components.Forms.Configs; using Util.Ui.NgZorro.Components.Links; using Util.Ui.NgZorro.Configs; using Util.Ui.NgZorro.Enums; @@ -326,11 +327,17 @@ public void TestSpaceItem() { } /// - /// 测试查询表单链接 + /// 测试查询表单链接 - 查询条件数量超过初始显示数量则显示 /// [Fact] - public void TestIsSearch() { + public void TestIsSearch_1() { _wrapper.SetContextAttribute( UiConst.IsSearch, true ); + var formShareConfig = new FormShareConfig { SearchFormShowNumber = 1 }; + formShareConfig.AddColumnId( "a" ); + formShareConfig.AddColumnId( "b" ); + formShareConfig.AddColumnId( "action" ); + _wrapper.SetItem( formShareConfig ); + var result = new StringBuilder(); result.Append( "" ); result.Append( "{{expand?'收起':'展开'}}" ); @@ -340,12 +347,18 @@ public void TestIsSearch() { } /// - /// 测试查询表单链接 - 多语言 + /// 测试查询表单链接 - 查询条件数量超过初始显示数量则显示 - 多语言 /// [Fact] - public void TestIsSearch_i18n() { + public void TestIsSearch_2() { NgZorroOptionsService.SetOptions( new NgZorroOptions { EnableI18n = true } ); _wrapper.SetContextAttribute( UiConst.IsSearch, true ); + var formShareConfig = new FormShareConfig { SearchFormShowNumber = 1 }; + formShareConfig.AddColumnId( "a" ); + formShareConfig.AddColumnId( "b" ); + formShareConfig.AddColumnId( "action" ); + _wrapper.SetItem( formShareConfig ); + var result = new StringBuilder(); result.Append( "" ); result.Append( "{{expand?('util.collapse'|i18n):('util.expand'|i18n)}}" ); @@ -354,6 +367,20 @@ public void TestIsSearch_i18n() { Assert.Equal( result.ToString(), GetResult() ); } + /// + /// 测试查询表单链接 - 查询条件数量少于等于初始显示数量则不显示 + /// + [Fact] + public void TestIsSearch_3() { + _wrapper.SetContextAttribute( UiConst.IsSearch, true ); + var formShareConfig = new FormShareConfig { SearchFormShowNumber = 2 }; + formShareConfig.AddColumnId( "a" ); + formShareConfig.AddColumnId( "b" ); + formShareConfig.AddColumnId( "action" ); + _wrapper.SetItem( formShareConfig ); + Assert.Empty( GetResult() ); + } + /// /// 测试显示表格设置 /// diff --git a/test/Util.Ui.NgZorro.Tests/Tables/TableRowTagHelperTest.cs b/test/Util.Ui.NgZorro.Tests/Tables/TableRowTagHelperTest.cs index 540829a53..84358fb49 100644 --- a/test/Util.Ui.NgZorro.Tests/Tables/TableRowTagHelperTest.cs +++ b/test/Util.Ui.NgZorro.Tests/Tables/TableRowTagHelperTest.cs @@ -2,11 +2,12 @@ using Util.Helpers; using Util.Ui.Configs; using Util.Ui.NgZorro.Components.Tables; +using Util.Ui.NgZorro.Configs; using Util.Ui.TagHelpers; using Xunit; using Xunit.Abstractions; -namespace Util.Ui.NgZorro.Tests.Tables; +namespace Util.Ui.NgZorro.Tests.Tables; /// /// 表格行测试 @@ -106,6 +107,17 @@ public void TestSelectOnlyOnClick_2() { Assert.Equal( result.ToString(), GetResult() ); } + /// + /// 测试勾选样式 + /// + [Fact] + public void TestEnableTableRowCheckedClass() { + NgZorroOptionsService.SetOptions( new NgZorroOptions { EnableTableRowCheckedClass = true } ); + var result = new StringBuilder(); + result.Append( "" ); + Assert.Equal( result.ToString(), GetResult() ); + } + /// /// 测试行单击事件 /// diff --git a/test/Util.Ui.Tests/Razor/GenerateHtmlFilterTest.cs b/test/Util.Ui.Tests/Razor/GenerateHtmlFilterTest.cs index 6d97e6e00..0c83964cd 100644 --- a/test/Util.Ui.Tests/Razor/GenerateHtmlFilterTest.cs +++ b/test/Util.Ui.Tests/Razor/GenerateHtmlFilterTest.cs @@ -104,5 +104,27 @@ public void TestGetPath_11() { var result = GenerateHtmlFilter.GetPath( "FetchData/Test/NavMenu", "/a", htmlSuffix: "html" ); Assert.Equal( "/a/fetch-data/test/html/nav-menu.html", result ); } + + /// + /// Razor生成版本为 v1 ,使用传统生成路径逻辑 + /// + [Fact] + public void TestGetPath_12() { + var options = new RazorOptions { + GenerateHtmlVersion = "v1" + }; + var result = GenerateHtmlFilter.GetPath( "FetchData/Test/NavMenu", options ); + Assert.Equal( "/ClientApp/src/app/fetch-data/test/html/nav-menu.component.html", result ); + } + + /// + /// Razor生成版本不为 v1 ,使用新的生成路径逻辑 + /// + [Fact] + public void TestGetPath_13() { + var options = new RazorOptions(); + var result = GenerateHtmlFilter.GetPath( "src/app/fetch-data/test/nav-menu.component", options ); + Assert.Equal( "/ClientApp/src/app/fetch-data/test/html/nav-menu.component.html", result ); + } } }