添加项目文件。

This commit is contained in:
2026-04-25 10:21:34 +08:00
parent c04cb001fb
commit 2e763dd1c3
98 changed files with 89974 additions and 0 deletions

View File

@@ -0,0 +1,84 @@
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System;
namespace AcdiuTools.TagHelpers
{
/// <summary>
/// 自动为当前激活的导航链接添加 'active' 类和 'aria-current="page"' 属性。
/// <para>
/// 用法示例:
/// &lt;a asp-active-link="true" asp-controller="Home" asp-action="Index" class="nav-link"&gt;首页&lt;/a&gt;
/// </para>
/// </summary>
[HtmlTargetElement("a", Attributes = "asp-active-link")]
public class ActiveLinkTagHelper : TagHelper
{
/// <summary>
/// 获取或设置当前视图的上下文,用于读取路由数据。
/// </summary>
[ViewContext]
[HtmlAttributeNotBound]
public ViewContext ViewContext { get; set; } = default!;
/// <summary>
/// 是否启用自动激活检测。对应 HTML 属性: asp-active-link
/// </summary>
[HtmlAttributeName("asp-active-link")]
public bool IsActiveLink { get; set; }
/// <summary>
/// 目标控制器名称。映射自内置的 asp-controller 属性。
/// </summary>
[HtmlAttributeName("asp-controller")]
public string? Controller { get; set; }
/// <summary>
/// 目标 Action 名称。映射自内置的 asp-action 属性。
/// </summary>
[HtmlAttributeName("asp-action")]
public string? Action { get; set; }
/// <summary>
/// 执行 TagHelper 逻辑。
/// </summary>
/// <param name="context">TagHelper 上下文。</param>
/// <param name="output">TagHelper 输出内容。</param>
public override void Process(TagHelperContext context, TagHelperOutput output)
{
// 如果未设置 asp-active-link="true",则不执行逻辑
if (!IsActiveLink) return;
// 1. 获取当前路由中的 Controller 和 Action
var routeData = ViewContext.RouteData.Values;
string? currentController = routeData["controller"]?.ToString();
string? currentAction = routeData["action"]?.ToString();
// 2. 确定目标 Controller 和 Action (如果未指定,则默认为当前页面或 Index)
string targetController = Controller ?? currentController ?? string.Empty;
string targetAction = Action ?? "Index";
// 3. 忽略大小写进行匹配
bool isActive = string.Equals(currentController, targetController, StringComparison.OrdinalIgnoreCase) &&
string.Equals(currentAction, targetAction, StringComparison.OrdinalIgnoreCase);
if (isActive)
{
// 4. 合并 Class 属性,确保不覆盖原有样式
var existingClasses = output.Attributes["class"]?.Value?.ToString();
if (string.IsNullOrWhiteSpace(existingClasses))
{
output.Attributes.SetAttribute("class", "active");
}
else if (!existingClasses.Contains("active"))
{
output.Attributes.SetAttribute("class", $"{existingClasses} active");
}
// 5. 增强无障碍支持
output.Attributes.SetAttribute("aria-current", "page");
}
}
}
}

View File

@@ -0,0 +1,95 @@
using Microsoft.AspNetCore.Razor.TagHelpers;
using System.Threading.Tasks;
namespace AcdiuTools.TagHelpers
{
/// <summary>
/// <see langword="Bootstrap Icons"></see> SVG Sprite 封装标签
/// 用法: <bsicon i="heart-fill" w="16" h="16" f="red" />
/// </summary>
[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";
/// <summary>
/// 图标名称 (必填)
/// </summary>
[HtmlAttributeName("i")]
public required string IconName { get; set; }
/// <summary>
/// 宽度 (默认为空,若为空时设置了高度,则使用高度的值,否则使用默认值 14)
/// </summary>
[HtmlAttributeName("w")]
public string Width { get; set; } = string.Empty;
/// <summary>
/// 高度 (默认为空,若为空时设置了宽度,则使用宽度的值,否则使用默认值 14)
/// </summary>
[HtmlAttributeName("h")]
public string Height { get; set; } = string.Empty;
/// <summary>
/// 填充颜色 (默认 currentColor 即当前元素颜色)
/// </summary>
[HtmlAttributeName("f")]
public string Fill { get; set; } = "currentColor";
/// <summary>
/// 自定义 class 名称,允许用户添加额外的样式类 (默认空)
/// </summary>
[HtmlAttributeName("cn")]
public string ClassName { get; set; } = string.Empty;
/// <summary>
/// 根据指定的图标名称和属性处理标签助手以渲染SVG矢量图标
/// </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))
{
output.SuppressOutput(); // 如果没写图标名,则不渲染
return;
}
// 1. 将外层标签替换为 <svg>
output.TagName = "svg";
output.TagMode = TagMode.StartTagAndEndTag;
// 2. 设置 SVG 基础属性
// 合并默认 class "bi" 和用户自定义的 cn
var finalClass = string.IsNullOrWhiteSpace(ClassName) ? "" : $"{ClassName}";
output.Attributes.SetAttribute("class", finalClass);
// 如果设置了 w 或 h则优先使用它们否则使用 wh 的值
// 设定机制:
// 如果 w 和 h 中任意一个不为空,则使用不为空的 w 或 h 的值同时设置宽高;
// 如果 w 和 h 都不为空,则分别使用它们设置宽高;
// 如果 w 和 h 都为空,则使用默认的 14 同时设置宽高
output.Attributes.SetAttribute("width", !string.IsNullOrWhiteSpace(Width) ? Width : (!string.IsNullOrWhiteSpace(Height) ? Height : "14"));
output.Attributes.SetAttribute("height", !string.IsNullOrWhiteSpace(Height) ? Height : (!string.IsNullOrWhiteSpace(Width) ? Width : "14"));
output.Attributes.SetAttribute("fill", Fill);
output.Attributes.SetAttribute("viewBox", "0 0 16 16"); // 保证矢量对齐
//output.Attributes.SetAttribute("xmlns", "http://www.w3.org/2000/svg");
// 3. 构造内部的 <use> 节点
var content = $@"<use xlink:href=""{spritePath}#{IconName}""></use>";
output.Content.SetHtmlContent(content);
}
}
}

View File

@@ -0,0 +1,46 @@
using AcdiuTools.Services;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System.Threading.Tasks;
namespace AcdiuTools.TagHelpers
{
/// <summary>
/// 主题切换按钮 Tag Helper
/// 用法: <theme-toggle />
/// </summary>
[HtmlTargetElement("theme-toggle")]
public class ThemeToggleTagHelper(IThemeService themeService) : TagHelper
{
private readonly IThemeService _themeService = themeService;
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
// 获取当前主题
var currentTheme = await _themeService.GetUserThemeAsync();
bool isDark = currentTheme == "dark";
// 设置标签名为 button
output.TagName = "button";
output.TagMode = TagMode.StartTagAndEndTag;
// 添加基础 CSS 类 (Bootstrap)
output.Attributes.Add("class", "btn btn-outline-secondary border-0");
output.Attributes.Add("id", "theme-toggle-btn");
output.Attributes.Add("type", "button");
output.Attributes.Add("aria-label", "Toggle Theme");
// 添加 Tooltip (Bootstrap data-bs-toggle)
output.Attributes.Add("data-bs-toggle", "tooltip");
output.Attributes.Add("data-bs-placement", "bottom");
output.Attributes.Add("title", isDark ? "切换到浅色模式" : "切换到深色模式");
// 根据当前主题设置图标内容
// 使用 Bootstrap Icons 或 FontAwesome
string iconHtml = isDark
? "<i class=\"bi bi-sun-fill\"></i>" // 深色模式下显示太阳图标(暗示切换到亮)
: "<i class=\"bi bi-moon-stars-fill\"></i>"; // 浅色模式下显示月亮图标
output.Content.SetHtmlContent(iconHtml);
}
}
}