This commit is contained in:
2026-04-26 01:24:26 +08:00
12 changed files with 186 additions and 248 deletions

View File

@@ -14,4 +14,12 @@
<None Include="compilerconfig.json" /> <None Include="compilerconfig.json" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Constants\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Library\Acdiu.AspNetCore.Mvc.Commons\Acdiu.AspNetCore.Mvc.Commons.csproj" />
</ItemGroup>
</Project> </Project>

View File

@@ -1,3 +1,4 @@
<Solution> <Solution>
<Project Path="../../Library/Acdiu.AspNetCore.Mvc.Commons/Acdiu.AspNetCore.Mvc.Commons.csproj" Id="a42045c6-be82-461d-b4fb-3f20bc004abf" />
<Project Path="AcdiuTools.csproj" /> <Project Path="AcdiuTools.csproj" />
</Solution> </Solution>

View File

@@ -1,28 +0,0 @@
namespace AcdiuTools.Constants
{
/// <summary>
/// 全局 UI 常量定义
/// </summary>
public static class UIConstants
{
/// <summary>
/// 资源路径常量
/// </summary>
public static class Paths
{
/// <summary>
/// 默认 Bootstrap Icons SVG Sprite 文件路径
/// </summary>
public const string DefaultIconSprite = "/lib/bootstrap-icons-1.13.1/bootstrap-icons.svg";
}
/// <summary>
/// BS SVG 图标默认需要添加的 CSS 类名
/// </summary>
public static class Classes
{
public const string DefaultWidth = "w-1r";
public const string DefaultHeight = "h-1r";
}
}
}

View File

@@ -36,9 +36,12 @@ app.UseRouting();
// 【关键】启用 Session 中间件 // 【关键】启用 Session 中间件
app.UseSession(); app.UseSession();
// 启用静态文件中间件,确保 wwwroot 中的资源可访问
app.UseStaticFiles();
app.UseAuthorization(); app.UseAuthorization();
app.MapStaticAssets(); app.MapStaticAssets();
app.MapControllerRoute( app.MapControllerRoute(

View File

@@ -1,167 +0,0 @@
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.VisualBasic;
using System.Threading.Tasks;
using AcdiuTools.Constants;
namespace AcdiuTools.TagHelpers
{
/// <summary>
/// <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
{
// 内部逻辑变量
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 string I { get => _iconName; set => _iconName = value; }
/// <summary>
/// 图标名称 (别名)。
/// </summary>
[HtmlAttributeName("icon")]
public string Icon { get => _iconName; set => _iconName = value; }
/// <summary>
/// 宽度。支持数字或 CSS 单位(如 16, 1rem
/// <remarks>若为空则尝试使用高度值,若均为空则添加默认响应式类名。</remarks>
/// </summary>
[HtmlAttributeName("w")]
public string W { get => _width; set => _width = value; }
/// <summary>
/// 宽度 (别名)。
/// </summary>
[HtmlAttributeName("width")]
public string Width { get => _width; set => _width = value; }
/// <summary>
/// 高度。支持数字或 CSS 单位。
/// </summary>
[HtmlAttributeName("h")]
public string H { get => _height; set => _height = value; }
/// <summary>
/// 高度 (别名)。
/// </summary>
[HtmlAttributeName("height")]
public string Height { get => _height; set => _height = value; }
/// <summary>
/// 填充颜色。默认为 <c>currentColor</c>。
/// </summary>
[HtmlAttributeName("f")]
public string F { get => _fill; set => _fill = value; }
/// <summary>
/// 填充颜色 (别名)。
/// </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 Sprite 路径。
/// <remarks>不填则使用系统默认常量路径。</remarks>
/// </summary>
[HtmlAttributeName("path")]
public string CustomPath { get; set; } = string.Empty;
#endregion
/// <summary>
/// 处理标签渲染逻辑
/// </summary>
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);
}
else
{
// 均未设置,则应用响应式默认类名并去重
output.Attributes.SetAttribute("class", BuildResponsiveClasses(ClassName));
}
// 3. 构建内部内容
string finalPath = string.IsNullOrWhiteSpace(CustomPath)
? UIConstants.Paths.DefaultIconSprite
: CustomPath;
output.Content.SetHtmlContent($@"<use xlink:href=""{finalPath}#{_iconName}""></use>");
}
/// <summary>
/// 构建响应式类名字符串,确保默认类名存在且去重
/// </summary>
private string BuildResponsiveClasses(string userClassName)
{
var classes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
UIConstants.Classes.DefaultWidth,
UIConstants.Classes.DefaultHeight
};
if (!string.IsNullOrWhiteSpace(userClassName))
{
var userParts = userClassName.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var item in userParts) classes.Add(item);
}
return string.Join(" ", classes);
}
}
}

