如果你是刚入手的小米 WiFi 电力猫,那么按说明书一步一步配置即可。这里记录一下移机的重置方法。
本次移机的场景是电力猫从一台小米路由器更换到另一台小米路由器的配置,两台小米路由器 WIFI 的 SSD 不同(假设为 WIFI-A 和 WIFI-B)。
错误做法:打开米家,切换到 WIFI-B 所在的房间,点击右上角的添加新设备,根据介绍插上子猫,并按子猫的配置键 5 秒以上。连上“xiaomi-plc-xxx”,米家显示正在连接,但到达 100% 后显示连接超时。
可能的错误原因:
没有插上主猫
该电子猫已经在米家上添加过了(在 WIFI-A 所在的房间),就无法添加到其它房间了(仅猜测,未实测)
于是,同时插上主猫和子猫,并且路由器和主猫使用网线连接,什么都不设置,过一两分钟,两猫自动会变成蓝灯(表示连接成功)。这时,如果刚才那步没有重置成功的话,打开米家主界面上的“小米电力线wifi扩展器”(如果没有删除过,还在 A 房间),里面会显示网络名称为 WIFI-A,还可能没有开“WIFI漫游”。这样虽然能连接上网,但有两个 SSD,不能实现漫游。
这时,按子猫配置键 5 秒以上,并静等一分钟以上,变回蓝灯后,再打开米家,就会看到网络名称可能是 WIFI-B-Plus,这时"WIFI漫游”也能打开了,完美扩展了 WIFI。
综上实践得出:
WIFI 是由子猫负责的,所以跟 SSD 有关的配置由子猫负责;
一台设备(这里指电子猫套餐)只能添加到米家一次,但可以更改设备所在的房间。
正确的重置电力猫步骤是:
同时插上主猫和子猫
主猫连上路由器
子猫按配置键 5 秒以上,等待一分钟以上
打开米家,进入“小米电力线wifi扩展器”,打开 WIFI 漫游
以上仅在使用小米路由器情况下实测,非小米路由器未尝试。

二维码类型 | 可扫码授权的微信号 | 其它微信号 | ||
直接扫码 | 将二维码截图发送给可扫码授权的微信号 | 无需劳烦管理员的方式 | ||
登录微信公众平台 | 管理员与运营者 | 扫码后需管理员验证登录(管理员通过“公众平台安全助手”接收消息,点开详情后点击“确定”) | 授权人用摄像头扫码①,点击“确定” | 成为运营者② |
设置公众号IP白名单 | 管理员与长期运营者 | 无法完成 | 授权人用摄像头扫码①,点击“确定” | 成为长期运营者② |
公众号绑定运营者 | 管理员 | 无法完成 | 管理员用摄像头扫码①,点击“确定” | 无 |
小程序添加/删除项目成员 | 管理员 | 无法完成 | 管理员直接识别二维码,点击“确定” | 无 |
小程序修改名称、修改简称、修改登录邮箱、修改密码 | 管理员 | 无法完成 | “修改简称”管理员用摄像头扫码①,点击“确定”,其它未测 | 无 |
小程序修改业务域名 | 管理员、有“开发者”权限的项目成员 | 无法完成 | 授权人直接识别二维码,点击“确定” | 成为有“开发者”权限的项目成员 |
微信开放平台绑定小程序 | 管理员 | 无法完成 | 管理员用摄像头扫码①,点击“确定” | 无 |
① 摄像头扫码:无法直接用“识别二维码”,必须把二维码发送到身边的其它设备,再使用手机摄像头完成扫码。
② 成为运营者要求绑定银行卡;成为运营者将占用“微信号绑定的帐号”数量,详见说明。若达到上限,只有法定代表人可继续成为运营者。在公众号“公众平台安全助手”中可查询绑定的帐号。
另外:
小程序添加/删除体验成员无须管理员授权
如果管理员未关注公众号“公众平台安全助手”,待测……

