h5 录音 自动生成proto Js语句 UglifyJS-- 对你的js做了什么 【原码笔记】-- protobuf.js 与 Long.js 【微信开发】-- 发送模板消息 能编程与会编程 vue2入坑随记(二) -- 自定义动态组件 微信上传图片
得益于前辈的分享,做了一个h5录音的demo。效果图如下:

点击开始录音会先弹出确认框:

首次确认允许后,再次录音不需要再确认,但如果用户点击禁止,则无法录音:

点击发送 将录音内容发送到对话框中。点击即可播放。点击获取录音即可下载最后一次的音频:

播放下载都是围绕blob文件。播放就是让隐藏的audio标签的地址指向内存中的blob:
this.play = function (audio,blob) {
blob=blob||this.getBlob().blob;
audio.src = URL.createObjectURL(blob);
};
createObjectURL 我们在用base64显示图片的时候也可以用到。
img.src = URL.createObjectURL(blob);
这样比一长串的字符串好看很多。同理如果你想销毁该地址对应的数据而节省内存可以这样:
URL.revokeObjectURL(img.src);
扯远了点。下载就是模拟a标签的点击。
function downloadRecord(record){
var save_link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a')
save_link.href = URL.createObjectURL(record);
var now=new Date;
save_link.download = now.Format("yyyyMMddhhmmss");
fake_click(save_link);
}
function fake_click(obj) {
var ev = document.createEvent('MouseEvents');
ev.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
obj.dispatchEvent(ev);
}
每次发送 ,其实是讲音频数据缓存下来,标记下id。下次点击的时候根据id获取缓存的数据,然后叫给audio元素播放:
var msg={};
//发送音频片段
var msgId=1;
function send(){
if(!recorder){
showError("请先录音");
return;
}
var data=recorder.getBlob();
if(data.duration==0){
showError("请先录音");
return;
}
msg[msgId]=data;
recorder.clear();
console.log(data);
var dur=data.duration/10;
var str="<div class='warper'><div id="+msgId+" class='voiceItem'>"+dur+"s</div></div>"
$(".messages").append(str);
msgId++;
}
$(document).on("click",".voiceItem",function(){
var id=$(this)[0].id;
var data=msg[id];
playRecord(data.blob);
})
内部是基于AudioContext实现:兼容性如下,基本上只能在谷歌和火狐浏览器里面玩。很可惜微信和ios目前不支持的。如果电脑没有音频驱动或者没有麦都会报错提示。
有兴趣的朋友可以玩玩。未来移动端支持就更好了。
源码:https://files.cnblogs.com/files/stoneniqiu/Voice.zip
参考博客:
自动生成proto Js语句
2017-07-28 20:02 by stoneniqiu, 789 阅读, 0 评论, 收藏, 编辑在与后端的WebSocket通信时,前端要带一个proto文件是一个累赘的事情。首先是明显的曝光了协议实体对象,再一个浏览器客户端很容易会缓存该文件,新的协议更新可能导致客户端不能使用,另外在cdn服务器上还需要配置.proto类型客户端才能下载过去。真是遗毒不浅,自己使用的时候会注意这些,但给别人使用的时候就很不乐观了,所以这次全部将proto文件转成JavaScript对象,省去协议文件和加载的步骤。
先看代码:
function createProto(name) {
var args = [].slice.call(arguments, 1);
var obj = new protobuf.Type(name);
for (var i = 0; i < args.length; i++) {
var p = args[i];
var key = i + 1;
obj.add(new protobuf.Field(p[0], key, p[1] || "string"));
}
return obj;
}
function createEnum(name,list) {
var obj = new protobuf.Enum(name);
for (var i = 0; i < list.length; i++) {
obj.add(list[i],i);
}
return obj;
}
function loadProto(callback) {
if (typeof protobuf == "undefined") return;//说明浏览器加载失败 root = new protobuf.Root().define("IMEntity");
root.add(createProto("Token", ["UserID"], ["Token"], ["Device"], ["Version", "int32"], ["Appkey"]));
root.add(createProto("Feedback", ["ResultCode", "int32"], ["ResultData"], ["Seq", "int32"], ["MsgID"]));
root.add(createEnum("ReceiptType", ["Receive", "Read"]));
//...
util.triggerCallback(callback);
};
proto 主要有两种类型,Type和Enum。Type对应协议中的message,相当于是类。Enum就是枚举类型
var Root = protobuf.Root,
Type = protobuf.Type,
Field = protobuf.Field;
var AwesomeMessage = new Type("AwesomeMessage").add(new Field("awesomeField", 1, "string"));
var root = new Root().define("awesomepackage").add(AwesomeMessage);
枚举的创建不要需要Field。只需要add 字段名即可。那么接下来的问题是,手写root.add 也很烦,因为要一个一个对照属性,不断的复制粘贴,很容易出错。所以又做了个自动生成代码的页面:
<textarea id="content">
//登陆Token
message Token{
string UserID = 1; //登陆接口返回的IMUserID
string Token = 2; //登陆接口返回的Token
string Device = 3; //客户端设备号
int32 Version = 4; //版本号,发布前与服务端约定值
string Appkey = 5; //客户端Appkey
}
//收到私信
message ReceivePrivateMessage{
string MsgID = 1; //消息id
string SenderID = 2; //发送者id
string ReceiverID = 3; //接收者id
string Content = 4; //消息内容。客户端转换成业务相关的实体后,再做后续处理(客户端使用,服务器不做任何处理,原样下发)
bool Ack = 5; //是否需要已读回执
int32 SendDateTime = 6; //消息发送时间
int32 ContentType = 7; //内容类型(客户端使用,服务器不做任何处理,原样下发)
}
//回执类型
enum ReceiptType{
Receive = 0; //已收回执(收到消息后立即发送给服务器的回执)
Read = 1; //已读回执(用户进入消息阅读界面后发送给服务器的回执)
}
</textarea>
<div id="result"></div>
<script>
function start() {
$("#result").html("");
$("#result").append('root = new protobuf.Root().define("IMProtoEntity")<br>');
var reg = /("([^\\\"]*(\\.)?)*")|('([^\\\']*(\\.)?)*')|(\/{2,}.*?(\r|\n))|(\/\*(\n|.)*?\*\/)/g,// 过滤注释
str = $('#content').val(); // 欲处理的文本
// console.log(str.match(reg));// 打印出:匹配子串
var news = str.replace(reg, "");
// console.log(news); // 打印出:原文本
var reg1 = /[message|enum].*?{/mg;
var regobj = /{[^}{]*?}/g;//新地址
var names = news.match(reg1);
var protos = news.match(regobj);
// console.log(names, protos);
var root = {};
for (var i = 0; i < names.length; i++) {
var rawname = names[i];
var rawObj = protos[i];
//if (~rawname.indexOf("message"))
if (!rawObj) continue;
var name = rawname.replace("{", '').replace("message ", '').replace("enum ", '');
var obj = { name: name };
if (~rawname.indexOf("enum")) {
obj["type"] = "enum";
}
rawObj = rawObj.replace("{", '').replace("}", '');
var protolist = rawObj.split(';');
// console.log("protolist", protolist);
var plist = [];
for (var j = 0; j < protolist.length; j++) {
var p = $.trim(protolist[j]);
if (p) {
var args = [];
var list = p.split(' ');
// console.log("list", list);
list.forEach(function (n) {
n && args.push(n);
}),
// console.log("args", args);
plist.push(args);
}
}
obj.list = plist;
console.log(obj);
toProto(obj);
}
}
start();
function toProto(obj) {
var root = "root";
var fun = "createProto";
var enumfun = "createEnum";
var str = root + '.add(';
var args;
if (!obj.type) {//message
args = '';
for (var i = 0; i < obj.list.length; i++) {
var item = obj.list[i];
//老协议2.0
if (item[0] == "required" || item[0] == "optional") {
item.shift();
}
//新协议3.0
if (item[0] != "string") {
args += '["' + item[1] + '","' + item[0] + '"]';
} else {
args += '["' + item[1] + '"]';
}
if (i < obj.list.length - 1) args += ",";
}
} else {//enum
args = '[';
for (var i = 0; i < obj.list.length; i++) {
var item = obj.list[i];
args += '"' + item[0] + '"';
if (i < obj.list.length - 1) args += ",";
}
args += ']';
}
var all = str + (obj.type ? enumfun : fun) + '("' + obj.name + '",' + args + '));';
// console.log(all);
$("#result").append(all + "<br>");
}
</script>
然后页面上会得到:

红色部分复制到工程里面就可以用了。当然要带上createProto和createEnum两个方法。proto的格式要规范,毕竟start里面是以空格split的。相对于protobuf.load("xx.proto",callback)的方式要好很多。load对位置要求比较死板,一定要在根目录。而且有类型不存在就会报错,终止程序。add方法不存在找不到类型的错误。另外速度也快了很多。
UglifyJS-- 对你的js做了什么
2017-07-27 08:38 by stoneniqiu, 1118 阅读, 4 评论, 收藏, 编辑
也不是闲着没事去看压缩代码,但今天调试自己代码的时候发现有点意思。因为是自己写的,虽然压缩了,格式化之后还是很好辨认。当然作为min的首要准则不是可读性,而是精简。那么它会尽量的缩短代码,尽量的保持一行,最大化的减少的空白。我们常用的分号都会被替换成了逗号,短句变成了连贯的长句。
1.立即执行函数

2.变量名替换
3.函数置顶
var self=this;
function a(){}
self.a=a;
function b(){}
self.b=b;
return self;
会替换成:
function a(){}
function b(){}
var s={}
return s.a={},s.b={},s
注意到最后的s 不能漏了,return会以最后一个表达式的结果为准。
function rt(n) {
return n;
}
function xx() {
return rt(1), rt(2);
}
执行xx()得到的是2,如果 rt(2)后面还有个不返回值的函数执行,那么xx()会得到undefined。
4.bool值替换
false-->!1 true-->!0
5.if
if语句是压缩最多的地方。
function load() {
if (t) {
x = false;
log("error");
return;
}
console.log("22")
}
比如我的原函数大概是这样。压缩后成了这样:
if (t) return x =!1,void log("error")
function foo() {
if (!x) {
return;
}
console.log("doA");
console.log("doB");
}
压缩后:
function f() {
x || console.log("doA"), console.log("doB");
}
这样蛮不错的。同理:
if(x&&y){
doa();
dob();
}
doc();
--> x&&y&&(doa(),dob()),doc()
原本四行变成了一行代码。
3).为了合并一行,这也行:
console.log("doA");
console.log("doB");
if (x>0) {
console.log("true");
}
合并成这样:
if (console.log("doA"), console.log("doB"), x > 0) console.log("true");
平时这么写可能不太友好,重点是在if语句中,最后一句才是判断句。结合之前的return。想必对逗号语句有了深刻的认识。
4)throw也不放过
if (errMsg) {
util.triggerCallback(fail, "模型验证错误");
throw Error(errMsg);
}
压缩后:
if (a) throw x.triggerCallback(o, "模型验证错误"), Error(a)
调换了语句的顺序,把throw看成return 就明白了。
5) if else
这个会替换成三元表达式 a?b:c 。
6.数字处理
7. while
var offset = 0;
while (true) {
if (offset >= bufferLength) {
break;
}
}
会替换成这样:
for (var n = 0; ; ) {
if (n >= K)
break
}
确实不错,节省了一行代码。
以上只是独自对比自己的代码发现的一些东西,有的可以在平时的编码中用起来,当然不是追求所有代码都写成一行,这样可读性比较差,另外可能你下次看压缩代码就不那么费劲了。欢迎补充。
protobuf.js的结构和webpack的加载之后的结构很相似。这样的模块化组合是个不错的结构方式。1个是适应了不同的加载方式,2个模块直接很独立。webpack的功能更全一点。但如果自己封装js库这样够用了。而且模块对外统一接口 module.exports。这和node很像。
(function(global, undefined) {
"use strict";
(function prelude(modules, cache, entries) {
function $require(name) {
var $module = cache[name];
//没有就去加载
if (!$module)
modules[name][0].call($module = cache[name] = { exports: {} }, $require, $module, $module.exports);
return $module.exports;
}
//曝光成全局
var proto = global.proto = $require(entries[0]);
// AMD
if (typeof define === "function" && define.amd) {
define(["long"], function(Long) {
if (Long && Long.isLong) {
proto.util.Long = Long;
proto.configure();
}
});
return proto;
}
//CommonJS
if (typeof module === "object" && module && module.exports)
module.exports = proto;
})
//传参
({
1: [function (require, module, exports) {
function first() {
console.log("first");
}
module.exports = first;
}, {}],
2: [function(require, module, exports) {
function second() {
console.log("second");
}
module.exports = second;
}],
3: [function (require, module, exports) {
var proto = {};
proto.first = require(1);
proto.second = require(2);
proto.build = "full";
module.exports = proto;
}]
}, {}, [3]);
})(typeof window==="object"&&window||typeof self==="object"&&self||this)
在处理超过16位的整形就得使用Long.js了。 主要是fromString和toString。
function fromString(str, unsigned, radix) {
if (str.length === 0)
throw Error('empty string');
if (str === "NaN" || str === "Infinity" || str === "+Infinity" || str === "-Infinity")
return ZERO;
if (typeof unsigned === 'number') {
// For goog.math.long compatibility
radix = unsigned,
unsigned = false;
} else {
unsigned = !!unsigned;
}
radix = radix || 10;
if (radix < 2 || 36 < radix)
throw RangeError('radix');
var p;
if ((p = str.indexOf('-')) > 0)
throw Error('interior hyphen');
else if (p === 0) {
return fromString(str.substring(1), unsigned, radix).neg();
}
// Do several (8) digits each time through the loop, so as to
// minimize the calls to the very expensive emulated div.
var radixToPower = fromNumber(pow_dbl(radix, 8));
var result = ZERO;
for (var i = 0; i < str.length; i += 8) {
var size = Math.min(8, str.length - i),
value = parseInt(str.substring(i, i + size), radix);
if (size < 8) {
var power = fromNumber(pow_dbl(radix, size));
result = result.mul(power).add(fromNumber(value));
} else {
result = result.mul(radixToPower);
result = result.add(fromNumber(value));
}
}
result.unsigned = unsigned;
return result;
}
fromstring的思路是把字符串8位一个截取。然后转成Long型(高位,地位,符号位) 加起来。最后是一个Long型。 4294967296 是2的32次方。每次操作之前都会有一个基数的操作 mul(radixToPower)或者mul(power)这两者都是保证result的位数是正确的。
比如{low:123} 和{low:1} 相加之前,先要让{low:123}乘以10,得到{low:1230}再与{low:1}进行位操作。因为第一个是高位,不能直接相加。