View File

@@ -1,8 +1,8 @@
@{ @{
ViewData["Title"] = "Home Page"; ViewData["Title"] = "首页";
} }
<div class="text-center"> <div class="text-center">
<h1 class="display-4">Welcome</h1> <h1 class="display-4">欢迎</h1>
<p>Learn about <a href="https://learn.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p> <p>了解更多关于 <a href="https://learn.microsoft.com/aspnet/core">使用 ASP.NET Core 构建 Web 应用</a> 的信息。</p>
</div> </div>

View File

@@ -1,6 +1,6 @@
@{ @{
ViewData["Title"] = "Privacy Policy"; ViewData["Title"] = "隐私";
} }
<h1>@ViewData["Title"]</h1> <h1>@ViewData["Title"]</h1>
<p>Use this page to detail your site's privacy policy.</p> <p>使用此页面详细说明您网站的隐私政策。</p>

View File

@@ -1,4 +1,6 @@
<!DOCTYPE html> @using Acdiu.AspNetCore.Mvc.Commons
<!DOCTYPE html>
<html lang="zh-cn" data-bs-theme="@ViewBag.CurrentTheme"> <html lang="zh-cn" data-bs-theme="@ViewBag.CurrentTheme">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
@@ -6,13 +8,14 @@
<title>@ViewData["Title"] - AcdiuTools</title> <title>@ViewData["Title"] - AcdiuTools</title>
<script type="importmap"></script> <script type="importmap"></script>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="~/lib/bootstrap-icons-1.13.1\font\bootstrap-icons.min.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" /> <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/AcdiuTools.styles.css" asp-append-version="true" /> <link rel="stylesheet" href="~/AcdiuTools.styles.css" asp-append-version="true" />
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="~/lib/bootstrap-icons-1.13.1/font/bootstrap-icons.min.css" />
<link rel="stylesheet" href="~/ac/ac.min.css" asp-append-version="true" /> <link rel="stylesheet" href="~/ac/ac.min.css" asp-append-version="true" />
</head> </head>
<body> <body>
@@ -38,8 +41,10 @@
</ul> </ul>
</div> </div>
@* 主题切换按钮 *@ <div>
<theme-toggle /> @* 主题切换按钮 *@
<theme-toggle />
</div>
</div> </div>
</nav> </nav>
</header> </header>
@@ -49,7 +54,7 @@
</main> </main>
</div> </div>
<footer class="border-top footer text-muted"> <footer class="border-top footer text-muted mt-3">
<div class="container"> <div class="container">
&copy; 2018-@DateTime.Now.Year - <a asp-area="" asp-controller="Home" asp-action="Index">AcdiuTools</a> &copy; 2018-@DateTime.Now.Year - <a asp-area="" asp-controller="Home" asp-action="Index">AcdiuTools</a>
</div> </div>

View File

@@ -1,4 +1,5 @@
@using AcdiuTools @using AcdiuTools
@using AcdiuTools.Models @using AcdiuTools.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, AcdiuTools @addTagHelper *, AcdiuTools
@addTagHelper *, Acdiu.AspNetCore.Mvc.Commons

View File