当使用在线编辑器编辑一篇文章(或从 Word 复制)后,会得到包含 HTML 标签的字符串内容,可以直接将它输出到页面上而不需要进行 HTML 编码。
但是,当我们需要改变图片大小时,我们发现有些图片的尺寸是直接使用 style 属性固定的,除了用 JS 进行后期处理,我们可以在服务端对 <img /> 进行修正。
这个场景会在小程序开发的时候遇到。
我们可以在客户端用 JS 进行处理,也可以在服务端用类似的方法处理(使用正则表达式)。参此文
这里使用 HtmlAgilityPack 通过递归节点来处理:
/// <summary>
/// 给所有指定节点添加样式
/// </summary>
/// <param name="html"></param>
/// <param name="tag">节点名称(小写),如:img</param>
/// <param name="styles">要添加的样式,如:max-width:100%;</param>
/// <returns></returns>
public static string AddStyleToHtmlNode(string html, string tag, string styles)
{
HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(html);
for (var i = 0; i < doc.DocumentNode.ChildNodes.Count; i++)
{
doc.DocumentNode.ChildNodes[i] = AddStyleToHtmlNode(doc.DocumentNode.ChildNodes[i], tag, styles);
}
return doc.DocumentNode.OuterHtml;
}
private static HtmlNode AddStyleToHtmlNode(HtmlNode node, string tag, string styles)
{
if (node.Name == tag)
{
var style = node.GetAttributeValue("style", null);
node.SetAttributeValue("style", ((string.IsNullOrWhiteSpace(style) ? "" : style.Trim() + ";") + styles).Replace(";;", ";"));
}
for (var i = 0; i < node.ChildNodes.Count; i++)
{
node.ChildNodes[i] = AddStyleToHtmlNode(node.ChildNodes[i], tag, styles);
}
return node;
}
直接调用:
Content = zStringHTML_190126.AddStyleToHtmlNode(Content, "img", "max-width:100%;height:auto;");

当使用在线编辑器编辑一篇文章(或从 Word 复制)后,会得到包含 HTML 标签的字符串内容,可以直接将它输出到页面上而不需要进行 HTML 编码。
但是,当我们需要改变图片大小时,我们发现有些图片的尺寸是直接使用 style 属性固定的,除了用 JS 进行后期处理,我们可以在服务端对 <img /> 进行修正。
这个场景会在小程序开发的时候遇到。
网上一般使用正则表达式直接将 <img
替换成 <img style="max-width: 100%; height: auto;"
,缺点是如果该 <img /> 本身就带有 style 属性,那么会出现一个标签两个 style,很多情况导致这两个样式同时失效,所以我们应针对有 style 和无 style 分别处理。
// 把 <img src="a.jpg" style="display: block;" /> 替换成 <img src="a.jpg" style="display: block;;max-width:100%;height:auto;" />
Content = Content.replace(/(\<img\s+[^>]*style\s*\=\s*['"][^'"]*)(['"])/gi, '$1;max-width:100%;height:auto;$2');
// 把 <img src="b.jpg" /> 替换成 <img src="b.jpg" style="max-width:100%;height:auto;" />
Content = Content.replace(/(\<img\s+((?!style).)+?)(\/?>)/gi, '$1 style="max-width:100%;height:auto;" $3');
复制以上代码时,半角空格可能会变成全角空格,请注意修正。
当有 style 时,我们将 max-width: 100%; height: auto;
追加在原样式之后,以重写原样式。这里没有直接判断原样式是否以 ;
结尾,而是直接追加 ;
,这并不会影响实现展示效果。
在判断没有 style 用到正则表达式的“断言”,参:https://blog.csdn.net/xuyangxinlei/article/details/81359366

