diff --git a/Constants/UIConstants.cs b/Constants/UIConstants.cs new file mode 100644 index 0000000..a15ccbe --- /dev/null +++ b/Constants/UIConstants.cs @@ -0,0 +1,28 @@ +namespace AcdiuTools.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 + { + public const string DefaultWidth = "w-1r"; + public const string DefaultHeight = "h-1r"; + } + } +} diff --git a/TagHelpers/BsIconTagHelper.cs b/TagHelpers/BsIconTagHelper.cs index 3a6188e..cfa5a3c 100644 --- a/TagHelpers/BsIconTagHelper.cs +++ b/TagHelpers/BsIconTagHelper.cs @@ -1,127 +1,167 @@ using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.VisualBasic; using System.Threading.Tasks; +using AcdiuTools.Constants; namespace AcdiuTools.TagHelpers { /// - /// SVG Sprite 封装标签 - /// 用法: + /// 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 { - // 路径指向你存放总 SVG 的位置 - /// - /// 表示用于引用图标符号的 SVG矢量图库文件的相对路径 - /// - /// 此路径应指向包含所有 图标定义的SVG文件
- /// 如果图标库被移动或升级,则需要更新该值
- 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 属性定义 (支持别名) /// - /// 图标名称 (必填) + /// 图标名称 (必填)。对应 SVG 中的 ID。 /// [HtmlAttributeName("i")] - public required string IconName { get; set; } + public string I { get => _iconName; set => _iconName = value; } /// - /// 宽度 (默认为空,若为空时设置了高度,则使用高度的值,否则为元素 ClassName 添加 w-1r 以使其默认宽度为 1rem) + /// 图标名称 (别名)。 + /// + [HtmlAttributeName("icon")] + public string Icon { get => _iconName; set => _iconName = value; } + + /// + /// 宽度。支持数字或 CSS 单位(如 16, 1rem)。 + /// 若为空则尝试使用高度值,若均为空则添加默认响应式类名。 /// [HtmlAttributeName("w")] - public string Width { get; set; } = string.Empty; + public string W { get => _width; set => _width = value; } /// - /// 高度 (默认为空,若为空时设置了宽度,则使用宽度的值,否则为元素 ClassName 添加 h-1r 以使其默认高度为 1rem) + /// 宽度 (别名)。 + /// + [HtmlAttributeName("width")] + public string Width { get => _width; set => _width = value; } + + /// + /// 高度。支持数字或 CSS 单位。 /// [HtmlAttributeName("h")] - public string Height { get; set; } = string.Empty; + public string H { get => _height; set => _height = value; } /// - /// 填充颜色 (默认 currentColor 即当前元素颜色) + /// 高度 (别名)。 + /// + [HtmlAttributeName("height")] + public string Height { get => _height; set => _height = value; } + + /// + /// 填充颜色。默认为 currentColor。 /// [HtmlAttributeName("f")] - public string Fill { get; set; } = "currentColor"; + public string F { get => _fill; set => _fill = value; } /// - /// 自定义 class 名称,允许用户添加额外的样式类 (默认空) + /// 填充颜色 (别名)。 + /// + [HtmlAttributeName("fill")] + public string Fill { get => _fill; set => _fill = value; } + + /// + /// 自定义 CSS 类名。 + /// + [HtmlAttributeName("c")] + public string C { get; set; } = string.Empty; + + /// + /// 自定义 CSS 类名 (别名)。 /// [HtmlAttributeName("cn")] + public string CN { get; set; } = string.Empty; + + /// + /// 自定义 CSS 类名 (别名)。 + /// + [HtmlAttributeName("class")] public string ClassName { get; set; } = string.Empty; /// - /// 根据指定的图标名称和属性,处理标签助手以渲染SVG矢量图标 + /// 自定义 SVG Sprite 路径。 + /// 不填则使用系统默认常量路径。 + /// + [HtmlAttributeName("path")] + public string CustomPath { get; set; } = string.Empty; + + #endregion + + /// + /// 处理标签渲染逻辑 /// - /// 若未指定图标名称或为空,则输出被抑制且不生成SVG
- /// 渲染后,该方法会将原始标签替换为<svg>元素并设置标准的SVG属性
- /// 包括类名、宽度、高度、填充颜色和图标ID名称。该SVG引用了 中的图标
- /// 使用<use>元素引用的矢量图形
- /// 用于标签辅助程序执行的上下文,包含当前HTML标签及其属性的相关信息 - /// 标签辅助器的输出,用于修改渲染的HTML元素及其内容 public override void Process(TagHelperContext context, TagHelperOutput output) { - if (string.IsNullOrWhiteSpace(IconName)) + if (string.IsNullOrWhiteSpace(_iconName)) { - output.SuppressOutput(); // 如果没写图标名,则不渲染 + output.SuppressOutput(); return; } - // 1. 将外层标签替换为 + // 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. 构造内部的 节点 - var content = $@""; - - output.Content.SetHtmlContent(content); + output.Content.SetHtmlContent($@""); } /// - /// 合并类名的方法,确保默认的 w-1r 和 h-1r 类与用户提供的 ClassName 合并且去重 + /// 构建响应式类名字符串,确保默认类名存在且去重 /// - /// 用户提供的类名 - /// 合并且去重后的类名字符串 - public static string MergeClassNames(string userClassName) + private string BuildResponsiveClasses(string userClassName) { - // 定义默认的类列表 - var defaultClasses = new HashSet { "w-1r", "h-1r" }; - // 如果用户提供了 ClassName,则将其拆分成单个类并添加到集合中 + var classes = new HashSet(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); } } } \ No newline at end of file diff --git a/Views/Shared/_Layout.cshtml b/Views/Shared/_Layout.cshtml index 75277bf..aafec88 100644 --- a/Views/Shared/_Layout.cshtml +++ b/Views/Shared/_Layout.cshtml @@ -14,7 +14,6 @@ -
diff --git a/wwwroot/ac/ac.css b/wwwroot/ac/ac.css index 9e6562b..bade719 100644 --- a/wwwroot/ac/ac.css +++ b/wwwroot/ac/ac.css @@ -38,6 +38,16 @@ max-width: 1800px; } } +@media (min-width: 2560px) { + .container { + max-width: 2560px; + } +} +@media (min-width: 3800px) { + .container { + max-width: 3800px; + } +} @media (min-width: 3840px) { .container { max-width: 93.75vw; diff --git a/wwwroot/ac/ac.css.bak b/wwwroot/ac/ac.css.bak deleted file mode 100644 index 8ae9214..0000000 --- a/wwwroot/ac/ac.css.bak +++ /dev/null @@ -1,78 +0,0 @@ -:root { - /* --- 基础断点定义 (供参考) --- */ - /* 手机: < 768px */ - /* 平板: 768px - 1024px */ - /* 桌面: 1024px - 1920px */ - /* 4K屏: 3840px */ - /* --- 核心字号缩放逻辑 --- */ - /* 逻辑: 在移动端最小为 14px,在 1024px 以上开始线性增长,到 4K 达到 24px */ - /* 公式: clamp(最小值, 首选值, 最大值) */ - font-size: 14px; -} - -@media (min-width: 1024px) { - :root { - /* 1.2vw + 10px 是一个经过计算的动态值,确保 1024-3840 之间平滑过渡 */ - font-size: clamp(16px, 0.35vw + 12.4px, 24px); - } -} - -@media (min-width: 3840px) { - :root { - /* 针对 4K 以上屏幕,如果想继续缩放可保持 clamp,想锁定则固定 24px */ - font-size: 24px; - } -} - -/* --- 全局布局容器 --- */ -.container { - width: 100%; - margin: 0 auto; - padding: 0 1rem; - box-sizing: border-box; - /* 这里的 1rem 会随着上面定义的 font-size 自动缩放 */ -} - -@media (min-width: 768px) { - .container { - max-width: 720px; - } -} - -@media (min-width: 1024px) { - .container { - max-width: 960px; - } -} - -@media (min-width: 1920px) { - .container { - max-width: 1800px; - } -} - -@media (min-width: 3840px) { - .container { - max-width: 3600px; - } -} - -/* 为当前激活的 nav-link,增加加粗效果 */ -.nav-link.active { - font-weight: bold; - color: var(--bs-primary) !important; - border-bottom: 2px solid var(--bs-primary); -} - -.p-r-t { - position: relative; - top: -1px; -} - -.w-1r { - width: 1rem; -} - -.h-1r { - height: 1rem; -} \ No newline at end of file diff --git a/wwwroot/ac/ac.min.css b/wwwroot/ac/ac.min.css index 2f51eb0..c1b352d 100644 --- a/wwwroot/ac/ac.min.css +++ b/wwwroot/ac/ac.min.css @@ -1 +1 @@ -@charset "UTF-8";:root{font-size:14px;}@media(min-width:1024px){:root{font-size:16px;}}@media(min-width:3840px){:root{font-size:.625vw;}}.container{width:100%;margin:0 auto;padding:0 1rem;box-sizing:border-box;}@media(min-width:768px){.container{max-width:720px;}}@media(min-width:1024px){.container{max-width:960px;}}@media(min-width:1920px){.container{max-width:1800px;}}@media(min-width:3840px){.container{max-width:93.75vw;}}.w-1r{width:1rem;}.h-1r{height:1rem;}.nav-link.active{font-weight:bold;color:var(--bs-primary)!important;border-bottom:2px solid var(--bs-primary);}.p-r-t{position:relative;top:-1px;}mg-0{margin:0 auto;} \ No newline at end of file +@charset "UTF-8";:root{font-size:14px;}@media(min-width:1024px){:root{font-size:16px;}}@media(min-width:3840px){:root{font-size:.625vw;}}.container{width:100%;margin:0 auto;padding:0 1rem;box-sizing:border-box;}@media(min-width:768px){.container{max-width:720px;}}@media(min-width:1024px){.container{max-width:960px;}}@media(min-width:1920px){.container{max-width:1800px;}}@media(min-width:2560px){.container{max-width:2560px;}}@media(min-width:3800px){.container{max-width:3800px;}}@media(min-width:3840px){.container{max-width:93.75vw;}}.w-1r{width:1rem;}.h-1r{height:1rem;}.nav-link.active{font-weight:bold;color:var(--bs-primary)!important;border-bottom:2px solid var(--bs-primary);}.p-r-t{position:relative;top:-1px;}mg-0{margin:0 auto;} \ No newline at end of file diff --git a/wwwroot/ac/ac.scss b/wwwroot/ac/ac.scss index f8658b6..95596fc 100644 --- a/wwwroot/ac/ac.scss +++ b/wwwroot/ac/ac.scss @@ -1,7 +1,7 @@ // ======================================================== // 1. 变量与断点定义 (集中管理) // ======================================================== -$breakpoints: ( 'mobile': 768px, 'tablet': 1024px, 'desktop': 1920px, '4k': 3840px ); +$breakpoints: ( 'mobile': 768px, 'tablet': 1024px, 'desktop': 1920px, '2k': 2560px, '4k-40': 3800px, '4k': 3840px ); // 字号常量 $font-size-small: 14px; // 手机/平板基准 @@ -49,6 +49,7 @@ $container-paddings: 1rem; } /* --- 全局布局容器适配 --- */ +// 其中 4k-40 为准 4K, 以适配 PC 端部分浏览器最大化时边框占位从而影响到 4K 宽度判定 .container { width: 100%; margin: 0 auto; @@ -66,7 +67,15 @@ $container-paddings: 1rem; @include respond-to('desktop') { max-width: 1800px; } - // 4K 以上容器随之线性扩大 + + @include respond-to('2k') { + max-width: 2560px; + } + + @include respond-to('4k-40') { + max-width: 3800px; + } + // 当容器等于 4K 时,随之线性扩大 @include respond-to('4k') { // 保持容器宽度占比:(3600 / 3840) * 100vw = 93.75vw max-width: 93.75vw;