博客 (61)

测试在长度为 403 的字符串中查找,特意匹配最后几个字符:

string a = "";
for (int i = 0; i < 100; i++) { a += "aBcD"; }
a += "xYz";
string b = "xyz"; // 特意匹配最后几个字符

Stopwatch sw1 = Stopwatch.StartNew();
bool? r1 = null; 
for (int i = 0; i < 10000; i++) { r1 = a.IndexOf(b) >= 0; }
sw1.Stop();

Stopwatch sw2 = Stopwatch.StartNew();
bool? r2 = null; 
for (int i = 0; i < 10000; i++) { r2 = a.Contains(b); }
sw2.Stop();

Stopwatch sw3 = Stopwatch.StartNew();
bool? r3 = null; 
for (int i = 0; i < 10000; i++) { r3 = a.ToUpper().Contains(b.ToUpper()); }
sw3.Stop();

Stopwatch sw4 = Stopwatch.StartNew();
bool? r4 = null; 
for (int i = 0; i < 10000; i++) { r4 = a.ToLower().Contains(b.ToLower()); }
sw4.Stop();

Stopwatch sw5 = Stopwatch.StartNew();
bool? r5 = null; 
for (int i = 0; i < 10000; i++) { r5 = a.Contains(b, StringComparison.OrdinalIgnoreCase); }
sw5.Stop();

Stopwatch sw6 = Stopwatch.StartNew();
bool? r6 = null; 
for (int i = 0; i < 10000; i++) { r6 = a.Contains(b, StringComparison.CurrentCultureIgnoreCase); }
sw6.Stop();

Stopwatch sw7 = Stopwatch.StartNew();
bool? r7 = null; 
for (int i = 0; i < 10000; i++) { r7 = Regex.IsMatch(a, b); }
sw7.Stop();

Stopwatch sw8 = Stopwatch.StartNew();
bool? r8 = null; 
for (int i = 0; i < 10000; i++) { r8 = Regex.IsMatch(a, b, RegexOptions.IgnoreCase); }
sw8.Stop();

return Json(new
{
    IndexOf_________________ = sw1.Elapsed + " " + r1,
    Contains________________ = sw2.Elapsed + " " + r2,
    ToUpper_________________ = sw3.Elapsed + " " + r3,
    ToLower_________________ = sw4.Elapsed + " " + r4,
    OrdinalIgnoreCase_______ = sw5.Elapsed + " " + r5,
    CurrentCultureIgnoreCase = sw6.Elapsed + " " + r6,
    IsMatch_________________ = sw7.Elapsed + " " + r7,
    IsMatchIgnoreCase_______ = sw8.Elapsed + " " + r8,
});

结果参考:

{
"indexOf_________________": "00:00:00.1455812 False",
"contains________________": "00:00:00.0003791 False",
"toUpper_________________": "00:00:00.0038182 True",
"toLower_________________": "00:00:00.0026113 True",
"ordinalIgnoreCase_______": "00:00:00.0096550 True",
"currentCultureIgnoreCase": "00:00:00.1596517 True",
"isMatch_________________": "00:00:00.0053627 False",
"isMatchIgnoreCase_______": "00:00:00.0084132 True"
}


xoyozo 3 年前
1,604

POST

axios.post('AjaxCases', {
    following: true,
    success: null,
    page: 1,
}).then(function (response) {
    console.log(response.data)
}).catch(function (error) {
    console.log(error.response.data);
});
[HttpPost]
public IActionResult AjaxCases([FromBody] AjaxCasesRequestModel req)
{
    bool? following = req.following;
    bool? success = req.success;
    int page = req.page;
}

GET

axios.get('AjaxCase', {
    params: {
        int: 123,
    }
}).then(function (response) {
    console.log(response.data)
}).catch(function (error) {
    console.log(error.response.data);
});
[HttpGet]
public IActionResult AjaxCases([FromQuery] int id)
{
}

DELETE

格式与 GET 类似


* 若服务端获取参数值为 null,可能的情况如下:

  • 请检查相关枚举类型是否已设置 [JsonConverter] 属性,参:C# 枚举(enum)用法笔记,且传入的值不能为空字符串,可以传入 null 值,并将服务端 enum 类型改为可空。可以以 string 方式接收参数后再进行转换。

  • 模型中属性名称不能重复(大小写不同也不行)。

  • 布尔型属性值必须传递布尔型值,即在 JS 中,字符串 "true" 应 JSON.parse("true") 为 true 后再回传给服务端。


参考:jQuery 请求 .NET Core / .NET 5 / .NET 6

xoyozo 3 年前
2,644

今天发现在开发新项目时,微信分享失效了,开启 debug = true 后,在微信开发工具中提示:

{"errMsg":"updateTimelineShareData:fail, the permission value is offline verifying"}

在真机上提示:

{"errMsg":"config:ok"}

{"errMsg":"updateAppMessageShareData:ok"}

{"errMsg":"updateTimelineShareData"}

仍然找不出原因。

尝试打开已上线的项目,分享功能均正常。

偶然发现,当打开使用 JS-SDK 分享后的图文框的页面,能够正常分享,而直接点开链接的页面,没有分享按钮。

这难免不让人联想到最近GJ出的新政,新版微信已经开放淘宝、抖音等平台的链接打开权限,只是当点击链接的时候会出现一个风险提示页面:

d9dd5a9e715606dd77f4b4b47ac44ab.jpg

而一旦由这个页面进入的网页,分享功能均不能正常。

另外还测试了以下几种情况的分享功能也能正常:

公众号推送的图文或链接、公众号菜单、个人发送到公众号的链接、通过扫一扫打开的页面等。

-- 2021.9

xoyozo 3 年前
10,832
  1. HttpHelper类:

    using System;
    using System.Collections.Specialized;
    using System.IO;
    using System.Net;
    using System.Text;
    
    namespace ConsoleApplication1
    {
        public static class HttpHelper
        {
            private static readonly Encoding DEFAULTENCODE = Encoding.UTF8;
    
            /// <summary>
            /// HttpUploadFile
            /// </summary>
            /// <param name="url"></param>
            /// <param name="file"></param>
            /// <param name="data"></param>
            /// <returns></returns>
            public static string HttpUploadFile(string url, string file, NameValueCollection data)
            {
                return HttpUploadFile(url, file, data, DEFAULTENCODE);
            }
    
            /// <summary>
            /// HttpUploadFile
            /// </summary>
            /// <param name="url"></param>
            /// <param name="file"></param>
            /// <param name="data"></param>
            /// <param name="encoding"></param>
            /// <returns></returns>
            public static string HttpUploadFile(string url, string file, NameValueCollection data, Encoding encoding)
            {
                return HttpUploadFile(url, new string[] { file }, data, encoding);
            }
    
            /// <summary>
            /// HttpUploadFile
            /// </summary>
            /// <param name="url"></param>
            /// <param name="files"></param>
            /// <param name="data"></param>
            /// <returns></returns>
            public static string HttpUploadFile(string url, string[] files, NameValueCollection data)
            {
                return HttpUploadFile(url, files, data, DEFAULTENCODE);
            }
    
            /// <summary>
            /// HttpUploadFile
            /// </summary>
            /// <param name="url"></param>
            /// <param name="files"></param>
            /// <param name="data"></param>
            /// <param name="encoding"></param>
            /// <returns></returns>
            public static string HttpUploadFile(string url, string[] files, NameValueCollection data, Encoding encoding)
            {
                string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
                byte[] boundarybytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");
                byte[] endbytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n");
    
                //1.HttpWebRequest
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
                request.ContentType = "multipart/form-data; boundary=" + boundary;
                request.Method = "POST";
                request.KeepAlive = true;
                request.Credentials = CredentialCache.DefaultCredentials;
    
                using (Stream stream = request.GetRequestStream())
                {
                    //1.1 key/value
                    string formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}";
                    if (data != null)
                    {
                        foreach (string key in data.Keys)
                        {
                            stream.Write(boundarybytes, 0, boundarybytes.Length);
                            string formitem = string.Format(formdataTemplate, key, data[key]);
                            byte[] formitembytes = encoding.GetBytes(formitem);
                            stream.Write(formitembytes, 0, formitembytes.Length);
                        }
                    }
    
                    //1.2 file
                    string headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: application/octet-stream\r\n\r\n";
                    byte[] buffer = new byte[4096];
                    int bytesRead = 0;
                    for (int i = 0; i < files.Length; i++)
                    {
                        stream.Write(boundarybytes, 0, boundarybytes.Length);
                        string header = string.Format(headerTemplate, "file" + i, Path.GetFileName(files[i]));
                        byte[] headerbytes = encoding.GetBytes(header);                    
                        stream.Write(headerbytes, 0, headerbytes.Length);
                        using (FileStream fileStream = new FileStream(files[i], FileMode.Open, FileAccess.Read))
                        {                        
                            while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
                            {
                                stream.Write(buffer, 0, bytesRead);
                            }
                        }
                    }
    
                    //1.3 form end
                    stream.Write(endbytes, 0, endbytes.Length);
                }
                //2.WebResponse
                HttpWebResponse response = (HttpWebResponse)request.GetResponse();
                using (StreamReader stream = new StreamReader(response.GetResponseStream()))
                {
                    return stream.ReadToEnd();
                }
            }
        }
    }
  2. 调用示例:

    using System;
    using System.Collections.Specialized;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                NameValueCollection data = new NameValueCollection();
                data.Add("name", "木子屋");
                data.Add("url", "http://www.mzwu.com/");
                Console.WriteLine(HttpHelper.HttpUploadFile("http://localhost/Test", new string[] { @"E:\Index.htm", @"E:\test.rar" }, data));
    
                Console.ReadKey();
            }
        }
    }