function fromBits(lowBits, highBits, unsigned) {
return new Long(lowBits, highBits, unsigned);
}
fromBits 即转为Long对象。value%4294967296 得到低位。/得到高位。结果通过位移合并起来。mul是bit的乘法,add是bit的加法。 原理是讲一个64位的拆成四段。分别16位。this.low左移16位 就得到 low的32-17位是啥。 然后和addend对象的同位相加
最后的合并是通过|运算。位移之后再还原确实很巧妙。一时看上去都不大理解。
LongPrototype.add = function add(addend) {
if (!isLong(addend))
addend = fromValue(addend);
// Divide each number into 4 chunks of 16 bits, and then sum the chunks.
var a48 = this.high >>> 16;
var a32 = this.high & 0xFFFF;
var a16 = this.low >>> 16;
var a00 = this.low & 0xFFFF;
var b48 = addend.high >>> 16;
var b32 = addend.high & 0xFFFF;
var b16 = addend.low >>> 16;
var b00 = addend.low & 0xFFFF;
var c48 = 0, c32 = 0, c16 = 0, c00 = 0;
c00 += a00 + b00;
c16 += c00 >>> 16;
c00 &= 0xFFFF;
c16 += a16 + b16;
c32 += c16 >>> 16;
c16 &= 0xFFFF;
c32 += a32 + b32;
c48 += c32 >>> 16;
c32 &= 0xFFFF;
c48 += a48 + b48;
c48 &= 0xFFFF;
return fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned);
};
>>>和>>有什么区别??。
toString
LongPrototype.toString = function toString(radix) {
radix = radix || 10;
if (radix < 2 || 36 < radix)
throw RangeError('radix');
if (this.isZero())
return '0';
if (this.isNegative()) { // Unsigned Longs are never negative
if (this.eq(MIN_VALUE)) {
// We need to change the Long value before it can be negated, so we remove
// the bottom-most digit in this base and then recurse to do the rest.
var radixLong = fromNumber(radix),
div = this.div(radixLong),
rem1 = div.mul(radixLong).sub(this);
return div.toString(radix) + rem1.toInt().toString(radix);
} else
return '-' + this.neg().toString(radix);
}
// Do several (6) digits each time through the loop, so as to
// minimize the calls to the very expensive emulated div.
var radixToPower = fromNumber(pow_dbl(radix, 6), this.unsigned),
rem = this;
var result = '';
while (true) {
var remDiv = rem.div(radixToPower),
intval = rem.sub(remDiv.mul(radixToPower)).toInt() >>> 0,
digits = intval.toString(radix);
rem = remDiv;
if (rem.isZero())
return digits + result;
else {
while (digits.length < 6)
digits = '0' + digits;
result = '' + digits + result;
}
}
};
也是sub之后拼出来的。也就是fromstring的反向操作。
【微信开发】-- 发送模板消息
2017-06-28 20:25 by stoneniqiu, 8795 阅读, 7 评论, 收藏, 编辑
我们需要将一些行为的进展消息推送给用户。除了短信,发送微信模板消息也是不错的选择。模板消息免费、精准到达、而且可以引导用户回到网站上来。但它有两个前提条件。1个是认证的服务号,你才能选择模板。2个是被推送的用户必须关注了你的公众号,而且你也拿到了他的openid。