一、显示信息的命令
console.log("normal"); // 用于输出普通信息 console.info("information"); // 用于输出提示性信息 console.error("error"); // 用于输出错误信息 console.warn("warn"); // 用于输出警示信息
二、点位符:字符(%s)、整数(%d或%i)、浮点数(%f)和对象(%o);
console.log("%s","string"); //字符(%s) console.log("%d年%d月%d日",2016,8,29); //整数(%d或%i) console.log("圆周率是%f",3.1415926); //浮点数(%f) var dog = {}; dog.name = "大毛"; dog.color = "黄色"; dog.sex = "母狗"; console.log("%o",dog); //对象(%o)
三、信息分组 (console.group(),console.groupEnd())
console.group("第一组信息"); console.log("第一组第一条:我的博客"); console.log("第一组第二条:CSDN"); console.groupEnd(); console.group("第二组信息"); console.log("第二组第一条:程序爱好者QQ群"); console.log("第二组第二条:欢迎你加入"); console.groupEnd();
四、将对象以树状结构展现 (console.dir()可以显示一个对象所有的属性和方法)
var info = { name : "Alan", age : "27", grilFriend : "nothing", getName : function(){ return this.name; } } console.dir(info);
五、显示某个节点的内容 (console.dirxml()用来显示网页的某个节点(node)所包含的html/xml代码)
var node = document.getElementById("info"); node.innerHTML += "<p>追加的元素显示吗</p>"; console.dirxml(node);
六、判断变量是否是真 (console.assert()用来判断一个表达式或变量是否为真,只有表达式为false时,才输出一条相应作息,并且抛出一个异常)
var testObj = false; console.assert(testObj, '当testObj为false时才输出!');
七、计时功能 (console.time()和console.timeEnd(),用来显示代码的运行时间)
console.time("控制台计时器"); for(var i = 0; i < 10000; i++){ for(var j = 0; j < 10000; j++){} } console.timeEnd("控制台计时器");
八、性能分析performance profile (就是分析程序各个部分的运行时间,找出瓶颈所在,使用的方法是console.profile()和console.proileEnd();)
function All(){ // alert(11); for(var i = 0; i < 10; i++){ funcA(100); } funcB(1000); } function funcA(count){ for(var i = 0; i < count; i++){}; } function funcB(count){ for(var i = 0; i < count; i++){}; } console.profile("性能分析器"); All(); console.profileEnd();
详细的信息在chrome控制台里的"profile"选项里查看
九、console.count()统计代码被执行的次数
function myFunction(){ console.count("myFunction 被执行的次数"); } myFunction(); //myFunction 被执行的次数: 1 myFunction(); //myFunction 被执行的次数: 2 myFunction(); //myFunction 被执行的次数: 3
十、keys和values,要在浏览器里输入
十一、console.table表格显示方法
var mytable = [ { name: "Alan", sex : "man", age : "27" }, { name: "Wu", sex : "gril", age : "28" }, { name: "Tao", sex : "man and gril", age : "29" } ] console.table(mytable);
十二、Chrome 控制台中原生支持类jQuery的选择器,也就是说你可以用$加上熟悉的css选择器来选择DOM节。
$("body"); //选择body节点
十三、copy通过此命令可以将在控制台获取到的内容复制到剪贴板
copy(document.body); //复制body copy(document.getElementById("info")); //复制某id元素的的节点
十四、$_命令返回最近一次表达式执行的结果,$0-$4代表了最近5个你选择过的DOM节点
十五、利用控制台输出文字,图片,以%c开头,后面的文字就打印的信息,后面一个参数就是样式属性;
console.log("请在邮件中注明%c 来自:console","font-size:16px;color:red;font-weight:bold;");
这是一个数据可视化项目,基于D3.js。能够将历史数据排名转化为动态柱状图图表。
先来看看效果:
https://xoyozo.net/Demo/BarGraph
作者 Jannchie 见齐还提供了官方视频教程:
https://www.bilibili.com/video/av28087807
不过由于开源项目的不断更新,该教程的部分内容已失效,本文针对 2018-12-25 版本总结了一些常用的配置说明,仅供参考。
从 GitHub 下载源代码,在 src 目录中可以看到 4 个文件:
bargraph.html --> 运行示例页面
config.js --> 配置文件
stylesheet.css --> 样式表
visual.js --> 核心文件
作者并没有将代码封装为插件的方式,所以我们是通过修改 config.js 配置文件的方式应用到自己的项目中的。
visual.js 虽然是核心文件,但作者将部分示例中的代码也包含其中,但并不影响我们直接在自己的项目中引用。
以下是 config.js 中的主要属性:
属性 | 说明 | 参考值 |
encoding | 数据源(csv、json)等的文件编码 | GBK / UTF-8 等 |
max_number | 每个时间节点最多显示的条目数 | 10 |
showMessage | 控制是否显示顶部附加信息文字 | true / false |
auto_sort | 时间自动排序(详细含义、作用及限制见代码中注释) | true / false |
timeFormat | 时间格式,显示于图表右下角的时间 | %Y、%Y-%M-%D 等 |
reverse | 是否倒序 | true / false |
divide_by | 类型根据什么字段区分?如果是 name,则关闭类型显示 | |
divide_color_by | 颜色根据什么字段区分? 须要注意的是,如果配置成 name,则各条颜色不同(因为 name 值各异),如果配置成 type 等,那么相同 type 值的条颜色相同。 | 字段名 |
color | 指定部分或所有条的颜色,该项与 divide_color_by 设置有关。 | 以 divide_color_by = 'name' 为例,"中国"是其中的一项 name 值,那么该项将显示 #D62728 色: color: { '中国': '#D62728' } |
changeable_color | 若 true 则颜色的深浅将根据数据的增长率实时改变 | |
itemLabel | 左边文字 | |
typeLabel | 右边文字 | |
item_x | ||
interval_time | 时间点间隔时间 | 2 |
text_y | ||
text_x | ||
offset | ||
display_barInfo | 如果希望不显示,则可以设置较大的值,单位像素 | |
use_counter | ||
step | ||
format | 格式化数值 | |
left_margin | ||
right_margin | ||
top_margin | ||
bottom_margin | ||
dateLabel_x | ||
dateLabel_y | ||
allow_up | ||
enter_from_0 | ||
big_value | ||
use_semilogarithmic_coordinate | ||
long | ||
wait | 数据加载完成后开始播放前的等待时间 | 0 |
update_rate |

当我们用 ASP.NET Web API 作服务端接口时,如果客户端以 GET 方式请求的 URL 中,由于拼接错误导致“?”和“&”并列出现,会发生 400 Bad Request,那么需要在 IIS 中使用 URL 重写模块来纠正。
请求 URL 如:
http://www.abc.com/api/obj?&foo=bar
需要重写为:
http://www.abc.com/api/obj?foo=bar
打开 IIS 管理器中对应网站的 URL 重写,
添加空白规则。
匹配的 URL 不包含域名,如上述 URL 则匹配的范围是:
api/obj
不包含我们要查找的字符串 ?&,所以此处模式只能匹配所有:
.*
又因为重定向路径中需要包含这部分,所以用括号包裹,即:
(.*)
继续添加条件匹配:
条件输入:{QUERY_STRING},表示匹配查询字符串,
匹配的部分是“?”后面的字符串:
&foo=bar
那么可以填写模式:
^\&.*
因为 & 后面的部分仍然需要使用,所以用括号包裹:
^\&(.*)
“操作”中选择类型“重定向”,URL 填写:
{R:1}?{C:1}
其中 {R:1} 指代 api/obj,{C:1} 指代 foo=bar
取消“附加查询字符串”前面的勾
重定向类型在测试时可以选择 302 或 307,没问题后选择 301。
完成。
最终我们可以在网站的配置文件 web.config 中看到:
<rewrite>
<rules>
<rule name="uni-app-h5-replace-q" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{QUERY_STRING}" pattern="^\&(.*)" />
</conditions>
<action type="Redirect" url="{R:1}?{C:1}" appendQueryString="false" redirectType="Temporary" />
</rule>
</rules>
</rewrite>
我们要把这段代码复制到开发环境源代码的 web.config 中,以免发布网站时将我们设置好的配置覆盖掉。

uni-app 的 input,当 type = "text" 时,设置 confirm-type = "next" 可以提供键盘的“下一项”,通过 @confirm 触发设置下一个文本框的焦点,即可实现只操作键盘即可完成表单填写。
我们以从 a 组件跳转到 b 组件为例:
<input v-model="a" type="text" confirm-type="next" @confirm="moveNext('b')" />
uni-app 的组件只有属性和事件,不提供方法,那么我们只能设置提供了 focus 属性的组件的焦点。
<input v-model="b" :focus="focusList['b']" />
moveNext() 方法实现:
moveNext(dom){
this.focusList[dom] = true;
}
当然 focusList 必须先定义并初始化,否则设置 focus 为 true 将失效:
data() {
return {
a: '',
b: '',
focusList: {
b: false
}
}
}
几个注意点:
confirm-type = "next" 仅支持微信小程序
“下一项”按钮效果是一次性的,使用一次后再次在 a 上触摸“下一项”将不再跳转到 b,即使在 moveNext() 中先设置为 false 再设置为 true 也没用
其它组件也可以通过自带的方法实现跳转到带 focus 属性的组件(如在 picker 的 @change 中设置 this.focusList['b'] = true)
限制于 confirm-type = "next" 只当 type = "text" 时有效,因此非文本键盘(如数字键盘)就不能提供“下一项”功能
textarea 组件的右下角始终是“换行”,由其右上角的“完成”按钮来实现“下一项”功能
textarea 组件在“下一项”跳转过程中,组件可能会被 tabBar 遮挡(几率很大)

在学习和使用 ASP.NET Web API 之前,最好先对 RESTful 有所了解。它是一种软件架构风格、设计风格,而不是标准。
推荐视频教程:https://www.imooc.com/learn/811
讲师:会飞的鱼Xia
需时:2小时25分
REST API 测试工具:
在 Chrome 网上应用店中搜索:Restlet Client
或网站 https://client.restlet.com
>>> 资源路径
SCHEME :// HOST [ ":" PORT ] [ PATH [ "?" QUERY ]]
其中 PATH 中的资源名称应该使用复数名词,举例:
GET https://xoyozo.net/api/Articles/{id}
POST https://xoyozo.net/api/Articles
>>> HTTP verb(对应 CURD 操作):
方法 | 功能 | ASP.NET Web API 接口返回类型(一般的) |
---|---|---|
GET | 取一个或多个资源 | T 或 IEnumerable<T> |
POST | 增加一个资源 | T |
PUT | 修改一个资源 | T |
DELETE | 删除一个资源 | void |
PATCH | 更新一个资源(资源的部分属性) | |
HEAD | 获取报头 | |
OPTIONS | 查询服务器性能或资源相关选项和需求 |
>>> 过滤信息:
如分页、搜索等
>>> 常用状态码:
200 | OK | 指示请求已成功 |
201 | Created | 资源创建成功(常见用例是一个 PUT 请求的结果) |
202 | Accepted | 该请求已被接收但尚未起作用。它是非承诺的,这意味着HTTP中没有办法稍后发送指示处理请求结果的异步响应。 |
204 | No Content | 成功 PUT(修改)或 DELETE(删除)一个资源后返回空内容(ASP.NET Web API 中将接口返回类型设置为 void 即可) |
400 | Bad Request | 服务器无法理解请求(如必填项未填、内容超长、分页大小超限等),幂等 |
401 | Unauthorized | 未授权(如用户未登录时执行了须要登录才能执行的操作),通俗地讲,是服务器告诉你,“你没有通过身份验证 - 根本没有进行身份验证或验证不正确 - 但请重新进行身份验证并再试一次。”这是暂时的,服务器要求你在获得授权后再试一次。 |
403 | Forbidden | 服务器理解请求但拒绝授权(是永久禁止的并且与应用程序逻辑相关联(如不正确的密码、尝试删除其它用户发表的文章)、IP 被禁止等),通俗地讲,是服务器告诉你,“我很抱歉。我知道你是谁 - 我相信你说你是谁 - 但你只是没有权限访问此资源。” |
404 | Not Found | 找不到请求的资源(指资源不存在,如果找到但无权访问则应返回 403) |
405 | Method Not Allowed | 请求方法被禁用(HTTP 动词) |
500 | Internal Server Error | 服务器遇到阻止它履行请求的意外情况(如数据库连接失败) |
503 | Service Unavailable | 服务器尚未准备好处理请求(常见原因是服务器因维护或重载而停机,这是临时的,可用于在单线程处理事务时遇到被锁定时的响应,如抽奖活动、抢楼活动,防止因并发导致逻辑错误) |
>> 错误处理:
C# 例:throw new HttpResponseException(HttpStatusCode.NotFound);
PHP 例:throw new Exception('文章不存在', 404);
>>> 返回结果:
JSON/XML
不要返回密码等机密字段
>>> 其它:
身份验证窗口(浏览器弹窗,类似 alert,非页面表单),明文传输,安全性不高,不建议使用。实现:
在 Headers 中添加 Authorization:Basic “用户名:密码”的 Base64 编码

确定唯一凭证:
若项目仅提供微信端访问,那么使用微信的授权(即以微信 openid / unionid 作为唯一凭证)是一种体现注重用户体验的方式。如果项目需要应用到多个平台上,那么通过微信来授权变得不那么完美(譬如未安装微信客户端或平台不支持使用微信授权等)。因手机的使用率已明显高于电脑的使用率,再加上用户帐号实名认证的要求,以手机号码为作为帐号的唯一凭证是非常合理的。
登录唤起条件:
当用户打开需要登录授权的页面或触发需要登录授权才能执行的操作时。
场景实现:
平台 | 实现 |
微信网页/小程序 | 授权获取 openid / unionid 同时若未绑定手机号码则强制绑定(发送验证码的方式) 如果允许在微信上登录不同的用户帐号,那么需要提供【“手机号码 + 密码”登录】方式 |
手机 App | 提供【“手机号码 + 密码”登录】 提供【第三方帐号登录】,登录后强制绑定手机号码 参“京东”等 |
PC 端浏览器网页 | 提供【“手机号码 + 密码”登录】 登录框可切换成【扫二维码登录】,登录后强制绑定手机号码 |
手机浏览器网页 | 提供【“手机号码 + 密码”登录】 如果提供【第三方帐号登录】,那么可能需要在网页上手动输入第三方帐号的用户名密码,而非跳转到第三方 App 上(视第三方开放平台) |
l 【“手机号码 + 密码”登录】页面显示填写手机号码和密码框的表单,提供“忘记密码”、“短信验证码登录”、“手机快速注册”等功能。如果在可长期记住登录状态的场景中(如手机 App)也可取消密码,直接使用短信验证码登录。“手机快速注册”必须通过短信验证码实现实名认证。
l 【第三方帐号登录 / 扫二维码登录】App 中提供跳转相应第三方 App(如微信)授权登录,PC 端网页中提供微信扫二维码登录。登录获得 unionid,同时判断是否已经绑定过手机号码,若未绑则强制绑定,确保数据库中的每一条用户信息都有手机号码。
注:同一个微信帐号在不同的公众号和 App 中的 openid 不同,将这些公众号和 App 通过微信开放平台绑定后,同一个微信帐号的 unionid 是相同的,可用于判断同一用户。
流程图:
各平台登录设计:
