示例:
string s = "123456789";
return Json(new
{
// 12
a1 = s.Substring(0, 2),
a2 = s[0..2],
a3 = s[..2],
// 234
b1 = s.Substring(1, 3),
b2 = s[1..4],
// 23456789
c1 = s.Substring(1),
c2 = s.Substring(1, s.Length - 1),
c3 = s[1..],
c4 = s[1..9],
// 123456
d1 = s.Substring(0, s.Length - 3),
d2 = s[0..^3],
d3 = s[..^3],
// 234567
e1 = s.Substring(1, s.Length - 3),
e2 = s[1..^2],
});
Substring(n, m) 取从索引第 n 个字符开始,连续 m 个长度的字符;
范围运算符 [n..m] 取从索引第 n 个字符至索引第 m 个字符前的字符串;
范围运算符 [n..^m] 取从索引第 n 个字符至索引倒数第 m 个字符前的字符串;(在原字符串前再拼接一个同样的字符串,往前数索引更容易理解)
在范围运算符中,当 n 为 0 时可以省略,当 m 为原字符串长度时可以省略。

.menu-nav 包裹
<ul class="menu-nav">
...
</ul>
.menu-section 区段
<li class="menu-section">
<h4 class="menu-text">区段</h4>
<i class="menu-icon ki ki-bold-more-hor icon-md"></i>
</li>
.menu-item 菜单
<li class="menu-item" aria-haspopup="true">
<a href="###" class="menu-link">
<span class="svg-icon menu-icon">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect x="0" y="0" width="24" height="24" />
<path d="M3,16 L5,16 C5.55228475,16 6,15.5522847 6,15 C6,14.4477153 5.55228475,14 5,14 L3,14 L3,12 L5,12 C5.55228475,12 6,11.5522847 6,11 C6,10.4477153 5.55228475,10 5,10 L3,10 L3,8 L5,8 C5.55228475,8 6,7.55228475 6,7 C6,6.44771525 5.55228475,6 5,6 L3,6 L3,4 C3,3.44771525 3.44771525,3 4,3 L10,3 C10.5522847,3 11,3.44771525 11,4 L11,19 C11,19.5522847 10.5522847,20 10,20 L4,20 C3.44771525,20 3,19.5522847 3,19 L3,16 Z" fill="#000000" opacity="0.3" />
<path d="M16,3 L19,3 C20.1045695,3 21,3.8954305 21,5 L21,15.2485298 C21,15.7329761 20.8241635,16.200956 20.5051534,16.565539 L17.8762883,19.5699562 C17.6944473,19.7777745 17.378566,19.7988332 17.1707477,19.6169922 C17.1540423,19.602375 17.1383289,19.5866616 17.1237117,19.5699562 L14.4948466,16.565539 C14.1758365,16.200956 14,15.7329761 14,15.2485298 L14,5 C14,3.8954305 14.8954305,3 16,3 Z" fill="#000000" />
</g>
</svg>
</span>
<span class="menu-text">一级菜单(无子菜单)</span>
</a>
</li>
其中,<svg /> 可以替换成其它图标库,如:
<li class="menu-item" aria-haspopup="true">
<a href="###" class="menu-link">
<span class="svg-icon menu-icon">
<i class="fab fa-app-store-ios"></i>
</span>
<span class="menu-text">一级菜单(无子菜单)</span>
</a>
</li>
并修正样式:
.aside-menu .menu-nav > .menu-item > .menu-heading .menu-icon i,
.aside-menu .menu-nav > .menu-item > .menu-link .menu-icon i { height: 23px; width: 23px; line-height: 23px; text-align: center; vertical-align: middle; font-size: 1.25rem; }
.menu-item-submenu 表示该 .menu-item 有子菜单,即内部包含 .menu-submenu。该规则在任意一级菜单均有效。
.menu-item-open 表示该 .menu-item 当前是打开状态(高亮),否则为闭合状态,需配合 <i class="menu-arrow"></i> 使用。该规则在任意一级菜单均有效。
.menu-item-active 表示该 .menu-item 对应当前页面,效果为灰色背景且文字持续高亮。
.menu-item-parent 作为一级菜单在不同设备上展示的替代,位于一级菜单下的 .menu-submenu 下的 .menu-subnav 内部。
.menu-link 表示可点击(hover 效果),用于 .menu-item 下的 <a /> 或 .menu-item-parent 下的 <span />。
aria-haspopup="true" 每个 .menu-item 都有。
data-menu-toggle="hover" 每个 .menu-item-submenu 都有。
.menu-icon 一级菜单项的左侧图标,支持 <svg /> 或 <i />。
.menu-bullet 非一级菜单项的左侧图标,与 .menu-text 同级,可选 .menu-bullet-line / .menu-bullet-dot。
.menu-text 菜单文字。
.menu-label 菜单项右侧徽章,与 .menu-text 同级。
.menu-arrow 菜单项右侧箭头(当包含子菜单时使用),与 .menu-text 同级。
.menu-toggle 放在 .menu-item-submenu 内部的 .menu-link,与 .menu-item-submenu 上的 data-menu-toggle="hover" 同时出现。
完整示例:
<ul class="menu-nav">
<li class="menu-item" aria-haspopup="true">
<a href="index.html" class="menu-link">
<span class="svg-icon menu-icon">
<!--begin::Svg Icon | path:assets/media/svg/icons/Design/Layers.svg-->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<polygon points="0 0 24 0 24 24 0 24" />
<path d="M12.9336061,16.072447 L19.36,10.9564761 L19.5181585,10.8312381 C20.1676248,10.3169571 20.2772143,9.3735535 19.7629333,8.72408713 C19.6917232,8.63415859 19.6104327,8.55269514 19.5206557,8.48129411 L12.9336854,3.24257445 C12.3871201,2.80788259 11.6128799,2.80788259 11.0663146,3.24257445 L4.47482784,8.48488609 C3.82645598,9.00054628 3.71887192,9.94418071 4.23453211,10.5925526 C4.30500305,10.6811601 4.38527899,10.7615046 4.47382636,10.8320511 L4.63,10.9564761 L11.0659024,16.0730648 C11.6126744,16.5077525 12.3871218,16.5074963 12.9336061,16.072447 Z" fill="#000000" fill-rule="nonzero" />
<path d="M11.0563554,18.6706981 L5.33593024,14.122919 C4.94553994,13.8125559 4.37746707,13.8774308 4.06710397,14.2678211 C4.06471678,14.2708238 4.06234874,14.2738418 4.06,14.2768747 L4.06,14.2768747 C3.75257288,14.6738539 3.82516916,15.244888 4.22214834,15.5523151 C4.22358765,15.5534297 4.2250303,15.55454 4.22647627,15.555646 L11.0872776,20.8031356 C11.6250734,21.2144692 12.371757,21.2145375 12.909628,20.8033023 L19.7677785,15.559828 C20.1693192,15.2528257 20.2459576,14.6784381 19.9389553,14.2768974 C19.9376429,14.2751809 19.9363245,14.2734691 19.935,14.2717619 L19.935,14.2717619 C19.6266937,13.8743807 19.0546209,13.8021712 18.6572397,14.1104775 C18.654352,14.112718 18.6514778,14.1149757 18.6486172,14.1172508 L12.9235044,18.6705218 C12.377022,19.1051477 11.6029199,19.1052208 11.0563554,18.6706981 Z" fill="#000000" opacity="0.3" />
</g>
</svg>
<!--end::Svg Icon-->
</span>
<span class="menu-text">Dashboard</span>
</a>
</li>
<li class="menu-section">
<h4 class="menu-text">区段 A(一级菜单示例)</h4>
<i class="menu-icon ki ki-bold-more-hor icon-md"></i>
</li>
<li class="menu-item" aria-haspopup="true">
<a href="item1" class="menu-link">
<span class="svg-icon menu-icon">
<i class="fas fa-dice-one"></i>
</span>
<span class="menu-text">菜单 A</span>
</a>
</li>
<li class="menu-item" aria-haspopup="true">
<a href="item2" class="menu-link">
<span class="svg-icon menu-icon">
<i class="fas fa-dice-two"></i>
</span>
<span class="menu-text">菜单 B(带徽章)</span>
<span class="menu-label">
<span class="label label-rounded label-primary">6</span>
</span>
</a>
</li>
<li class="menu-item menu-item-active" aria-haspopup="true">
<a href="item2" class="menu-link">
<span class="svg-icon menu-icon">
<i class="fas fa-dice-three"></i>
</span>
<span class="menu-text">菜单 C(高亮)</span>
</a>
</li>
<li class="menu-section">
<h4 class="menu-text">区段 B(二级菜单示例)</h4>
<i class="menu-icon ki ki-bold-more-hor icon-md"></i>
</li>
<li class="menu-item menu-item-submenu" aria-haspopup="true" data-menu-toggle="hover">
<a href="javascript:;" class="menu-link menu-toggle">
<span class="svg-icon menu-icon">
<i class="fas fa-dice-one"></i>
</span>
<span class="menu-text">一级菜单(默认关闭)</span>
<i class="menu-arrow"></i>
</a>
<div class="menu-submenu">
<i class="menu-arrow"></i>
<ul class="menu-subnav">
<li class="menu-item menu-item-parent" aria-haspopup="true">
<span class="menu-link">
<span class="menu-text">一级菜单(默认关闭)</span>
</span>
</li>
<li class="menu-item" aria-haspopup="true">
<a href="custom/apps/inbox.html" class="menu-link">
<i class="menu-bullet menu-bullet-line"><span></span></i>
<span class="menu-text">二级菜单 a</span>
</a>
</li>
<li class="menu-item" aria-haspopup="true">
<a href="custom/apps/inbox.html" class="menu-link">
<i class="menu-bullet menu-bullet-line"><span></span></i>
<span class="menu-text">二级菜单 b(带徽章)</span>
<span class="menu-label">
<span class="label label-danger label-inline">new</span>
</span>
</a>
</li>
</ul>
</div>
</li>
<li class="menu-item menu-item-submenu menu-item-here menu-item-open" aria-haspopup="true" data-menu-toggle="hover">
<a href="javascript:;" class="menu-link menu-toggle">
<span class="svg-icon menu-icon">
<i class="fas fa-dice-two"></i>
</span>
<span class="menu-text">一级菜单(高亮/默认打开)</span>
<i class="menu-arrow"></i>
</a>
<div class="menu-submenu">
<i class="menu-arrow"></i>
<ul class="menu-subnav">
<li class="menu-item menu-item-parent" aria-haspopup="true">
<span class="menu-link">
<span class="menu-text">一级菜单(高亮/默认打开)</span>
</span>
</li>
<li class="menu-item menu-item-active" aria-haspopup="true">
<a href="custom/apps/inbox.html" class="menu-link">
<i class="menu-bullet menu-bullet-line"><span></span></i>
<span class="menu-text">二级菜单 c(高亮)</span>
</a>
</li>
<li class="menu-item" aria-haspopup="true">
<a href="custom/apps/inbox.html" class="menu-link">
<i class="menu-bullet menu-bullet-line"><span></span></i>
<span class="menu-text">二级菜单 d(带徽章)</span>
<span class="menu-label">
<span class="label label-danger label-inline">new</span>
</span>
</a>
</li>
</ul>
</div>
</li>
<li class="menu-section">
<h4 class="menu-text">区段 C(三级菜单示例)</h4>
<i class="menu-icon ki ki-bold-more-hor icon-md"></i>
</li>
<li class="menu-item menu-item-submenu menu-item-open menu-item-here" aria-haspopup="true" data-menu-toggle="hover">
<a href="javascript:;" class="menu-link menu-toggle">
<span class="svg-icon menu-icon">
<i class="fas fa-dice-one"></i>
</span>
<span class="menu-text">一级菜单</span>
<i class="menu-arrow"></i>
</a>
<div class="menu-submenu">
<i class="menu-arrow"></i>
<ul class="menu-subnav">
<li class="menu-item menu-item-parent" aria-haspopup="true">
<span class="menu-link">
<span class="menu-text">一级菜单</span>
</span>
</li>
<li class="menu-item menu-item-submenu menu-item-open menu-item-here" aria-haspopup="true" data-menu-toggle="hover">
<a href="javascript:;" class="menu-link menu-toggle">
<i class="menu-bullet menu-bullet-line"><span></span></i>
<span class="menu-text">二级菜单(默认打开)</span>
<span class="menu-label">
<span class="label label-rounded label-primary">6</span>
</span>
<i class="menu-arrow"></i>
</a>
<div class="menu-submenu">
<i class="menu-arrow"></i>
<ul class="menu-subnav">
<li class="menu-item" aria-haspopup="true">
<a href="custom/apps/user/list-default.html" class="menu-link">
<i class="menu-bullet menu-bullet-dot"><span></span></i>
<span class="menu-text">三级菜单 1</span>
</a>
</li>
<li class="menu-item menu-item-active" aria-haspopup="true">
<a href="custom/apps/user/list-datatable.html" class="menu-link">
<i class="menu-bullet menu-bullet-dot"><span></span></i>
<span class="menu-text">三级菜单 2(高亮)</span>
</a>
</li>
</ul>
</div>
</li>
<li class="menu-item menu-item-submenu" aria-haspopup="true" data-menu-toggle="hover">
<a href="javascript:;" class="menu-link menu-toggle">
<i class="menu-bullet menu-bullet-line"><span></span></i>
<span class="menu-text">二级菜单(默认关闭)</span>
<i class="menu-arrow"></i>
</a>
<div class="menu-submenu">
<i class="menu-arrow"></i>
<ul class="menu-subnav">
<li class="menu-item menu-item-submenu" aria-haspopup="true" data-menu-toggle="hover">
<a href="javascript:;" class="menu-link menu-toggle">
<i class="menu-bullet menu-bullet-dot"><span></span></i>
<span class="menu-text">二级菜单(默认关闭)</span>
<i class="menu-arrow"></i>
</a>
<div class="menu-submenu">
<i class="menu-arrow"></i>
<ul class="menu-subnav">
<li class="menu-item" aria-haspopup="true">
<a href="custom/apps/profile/profile-1/overview.html" class="menu-link">
<i class="menu-bullet menu-bullet-line"><span></span></i>
<span class="menu-text">三级菜单 3</span>
</a>
</li>
</ul>
</div>
</li>
</ul>
</div>
</li>
</ul>
</div>
</li>
</ul>
效果:
关于手风琴功能:Metronic 菜单已实现手风琴功能,即当展开一个闭合的菜单时,非当前页面所在菜单项的房系菜单会自动闭合。