转自 木子屋 3 年前
2,614

百度地图称之为地面叠加层(GroundOverlay)

类参考:https://mapopen-pub-jsapi.bj.bcebos.com/jsapi/reference/jsapi_reference_3_0.html#a3b18

示例DEMO:http://lbsyun.baidu.com/jsdemo.htm#gImageGround

var map = new BMapGL.Map('container');
map.centerAndZoom(new BMapGL.Point(117.200, 36.2437), 18);
map.enableScrollWheelZoom(true);
map.setTilt(45);
map.setDisplayOptions({
    poiText: false,  // 隐藏poi标注
    poiIcon: false,  // 隐藏poi图标
    building: false  // 隐藏楼块
});


var pStart = new BMapGL.Point(117.19635, 36.24093);
var pEnd = new BMapGL.Point(117.20350, 36.24764);
var bounds = new BMapGL.Bounds(new BMapGL.Point(pStart.lng, pEnd.lat), new BMapGL.Point(pEnd.lng, pStart.lat));
var imgOverlay = new BMapGL.GroundOverlay(bounds, {
    type: 'image',
    url: '/jsdemo/img/shouhuimap.png',
    opacity: 1
});
map.addOverlay(imgOverlay);


xoyozo 4 年前
6,039

.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>

效果:

image.png

关于手风琴功能:Metronic 菜单已实现手风琴功能,即当展开一个闭合的菜单时,非当前页面所在菜单项的房系菜单会自动闭合。

xoyozo 4 年前
3,934

通过 nuget 安装 UEditorNetCore

从 UEditor 官网 下载最新的包 ueditorx_x_x_x-utf8-net.zip

解压包,并复制到项目的 wwwroot/lib 目录下,删除 net 目录。

根据 UEditorNetCore 官方的使用说明进行操作

步骤中,控制器 UEditorController 接替原 controller.ashx 的功能,用于统一处理上传逻辑

原 config.json 复制到项目根目录,用于配置上传相关设置(若更改文件名和路径在 services.AddUEditorService() 中处理)。

个人喜欢将 xxxPathFormat 值改为:upload/ueditor/{yyyy}{mm}{dd}/{time}{rand:6},方便日后迁移附件后进行批量替换。

记得配置 catcherLocalDomain 项。


上传相关的身份验证和授权控制请在 UEditorController 中处理,如:

public void Do()
{
    if (用户未登录) { throw new System.Exception("请登录后再试"); }
    _ue.DoAction(HttpContext);
}


如果图片仅仅上传到网站目录下,那么这样配置就结束了,如果要上传到阿里云 OSS 等第三方图床,那么继续往下看。


因 UEditorNetCore 虽然实现了上传到 FTP 和 OSS 的功能,但未提供配置相关的账号信息的途径,所以只能(1)重写 action,或(2)下载 github 的源代码,将核心项目加入到您的项目中进行修改。

重写 action 不方便后期维护,这里记录修改源码的方式。

