diff --git a/build/version.props b/build/version.props index 16bc9019..288ea347 100644 --- a/build/version.props +++ b/build/version.props @@ -2,7 +2,7 @@ 7 1 - 35 + 36 $(VersionMajor).$(VersionMinor).$(VersionPatch) diff --git a/src/Util.Data.EntityFrameworkCore/UnitOfWorkBase.cs b/src/Util.Data.EntityFrameworkCore/UnitOfWorkBase.cs index 89486e17..921cb3cc 100644 --- a/src/Util.Data.EntityFrameworkCore/UnitOfWorkBase.cs +++ b/src/Util.Data.EntityFrameworkCore/UnitOfWorkBase.cs @@ -640,6 +640,7 @@ public abstract class UnitOfWorkBase : DbContext, IUnitOfWork, IFilterSwitch { if ( value != null ) result.Add( value ); } ); + continue; } var changeValue = GetPropertyChangeValue( property, propertyEntry ); if ( changeValue == null ) diff --git a/src/Util.Ui.Angular/Configs/AngularConst.cs b/src/Util.Ui.Angular/Configs/AngularConst.cs index 3db86a2f..e00ce437 100644 --- a/src/Util.Ui.Angular/Configs/AngularConst.cs +++ b/src/Util.Ui.Angular/Configs/AngularConst.cs @@ -77,6 +77,22 @@ public static class AngularConst { /// public const string BindFixed = "bind-fixed"; /// + /// 行数 + /// + public const string BindLines = "bind-lines"; + /// + /// 长度 + /// + public const string BindLength = "bind-length"; + /// + /// 全宽识别 + /// + public const string BindFullWidthRecognition = "bind-full-width-recognition"; + /// + /// 尾巴 + /// + public const string BindTail = "bind-tail"; + /// /// 名称 /// public const string BindName = "bind-name"; @@ -101,6 +117,10 @@ public static class AngularConst { /// public const string BindValue = "bind-value"; /// + /// 工具提示 + /// + public const string BindTooltip = "bind-tooltip"; + /// /// 提示 /// public const string BindPlaceholder = "bind-placeholder"; diff --git a/src/Util.Ui.NgAlain/Components/Ellipsis/Builders/EllipsisBuilder.cs b/src/Util.Ui.NgAlain/Components/Ellipsis/Builders/EllipsisBuilder.cs new file mode 100644 index 00000000..62c5c332 --- /dev/null +++ b/src/Util.Ui.NgAlain/Components/Ellipsis/Builders/EllipsisBuilder.cs @@ -0,0 +1,79 @@ +using Util.Ui.Angular.Builders; +using Util.Ui.Angular.Configs; +using Util.Ui.Configs; + +namespace Util.Ui.NgAlain.Components.Ellipsis.Builders; + +/// +/// 文本省略标签生成器 +/// +public class EllipsisBuilder : AngularTagBuilder { + /// + /// 配置 + /// + private readonly Config _config; + /// + /// 初始化文本省略标签生成器 + /// + /// 配置 + public EllipsisBuilder( Config config ) : base( config, "ellipsis" ) { + _config = config; + } + + /// + /// 配置工具提示 + /// + public EllipsisBuilder Tooltip() { + AttributeIfNotEmpty( "[tooltip]", _config.GetBoolValue( UiConst.Tooltip ) ); + AttributeIfNotEmpty( "[tooltip]", _config.GetValue( AngularConst.BindTooltip ) ); + return this; + } + + /// + /// 配置文本最大长度 + /// + public EllipsisBuilder Length() { + AttributeIfNotEmpty( "length", _config.GetValue( UiConst.Length ) ); + AttributeIfNotEmpty( "[length]", _config.GetValue( AngularConst.BindLength ) ); + return this; + } + + /// + /// 配置文本最大行数 + /// + public EllipsisBuilder Lines() { + AttributeIfNotEmpty( "lines", _config.GetValue( UiConst.Lines ) ); + AttributeIfNotEmpty( "[lines]", _config.GetValue( AngularConst.BindLines ) ); + return this; + } + + /// + /// 配置是否将全角字符的长度视为2来计算字符串长度 + /// + public EllipsisBuilder FullWidthRecognition() { + AttributeIfNotEmpty( "[fullWidthRecognition]", _config.GetBoolValue( UiConst.FullWidthRecognition ) ); + AttributeIfNotEmpty( "[fullWidthRecognition]", _config.GetValue( AngularConst.BindFullWidthRecognition ) ); + return this; + } + + /// + /// 配置溢出尾巴 + /// + public EllipsisBuilder Tail() { + AttributeIfNotEmpty( "tail", _config.GetValue( UiConst.Tail ) ); + AttributeIfNotEmpty( "[tail]", _config.GetValue( AngularConst.BindTail ) ); + return this; + } + + /// + /// 配置 + /// + public override void Config() { + base.Config(); + Tooltip().Length().Lines().FullWidthRecognition().Tail(); + var value = _config.GetValue( UiConst.Value ); + if ( value.IsEmpty() ) + return; + AppendContent( "{{" + value + "}}" ); + } +} \ No newline at end of file diff --git a/src/Util.Ui.NgAlain/Components/Ellipsis/EllipsisTagHelper.cs b/src/Util.Ui.NgAlain/Components/Ellipsis/EllipsisTagHelper.cs new file mode 100644 index 00000000..62c928c7 --- /dev/null +++ b/src/Util.Ui.NgAlain/Components/Ellipsis/EllipsisTagHelper.cs @@ -0,0 +1,77 @@ +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Util.Ui.Angular.TagHelpers; +using Util.Ui.Configs; +using Util.Ui.NgAlain.Components.Ellipsis.Helpers; +using Util.Ui.NgAlain.Components.Ellipsis.Renders; +using Util.Ui.Renders; + +namespace Util.Ui.NgAlain.Components.Ellipsis; + +/// +/// 文本省略组件,生成的标签为<ellipsis></ellipsis> +/// +[HtmlTargetElement( "util-ellipsis" )] +public class EllipsisTagHelper : AngularTagHelperBase { + /// + /// 配置 + /// + private Config _config; + /// + /// 属性表达式 + /// + public ModelExpression For { get; set; } + /// + /// [tooltip],移动到文本展示完整内容的提示,默认值: false + /// + public bool Tooltip { get; set; } + /// + /// [tooltip],移动到文本展示完整内容的提示,默认值: false + /// + public string BindTooltip { get; set; } + /// + /// length,在按照长度截取下的文本最大字符数,超过则截取省略 + /// + public int Length { get; set; } + /// + /// [length],在按照长度截取下的文本最大字符数,超过则截取省略 + /// + public string BindLength { get; set; } + /// + /// lines,在按照行数截取下最大的行数,超过则截取省略 + /// + public int Lines { get; set; } + /// + /// [lines],在按照行数截取下最大的行数,超过则截取省略 + /// + public string BindLines { get; set; } + /// + /// [fullWidthRecognition],是否将全角字符的长度视为2来计算字符串长度,默认值: false + /// + public bool FullWidthRecognition { get; set; } + /// + /// [fullWidthRecognition],是否将全角字符的长度视为2来计算字符串长度,默认值: false + /// + public string BindFullWidthRecognition { get; set; } + /// + /// tail,指定溢出尾巴,默认值: ... + /// + public string Tail { get; set; } + /// + /// [tail],指定溢出尾巴,默认值: ... + /// + public string BindTail { get; set; } + + /// + protected override void ProcessBefore( TagHelperContext context, TagHelperOutput output ) { + _config = new Config( context, output ); + var loader = new EllipsisExpressionLoader(); + loader.Load( _config ); + } + + /// + protected override IRender GetRender( TagHelperContext context, TagHelperOutput output, TagHelperContent content ) { + _config.Content = content; + return new EllipsisRender( _config ); + } +} \ No newline at end of file diff --git a/src/Util.Ui.NgAlain/Components/Ellipsis/Helpers/EllipsisExpressionLoader.cs b/src/Util.Ui.NgAlain/Components/Ellipsis/Helpers/EllipsisExpressionLoader.cs new file mode 100644 index 00000000..39ca0260 --- /dev/null +++ b/src/Util.Ui.NgAlain/Components/Ellipsis/Helpers/EllipsisExpressionLoader.cs @@ -0,0 +1,19 @@ +using Util.Ui.Configs; +using Util.Ui.Expressions; +using Util.Ui.NgZorro.Expressions; + +namespace Util.Ui.NgAlain.Components.Ellipsis.Helpers; + +/// +/// 文本省略表达式加载器 +/// +public class EllipsisExpressionLoader : NgZorroExpressionLoaderBase { + /// + /// 加载模型信息 + /// + /// 配置 + /// 模型表达式信息 + protected override void Load( Config config, ModelExpressionInfo info ) { + config.SetAttribute( UiConst.Value, info.SafePropertyName, false ); + } +} \ No newline at end of file diff --git a/src/Util.Ui.NgAlain/Components/Ellipsis/Renders/EllipsisRender.cs b/src/Util.Ui.NgAlain/Components/Ellipsis/Renders/EllipsisRender.cs new file mode 100644 index 00000000..b52834e9 --- /dev/null +++ b/src/Util.Ui.NgAlain/Components/Ellipsis/Renders/EllipsisRender.cs @@ -0,0 +1,38 @@ +using Util.Ui.Builders; +using Util.Ui.Configs; +using Util.Ui.NgAlain.Components.Ellipsis.Builders; +using Util.Ui.Renders; + +namespace Util.Ui.NgAlain.Components.Ellipsis.Renders; + +/// +/// 文本省略渲染器 +/// +public class EllipsisRender : RenderBase { + /// + /// 配置 + /// + private readonly Config _config; + + /// + /// 初始化文本省略渲染器 + /// + /// 配置 + public EllipsisRender( Config config ) { + _config = config; + } + + /// + /// 获取标签生成器 + /// + protected override TagBuilder GetTagBuilder() { + var builder = new EllipsisBuilder( _config ); + builder.Config(); + return builder; + } + + /// + public override IHtmlContent Clone() { + return new EllipsisBuilder( _config.Copy() ); + } +} \ No newline at end of file diff --git a/src/Util.Ui.NgAlain/Components/PageHeaders/Builders/PageHeaderBuilder.cs b/src/Util.Ui.NgAlain/Components/PageHeaders/Builders/PageHeaderBuilder.cs index c30fdde7..0f7c268a 100644 --- a/src/Util.Ui.NgAlain/Components/PageHeaders/Builders/PageHeaderBuilder.cs +++ b/src/Util.Ui.NgAlain/Components/PageHeaders/Builders/PageHeaderBuilder.cs @@ -213,8 +213,7 @@ public class PageHeaderBuilder : AngularTagBuilder { /// public override void Config() { base.Config(); - Title() - .AutoTitle().SyncTitle() + Title().AutoTitle().SyncTitle() .Home().HomeLink().HomeI18n() .AutoBreadcrumb().RecursiveBreadcrumb() .Loading().Wide().Fixed().FixedOffsetTop() diff --git a/src/Util.Ui.NgZorro/Components/Display/Helpers/DisplayExpressionLoader.cs b/src/Util.Ui.NgZorro/Components/Display/Helpers/DisplayExpressionLoader.cs index 75895c70..c5d8b2e9 100644 --- a/src/Util.Ui.NgZorro/Components/Display/Helpers/DisplayExpressionLoader.cs +++ b/src/Util.Ui.NgZorro/Components/Display/Helpers/DisplayExpressionLoader.cs @@ -39,7 +39,6 @@ public class DisplayExpressionLoader : NgZorroExpressionLoaderBase { return true; return false; } - /// /// 加载值 diff --git a/src/Util.Ui/Configs/UiConst.cs b/src/Util.Ui/Configs/UiConst.cs index c837fd21..b31b4dde 100644 --- a/src/Util.Ui/Configs/UiConst.cs +++ b/src/Util.Ui/Configs/UiConst.cs @@ -45,6 +45,10 @@ public static class UiConst { /// public const string DropdownRender = "dropdown-render"; /// + /// 行数 + /// + public const string Lines = "lines"; + /// /// 访问控制 /// public const string Acl = "acl"; @@ -57,6 +61,10 @@ public static class UiConst { /// public const string AclElseTemplateId = "acl-else-template-id"; /// + /// 尾巴 + /// + public const string Tail = "tail"; + /// /// 类型 /// public const string Type = "type"; @@ -429,6 +437,10 @@ public static class UiConst { /// public const string MinLengthMessage = "min-length-message"; /// + /// 长度 + /// + public const string Length = "length"; + /// /// 最大长度 /// public const string MaxLength = "max-length"; @@ -461,6 +473,10 @@ public static class UiConst { /// public const string Tooltip = "tooltip"; /// + /// 全宽识别 + /// + public const string FullWidthRecognition = "full-width-recognition"; + /// /// 链接 /// public const string Link = "link"; diff --git a/test/Util.Ui.NgAlain.Tests/Ellipsis/EllipsisTagHelperTest.Expression.cs b/test/Util.Ui.NgAlain.Tests/Ellipsis/EllipsisTagHelperTest.Expression.cs new file mode 100644 index 00000000..529ef6c9 --- /dev/null +++ b/test/Util.Ui.NgAlain.Tests/Ellipsis/EllipsisTagHelperTest.Expression.cs @@ -0,0 +1,22 @@ +using System.Text; +using Xunit; + +namespace Util.Ui.NgAlain.Tests.Ellipsis { + /// + /// 文本省略组件测试 + /// + public partial class EllipsisTagHelperTest { + /// + /// 测试解析表达式属性for + /// + [Fact] + public void TestFor_1() { + _wrapper.SetExpression( t => t.Code ); + var result = new StringBuilder(); + result.Append( "" ); + result.Append( "{{model.code}}" ); + result.Append( "" ); + Assert.Equal( result.ToString(), GetResult() ); + } + } +} \ No newline at end of file diff --git a/test/Util.Ui.NgAlain.Tests/Ellipsis/EllipsisTagHelperTest.cs b/test/Util.Ui.NgAlain.Tests/Ellipsis/EllipsisTagHelperTest.cs new file mode 100644 index 00000000..c3aae3d8 --- /dev/null +++ b/test/Util.Ui.NgAlain.Tests/Ellipsis/EllipsisTagHelperTest.cs @@ -0,0 +1,172 @@ +using System.Text; +using Util.Ui.Angular.Configs; +using Util.Ui.Configs; +using Util.Ui.NgAlain.Components.Ellipsis; +using Util.Ui.NgAlain.Tests.Samples; +using Util.Ui.TagHelpers; +using Xunit; +using Xunit.Abstractions; + +namespace Util.Ui.NgAlain.Tests.Ellipsis { + /// + /// 文本省略组件测试 + /// + public partial class EllipsisTagHelperTest { + /// + /// 输出工具 + /// + private readonly ITestOutputHelper _output; + /// + /// TagHelper包装器 + /// + private readonly TagHelperWrapper _wrapper; + + /// + /// 测试初始化 + /// + public EllipsisTagHelperTest( ITestOutputHelper output ) { + _output = output; + _wrapper = new EllipsisTagHelper().ToWrapper(); + } + + /// + /// 获取结果 + /// + private string GetResult() { + var result = _wrapper.GetResult(); + _output.WriteLine( result ); + return result; + } + + /// + /// 测试默认输出 + /// + [Fact] + public void TestDefault() { + var result = new StringBuilder(); + result.Append( "" ); + Assert.Equal( result.ToString(), GetResult() ); + } + + /// + /// 测试工具提示 + /// + [Fact] + public void TestTooltip() { + _wrapper.SetContextAttribute( UiConst.Tooltip, true ); + var result = new StringBuilder(); + result.Append( "" ); + Assert.Equal( result.ToString(), GetResult() ); + } + + /// + /// 测试工具提示 + /// + [Fact] + public void TestBindTooltip() { + _wrapper.SetContextAttribute( AngularConst.BindTooltip, "true" ); + var result = new StringBuilder(); + result.Append( "" ); + Assert.Equal( result.ToString(), GetResult() ); + } + + /// + /// 测试文本最大长度 + /// + [Fact] + public void TestLength() { + _wrapper.SetContextAttribute( UiConst.Length, 2 ); + var result = new StringBuilder(); + result.Append( "" ); + Assert.Equal( result.ToString(), GetResult() ); + } + + /// + /// 测试文本最大长度 + /// + [Fact] + public void TestBindLength() { + _wrapper.SetContextAttribute( AngularConst.BindLength, "2" ); + var result = new StringBuilder(); + result.Append( "" ); + Assert.Equal( result.ToString(), GetResult() ); + } + + /// + /// 测试文本最大行数 + /// + [Fact] + public void TestLines() { + _wrapper.SetContextAttribute( UiConst.Lines, 2 ); + var result = new StringBuilder(); + result.Append( "" ); + Assert.Equal( result.ToString(), GetResult() ); + } + + /// + /// 测试文本最大行数 + /// + [Fact] + public void TestBindLines() { + _wrapper.SetContextAttribute( AngularConst.BindLines, "2" ); + var result = new StringBuilder(); + result.Append( "" ); + Assert.Equal( result.ToString(), GetResult() ); + } + + /// + /// 测试是否将全角字符的长度视为2来计算字符串长度 + /// + [Fact] + public void TestFullWidthRecognition() { + _wrapper.SetContextAttribute( UiConst.FullWidthRecognition, true ); + var result = new StringBuilder(); + result.Append( "" ); + Assert.Equal( result.ToString(), GetResult() ); + } + + /// + /// 测试是否将全角字符的长度视为2来计算字符串长度 + /// + [Fact] + public void TestBindFullWidthRecognition() { + _wrapper.SetContextAttribute( AngularConst.BindFullWidthRecognition, "true" ); + var result = new StringBuilder(); + result.Append( "" ); + Assert.Equal( result.ToString(), GetResult() ); + } + + /// + /// 测试溢出尾巴 + /// + [Fact] + public void TestTail() { + _wrapper.SetContextAttribute( UiConst.Tail, "a" ); + var result = new StringBuilder(); + result.Append( "" ); + Assert.Equal( result.ToString(), GetResult() ); + } + + /// + /// 测试溢出尾巴 + /// + [Fact] + public void TestBindTail() { + _wrapper.SetContextAttribute( AngularConst.BindTail, "a" ); + var result = new StringBuilder(); + result.Append( "" ); + Assert.Equal( result.ToString(), GetResult() ); + } + + /// + /// 测试内容 + /// + [Fact] + public void TestAppendContent() { + _wrapper.AppendContent( "a" ); + var result = new StringBuilder(); + result.Append( "a" ); + Assert.Equal( result.ToString(), GetResult() ); + } + } +} \ No newline at end of file