先在模板库中找到自己的想要的模板,添加到“我的模板”中。

展开详情,我们可以看到示例。接下来用C#代码发送一次:
从官方文档的示例中我们可以看到除了推送人的openid,还可以设置每个字段的颜色及跳转地址。先可以定义以个TempModel对象:
public class TemplateModel
{
public string touser { get; set; }
public string template_id { get; set; }
public string url { get; set; }
public string topcolor { get; set; }
public TemplateData data { get; set; }
public TemplateModel(string hello,string state,string reason,string last)
{
data=new TemplateData()
{
first = new TempItem(hello),
keyword1 = new TempItem(state),
keyword2 = new TempItem(reason),
remark = new TempItem(last)
};
}
}
public class TemplateData
{
public TempItem first { get; set; }
public TempItem keyword1 { get; set; }
public TempItem keyword2 { get; set; }
public TempItem remark { get; set; }
}
public class TempItem
{
public TempItem(string v,string c = "#173177")
{
value = v;
color = c;
}
public string value { get; set; }
public string color { get; set; }
}
还有一个返回结果对象:
public class OpenApiResult
{
public int error_code { get; set; }
public string error_msg { get; set; }
public string msg_id { get; set; }
}
然后定义一个发送方法:
using SendHelp= Senparc.Weixin.CommonAPIs.CommonJsonSend;
public OpenApiResult SendTemplateMessage(string token,TemplateModel model)
{
var url = string.Format("https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={0}", token);
try
{
var res = SendHelp.Send<OpenApiResult>(token, url, model);
return res;
}
catch (Exception e)
{
return new OpenApiResult(){error_code = -1,error_msg = e.Message};
}
}
SendHelp是基于Senparac.Weixin 最后就可以调用了:
public ActionResult SendMessage()
{
var token = getToken();
var toUserId = "oBSBmwQjqwjfzQlKsFNjxFLSiIQM";
var data = new TemplateModel("你好,stoneniqiu","审核通过","资料完整","祝你生活幸福!");
data.touser = toUserId;
data.template_id = "gXmkeL7Kc-KUy2EQzKqjPKY-kzFyatTztiYFKCaUPO4";
data.url = "http://www.xxx.com/xx/xx";
data.topcolor = "#FF0000";
var res=wxDeviceService.SendTemplateMessage(token, data);
return View(res);
}
token即通过AppID和APPSECRET获取。发送之后,手机上马上收到消息。这里的url就是下图详情的跳转地址。 只能是注册域名下面的地址,不能跳到别的域名去。

