diff --git a/Acdiu.AspNetCore.Mvc.Commons.csproj b/Acdiu.AspNetCore.Mvc.Commons.csproj new file mode 100644 index 0000000..fd3b38e --- /dev/null +++ b/Acdiu.AspNetCore.Mvc.Commons.csproj @@ -0,0 +1,21 @@ + + + + net10.0 + enable + enable + favicon.ico + True + + + + + + + + + + + + + diff --git a/Constants/UIConstants.cs b/Constants/UIConstants.cs new file mode 100644 index 0000000..1e84b4c --- /dev/null +++ b/Constants/UIConstants.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Acdiu.AspNetCore.Mvc.Commons.Constants +{ + /// + /// 全局 UI 常量定义 + /// + public static class UIConstants + { + /// + /// 资源路径常量 + /// + public static class Paths + { + /// + /// 默认 Bootstrap Icons SVG Sprite 文件路径 + /// + public const string DefaultIconSprite = "/lib/bootstrap-icons-1.13.1/bootstrap-icons.svg"; + } + + /// + /// BS SVG 图标默认需要添加的 CSS 类名 + /// + public static class Classes + { + /// + /// 默认宽度类名,适配响应式根字号(1rem)。如果用户未指定宽度,则默认添加这些类名以确保图标大小适中且响应式。用户可以通过添加其他类名覆盖这些默认值。 + /// + public const string DefaultWidth = "w-1r"; + /// + /// 默认高度类名,适配响应式根字号(1rem)。如果用户未指定高度,则默认添加这些类名以确保图标大小适中且响应式。用户可以通过添加其他类名覆盖这些默认值。 + /// + public const string DefaultHeight = "h-1r"; + } + } +} diff --git a/Extensions/StringExtensions.cs b/Extensions/StringExtensions.cs new file mode 100644 index 0000000..6579ca6 --- /dev/null +++ b/Extensions/StringExtensions.cs @@ -0,0 +1,34 @@ +using Acdiu.AspNetCore.Mvc.Commons.Constants; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Acdiu.AspNetCore.Mvc.Commons.Extensions +{ + /// + /// 字符串扩展方法类,提供与 UI 相关的字符串处理功能。 + /// + public static class StringExtensions + { + /// + /// 转换为响应式类名,默认包含默认宽高 "w-1r h-1r" 类名,并且用户可以添加其他类名,最终返回一个包含所有不重复的类名的字符串。 + /// + /// 一个包含所有不重复的类名的字符串 + public static string ToResponsiveClass(this string userClassName) + { + var classes = new HashSet(StringComparer.OrdinalIgnoreCase) + { + UIConstants.Classes.DefaultWidth, + UIConstants.Classes.DefaultHeight + }; + + if (!string.IsNullOrWhiteSpace(userClassName)) + { + var userParts = userClassName.Split([' '], StringSplitOptions.RemoveEmptyEntries); + foreach (var item in userParts) classes.Add(item); + } + + return string.Join(" ", classes); + } + } +} diff --git a/TagHelpers/BsIconTagHelper.cs b/TagHelpers/BsIconTagHelper.cs new file mode 100644 index 0000000..02e1210 --- /dev/null +++ b/TagHelpers/BsIconTagHelper.cs @@ -0,0 +1,166 @@ +using Acdiu.AspNetCore.Mvc.Commons.Constants; +using Acdiu.AspNetCore.Mvc.Commons.Extensions; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.VisualBasic; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Acdiu.AspNetCore.Mvc.Commons.TagHelpers +{ + /// + /// SVG Sprite 封装标签。 + /// 支持自动缩放逻辑:若未指定宽高,则默认添加 w-1r/h-1r 类名以适配响应式根字号。 + /// + /// + /// 用法: <bsicon i="heart-fill" w="20" f="red" /> + /// 或: <bsicon icon="alarm" width="1rem" cn="my-style" /> + /// + [HtmlTargetElement("bsicon")] + public class BsIconTagHelper : TagHelper + { + // 内部逻辑变量 + private string _iconName = string.Empty; + private string _width = string.Empty; + private string _height = string.Empty; + private string _fill = "currentColor"; + private string _className = string.Empty; + private string _customPath = string.Empty; + + #region 属性定义 (支持别名) + + /// + /// 图标名称 (必填)。对应 SVG 中的 ID。 + /// + [HtmlAttributeName("i")] + public string I { get => _iconName; set => _iconName = value; } + + /// + /// 图标名称 (别名)。 + /// + [HtmlAttributeName("icon")] + public string Icon { get => _iconName; set => _iconName = value; } + + /// + /// 宽度。支持数字或 CSS 单位(如 16, 1rem)。 + /// 若为空则尝试使用高度值,若均为空则添加默认响应式类名。 + /// + [HtmlAttributeName("w")] + public string W { get => _width; set => _width = value; } + + /// + /// 宽度 (别名)。 + /// + [HtmlAttributeName("width")] + public string Width { get => _width; set => _width = value; } + + /// + /// 高度。支持数字或 CSS 单位。 + /// + [HtmlAttributeName("h")] + public string H { get => _height; set => _height = value; } + + /// + /// 高度 (别名)。 + /// + [HtmlAttributeName("height")] + public string Height { get => _height; set => _height = value; } + + /// + /// 填充颜色。默认为 currentColor。 + /// + [HtmlAttributeName("f")] + public string F { get => _fill; set => _fill = value; } + + /// + /// 填充颜色 (别名)。 + /// + [HtmlAttributeName("fill")] + public string Fill { get => _fill; set => _fill = value; } + + /// + /// 自定义 CSS 类名。 + /// + [HtmlAttributeName("c")] + public string C { get => _className; set => _className = value; } + + /// + /// 自定义 CSS 类名 (别名)。 + /// + [HtmlAttributeName("cn")] + public string CN { get => _className; set => _className = value; } + + /// + /// 自定义 CSS 类名 (别名)。 + /// + [HtmlAttributeName("class")] + public string ClassName { get => _className; set => _className = value; } + + /// + /// 自定义 SVG Sprite 路径。 + /// 不填则使用系统默认常量路径。 + /// + [HtmlAttributeName("p")] + public string P { get => _customPath; set => _customPath = value; } + + /// + /// 自定义 SVG Sprite 路径。 + /// 不填则使用系统默认常量路径。 + /// + [HtmlAttributeName("path")] + public string Path { get => _customPath; set => _customPath = value; } + + /// + /// 自定义 SVG Sprite 路径。 + /// 不填则使用系统默认常量路径。 + /// + [HtmlAttributeName("path")] + public string CustomPath { get => _customPath; set => _customPath = value; } + + #endregion + + /// + /// 处理标签渲染逻辑 + /// + public override void Process(TagHelperContext context, TagHelperOutput output) + { + if (string.IsNullOrWhiteSpace(_iconName)) + { + output.SuppressOutput(); + return; + } + + // 1. 设置基础标签属性 + output.TagName = "svg"; + output.TagMode = TagMode.StartTagAndEndTag; + output.Attributes.SetAttribute("fill", _fill); + output.Attributes.SetAttribute("viewBox", "0 0 16 16"); + + // 2. 处理宽高与类名逻辑 + bool hasW = !string.IsNullOrWhiteSpace(_width); + bool hasH = !string.IsNullOrWhiteSpace(_height); + + if (hasW || hasH) + { + // 只要设置了任意一个,就手动赋值 width/height 属性 + output.Attributes.SetAttribute("width", hasW ? _width : _height); + output.Attributes.SetAttribute("height", hasH ? _height : _width); + + // 设置类名(不添加默认响应式类) + output.Attributes.SetAttribute("class", _className.ToResponsiveClass()); + } + else + { + // 均未设置,则应用响应式默认类名并去重 + output.Attributes.SetAttribute("class", _className.ToResponsiveClass()); + } + + // 3. 构建内部内容 + string finalPath = string.IsNullOrWhiteSpace(_customPath) + ? UIConstants.Paths.DefaultIconSprite + : _customPath; + + output.Content.SetHtmlContent($@""); + } + } +} diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..8951969 Binary files /dev/null and b/favicon.ico differ