1 /**! 2 * 微信内置浏览器的Javascript API,功能包括: 3 * 4 * 1、分享到微信朋友圈 5 * 2、分享给微信好友 6 * 3、分享到腾讯微博 7 * 4、新的分享接口,包含朋友圈、好友、微博的分享(for iOS) 8 * 5、隐藏/显示右上角的菜单入口 9 * 6、隐藏/显示底部浏览器工具栏 10 * 7、获取当前的网络状态 11 * 8、调起微信客户端的图片播放组件 12 * 9、关闭公众平台Web页面 13 * 10、判断当前网页是否在微信内置浏览器中打开 14 * 11、增加打开扫描二维码 15 * 12、支持WeixinApi的错误监控 16 * 13、检测应用程序是否已经安装(需要官方开通权限) 17 * 14、发送电子邮件 18 * 15、禁止用户分享 19 * 20 * @author guanguoxiang(http://www.ggxapp.com) 21 */ 22 (function (window) { 23 24 "use strict"; 25 26 /** 27 * 定义WeixinApi 28 */ 29 var WeixinApi = { 30 version: 4.1 31 }; 32 33 // 将WeixinApi暴露到window下:全局可使用,对旧版本向下兼容 34 window.WeixinApi = WeixinApi; 35 36 /////////////////////////// CommonJS ///////////////////////////////// 37 if (typeof define === 'function' && (define.amd || define.cmd)) { 38 if (define.amd) { 39 // AMD 规范,for:requirejs 40 define(function () { 41 return WeixinApi; 42 }); 43 } else if (define.cmd) { 44 // CMD 规范,for:seajs 45 define(function (require, exports, module) { 46 module.exports = WeixinApi; 47 }); 48 } 49 } 50 51 /** 52 * 对象简单继承,后面的覆盖前面的,继承深度:deep=1 53 * @private 54 */ 55 var _extend = function () { 56 var result = {}, obj, k; 57 for (var i = 0, len = arguments.length; i < len; i++) { 58 obj = arguments[i]; 59 if (typeof obj === 'object') { 60 for (k in obj) { 61 obj[k] && (result[k] = obj[k]); 62 } 63 } 64 } 65 return result; 66 }; 67 68 /** 69 * 内部私有方法,分享用 70 * @private 71 */ 72 var _share = function (cmd, data, callbacks) { 73 callbacks = callbacks || {}; 74 75 // 分享过程中的一些回调 76 var progress = function (resp) { 77 switch (true) { 78 // 用户取消 79 case /:cancel$/i.test(resp.err_msg) : 80 callbacks.cancel && callbacks.cancel(resp); 81 break; 82 // 发送成功 83 case /:(confirm|ok)$/i.test(resp.err_msg): 84 callbacks.confirm && callbacks.confirm(resp); 85 break; 86 // fail 发送失败 87 case /:fail$/i.test(resp.err_msg) : 88 default: 89 callbacks.fail && callbacks.fail(resp); 90 break; 91 } 92 // 无论成功失败都会执行的回调 93 callbacks.all && callbacks.all(resp); 94 }; 95 96 // 执行分享,并处理结果 97 var handler = function (theData, argv) { 98 99 // 加工一下数据 100 if (cmd.menu == 'menu:share:timeline' || 101 (cmd.menu == 'general:share' && argv.shareTo == 'timeline')) { 102 103 var title = theData.title; 104 theData.title = theData.desc || title; 105 theData.desc = title || theData.desc; 106 } 107 108 // 新的分享接口,单独处理 109 if (cmd.menu === 'general:share') { 110 // 如果是收藏操作,并且在wxCallbacks中配置了favorite为false,则不执行回调 111 if (argv.shareTo == 'favorite' || argv.scene == 'favorite') { 112 if (callbacks.favorite === false) { 113 return argv.generalShare(theData, function () { 114 }); 115 } 116 } 117 if (argv.shareTo === 'timeline') { 118 WeixinJSBridge.invoke('shareTimeline', theData, progress); 119 } else if (argv.shareTo === 'friend') { 120 WeixinJSBridge.invoke('sendAppMessage', theData, progress); 121 } else if (argv.shareTo === 'QQ') { 122 WeixinJSBridge.invoke('shareQQ', theData, progress); 123 }else if (argv.shareTo === 'weibo') { 124 WeixinJSBridge.invoke('shareWeibo', theData, progress); 125 } 126 } else { 127 WeixinJSBridge.invoke(cmd.action, theData, progress); 128 } 129 }; 130 131 // 监听分享操作 132 WeixinJSBridge.on(cmd.menu, function (argv) { 133 callbacks.dataLoaded = callbacks.dataLoaded || new Function(); 134 if (callbacks.async && callbacks.ready) { 135 WeixinApi["_wx_loadedCb_"] = callbacks.dataLoaded; 136 if (WeixinApi["_wx_loadedCb_"].toString().indexOf("_wx_loadedCb_") > 0) { 137 WeixinApi["_wx_loadedCb_"] = new Function(); 138 } 139 callbacks.dataLoaded = function (newData) { 140 callbacks.__cbkCalled = true; 141 var theData = _extend(data, newData); 142 theData.img_url = theData.imgUrl || theData.img_url; 143 delete theData.imgUrl; 144 WeixinApi["_wx_loadedCb_"](theData); 145 handler(theData, argv); 146 }; 147 // 然后就绪 148 if (!(argv && (argv.shareTo == 'favorite' || argv.scene == 'favorite') && callbacks.favorite === false)) { 149 callbacks.ready && callbacks.ready(argv, data); 150 // 如果设置了async为true,但是在ready方法中并没有手动调用dataLoaded方法,则自动触发一次 151 if (!callbacks.__cbkCalled) { 152 callbacks.dataLoaded({}); 153 callbacks.__cbkCalled = false; 154 } 155 } 156 } else { 157 // 就绪状态 158 var theData = _extend(data); 159 if (!(argv && (argv.shareTo == 'favorite' || argv.scene == 'favorite') && callbacks.favorite === false)) { 160 callbacks.ready && callbacks.ready(argv, theData); 161 } 162 handler(theData, argv); 163 } 164 }); 165 }; 166 167 /** 168 * 分享到微信朋友圈 169 * @param {Object} data 待分享的信息 170 * @p-config {String} appId 公众平台的appId(服务号可用) 171 * @p-config {String} imgUrl 图片地址 172 * @p-config {String} link 链接地址 173 * @p-config {String} desc 描述 174 * @p-config {String} title 分享的标题 175 * 176 * @param {Object} callbacks 相关回调方法 177 * @p-config {Boolean} async ready方法是否需要异步执行,默认false 178 * @p-config {Function} ready(argv, data) 就绪状态 179 * @p-config {Function} dataLoaded(data) 数据加载完成后调用,async为true时有用,也可以为空 180 * @p-config {Function} cancel(resp) 取消 181 * @p-config {Function} fail(resp) 失败 182 * @p-config {Function} confirm(resp) 成功 183 * @p-config {Function} all(resp) 无论成功失败都会执行的回调 184 */ 185 WeixinApi.shareToTimeline = function (data, callbacks) { 186 _share({ 187 menu: 'menu:share:timeline', 188 action: 'shareTimeline' 189 }, { 190 "appid": data.appId ? data.appId : '', 191 "img_url": data.imgUrl, 192 "link": data.link, 193 "desc": data.desc, 194 "title": data.title, 195 "img_width": "640", 196 "img_height": "640" 197 }, callbacks); 198 }; 199 200 /** 201 * 发送给微信上的好友 202 * @param {Object} data 待分享的信息 203 * @p-config {String} appId 公众平台的appId(服务号可用) 204 * @p-config {String} imgUrl 图片地址 205 * @p-config {String} link 链接地址 206 * @p-config {String} desc 描述 207 * @p-config {String} title 分享的标题 208 * 209 * @param {Object} callbacks 相关回调方法 210 * @p-config {Boolean} async ready方法是否需要异步执行,默认false 211 * @p-config {Function} ready(argv, data) 就绪状态 212 * @p-config {Function} dataLoaded(data) 数据加载完成后调用,async为true时有用,也可以为空 213 * @p-config {Function} cancel(resp) 取消 214 * @p-config {Function} fail(resp) 失败 215 * @p-config {Function} confirm(resp) 成功 216 * @p-config {Function} all(resp) 无论成功失败都会执行的回调 217 */ 218 WeixinApi.shareToFriend = function (data, callbacks) { 219 _share({ 220 menu: 'menu:share:appmessage', 221 action: 'sendAppMessage' 222 }, { 223 "appid": data.appId ? data.appId : '', 224 "img_url": data.imgUrl, 225 "link": data.link, 226 "desc": data.desc, 227 "title": data.title, 228 "img_width": "640", 229 "img_height": "640" 230 }, callbacks); 231 }; 232 233 234 /** 235 * 分享到腾讯微博 236 * @param {Object} data 待分享的信息 237 * @p-config {String} link 链接地址 238 * @p-config {String} desc 描述 239 * 240 * @param {Object} callbacks 相关回调方法 241 * @p-config {Boolean} async ready方法是否需要异步执行,默认false 242 * @p-config {Function} ready(argv, data) 就绪状态 243 * @p-config {Function} dataLoaded(data) 数据加载完成后调用,async为true时有用,也可以为空 244 * @p-config {Function} cancel(resp) 取消 245 * @p-config {Function} fail(resp) 失败 246 * @p-config {Function} confirm(resp) 成功 247 * @p-config {Function} all(resp) 无论成功失败都会执行的回调 248 */ 249 WeixinApi.shareToWeibo = function (data, callbacks) { 250 _share({ 251 menu: 'menu:share:weibo', 252 action: 'shareWeibo' 253 }, { 254 "content": data.desc, 255 "url": data.link 256 }, callbacks); 257 }; 258 259 /** 260 * 新的分享接口 261 * @param {Object} data 待分享的信息 262 * @p-config {String} appId 公众平台的appId(服务号可用) 263 * @p-config {String} imgUrl 图片地址 264 * @p-config {String} link 链接地址 265 * @p-config {String} desc 描述 266 * @p-config {String} title 分享的标题 267 * 268 * @param {Object} callbacks 相关回调方法 269 * @p-config {Boolean} async ready方法是否需要异步执行,默认false 270 * @p-config {Function} ready(argv, data) 就绪状态 271 * @p-config {Function} dataLoaded(data) 数据加载完成后调用,async为true时有用,也可以为空 272 * @p-config {Function} cancel(resp) 取消 273 * @p-config {Function} fail(resp) 失败 274 * @p-config {Function} confirm(resp) 成功 275 * @p-config {Function} all(resp) 无论成功失败都会执行的回调 276 */ 277 WeixinApi.generalShare = function (data, callbacks) { 278 _share({ 279 menu: 'general:share' 280 }, { 281 "appid": data.appId ? data.appId : '', 282 "img_url": data.imgUrl, 283 "link": data.link, 284 "desc": data.desc, 285 "title": data.title, 286 "img_width": "640", 287 "img_height": "640" 288 }, callbacks); 289 }; 290 291 /** 292 * 设置页面禁止分享:包括朋友圈、好友、腾讯微博、qq 293 * @param callback 294 */ 295 WeixinApi.disabledShare = function (callback) { 296 callback = callback || function () { 297 alert('当前页面禁止分享!'); 298 }; 299 ['menu:share:timeline', 'menu:share:appmessage', 'menu:share:qq', 300 'menu:share:weibo', 'general:share'].forEach(function (menu) { 301 WeixinJSBridge.on(menu, function () { 302 callback(); 303 return false; 304 }); 305 }); 306 }; 307 308 /** 309 * 加关注(此功能只是暂时先加上,不过因为权限限制问题,不能用,如果你的站点是部署在*.qq.com下,也许可行) 310 * @param {String} appWeixinId 微信公众号ID 311 * @param {Object} callbacks 回调方法 312 * @p-config {Function} fail(resp) 失败 313 * @p-config {Function} confirm(resp) 成功 314 */ 315 WeixinApi.addContact = function (appWeixinId, callbacks) { 316 callbacks = callbacks || {}; 317 WeixinJSBridge.invoke("addContact", { 318 webtype: "1", 319 username: appWeixinId 320 }, function (resp) { 321 var success = !resp.err_msg || "add_contact:ok" == resp.err_msg 322 || "add_contact:added" == resp.err_msg; 323 if (success) { 324 callbacks.success && callbacks.success(resp); 325 } else { 326 callbacks.fail && callbacks.fail(resp); 327 } 328 }) 329 }; 330 331 /** 332 * 调起微信Native的图片播放组件。 333 * 这里必须对参数进行强检测,如果参数不合法,直接会导致微信客户端crash 334 * 335 * @param {String} curSrc 当前播放的图片地址 336 * @param {Array} srcList 图片地址列表 337 */ 338 WeixinApi.imagePreview = function (curSrc, srcList) { 339 if (!curSrc || !srcList || srcList.length == 0) { 340 return; 341 } 342 WeixinJSBridge.invoke('imagePreview', { 343 'current': curSrc, 344 'urls': srcList 345 }); 346 }; 347 348 /** 349 * 显示网页右上角的按钮 350 */ 351 WeixinApi.showOptionMenu = function () { 352 WeixinJSBridge.call('showOptionMenu'); 353 }; 354 355 356 /** 357 * 隐藏网页右上角的按钮 358 */ 359 WeixinApi.hideOptionMenu = function () { 360 WeixinJSBridge.call('hideOptionMenu'); 361 }; 362 363 /** 364 * 显示底部工具栏 365 */ 366 WeixinApi.showToolbar = function () { 367 WeixinJSBridge.call('showToolbar'); 368 }; 369 370 /** 371 * 隐藏底部工具栏 372 */ 373 WeixinApi.hideToolbar = function () { 374 WeixinJSBridge.call('hideToolbar'); 375 }; 376 377 /** 378 * 返回如下几种类型: 379 * 380 * network_type:wifi wifi网络 381 * network_type:edge 非wifi,包含3G/2G 382 * network_type:fail 网络断开连接 383 * network_type:wwan 2g或者3g 384 * 385 * 使用方法: 386 * WeixinApi.getNetworkType(function(networkType){ 387 * 388 * }); 389 * 390 * @param callback 391 */ 392 WeixinApi.getNetworkType = function (callback) { 393 if (callback && typeof callback == 'function') { 394 WeixinJSBridge.invoke('getNetworkType', {}, function (e) { 395 // 在这里拿到e.err_msg,这里面就包含了所有的网络类型 396 callback(e.err_msg); 397 }); 398 } 399 }; 400 401 /** 402 * 关闭当前微信公众平台页面 403 * @param {Object} callbacks 回调方法 404 * @p-config {Function} fail(resp) 失败 405 * @p-config {Function} success(resp) 成功 406 */ 407 WeixinApi.closeWindow = function (callbacks) { 408 callbacks = callbacks || {}; 409 WeixinJSBridge.invoke("closeWindow", {}, function (resp) { 410 switch (resp.err_msg) { 411 // 关闭成功 412 case 'close_window:ok': 413 callbacks.success && callbacks.success(resp); 414 break; 415 416 // 关闭失败 417 default : 418 callbacks.fail && callbacks.fail(resp); 419 break; 420 } 421 }); 422 }; 423 424 /** 425 * 当页面加载完毕后执行,使用方法: 426 * WeixinApi.ready(function(Api){ 427 * // 从这里只用Api即是WeixinApi 428 * }); 429 * @param readyCallback 430 */ 431 WeixinApi.ready = function (readyCallback) { 432 433 /** 434 * 加一个钩子,同时解决Android和iOS下的分享问题 435 * @private 436 */ 437 var _hook = function () { 438 var _WeixinJSBridge = {}; 439 Object.keys(WeixinJSBridge).forEach(function (key) { 440 _WeixinJSBridge[key] = WeixinJSBridge[key]; 441 }); 442 Object.keys(WeixinJSBridge).forEach(function (key) { 443 if (typeof WeixinJSBridge[key] === 'function') { 444 WeixinJSBridge[key] = function () { 445 try { 446 var args = arguments.length > 0 ? arguments[0] : {}, 447 runOn3rd_apis = args.__params ? args.__params.__runOn3rd_apis || [] : []; 448 ['menu:share:timeline', 'menu:share:appmessage', 'menu:share:weibo', 449 'menu:share:qq', 'general:share'].forEach(function (menu) { 450 runOn3rd_apis.indexOf(menu) === -1 && runOn3rd_apis.push(menu); 451 }); 452 } catch (e) { 453 } 454 return _WeixinJSBridge[key].apply(WeixinJSBridge, arguments); 455 }; 456 } 457 }); 458 }; 459 460 if (readyCallback && typeof readyCallback == 'function') { 461 var Api = this; 462 var wxReadyFunc = function () { 463 _hook(); 464 readyCallback(Api); 465 }; 466 if (typeof window.WeixinJSBridge == "undefined") { 467 if (document.addEventListener) { 468 document.addEventListener('WeixinJSBridgeReady', wxReadyFunc, false); 469 } else if (document.attachEvent) { 470 document.attachEvent('WeixinJSBridgeReady', wxReadyFunc); 471 document.attachEvent('onWeixinJSBridgeReady', wxReadyFunc); 472 } 473 } else { 474 wxReadyFunc(); 475 } 476 } 477 }; 478 479 /** 480 * 判断当前网页是否在微信内置浏览器中打开 481 */ 482 WeixinApi.openInWeixin = function () { 483 return /MicroMessenger/i.test(navigator.userAgent); 484 }; 485 486 /* 487 * 打开扫描二维码 488 * @param {Object} callbacks 回调方法 489 * @p-config {Boolean} needResult 是否直接获取分享后的内容 490 * @p-config {String} desc 扫描二维码时的描述 491 * @p-config {Function} fail(resp) 失败 492 * @p-config {Function} success(resp) 成功 493 */ 494 WeixinApi.scanQRCode = function (callbacks) { 495 callbacks = callbacks || {}; 496 WeixinJSBridge.invoke("scanQRCode", { 497 needResult: callbacks.needResult ? 1 : 0, 498 desc: callbacks.desc || 'WeixinApi Desc' 499 }, function (resp) { 500 switch (resp.err_msg) { 501 // 打开扫描器成功 502 case 'scanQRCode:ok': 503 case 'scan_qrcode:ok': 504 callbacks.success && callbacks.success(resp); 505 break; 506 507 // 打开扫描器失败 508 default : 509 callbacks.fail && callbacks.fail(resp); 510 break; 511 } 512 }); 513 }; 514 515 /** 516 * 检测应用程序是否已安装 517 * by mingcheng 2014-10-17 518 * 519 * @param {Object} data 应用程序的信息 520 * @p-config {String} packageUrl 应用注册的自定义前缀,如 xxx:// 就取 xxx 521 * @p-config {String} packageName 应用的包名 522 * 523 * @param {Object} callbacks 相关回调方法 524 * @p-config {Function} fail(resp) 失败 525 * @p-config {Function} success(resp) 成功,如果有对应的版本信息,则写入到 resp.version 中 526 * @p-config {Function} all(resp) 无论成功失败都会执行的回调 527 */ 528 WeixinApi.getInstallState = function (data, callbacks) { 529 callbacks = callbacks || {}; 530 531 WeixinJSBridge.invoke("getInstallState", { 532 "packageUrl": data.packageUrl || "", 533 "packageName": data.packageName || "" 534 }, function (resp) { 535 var msg = resp.err_msg, match = msg.match(/state:yes_?(.*)$/); 536 if (match) { 537 resp.version = match[1] || ""; 538 callbacks.success && callbacks.success(resp); 539 } else { 540 callbacks.fail && callbacks.fail(resp); 541 } 542 543 callbacks.all && callbacks.all(resp); 544 }); 545 }; 546 547 /** 548 * 发送邮件 549 * @param {Object} data 邮件初始内容 550 * @p-config {String} subject 邮件标题 551 * @p-config {String} body 邮件正文 552 * 553 * @param {Object} callbacks 相关回调方法 554 * @p-config {Function} fail(resp) 失败 555 * @p-config {Function} success(resp) 成功 556 * @p-config {Function} all(resp) 无论成功失败都会执行的回调 557 */ 558 WeixinApi.sendEmail = function (data, callbacks) { 559 callbacks = callbacks || {}; 560 WeixinJSBridge.invoke("sendEmail", { 561 "title": data.subject, 562 "content": data.body 563 }, function (resp) { 564 if (resp.err_msg === 'send_email:sent') { 565 callbacks.success && callbacks.success(resp); 566 } else { 567 callbacks.fail && callbacks.fail(resp); 568 } 569 callbacks.all && callbacks.all(resp); 570 }) 571 }; 572 573 /** 574 * 开启Api的debug模式,比如出了个什么错误,能alert告诉你,而不是一直很苦逼的在想哪儿出问题了 575 * @param {Function} callback(error) 出错后的回调,默认是alert 576 */ 577 WeixinApi.enableDebugMode = function (callback) { 578 /** 579 * @param {String} errorMessage 错误信息 580 * @param {String} scriptURI 出错的文件 581 * @param {Long} lineNumber 出错代码的行号 582 * @param {Long} columnNumber 出错代码的列号 583 */ 584 window.onerror = function (errorMessage, scriptURI, lineNumber, columnNumber) { 585 586 // 有callback的情况下,将错误信息传递到options.callback中 587 if (typeof callback === 'function') { 588 callback({ 589 message: errorMessage, 590 script: scriptURI, 591 line: lineNumber, 592 column: columnNumber 593 }); 594 } else { 595 // 其他情况,都以alert方式直接提示错误信息 596 var msgs = []; 597 msgs.push("额,代码有错。。。"); 598 msgs.push(" 错误信息:", errorMessage); 599 msgs.push(" 出错文件:", scriptURI); 600 msgs.push(" 出错位置:", lineNumber + '行,' + columnNumber + '列'); 601 alert(msgs.join('')); 602 } 603 } 604 }; 605 606 })(window);