将源码改造为可配置账号的上传到 OSS 的功能,具体步骤如下:

  1. 将 Consts.cs 从项目中排除。

  2. 打开 Handlers/UploadHandler.cs,找到 UploadConfig 类,将

    FtpUpload 改为 OssUpload,

    FtpAccount 改为 OssAccessKeyId,

    FtpPwd 改为 OssAccessKeySecret,

    FtpIp 改为 OssAccessEndpoint,

    再添加一个属性 OssBucketName。

  3. 打开 Handlers/UploadHandler.cs,找到 Process() 方法, 

    将 UploadConfig.FtpUpload 改为 UploadConfig.OssUpload,

    将 Consts.AliyunOssServer.* 改为 UploadConfig.Oss*。

  4. 打开 Handlers/CrawlerHandler.cs,找到 Fetch() 方法,

    将 Config.GetValue<bool>("catcherFtpUpload") 改为 Config.GetValue<bool>("OssUpload"),

    将 Consts.AliyunOssServer.* 改为 Config.GetString("Oss*")。

  5. 打开 UEditorActionCollection.cs,找到 UploadImageAction,将

    FtpUpload = Config.GetValue<bool>("imageFtpUpload"),
    FtpAccount = Consts.ImgFtpServer.account,
    FtpPwd = Consts.ImgFtpServer.pwd,
    FtpIp = Consts.ImgFtpServer.ip,

    替换为

    OssUpload = Config.GetValue<bool>("OssUpload"),
    OssAccessKeyId = Config.GetString("OssAccessKeyId"),
    OssAccessKeySecret = Config.GetString("OssAccessKeySecret"),
    OssAccessEndpoint = Config.GetString("OssAccessEndpoint"),
    OssBucketName = Config.GetString("OssBucketName"),

    其余 3 个 Action(UploadScrawlAction、UploadVideoAction、UploadFileAction)按同样的方式修改。

    在所有创建 UploadHandler 对象时补充添加 SaveAbsolutePath 属性。

  6. 打开 config.json,添加相关配置项(注:配置文件中的 *FtpUpload 全部废弃,统一由 OssUpload 决定)

    "OssUpload": true,
    "OssAccessEndpoint": "",
    "OssAccessKeyId": "",
    "OssAccessKeySecret": "",
    "OssBucketName": "",

    将 xxxUrlPrefix 的值改为 OSS 对应的 CDN 网址(以 / 结尾,如://cdn.xoyozo.net/)。


其它注意点:

  • 若使用 UEditorNetCore github 提供的源代码类库代替 nuget,且使用本地存储,那么需要将 Handlers 目录中与定义 savePath 相关的代码(查找  var savePath)替换成被注释的行

xoyozo 4 年前
4,633

核心文件路径:/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 请求参数
query: {
}
headers 自定义请求的头
{
    'x-my-custom-header': 'some value',
    'x-test-header': 'the value'
}
map 数据地图,作用是对返回的数据进行整理和定位
function (raw) {
    console.log(raw)
    // sample data mapping
    var dataSet = raw;
    if (typeof raw.data !== 'undefined') {
        dataSet = raw.data;
    }
    return dataSet;
}

layout:

属性 功能
theme 主题 默认 default
class 包裹的 CSS 样式
scroll 在需要时显示横向或纵向滚动条 默认 false
height 表格高度 默认 null
minHeight 表格最小高度 默认 null
footer 是否显示表格底部 默认 false
header 是否显示表头 默认 true
customScrollbar 自定义的滚动条 默认 true
spinner Loading 样式
{
	overlayColor: '#000000',
	opacity: 0,
	type: 'loader',
	state: 'primary',
	message: true,
}
icons 表格中的 icon
{
	sort: {
	    asc: 'flaticon2-arrow-up', 
	    desc: 'flaticon2-arrow-down'
	},
	pagination: {
		next: 'flaticon2-next',
		prev: 'flaticon2-back',
		first: 'flaticon2-fast-back',
		last: 'flaticon2-fast-next',
		more: 'flaticon-more-1',
	},
	rowDetail: {
	    expand: 'fa fa-caret-down', 
	    collapse: 'fa fa-caret-right'
	},
}
sortable 是否支持按列排序 默认 true
resizable
是否支持鼠标拖动改变列宽 默认 false
filterable 在列中过滤 默认 false
pagination
显示分页信息 默认 true
editable
行内编辑 默认 false
columns
见本文下方
search
搜索
{
	// 按回车键确认
	onEnter: false,
	// 文本内容
	input: null,
	// 搜索延时
	delay: 400,
	// 键名
	key: null
}

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 行,简体中文示例:

translate: {
    records: {
        processing: '加载中...',
        noRecords: '没有找到相关内容',
    },
    toolbar: {
        pagination: {
            items: {
                default: {
                    first: '首页',
                    prev: '前一页',
                    next: '后一页',
                    last: '末页',
                    more: '更多',
                    input: '请输入跳转页码',
                    select: '设置每页显示项数',
                },
                info: '当前第 {{start}} - {{end}} 项 共 {{total}} 项',
            },
        },
    },
},


extensions

暂时没有找到对字符串内容进行自动 HTML 编码的属性,这可能带来 XSS 攻击风险,在 remote 方式中必须在服务端预先 HtmlEncode。即使在 layout.columns.template 中进行处理也是无济于事,恶意代码会在 ajax 加载完成后立即执行。


方法和事件:待完善。


更多信息请查询官方文档:https://keenthemes.com/keen/?page=docs&section=html-components-datatable

xoyozo 4 年前
4,723

最近,Microsoft 将其针对Web API 的默认序列化从 Newtonsoft JsonConvert 更改为 System.Text.Json JsonSerializer。

using System.Text.Json;
string s = JsonSerializer.Serialize(object);
var obj = JsonSerializer.Deserialize<T>(string);

System.Text.Json 命名空间:https://docs.microsoft.com/zh-cn/dotnet/api/system.text.json?view=net-5.0

如何从 Newtonsoft.Json 迁移到 System.Text.Json:https://docs.microsoft.com/zh-cn/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to?pivots=dotnet-5-0


不序列化属性

[System.Text.Json.Serialization.JsonIgnore]

当值为 null 时不序列化

[System.Text.Json.Serialization.JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]


格式化/美化

System.Text.Json.JsonSerializer.Serialize(Obj, new System.Text.Json.JsonSerializerOptions {
    WriteIndented = true
})


不编码中文(建议默认需要编码,可防止页面显示乱码,除非需要放在 <pre /> 标签中直接显示,可友好显示中文)

System.Text.Json.JsonSerializer.Serialize(Obj, new System.Text.Json.JsonSerializerOptions {
    Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
})


xoyozo 4 年前
5,922

本文适用于 Window,CentOS(Linux) 系统请移步:http://xoyozo.net/Blog/Details/inotify-tools


FileSystemWatcher

关于监视文件系统事件的介绍

https://docs.microsoft.com/zh-cn/previous-versions/visualstudio/visual-studio-2008/ch2s8yd7(v=vs.90)


以控制台应用程序为例:

using System;
using System.IO;
using System.Security.Permissions;

public class Watcher
{
    public static void Main()
    {
        Run();
    }

    [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
    private static void Run()
    {
        string[] args = Environment.GetCommandLineArgs();

        // If a directory is not specified, exit program.
        if (args.Length != 2)
        {
            // Display the proper way to call the program.
            Console.WriteLine("Usage: Watcher.exe (directory)");
            return;
        }

        // Create a new FileSystemWatcher and set its properties.
        using (FileSystemWatcher watcher = new FileSystemWatcher())
        {
            watcher.Path = args[1];

            // Watch for changes in LastAccess and LastWrite times, and
            // the renaming of files or directories.
            watcher.NotifyFilter = NotifyFilters.LastAccess
                                 | NotifyFilters.LastWrite
                                 | NotifyFilters.FileName
                                 | NotifyFilters.DirectoryName;

            // Only watch text files.
            watcher.Filter = "*.txt";

            // Add event handlers.
            watcher.Changed += OnChanged;
            watcher.Created += OnChanged;
            watcher.Deleted += OnChanged;
            watcher.Renamed += OnRenamed;

            // Begin watching.
            watcher.EnableRaisingEvents = true;

            // Wait for the user to quit the program.
            Console.WriteLine("Press 'q' to quit the sample.");
            while (Console.Read() != 'q') ;
        }
    }

    // Define the event handlers.
    private static void OnChanged(object source, FileSystemEventArgs e) =>
        // Specify what is done when a file is changed, created, or deleted.
        Console.WriteLine($"File: {e.FullPath} {e.ChangeType}");

    private static void OnRenamed(object source, RenamedEventArgs e) =>
        // Specify what is done when a file is renamed.
        Console.WriteLine($"File: {e.OldFullPath} renamed to {e.FullPath}");
}


xoyozo 4 年前
3,237