This commit is contained in:
2026-04-25 22:51:57 +08:00
7 changed files with 155 additions and 147 deletions

View File

@@ -1,127 +1,167 @@
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.VisualBasic;
using System.Threading.Tasks;
using AcdiuTools.Constants;
namespace AcdiuTools.TagHelpers
{
/// <summary>
/// <see langword="Bootstrap Icons"></see> SVG Sprite 封装标签
/// 用法: <bsicon i="heart-fill" w="16" h="16" f="red" />
/// <see langword="Bootstrap Icons"/> SVG Sprite 封装标签
/// <para>支持自动缩放逻辑:若未指定宽高,则默认添加 w-1r/h-1r 类名以适配响应式根字号。</para>
/// </summary>
/// <example>
/// 用法: &lt;bsicon i="heart-fill" w="20" f="red" /&gt;
/// 或: &lt;bsicon icon="alarm" width="1rem" cn="my-style" /&gt;
/// </example>
[HtmlTargetElement("bsicon")]
public class BsIconTagHelper : TagHelper
{
// 路径指向你存放总 SVG 的位置
/// <summary>
/// 表示用于引用图标符号的 <see langword="Bootstrap Icons"></see> SVG矢量图库文件的相对路径
/// </summary>
/// <remarks>此路径应指向包含所有 <see langword="Bootstrap"></see> 图标定义的SVG文件<br/>
/// 如果图标库被移动或升级,则需要更新该值</remarks>
const string spritePath = "/lib/bootstrap-icons-1.13.1/bootstrap-icons.svg";
// 内部逻辑变量
private string _iconName = string.Empty;
private string _width = string.Empty;
private string _height = string.Empty;
private string _fill = "currentColor";
#region ()
/// <summary>
/// 图标名称 (必填)
/// 图标名称 (必填)。对应 SVG 中的 ID。
/// </summary>
[HtmlAttributeName("i")]
public required string IconName { get; set; }
public string I { get => _iconName; set => _iconName = value; }
/// <summary>
/// 宽度 (默认为空,若为空时设置了高度,则使用高度的值,否则为元素 ClassName 添加 w-1r 以使其默认宽度为 1rem)
/// 图标名称 (别名)。
/// </summary>
[HtmlAttributeName("icon")]
public string Icon { get => _iconName; set => _iconName = value; }
/// <summary>
/// 宽度。支持数字或 CSS 单位(如 16, 1rem
/// <remarks>若为空则尝试使用高度值,若均为空则添加默认响应式类名。</remarks>
/// </summary>
[HtmlAttributeName("w")]
public string Width { get; set; } = string.Empty;
public string W { get => _width; set => _width = value; }
/// <summary>
/// 度 (默认为空,若为空时设置了宽度,则使用宽度的值,否则为元素 ClassName 添加 h-1r 以使其默认高度为 1rem)
/// 度 (别名)。
/// </summary>
[HtmlAttributeName("width")]
public string Width { get => _width; set => _width = value; }
/// <summary>
/// 高度。支持数字或 CSS 单位。
/// </summary>
[HtmlAttributeName("h")]
public string Height { get; set; } = string.Empty;
public string H { get => _height; set => _height = value; }
/// <summary>
/// 填充颜色 (默认 currentColor 即当前元素颜色)
/// 高度 (别名)。
/// </summary>
[HtmlAttributeName("height")]
public string Height { get => _height; set => _height = value; }
/// <summary>
/// 填充颜色。默认为 <c>currentColor</c>。
/// </summary>
[HtmlAttributeName("f")]
public string Fill { get; set; } = "currentColor";
public string F { get => _fill; set => _fill = value; }
/// <summary>
/// 自定义 class 名称,允许用户添加额外的样式类 (默认空)
/// 填充颜色 (别名)。
/// </summary>
[HtmlAttributeName("fill")]
public string Fill { get => _fill; set => _fill = value; }
/// <summary>
/// 自定义 CSS 类名。
/// </summary>
[HtmlAttributeName("c")]
public string C { get; set; } = string.Empty;
/// <summary>
/// 自定义 CSS 类名 (别名)。
/// </summary>
[HtmlAttributeName("cn")]
public string CN { get; set; } = string.Empty;
/// <summary>
/// 自定义 CSS 类名 (别名)。
/// </summary>
[HtmlAttributeName("class")]
public string ClassName { get; set; } = string.Empty;
/// <summary>
/// 根据指定的图标名称和属性处理标签助手以渲染SVG矢量图标
/// 自定义 SVG Sprite 路径。
/// <remarks>不填则使用系统默认常量路径。</remarks>
/// </summary>
[HtmlAttributeName("path")]
public string CustomPath { get; set; } = string.Empty;
#endregion
/// <summary>
/// 处理标签渲染逻辑
/// </summary>
/// <remarks>若未指定图标名称或为空则输出被抑制且不生成SVG<br/>
/// 渲染后,该方法会将原始标签替换为&lt;svg&gt;元素并设置标准的SVG属性<br/>
/// 包括类名、宽度、高度、填充颜色和图标ID名称。该SVG引用了 <see langword="Bootstrap Icons"></see> 中的图标<br/>
/// 使用&lt;use&gt;元素引用的矢量图形</remarks>
/// <param name="context">用于标签辅助程序执行的上下文包含当前HTML标签及其属性的相关信息</param>
/// <param name="output">标签辅助器的输出用于修改渲染的HTML元素及其内容</param>
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (string.IsNullOrWhiteSpace(IconName))
if (string.IsNullOrWhiteSpace(_iconName))
{
output.SuppressOutput(); // 如果没写图标名,则不渲染
output.SuppressOutput();
return;
}
// 1. 将外层标签替换为 <svg>
// 1. 设置基础标签属性
output.TagName = "svg";
output.TagMode = TagMode.StartTagAndEndTag;
output.Attributes.SetAttribute("fill", _fill);
output.Attributes.SetAttribute("viewBox", "0 0 16 16");
// 2. 设置 SVG 基础属性
// 如果设置了 w 或 h则优先使用它们否则使用 wh 的值
// 设定机制:
// 如果 w 和 h 中任意一个不为空,则使用不为空的 w 或 h 的值同时设置宽高;
// 如果 w 和 h 都不为空,则分别使用它们设置宽高;
// 如果 w 和 h 都为空,则为其添加默认的 w-1r 和 h-1r 类以设置默认宽高为 1rem
if (!string.IsNullOrWhiteSpace(Width) || !string.IsNullOrWhiteSpace(Height))
// 2. 处理宽高与类名逻辑
bool hasW = !string.IsNullOrWhiteSpace(_width);
bool hasH = !string.IsNullOrWhiteSpace(_height);
if (hasW || hasH)
{
// 如果用户设置了宽或高,则不添加默认的 w-1r 和 h-1r 类
output.Attributes.SetAttribute("width", !string.IsNullOrWhiteSpace(Width) ? Width : Height);
output.Attributes.SetAttribute("height", !string.IsNullOrWhiteSpace(Height) ? Height : Width);
// 只要设置了任意一个,就手动赋值 width/height 属性
output.Attributes.SetAttribute("width", hasW ? _width : _height);
output.Attributes.SetAttribute("height", hasH ? _height : _width);
// 继续添加用户自定义的 ClassName (如果有的话)
output.Attributes.SetAttribute("class", MergeClassNames(string.IsNullOrWhiteSpace(ClassName) ? "" : $"{ClassName}"));
// 设置类名(不添加默认响应式类)
output.Attributes.SetAttribute("class", ClassName);
}
else
{
// 如果没有设置宽高,则添加默认的 w-1r 和 h-1r 类并与用户自定义的 ClassName 合并
// 如果提供了 ClassName则判断是否为空如果不为空则将其与默认的 w-1r 和 h-1r 类合并
// 合并时使用 HashSet 来去重,确保不会重复添加相同的类名
output.Attributes.SetAttribute("class", MergeClassNames(ClassName));
// 均未设置,则应用响应式默认类名并去重
output.Attributes.SetAttribute("class", BuildResponsiveClasses(ClassName));
}
output.Attributes.SetAttribute("fill", Fill);
output.Attributes.SetAttribute("viewBox", "0 0 16 16"); // 保证矢量对齐
//output.Attributes.SetAttribute("xmlns", "http://www.w3.org/2000/svg");
// 3. 构建内部内容
string finalPath = string.IsNullOrWhiteSpace(CustomPath)
? UIConstants.Paths.DefaultIconSprite
: CustomPath;
// 3. 构造内部的 <use> 节点
var content = $@"<use xlink:href=""{spritePath}#{IconName}""></use>";
output.Content.SetHtmlContent(content);
output.Content.SetHtmlContent($@"<use xlink:href=""{finalPath}#{_iconName}""></use>");
}
/// <summary>
/// 合并类名的方法,确保默认的 w-1r 和 h-1r 类与用户提供的 ClassName 合并且去重
/// 构建响应式类名字符串,确保默认类名存在且去重
/// </summary>
/// <param name="userClassName">用户提供的类名</param>
/// <returns>合并且去重后的类名字符串</returns>
public static string MergeClassNames(string userClassName)
private string BuildResponsiveClasses(string userClassName)
{
// 定义默认的类列表
var defaultClasses = new HashSet<string> { "w-1r", "h-1r" };
// 如果用户提供了 ClassName则将其拆分成单个类并添加到集合中
var classes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
UIConstants.Classes.DefaultWidth,
UIConstants.Classes.DefaultHeight
};
if (!string.IsNullOrWhiteSpace(userClassName))
{
var userClasses = userClassName.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var cls in userClasses)
{
defaultClasses.Add(cls);
}
var userParts = userClassName.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var item in userParts) classes.Add(item);
}
// 将集合中的类名合并成一个字符串返回
return string.Join(" ", defaultClasses);
return string.Join(" ", classes);
}
}
}