得益于前辈的分享,做了一个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目前不支持的。如果电脑没有音频驱动或者没有麦都会报错提示。
有兴趣的朋友可以玩玩。未来移动端支持就更好了。
源码:http://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,但该用户没有关注公众号,发送时会抛出下面的错误:
相关部分代码:http://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部分源码:http://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 = "上传失败!"}); }
这样安卓是能上传了,但是也没有了进度条。