核心文件路径:/theme/html/demo*/src/js/components/core.datatable.js
所有参数的默认值见该文件 3369 行起。
data:
属性 | 功能 | 值 |
type | 数据源类型 | local / remote |
source | 数据源 | 链接或对象(见下方) |
pageSize | 每页项数 | 默认 10 |
saveState | 刷新、重新打开、返回时仍保持状态 | 默认 true |
serverPaging | 是否在服务端实现分页 | 默认 false |
serverFiltering | 是否在服务端实现筛选 | 默认 false |
serverSorting | 是否在服务端实现排序 | 默认 false |
autoColumns | 为远程数据源启用自动列功能 | 默认 false |
attr |
data.source:
属性 | 功能 | 值 |
url | 数据源地址 | |
params | 请求参数 |
|
headers |
自定义请求的头 |
|
map | 数据地图,作用是对返回的数据进行整理和定位 |
|
layout:
属性 | 功能 | 值 |
theme | 主题 | 默认 default |
class | 包裹的 CSS 样式 | |
scroll | 在需要时显示横向或纵向滚动条 | 默认 false |
height | 表格高度 | 默认 null |
minHeight | 表格最小高度 | 默认 null |
footer | 是否显示表格底部 | 默认 false |
header | 是否显示表头 | 默认 true |
customScrollbar | 自定义的滚动条 | 默认 true |
spinner |
Loading 样式 |
|
icons | 表格中的 icon |
|
sortable | 是否支持按列排序 | 默认 true |
resizable |
是否支持鼠标拖动改变列宽 | 默认 false |
filterable | 在列中过滤 | 默认 false |
pagination |
显示分页信息 | 默认 true |
editable |
行内编辑 | 默认 false |
columns |
列 | 见本文下方 |
search |
搜索 |
|
layout.columns:
属性 | 功能 | 解释 |
field | 字段名 | 对应 JSON 的属性名,点击表头时作为排序字段名 |
title | 表头名 | 显示在表格头部 |
sortable | 默认排序方式 | 可选:'asc' / 'desc' |
width | 单元格最小宽度 | 值与 CSS 值一致,填数字时默认单位 px |
type | 数据类型 | 'number' / 'date' 等,与本地排序有关 |
format | 数据格式化 | 例格式化日期:'YYYY-MM-DD' |
selector | 是否显示选择框 | 布尔值或对象,如:{ class: '' } |
textAlign | 文字对齐方式 | 'center' |
overflow | 内容超过单元格宽度时是否显示 | 'visible':永远显示 |
autoHide | 自适应显示/隐藏 | 布尔值 |
template | 用于显示内容的 HTML 模板 | function(row) { return row.Id; } |
sortCallback | 排序回调 | 自定义排序方式,参 local-sort.js |
其它:
属性 | 功能 | 解释 |
translate | 翻译 |
参 core.datatable.js 3512 行,简体中文示例:
|
extensions |
暂时没有找到对字符串内容进行自动 HTML 编码的属性,这可能带来 XSS 攻击风险,在 remote 方式中必须在服务端预先 HtmlEncode。即使在 layout.columns.template 中进行处理也是无济于事,恶意代码会在 ajax 加载完成后立即执行。
方法和事件:待完善。
更多信息请查询官方文档:https://keenthemes.com/keen/?page=docs§ion=html-components-datatable