但如果你只是拿到了用户的openid,但该用户没有关注公众号,发送时会抛出下面的错误:
相关部分代码:https://files.cnblogs.com/files/stoneniqiu/wx-template.zip
官方文档:https://mp.weixin.qq.com/advanced/tmplmsg?action=faq&token=1798469214&lang=zh_CN
全部错误类型:http://www.szdyhd.com/news/view/webdesign/2016/0614/519.html
几个月前因为一个事情被diao了。起因是临近上线的时候项目后端统一了消息协议(.proto),然后要我前端也支持。我研究了一天,没走通,要么依赖项太多,要么一直报错,而且需要使用的对象兼容性有问题。当时心里有些急,也有几份抵触这种方案,于是在会上说出了我的想法:能不能友好的发发json,兼容性好也不需要什么第三方解析。结果自然是被否决了,理由是大厂出品的,怎么可能不能用呢,用屁股想想就知道?你为啥遇到问题就想着退缩呢。我无语凝噎。重要是给我强调了能编程与会编程是不一样的。
开完会情绪有点低落,回到座位上打开github 继续找方案,不一会儿居然找到了!啪啪打脸的感觉好酸爽。然后开始思索...
是否抵触
一件事带有抵触情绪之后,那基本是做不好的。在找方案的时候,眼中只看到“NO”,而自动忽略了“YES”。解决方法在你眼前你可能都看不到。就像一个人不想做事就找借口,这些借口在他自己看来都是很合理的。要带有这种情绪,那不如先别开始,要么自我消化,要么拿更好的方案去说服别人。
只要有一种解释是对自己有利的,我们便不想去推敲和反驳,再漏洞百出的事情看上去也不无可能,而且只要一种解释是可能的,我们就认定是一定的,强大的情绪大脑会阻止理性大脑往深入的想。而对于自己不利的解释,我们或忽略,或者异常仔细的去推敲,抓住一个漏洞则相信自己推翻了所有的解释。----《暗时间》
所以一件事情在自己着手做的时候,先整理好类似的情绪。很多时候摆在我们面前的方案很多,如果是你熟悉的领域当然好选择,利弊清晰,能快速判断。但不熟悉的时候,就要注意到每个方案所适用的环境和条件,要归类和总结,自己所遇到的问题本身已经提供了一些前提条件。用这些条件先过滤到一部分。而不是瞎猫抓死耗子一样一个个去碰。
不要等待
编程工作中我们经常遇到需求不完整,接口文档没有,接口返回值错,误诸如此类 别人的因素,然后导致项目进度缓慢或者停滞。其实我想说,编程简直太轻松愉快了,因为还有人给你提供需求,提供设计稿,你实现就行了。实际生活中很多事情根本没有人会告诉你需求,比如自己装修,你需要定风格,然后选材料,按照流程喊施工队伍,考虑风格和成本,考虑材料合不合适,喜不喜欢,甲醛高不高等等。建材市场鱼龙混杂,装修师傅可能阳奉阴违。即使你监工他都敢用料不够,总有一款让你交点学费。 可能很忙的时候,父母又生病了..... 生活的事情更考验的人的协作和沟通能力、承压能力。没有谁告诉你截止日期和责任,但是你没做好,就是没有做好,不要说什么装修的人坑了你。工作的事情责任范围清晰,不属于你的锅你都可以甩的远远的。但如果团队内部都是这样的氛围,那又能成什么事。雪崩的时候,没有一片雪花觉得自己有责任,应该积极主动起来,因为等待耗费的也是自己的时间。
做完了还是做好了
按照需求和效果图,你可能很快就实现了所有功能和效果。但是自己测试了吗?可用性高吗?稳定吗?兼容性如何?功能做完往往只到了一个阶段,后面还需要自己做检验性工作。确保交出去的东西是ok的,达到预期的。有信心对别人说我做好了,让别人用的放心。而不是说一句我做完了,暗藏的坑让别人先踩。有时间还应该思考一下,代码是否可以更简洁?接口设计是否可以更简明?多个类似方案是否可以统一成产品?思考的价值总会为你省下未来的时间,特别是重复劳动的时间、遇到问题沟通的时间。
知识体系完善
你选择的方案,都是你所知道的方案。也就是说,你的知识范围,决定了你的处理能力范围。甚至于,有的知识你会,但关键的时候你未必想的出来。这个过程就像解题,自己苦思冥想不得,看到答案恍然大悟。为什么呢?你的大脑就像一个图书馆,知识的碎片就如书架上的书,你想用的时候,发现找不到地址了。
知识分两种,一种是我们通常所谓的知识,即领域知识。二是关于我们大脑吸收知识的机制的知识,后者不妨成为元知识。虽说这也是领域知识,但跟其他的领域知识不同的是,它指导我们学习其它所有的领域知识。
除了完善我们的领域知识,也许需要补充一下其他领域的,譬如心理学和思维方面的,当然最好是到生活中去解决问题。
小结:会编程是基于现有经验办事的能力,而能编程是对整个事情的解决能力。在学习一门技术的成本差不多的情况下,差别就来自于编程之外的能力。
学习了Vue全家桶和一些UI基本够用了,但是用元素的方式使用组件还是不够灵活,比如我们需要通过js代码直接调用组件,而不是每次在页面上通过属性去控制组件的表现。下面讲一下如何定义动态组件。
Vue.extend
思路就是拿到组件的构造函数,这样我们就可以new了。而Vue.extend可以做到:https://cn.vuejs.org/v2/api/#Vue-extend
// 创建构造器
var Profile = Vue.extend({
template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
data: function () {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
})
// 创建 Profile 实例,并挂载到一个元素上。
new Profile().$mount('#mount-point')
官方提供了这个示例,我们进行一下改造,做一个简单的消息提示框。
动态组件实现
创建一个vue文件。widgets/alert/src/main.vue
<template>
<transition name="el-message-fade">
<div v-show="visible" class="my-msg">{{message}}</div>
</transition>
</template>
<script >
export default{
data(){
return{
message:'',
visible:true
}
},
methods:{
close(){
setTimeout(()=>{
this.visible = false;
},2000)
},
},
mounted() {
this.close();
}
}
</script>
这是我们组件的构成。如果是第一节中,我们可以把他放到components对象中就可以用了,但是这儿我们要通过构造函数去创建它。再创建一个widgets/alert/src/main.js
import Vue from 'vue';
let MyMsgConstructor = Vue.extend(require('./main.vue'));
let instance;
var MyMsg=function(msg){
instance= new MyMsgConstructor({
data:{
message:msg
}})
//如果 Vue 实例在实例化时没有收到 el 选项,则它处于“未挂载”状态,没有关联的 DOM 元素。可以使用 vm.$mount() 手动地挂载一个未挂载的实例。
instance.$mount();
document.body.appendChild(instance.$el)
return instance;
}
export default MyMsg;
require('./main.vue')返回的是一个组件初始对象,对应Vue.extend( options )中的options,这个地方等价于下面的代码:
import alert from './main.vue' let MyMsgConstructor = Vue.extend(alert);
而MyMsgConstructor如下。

参考源码中的this._init,会对参数进行合并,再按照生命周期运行:
Vue.prototype._init = function (options) {
...// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options);
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
// expose real self
vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
initInjections(vm); // resolve injections before data/props
initState(vm);
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created');
...
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
};
而调用$mount()是为了获得一个挂载实例。这个示例就是instance.$el。

可以在构造方法中传入el对象(注意上面源码中的mark部分,也是进行了挂载vm.$mount(vm.$options.el),但是如果你没有传入el,new之后不会有$el对象的,就需要手动调用$mount()。这个方法可以直接传入元素id。
instance= new MessageConstructor({
el:".leftlist",
data:{
message:msg
}})
这个el不能直接写在vue文件中,会报错。接下来我们可以简单粗暴的将其设置为Vue对象。
调用
在main.js引入我们的组件:
//..
import VueResource from 'vue-resource'
import MyMsg from './widgets/alert/src/main.js';
//..
//Vue.component("MyMsg", MyMsg);
Vue.prototype.$mymsg = MyMsg;
然后在页面上测试一下:
<el-button type="primary" @click='test'>主要按钮</el-button> //..
methods:{
test(){
this.$mymsg("hello vue");
}
}
这样就实现了基本的传参。最好是在close方法中移除元素:
close(){
setTimeout(()=>{
this.visible = false;
this.$el.parentNode.removeChild(this.$el);
},2000)
},
回调处理
回调和传参大同小异,可以直接在构造函数中传入。先修改下main.vue中的close方法:
export default{
data(){
return{
message:'',
visible:true
}
},
methods:{
close(){
setTimeout(()=>{
this.visible = false;
this.$el.parentNode.removeChild(this.$el);
if (typeof this.onClose === 'function') {
this.onClose(this);
}
},2000)
},
},
mounted() {
this.close();
}
}
如果存在onClose方法就执行这个回调。而在初始状态并没有这个方法。然后在main.js中可以传入
var MyMsg=function(msg,callback){
instance= new MyMsgConstructor({
data:{
message:msg
},
methods:{
onClose:callback
}
})
这里的参数和原始参数是合并的关系,而不是覆盖。这个时候再调用的地方修改下,就可以执行回调了。
test(){
this.$mymsg("hello vue",()=>{
console.log("closed..")
});
},
你可以直接重写close方法,但这样不推荐,因为可能搞乱之前的逻辑且可能存在重复的编码。现在就灵活多了。
统一管理
如果随着自定义动态组件的增加,在main.js中逐个添加就显得很繁琐。所以这里我们可以让widgets提供一个统一的出口,日后也方便复用。在widgets下新建一个index.js
import MyMsg from './alert/src/main.js';
const components = [MyMsg];
let install =function(Vue){
components.map(component => {
Vue.component(component.name, component);
});
Vue.prototype.$mymsg = MyMsg;
}
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue);
};
export default {
install
}
在这里将所有自定义的组件通过Vue.component注册。最后export一个install方法就可以了。因为接下来要使用Vue.use。
安装 Vue.js 插件。如果插件是一个对象,必须提供
install方法。如果插件是一个函数,它会被作为 install 方法。install 方法将被作为 Vue 的参数调用。
也就是把所有的组件当插件提供:在main.js中加入下面的代码即可。
... import VueResource from 'vue-resource' import Widgets from './Widgets/index.js' ... Vue.use(Widgets)
这样就很简洁了。
小结: 通过Vue.extend和Vue.use的使用,我们自定义的组件更具有灵活性,而且结构很简明,基于此我们可以构建自己的UI库。以上来自于对Element源码的学习。
widgets部分源码:https://files.cnblogs.com/files/stoneniqiu/widgets.zip
低版本的安卓上传图片是个问题,能出现选择图片,但点击图片后没有反应,转成base64也无解。于是改为用微信的接口上传。和之前的微信分享功能都是基于微信的jssdk。
步骤比我们平时上传到服务器多一步,他是先调用chooseeImage方法获得用户要上传的图片id。然后上传到微信的服务器,微信的服务器默认只保存三天,所以还要让后台下载到自己的服务器上,然后返回地址,前端显示,这样才算完成。
var time = '@ViewBag.Share.timestamp';
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: '@ViewBag.Share.appId', // 必填,公众号的唯一标识
timestamp: parseInt(time), // 必填,生成签名的时间戳
nonceStr: '@ViewBag.Share.nonceStr', // 必填,生成签名的随机串
signature: '@ViewBag.Share.signature',// 必填,签名,见附录1
jsApiList: ["chooseImage", "previewImage", "uploadImage", "downloadImage"] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
});
wx.ready(function() {
$(document).on("click", ".ctcon", function() {
wx.chooseImage({
count: 1, // 默认9
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success: function (res) {
var localIds = res.localIds; // 返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片
uploadimg(localIds[0].toString());
}
});
});
function uploadimg(lid) {
wx.uploadImage({
localId: lid, // 需要上传的图片的本地ID,由chooseImage接口获得
isShowProgressTips: 1, // 默认为1,显示进度提示
success: function (res) {
var serverId = res.serverId; // 返回图片的服务器端ID
//alert(serverId);
$.post("/Question/DownWxImage", { serverId: serverId }, function(res) {
//alert(res.SaveName);
if (res.Success === true) {
// 显示图片
}
});
});
}
});
count表示让用户选择图片的张数,然后这里的localIds要tostring。不然上传不了。uploadImage执行完了就可以通知让后台上传:
public ActionResult DownWxImage(string serverId)
{
var token = getToken();
var url = string.Format("http://file.api.weixin.qq.com/cgi-bin/media/get?access_token={0}&media_id={1}",
token, serverId);//图片下载地址
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(url);
req.Method = "GET";
using (WebResponse wr = req.GetResponse())
{
HttpWebResponse myResponse = (HttpWebResponse)req.GetResponse();
var strpath = myResponse.ResponseUri.ToString();
WebClient mywebclient = new WebClient();
var path = "/Content/UploadFiles/mobile/";
var uploadpath = Server.MapPath(path);
if (!Directory.Exists(uploadpath))
{
Directory.CreateDirectory(uploadpath);
}
string saveName = Encrypt.GenerateOrderNumber() + ".jpg";
var savePath = uploadpath + saveName;
try
{
mywebclient.DownloadFile(strpath, savePath);
return Json(new { Success = true, SaveName = path + saveName });
}
catch (Exception ex)
{
savePath = ex.ToString();
}
}
return Json(new {Success = false, Message = "上传失败!"});
}
这样安卓是能上传了,但是也没有了进度条。


浙公网安备 33010602011771号