@@ -9,10 +9,7 @@
} }
@media (min-width: 3840px) { @media (min-width: 3840px) {
:root { :root {
/* 逻辑:从 3840px 开始,字号从 24px 线性向上。 font-size: clamp(20px, 0.5208333333vw, 100vw);
公式:(24 / 3840) * 100vw = 0.625vw
*/
font-size: 0.625vw;
} }
} }
@@ -54,7 +51,33 @@
} }
} }
/* 这里的 1rem 会随着 :root 的 font-size 变化而自动缩放 */ /* --- 工具与组件样式 --- */
.nav-link:visited {
color: inherit;
}
.nav-link.active {
font-weight: bold;
color: var(--bs-link-hover-color, rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1))) !important;
border-bottom: 2px solid var(--bs-link-hover-color, rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1)));
}
.p-r-t {
position: relative;
top: -1px;
}
.mg-0 {
margin: 0 auto;
}
a {
text-decoration: none;
color: var(--bs-link-hover-color, rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1)));
}
a:visited {
color: var(--bs-purple, rgba(var(--bs-primary), var(--bs-link-opacity, 0.8)));
}
.w-1r { .w-1r {
width: 1rem; width: 1rem;
} }
@@ -63,17 +86,74 @@
height: 1rem; height: 1rem;
} }
.nav-link.active { .w-1_5r {
font-weight: bold; width: 1.5rem;
color: var(--bs-primary) !important;
border-bottom: 2px solid var(--bs-primary);
} }
.p-r-t { .h-1_5r {
position: relative; height: 1.5rem;
top: -1px;
} }
mg-0 { .w-2r {
margin: 0 auto; width: 2rem;
}
.h-2r {
height: 2rem;
}
.w-2_5r {
width: 2.5rem;
}
.h-2_5r {
height: 2.5rem;
}
.w-3r {
width: 3rem;
}
.h-3r {
height: 3rem;
}
.w-3_5r {
width: 3.5rem;
}
.h-3_5r {
height: 3.5rem;
}
.w-4r {
width: 4rem;
}
.h-4r {
height: 4rem;
}
.w-4_5r {
width: 4.5rem;
}
.h-4_5r {
height: 4.5rem;
}
.w-5r {
width: 5rem;
}
.h-5r {
height: 5rem;
}
.w-5_5r {
width: 5.5rem;
}
.h-5_5r {
height: 5.5rem;
} }

View File

@@ -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: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;} @charset "UTF-8";:root{font-size:14px;}@media(min-width:1024px){:root{font-size:16px;}}@media(min-width:3840px){:root{font-size:clamp(20px,.5208333333vw,100vw);}}.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;}}.nav-link:visited{color:inherit;}.nav-link.active{font-weight:bold;color:var(--bs-link-hover-color,rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,1)))!important;border-bottom:2px solid var(--bs-link-hover-color,rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,1)));}.p-r-t{position:relative;top:-1px;}.mg-0{margin:0 auto;}a{text-decoration:none;color:var(--bs-link-hover-color,rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,1)));}a:visited{color:var(--bs-purple,rgba(var(--bs-primary),var(--bs-link-opacity,.8)));}.w-1r{width:1rem;}.h-1r{height:1rem;}.w-1_5r{width:1.5rem;}.h-1_5r{height:1.5rem;}.w-2r{width:2rem;}.h-2r{height:2rem;}.w-2_5r{width:2.5rem;}.h-2_5r{height:2.5rem;}.w-3r{width:3rem;}.h-3r{height:3rem;}.w-3_5r{width:3.5rem;}.h-3_5r{height:3.5rem;}.w-4r{width:4rem;}.h-4r{height:4rem;}.w-4_5r{width:4.5rem;}.h-4_5r{height:4.5rem;}.w-5r{width:5rem;}.h-5r{height:5rem;}.w-5_5r{width:5.5rem;}.h-5_5r{height:5.5rem;}

View File