本文适用于 CentOS(Linux),Window 系统请移步:https://xoyozo.net/Blog/Details/FileSystemWatcher
安装
参照官方说明:https://github.com/inotify-tools/inotify-tools/wiki
以 CentOS 为例:
安装 EPEL :
yum install -y epel-release && yum update
安装 inotify-tools:
yum install inotify-tools
在 CentOS-7 中
yum --enablerepo=epel install inotify-tools
v3.14-8.el7.×86_64 as of 4-18-2018
配置
创建 Shell 脚本文件:
#!/bin/bash
inotifywait -mrq -e modify,attrib,move,create,delete /要监视的目录 | while read dir event file;
do
curl -d "df=${dir}${file}&ev=${event}" https://xxx.xxx.xxx/api/inotify/
done
并将该文件设置为可执行:chmod +x xxx.sh
注:上述示例将对文件的部分操作事件信息传递到远程接口。inotifywait 与 curl 的用法请自行百度。
如果需要忽略部分文件路径,可以使用正则表达式进行过滤,例:
#!/bin/bash
inotifywait -mrq -e modify,attrib,move,create,delete /要监视的目录 | while read dir event file;
do
df=${dir}${file};
if [[ ! $df =~ ^/www/wwwroot/[0-9a-z]+.xxx.com/[0-9a-zA-Z]+/Runtime/Cache/[0-9a-zA-Z]+/[0-9a-f]{32}.php$
&& ! $df =~ ^/www/wwwroot/[0-9a-z]+.xxx.com/[0-9a-zA-Z]+/Runtime/Logs/[0-9a-zA-Z]+/[0-9]{2}_[0-9]{2}_[0-9]{2}.log$
]]; then
curl -d "df=${df}&ev=${event}" https://xxx.xxx.xxx/api/inotify/
else
echo "Ignored: $df"
fi
done
注意:bash 中使用 [[ string =~ regex ]] 表达式进行正则匹配,“!”取反,“&&”为且,书写时不要吝啬使用空格,否则程序可能不会按预期运行。
执行
先直接执行 sh 命令,排查一些错误。
Failed to watch /www/wwwroot; upper limit on inotify watches reached!
Please increase the amount of inotify watches allowed per user via `/proc/sys/fs/inotify/max_user_watches'.
因被监视的目录中文件数超过默认值 8192 提示失败,更改该值即可。
echo 8192000 > /proc/sys/fs/inotify/max_user_watches
设置开机自动运行,参:https://xoyozo.net/Blog/Details/linux-init-d。

ASP.NET MVC 使用 Authorize 过滤器验证用户登录。Authorize 过滤器首先运行在任何其它过滤器或动作方法之前,主要用来做登录验证或者权限验证。
示例:使用 Authorize 过滤器实现简单的用户登录验证。
1、创建登录控制器 LoginController
/// <summary>
/// 登录控制器
/// </summary>
[AllowAnonymous]
public class LoginController : Controller
{
/// <summary>
/// 登录页面
/// </summary>
public ActionResult Index()
{
return View();
}
/// <summary>
/// 登录
/// </summary>
[HttpPost]
public ActionResult Login(string loginName, string loginPwd)
{
if (loginName == "admin" && loginPwd == "123456")
{
// 登录成功
Session["LoginName"] = loginName;
return RedirectToAction("Index", "Home");
}
else
{
// 登录失败
return RedirectToAction("Index", "Login");
}
}
/// <summary>
/// 注销
/// </summary>
public ActionResult Logout()
{
Session.Abandon();
return RedirectToAction("Index", "Login");
}
}
注意:在登录控制器 LoginController 上添加 AllowAnonymous 特性,该特性用于标记在授权期间要跳过 AuthorizeAttribute 的控制器和操作。
2、创建登录页面
@{
ViewBag.Title = "登录页面";
Layout = null;
}
<h2>登录页面</h2>
<form action='@Url.Action("Login","Login")' id="form1" method="post">
用户:<input type="text" name="loginName" /><br />
密码:<input type="password" name="loginPwd" /><br />
<input type="submit" value="登录">
</form>
效果图:
3、创建主页控制器 LoginController
public class HomeController : Controller
{
public ActionResult Index()
{
// 获取当前登录用户
string loginName = Session["LoginName"].ToString();
ViewBag.Message = "当前登录用户:" + loginName;
return View();
}
}
4、创建主页页面
@{
ViewBag.Title = "Index";
Layout = null;
}
<h2>Index</h2>
<h3>@ViewBag.Message</h3>
<a href="@Url.Action("Logout","Login")">注销</a>
效果图:
5、创建授权过滤器 LoginAuthorizeAttribute 类
创建 Filter 目录,在该目录下创建授权过滤器 LoginAuthorizeAttribute 类,继承 AuthorizeAttribute。
using System.Web.Mvc;
namespace MvcApp.Filter
{
/// <summary>
/// 授权过滤器
/// </summary>
public class LoginAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
// 判断是否跳过授权过滤器
if (filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true)
|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true))
{
return;
}
// 判断登录情况
if (filterContext.HttpContext.Session["LoginName"] == null || filterContext.HttpContext.Session["LoginName"].ToString()=="")
{
//HttpContext.Current.Response.Write("认证不通过");
//HttpContext.Current.Response.End();
filterContext.Result = new RedirectResult("/Login/Index");
}
}
}
}
通常 Authorize 过滤器也是在全局过滤器上面的,在 App_Start 目录下的 FilterConfig 类的 RegisterGlobalFilters 方法中添加:
using System.Web;
using System.Web.Mvc;
using MvcApp.Filter;
namespace MvcApp
{
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
// 添加全局授权过滤器
filters.Add(new LoginAuthorizeAttribute());
}
}
}
Global.asax 下的代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
namespace MvcApp
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
}
xoyozo:暂时没有按区域(Area)来统一配置的方法,建议加在 Controller 上。https://stackoverflow.com/questions/2319157/how-can-we-set-authorization-for-a-whole-area-in-asp-net-mvc
使用 JS 根据屏幕宽度来计算页面尺寸和字体大小并不是一个好办法。
rem 单位是跟随网页根元素的 font-size 改变而自动改变的,是一个相对的长度单位,非常适合在不同手机界面上自适应屏幕大小。
一般的手机浏览器的默认字体大小为 16px。即:
:root { font-size: 16px; } /* 在 HTML 网页中,:root 选择器相当于 html */
也就是说,如果我定义一个宽度为 1rem,那么它的实际宽度为 16px。反过来说,如果设计稿宽度为 1000px,那么相当于 1000÷16=62.5rem。因此,我们设置一个 62.5rem 的宽度,即可正常显示 1000px 的设计效果。
为了适应不同屏幕尺寸的手机,只需按比例换算更改 :root 的 font-size 即可。这个比例是由“窗口宽度 ÷ 设计稿宽度”得到的,姑且称它为“窗设比”。
以 iPhone X 逻辑宽度 375px 为例,设计稿宽度 1000px 时,窗设比为:0.375。:root 的 font-size 将会被重置为 16 * 0.375 = 6px。上面的 62.5rem 宽度将对应 iPhone X 宽度为 62.5 * 6 = 375px,正好是屏幕逻辑宽度。
根据这个思路,理论上,我们只需要使用 CSS 将 :root 的 font-size 设置为 1px,然后使用 JS 重置为与“窗设比”相乘的积(本例中将被重置为 0.375px)。这样,我们可以不通过换算,只需要更改单位,将设计稿中的 500px 直接写入到 CSS 为 500rem,即可实现在 iPhone X 上显示逻辑宽度为(500 * 0.375 =)187.5px,即设计稿中的 1/2 宽度对应到屏幕上 1/2 的宽度。
实际上,部分安卓手机(如华为某旗舰机)不支持 :root 的 font-size 小于 12px,其它一些浏览器也会出现小于 12px 的文字以 12px 显示。
为了避免在任何环节出现小于 12px 的 font-size 值,且不增加设计稿尺寸到 CSS 数值的换算难度,我们设计了以下思路:
约定“设样比(设计稿的 px 值与 CSS 的 rem 值的比)”为 100;
使用 JS 将 :root 的 font-size 重置为“设样比”与“窗设比(窗口宽度 ÷ 设计稿宽度)”的乘积;
计算 CSS 时的 rem 值只需从设计稿中获取 px 值再除以“设样比”100 即可。
这样做的另一个好处是,不用要求设计师必须按多少尺寸来设计,当然按主流尺寸来建议是可行的。
完整代码(jQuery 版):
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<title>Index</title>
<style>
body { margin: 0; text-align: center; }
#app { margin: 0 auto; background-color: #EEE; display: none; }
/* 长度计算方法:从设计稿获得长度(例如 160px),除以 px2rem 值(本例为 100),得到 1.6rem */
.whole { background-color: #D84C40; width: 10rem; }
.half { background-color: #3296FA; width: 5rem; }
.half2 { background-color: #3296FA; width: 5rem; margin-left: 5rem; }
</style>
</head>
<body>
<div id="app">
<div class="whole">100%</div>
<div class="half">50%</div>
<div class="half2">50%</div>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
// [函数] 重置尺寸
function fn_resize() {
// 设计稿宽度(px)
var designsWidth = 1000;
// 约定的“设计稿 px 值与 CSS rem 值的比”,为方便计算,一般无需改动
var px2rem = 100;
// 限制 #app 的最大宽度为设计稿宽度
$('#app').css('max-width', designsWidth).css('min-height', $(window).height());
// 重置 :root 的 font-size
var appWidth = Math.min($(window).width(), designsWidth);
$(':root').css('font-size', px2rem * appWidth / designsWidth);
$('#app').show();
}
// 页面加载完成后重置尺寸
$(fn_resize);
// 改变窗口大小时重置尺寸
$(window).resize(fn_resize);
</script>
</body>
</html>
完整代码(Vue2 版):
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<title>Index</title>
<style>
body { margin: 0; text-align: center; }
#app { margin: 0 auto; background-color: #EEE; }
/* 长度计算方法:从设计稿获得长度(例如 160px),除以 px2rem 值(本例为 100),得到 1.6rem */
.whole { background-color: #D84C40; width: 10rem; }
.half { background-color: #3296FA; width: 5rem; }
.half2 { background-color: #3296FA; width: 5rem; margin-left: 5rem; }
</style>
</head>
<body>
<div id="app" v-bind:style="{ maxWidth: designsWidth + 'px', minHeight: appMinHeight + 'px' }" style="display: none;" v-show="appShow">
<div class="whole">100%</div>
<div class="half">50%</div>
<div class="half2">50%</div>
<div>appWidth: {{ appWidth }}</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script>
var app = new Vue({
el: '#app',
data: {
// 设计稿宽度(px)
designsWidth: 1000,
// 约定的“设计稿 px 值与 CSS rem 值的比”,为方便计算,一般无需改动
px2rem: 100,
// #app 的 width
appWidth: 0, // 勿改
appMinHeight: 0, // 勿改
appShow: false, // 勿改
},
mounted: function () {
// 页面加载完成后重置尺寸
this.fn_resize();
const that = this;
// 改变窗口大小时重置尺寸
window.onresize = () => {
return (() => {
console.log('RUN window.onresize()');
that.fn_resize();
})();
};
},
watch: {
// 侦听 appWidth 更改 root 的 font-size
appWidth: function () {
console.log('RUN watch: appWidth');
var root = document.getElementsByTagName("html")[0];
root.style.fontSize = (this.px2rem * this.appWidth / this.designsWidth) + 'px';
this.appShow = true;
}
},
methods: {
fn_resize: function () {
console.log('RUN methods: fn_resize()');
this.appWidth = Math.min(document.body.clientWidth, this.designsWidth);
this.appMinHeight = document.documentElement.clientHeight;
}
}
});
</script>
</body>
</html>
完整代码(Vue3 版):
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<title>Index</title>
<style>
body { margin: 0; text-align: center; }
#app > div { display: none; margin: 0 auto; background-color: #EEE; }
/* 长度计算方法:从设计稿获得长度(例如 160px),除以 px2rem 值(本例为 100),得到 1.6rem */
.whole { background-color: #D84C40; width: 10rem; }
.half { background-color: #3296FA; width: 5rem; }
.half2 { background-color: #3296FA; width: 5rem; margin-left: 5rem; }
</style>
</head>
<body>
<div id="app">
<div style="display: none;" v-bind:style="{ maxWidth: designsWidth + 'px', minHeight: appMinHeight + 'px', display: appShow ? 'block' : 'none' }">
<div class="whole">100%</div>
<div class="half">50%</div>
<div class="half2">50%</div>
<div>appWidth: {{ appWidth }}</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@3"></script>
<script>
const app = Vue.createApp({
data: function () {
return {
// 设计稿宽度(px)
designsWidth: 1000,
// 约定的“设计稿 px 值与 CSS rem 值的比”,为方便计算,一般无需改动
px2rem: 100,
// #app 的 width
appWidth: 0, // 勿改
appMinHeight: 0, // 勿改
appShow: false, // 勿改
}
},
mounted: function () {
// 页面加载完成后重置尺寸
this.fn_resize();
const that = this;
// 改变窗口大小时重置尺寸
window.onresize = () => {
return (() => {
console.log('RUN window.onresize()');
that.fn_resize();
})();
};
},
watch: {
// 侦听 appWidth 更改 root 的 font-size
appWidth: function () {
console.log('RUN watch: appWidth');
var root = document.getElementsByTagName("html")[0];
root.style.fontSize = (this.px2rem * this.appWidth / this.designsWidth) + 'px';
this.appShow = true;
}
},
methods: {
fn_resize: function () {
console.log('RUN methods: fn_resize()');
this.appWidth = Math.min(document.body.clientWidth, this.designsWidth);
this.appMinHeight = document.documentElement.clientHeight;
}
}
});
const vm = app.mount('#app');
</script>
</body>
</html>
示例中 CSS 初始 :root 的 font-size 为 16px(一个较小值,防止页面加载时瞬间出现大号文字影响用户体验),经过 fn_resize 后,:root 的 font-size 被设置为(100 * 375 / 1000 =)37.5px(iPhone X 中),那么宽度为 1000px 的设计稿中的 500px 换算到 CSS 中为 5rem,也即 37.5 * 5 = 187.5px,就是 iPhone X 的屏幕宽度的一半。
示例中 id 为 app 的 div 是用来在 PC 浏览器中限制页面内容最大宽度的(类似微信公众号发布的文章),如果网页不需要在 PC 端显示,jQuery 版代码中的 $('#app').width() 可以用 $(window).width() 来代替。
这个 div#app 一般设计有背景色,用于在宽度超过设计稿的设备上显示时区别于 body 的背景。但是当网页内容不超过一屏时,div#app 高度小于窗口,示例中与 appMinHeight 相关的代码就是为了解决这个问题。
示例中 div#app 隐藏/显示相关代码用于解决在页面加载初期由于 font-size 值变化引起的一闪而过的排版错乱。
最后补充一点,如果改变窗口大小时涉及执行耗时的操作,为避免页面卡顿,可以参考这篇文章添加函数防抖:https://xoyozo.net/Blog/Details/js-function-anti-shake

安装 Nuget 包:
项目根目录创建绑定配置文件:bundleconfig.json
示例:
[
{
"outputFileName": "wwwroot/css/site.min.css",
"inputFiles": [
"wwwroot/css/site.css",
"wwwroot/css/custom.css"
]
},
{
"outputFileName": "wwwroot/js/site.min.js",
"inputFiles": [
"wwwroot/js/site.js"
],
"minify": {
"enabled": true,
"renameLocals": true
},
"sourceMap": false
}
]
配置文件中所有路径都相对于项目根目录(而非静态文件根目录 wwwroot),因此配置的路径都要以“wwwroot”开头。
outputFileName 是压缩合并后的文件,inputFiles 是被压缩合并的原始文件集合。
对于 js 配置部分,minify.enabled 配置是否缩小,renameLocals 配置是否修改变量名,sourceMap 配置是否生成 map 映射文件。
引用示例:
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
asp-append-version 表示是否在引用路径添加版本参数,可实现在文件有修改时及时在客户端浏览器中生效。
* 注意:有时候“生成”不一定生效,“重新生成”肯定会生效。
更多高级用法请参考官方文档。

按这两篇文章部署即可:
ASP.NET Core 缓存(Cache)之 Redis 缓存
ASP.NET Core 会话状态(Session State)
存储内容示例:
图中,
第 1 项为浏览器 A 中,在 Session 中设置键 count 值为 1 的结果;
第 2 项为浏览器 B 中,在 Session 中设置键 count 值为 3 的结果;
而第 3 项为设置 Cache 键 count 值为 8 的结果。

按这两篇文章部署即可:
ASP.NET Core 缓存(Cache)之 SQL Server 缓存
ASP.NET Core 会话状态(Session State)
存储内容示例:
图中,
第 1 项为浏览器 A 中,在 Session 中设置键 count 值为 3 的结果;
第 3 项为浏览器 B 中,在 Session 中设置键 count 值为 1 的结果;
而第 2 项为设置 Cache 键 count 值为 5 的结果。

打开 appsettings.json,添加一项配置(如下方示例中的“SiteOptions”项)
* 注意,如需配置开发环境与生产环境不同的值,可单独在 appsettings.Development.json 文件中配置不同项,格式层次须一致;
C# 中习惯用强类型的方式来操作对象,那么在项目根目录添加类(类名以 SiteOptions为例),格式与 appsettings.json 中保持一致:
public class SiteOptions
{
public ERPOptions ERP { get; set; }
public WeixinOpenOptions WeixinOpen { get; set; }
public WeixinMPOptions WeixinMP { get; set; }
public SMSOptions SMS { get; set; }
public AliyunOSSOptions AliyunOSS { get; set; }
/// <summary>
/// 单个文件上传的最大大小(MB)
/// </summary>
public int MaxSizeOfSigleFile_MB { get; set; }
/// <summary>
/// 单个文件上传的最大大小(字节)
/// </summary>
public int MaxSizeOfSigleFile_B => MaxSizeOfSigleFile_MB * 1024 * 1024;
public class ERPOptions
{
public int ChannelId { get; set; }
public string AppKey { get; set; }
}
public class WeixinOpenOptions
{
public string AppId { get; set; }
public string AppSecret { get; set; }
}
public class WeixinMPOptions
{
public string AppId { get; set; }
public string AppSecret { get; set; }
}
public class SMSOptions
{
public string AppKey { get; set; }
}
public class AliyunOSSOptions
{
public string Endpoint { get; set; }
public string AccessKeyId { get; set; }
public string AccessKeySecret { get; set; }
public string BucketName { get; set; }
/// <summary>
/// 格式://域名/
/// </summary>
public string CdnUrl { get; set; }
}
}
在 Startup 中注入 IConfiguration,并在 ConfigureServices() 方法中添加服务(注意使用 GetSection() 映射到自命名的“SiteOptions”项)
在控制器中使用:
在控制器类中键入“ctor”,并按两次 Tab 键,创建构造函数
在构造函数中注入“IOptions”,并在 Action 中使用
using Microsoft.Extensions.Options;
public class TestController : Controller
{
private readonly IOptions<SiteOptions> options;
public TestController(IOptions<SiteOptions> options)
{
this.options = options;
}
public IActionResult Index()
{
return View(options.Value.ERP.ChannelId.ToString());
}
}
在视图中使用:
@using Microsoft.Extensions.Options
@inject IOptions<SiteOptions> options
@options.Value.ERP.ChannelId