@@ -6,11 +6,29 @@ $breakpoints: ( 'mobile': 768px, 'tablet': 1024px, 'desktop': 1920px, '2k': 2560
// 字号常量 // 字号常量
$font-size-small: 14px; // 手机/平板基准 $font-size-small: 14px; // 手机/平板基准
$font-size-standard: 16px; // 桌面基准 (1024px - 3840px) $font-size-standard: 16px; // 桌面基准 (1024px - 3840px)
$font-size-4k-base: 24px; // 4K 触发点字号 $font-size-4k-base: 20px; // 4K 触发点字号
// 容器配置 // 容器配置
$container-paddings: 1rem; $container-paddings: 1rem;
/// 去除单位函数
@function strip-unit($number) {
@if type-of($number) == 'number' and not unitless($number) {
@return $number / ($number * 0 + 1);
}
@return $number;
}
/// 计算基于屏幕宽度的线性 VW 比例
/// @param $size - 目标字号 (如 24px)
/// @param $breakpoint - 触发断点 (如 3840)
@function get-linear-vw($size, $breakpoint) {
$raw-size: strip-unit($size);
$raw-breakpoint: strip-unit($breakpoint);
@return ($raw-size / $raw-breakpoint) * 100vw;
}
// ======================================================== // ========================================================
// 2. 媒体查询 Mixin // 2. 媒体查询 Mixin
// ======================================================== // ========================================================
@@ -35,21 +53,19 @@ $container-paddings: 1rem;
:root { :root {
// --- [阶段 A] 默认状态 (手机/小屏幕) --- // --- [阶段 A] 默认状态 (手机/小屏幕) ---
font-size: $font-size-small; font-size: $font-size-small;
// --- [阶段 B] 1024px 以上到 4K 之前保持标准 16px --- // --- [阶段 B] 1024px 以上到 4K 之前: 保持标准 16px ---
@include respond-to('tablet') { @include respond-to('tablet') {
font-size: $font-size-standard; font-size: $font-size-standard;
} }
// --- [阶段 C] 大于 4K开启线性增长 --- // --- [阶段 C] 大于 4K: 开启线性增长 ---
@include respond-to('4k') { @include respond-to('4k') {
/* 逻辑:从 3840px 开始,字号 24px 线性向上。 // 锁定最小字号 20px,确保在任何情况下都不会比基准值小
公式:(24 / 3840) * 100vw = 0.625vw font-size: clamp(#{$font-size-4k-base}, get-linear-vw($font-size-4k-base, 3840), 100vw);
*/
font-size: 0.625vw;
} }
} }
/* --- 全局布局容器适配 --- */ /* --- 全局布局容器适配 --- */
// 其中 4k-40 为准 4K, 以适配 PC 端部分浏览器最大化时边框占位从而影响到 4K 宽度判定 // 其中 4k-40 为准 4K (即 3840px - 40px), 以适配 PC 端部分浏览器最大化时边框占位从而影响到 4K 宽度判定
.container { .container {
width: 100%; width: 100%;
margin: 0 auto; margin: 0 auto;
@@ -77,28 +93,24 @@ $container-paddings: 1rem;
} }
// 当容器等于 4K 时,随之线性扩大 // 当容器等于 4K 时,随之线性扩大
@include respond-to('4k') { @include respond-to('4k') {
// 保持容器宽度占比(3600 / 3840) * 100vw = 93.75vw // 保持容器宽度占比: (3600 / 3840) * 100vw = 93.75vw
max-width: 93.75vw; max-width: (3600 / 3840) * 100vw;
} }
} }
// ======================================================== // ========================================================
// 4. 工具类与组件样式 // 4. 工具类与组件样式
// ======================================================== // ========================================================
/* 这里的 1rem 会随着 :root 的 font-size 变化而自动缩放 */ // 这里的 1rem 会随着 :root 的 font-size 变化而自动缩放
.w-1r { /* --- 工具与组件样式 --- */
width: 1rem;
}
.h-1r {
height: 1rem;
}
.nav-link { .nav-link {
&:visited {
color: inherit;
}
&.active { &.active {
font-weight: bold; font-weight: bold;
color: var(--bs-primary) !important; color: var(--bs-link-hover-color, rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1))) !important;
border-bottom: 2px solid var(--bs-primary); border-bottom: 2px solid var(--bs-link-hover-color, rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1)));
} }
} }
@@ -107,6 +119,29 @@ $container-paddings: 1rem;
top: -1px; top: -1px;
} }
mg-0 { .mg-0 {
margin: 0 auto; margin: 0 auto;
} }
// 去除 a 标签默认下划线
a {
text-decoration: none;
color: var(--bs-link-hover-color, rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1)));
// 访问过的链接变更颜色,提供视觉反馈
&:visited {
color: var(--bs-purple, rgba(var(--bs-primary), var(--bs-link-opacity, 0.8)));
}
}
// 定义宽与高的工具类,使用 rem 单位以适配响应式字体大小
$widthOrheight-map: ( '1r': 1rem, '1_5r': 1.5rem, '2r': 2rem, '2_5r': 2.5rem, '3r': 3rem, '3_5r': 3.5rem, '4r': 4rem, '4_5r': 4.5rem, '5r': 5rem, '5_5r': 5.5rem );
@each $key, $value in $widthOrheight-map {
.w-#{$key} {
width: $value;
}
.h-#{$key} {
height: $value;
}
}