- 技术选型:关于小程序webview中内嵌H5的重要提示
虽然小程序提供了不少原生能力,但有时我们想在小程序中使用web生态的能力时或者对接第三方活动页面时,会使用webview组件来承载网页,但这里有一些小程序中关于webview的重要提示,在了解之后可以进一步决定某些业务场景下还是否应该使用H5内嵌小程序实现业务。 提示: 1.小程序中的webview中的H5 ,需要添加web-view可信域名,需要在对应的网页的部署根目录中添加相应校验文件,如果对接的是第三方无法添加校验文件的,则无法打开。 2.小程序中的webview如需使用微信相关能力,须配置jssdk,须提供微信公众号账号主体,并按文档进行正确的页面授权和配置(需后端配合)。 3.小程序中的webview有较强的缓存(社区相关问题较多),需要用户手动清除通用存储并删除小程序才可以,如需H5每次部署后,不再有缓存,可寻找后端修改Nginx配置为无缓存 4.小程序中的webview中无论使用原生input拍照或jssdk提供的拍照能力,当IOS内存不足总会出现H5重新加载的情况,当前无解决方案(社区相关问题较多)。 参考JS-SDK说明文档web-view | 微信开放文档微信h5页面,调用拍照后,页面刷新 | 微信开放社区web-view嵌入的h5页面缓存严重,如何清除? | 微信开放社区
2023-11-27 - 「干货」新建项目/代码片段为什么原生导航栏不见了?页面也无法滚动了?
近期我在社区看到有不少网友在提问“顶部标题设置后怎么没有效果?”、“导航栏怎么不见了?”、“页面怎么无法滚动了?”… 我看下了帖子的相关截图,自己也验证了一下,原来近期发布的新版开发者工具,新建项目/代码片段会默认全局开启 Skyline 渲染引擎 。 可以点开[代码]app.json[代码]文件看下里面是否有[代码]"renderer": "skyline"[代码](下图红色框住的)。 [图片] 如果有的话,那就是全局开启了 Skyline 渲染引擎 。 Q1:页面不显示原生导航栏? A1:因为 Skyline 渲染引擎 不支持原生导航栏,所以就没显示原生导航栏了。 Q2:页面无法滚动? A2:因为 Skyline 渲染引擎 不支持页面全局滚动,所以页面就无法滚动了。 如果想用回原生导航栏和页面全局滚动,可以把[代码]app.json[代码]里面的[代码]"renderer": "skyline",[代码]去掉保存,重新编译一下项目就可以了。 关于 Skyline 渲染引擎 ,大家可以到官方文档查看:https://developers.weixin.qq.com/miniprogram/dev/framework/runtime/skyline/introduction.html 。
2023-08-06 - 体验版和线上版本不一致?
之前线上的小程序突然 无法获取位置了 但是体验版就正常 请问是有什么规则更改了吗?
2023-06-16 - 微信小程序头像昵称实战篇
2022-08-25 api文档地址: https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/userProfile.html 目前的api变更后,得到的地址为 临时地址, 这个是文档没有说明的, 最佳实践,是需要把得到的地址上传到自己的服务器,然后用服务器返回的地址作为 真实头像的永久地址. 核心点说明: //获取到api返回的新地址路径 onChooseAvatar(e) { this.avatarUrl = e.detail.avatarUrl console.log('e.detail', e.detail) // this.updateUserInfo(); this.uploadFile(); }, /* 上传 头像 转 话格式*/ uploadFile(){ uni.uploadFile({ url: config.webUrl + '/upload/uploadImages',//后台接口 filePath: this.avatarUrl,// 上传图片 url name:'image', // formData: this.formData, header: { 'content-type': 'multipart/form-data', 'token': uni.getStorageSync('token') }, // header 值 success: res => { let obj = JSON.parse(res.data) console.log('obj', obj) if (obj.code == 1) { let imgUrl = obj.data.full_path; this.userImg = imgUrl; this.updateUserInfo(); } else { uni.showToast({ icon: 'none', title: '图片太大,请重新选择!' }); } }, fail: e => { this.$toast('上传失败') } }); }, 这里需要注意, wx.uploadFile 返回的是字符串类型,需要前端自己处理一下数据结构: [图片] 完整代码如下: import config from "@/common/config.js"; import {debounce} from '@/utils/debounce.js' export default { data() { return { defaultAvatarUrl: 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0', avatarUrl: '', nick_name: '', userImg: '', } }, onLoad() { let userInfo = uni.getStorageSync('userInfo') || {}; let { nick_name,img_url } = {...userInfo}; this.userImg = img_url; this.nick_name = nick_name; }, methods: { onChooseAvatar(e) { this.avatarUrl = e.detail.avatarUrl console.log('e.detail', e.detail) // this.updateUserInfo(); this.uploadFile(); }, inputWord: debounce(function(e){ this.nick_name = e.detail.value console.log('this.nick_name.length',this.nick_name.length) let str = this.nick_name.trim(); if(str.length==0){ this.$toast('请输入合法的昵称') return } if((/[^/a-zA-Z0-9\u4E00-\u9FA5]/g).test(str)){ this.$toast('请输入中英文和数字') return } this.updateUserInfo() }, 1500), /* 上传 头像 转 话格式*/ uploadFile(){ uni.uploadFile({ url: config.webUrl + '/upload/uploadImages',//后台接口 filePath: this.avatarUrl,// 上传图片 url name:'image', // formData: this.formData, header: { 'content-type': 'multipart/form-data', 'token': uni.getStorageSync('token') }, // header 值 success: res => { let obj = JSON.parse(res.data) console.log('obj', obj) if (obj.code == 1) { let imgUrl = obj.data.full_path; this.userImg = imgUrl; this.updateUserInfo(); } else { uni.showToast({ icon: 'none', title: '图片太大,请重新选择!' }); } }, fail: e => { this.$toast('上传失败') } }); }, updateUserInfo(){ let self = this; uni.showLoading({}); let params = { img_url: this.userImg, nick_name: this.nick_name.trim(), } self.$http.post('updateUserInfo', params).then(res => { uni.hideLoading() if (res.data.code == 1) { self.$toast('修改成功!') }else { self.$toast(res.data.msg) } }) }, } } 请一键三连,争取升个级,谢谢各位道友! 补充一下,如果api不生效注意切换一下版本库: 我本地用的2.26.1 [图片] 实际效果图: [图片] [图片]
2022-11-24 - 一眼告诉你什么是订阅消息了,看完就懂订阅消息。
消息通知有两种: 一、A的动作后,发消息给A自己,这种容易解决,不多说明; 二、A动作后,发消息给B(比如管理员、店家、楼主),如何保证B收到消息?这种是本方案要解决的问题。 一张图片一眼告诉你什么是订阅消息,产品经理的设计UI居然让人一眼就知道订阅消息是什么玩意。 [图片] 用户 B (管理员、商家、组长、楼主)在知道订阅数不足后,打开小程序来续订阅数,否则没法收到订阅消息。 [图片] 补充一: 关于勾选按钮,请注意话述是:“总是保持以上选择,不再询问”,而不是:“总是同意接收订阅消息”,不要幻想就成了永久性订阅消息; 相当于你打电话订外卖,对店家说“老样子”,店家只会马上送一次外卖,而不是会以后每天自动给你送外卖了。 勾选和不勾选的区别是什么呢? 区别仅仅是:不勾选时,必须点击订阅10次,弹窗10次;勾选后,仍然必须点击订阅10次,但是不弹窗。无论如何“订阅”这个点击n次的动作少不了。 补充二: 一旦勾选后,就不可逆了,没有任何办法恢复或取消勾选了,除非你小程序MP后台换一次消息模板号(删除模板,重新添加一次)。 补充三: 关于如何保存订阅数。 保存在数据库中,笔者用的是云开发,数据库表user结构如下: { _id:'openid1', nickName:'老张', msg:{ "tempId1":5, "tempId2":7, } } 补充四: 关于如何获取订阅数。两种方式: 一、wx.requestSubscribeMessage的回调success里获取; 二、消息推送机制获取;https://developers.weixin.qq.com/miniprogram/dev/framework/server-ability/message-push.html
2022-09-21 - 如何申请设备消息?
设备消息接入指引一、申请类目 1.1 申请工具-设备管理类目登录小程序管理后台mp.weixin.qq.com,左侧最下方“设备”-基本设置-服务类目,添加“工具-设备管理”类目(该类目无需资质。如已有该类目可忽略此流程) [图片] 二、设备接入 2.1 开通设备管理功能进入小程序案例台-功能-硬件设备,阅读设备使用条件和接入流程等,点击“开通”。管理员扫码确认后开通成功,进入设备管理页面。 [图片] 2.2 添加硬件设备 (需审核)进入硬件设备,点击添加设备,按照每个字段对应的说明填写信息,如实填写设备相关信息,否则会导致审核不通过。 [图片] 2.3 获取设备 model_id设备注册成功后,可获得平台分配的 model_id ,model_id 是调用小程序设备能力相关接口的重要凭证。获取 model_id 后,小程序可按照相关文档指引调用「设备消息」等硬件能力。 2.4 申请设备能力(需审核) model_id 获取之后,在设备管理入口,点击右侧“申请设备能力”-“消息能力”。[图片] [图片] (设备消息能力审核通过状态) 三、获取设备消息 「小程序设备消息」能力指的是,只要用户在小程序内订阅通知,开发者就可以将这些事件以订阅消息的形式发送给用户。消息在微信内的产品形态,目前以“服务通知”形式呈现。 申请设备消息必须先完成“二、设备接入”。关于小程序订阅消息的基础介绍可参考文档 3.1 获取模板登录小程序管理后台——功能——订阅消息——公共模板库——长期订阅,查看可选用的设备消息模板。 [图片] 基于设备状态的变化,对于一些需要对用户进行提醒的情况,专门为设备提供了长期订阅消息,会以「服务通知」的形式下发给用户。目前可能用到的场景包括但不限于: 对生命财产可能造成影响的告警:烟雾/水浸/温度/防盗传感器告警、门锁被撬、陌生人闯入等影响到设备正常使用的提前预警:低电量预警、故障提醒、耗材用尽提醒等需要人工介入做进一步操作的提醒:门铃呼叫、洗衣完成等 如现有模板无法满足,需申请新增模板,请加群沟通。「硬件服务」沟通群: [图片] 申请格式如下: 申请事项:申请新增设备模板消息 小程序名称: 小程序主体: APPID: 设备消息场景描述: 所属行业:如:智能家居 设备型号:如:空气净化器 模板框架:如: 标题:空气净化器耗材提醒 提醒时间:mmdd hhmm 所在位置:卧室 提示说明:空气净化器滤网已到达更换时间,请及时更换。 属于哪种情况: 对生命财产可能造成影响的告警影响到设备正常使用的提前预警需要人工介入做进一步操作的提醒其他,请补充具体内容 3.2 获取模板ID选择设备消息模板中需要的关键词,并提交。 注意:设备消息模版的关键词内容由平台生成,为枚举值,开发者不能够自定义内容。 [图片] 提交后,可在“我的模板”中找到对应模板的模板 ID ,每个模板以 template_id 标记。 [图片] 3.3 获取设备票据获取 snTicket 用于「发起订阅」步骤。详见服务端设备票据接口 hardwareDevice.getSnTicket 。 四、触发设备消息4.1 发起用户订阅发起订阅调用 wx.requestSubscribeDeviceMessage 接口会有以下授权弹窗出现,用户同意订阅消息后,才会有设备消息发送至用户的微信会话 呼叫、告警等消息,可引导用户在小程序内完成设备消息订阅,用户需勾选左侧消息,并点击右侧强提醒按钮后提交“允许”。 [图片] (以上为小程序门禁设备长期消息弹窗样式) 示例代码 wx.requestSubscribeDeviceMessage({ sn: 'xxxx', snTicket: 'xxxxx', modelId: 'xxxxx', tmplIds: ['xxxxx'], success(res) { console.log('[wx.requestSubscribeDeviceMessage success]: ', res) // { 'QCpBsp1TGJ1ML-UIwAIMkdXpPGzxSfwJqsKsvMVs3io': 'accept' } }, fail(res) { console.log('[wx.requestSubscribeDeviceMessage fail]: ', res) } }) 接入文档:https://developers.weixin.qq.com/miniprogram/dev/framework/device/device-access.html#_5-%E5%8F%91%E9%80%81%E8%AE%BE%E5%A4%87%E6%B6%88%E6%81%AF 4.2 发送设备消息开发者通过微信服务端接口向用户推送设备消息。详见服务端设备消息发送接口 hardwareDevice.send 注意:开发者需要在小程序的合理场景内,给用户提供长期消息的授权弹窗。如用户实现房屋认证后通过用户操作触发“门禁机呼叫提醒”的订阅弹窗。
2022-10-19 - 为什么 console.log 打印对象显示的和手动点击展开不一样
微信开发者工具的调试器使用的是 chrome devtools-frontend https://github.com/ChromeDevTools/devtools-frontend 在使用 console.log 打印一个 Object 时,直接显示的是对象在调用 console.log 时的快照。如果之后再将打印的 Object 展开,会再次读取当前对象的值进行显示var a = {a: 1}; console.log(a); a.a = 2;[图片] 所以建议 console.log(JSON.stringify(a)); 进行打印 var a = {a: 1}; console.log(JSON.stringify(a)); a.a = 2;
2022-05-17 - 公众平台/小程序服务端API的access_token的内部设计
一、背景 对于使用过公众平台的API功能的开发者来说,access_token绝对不会陌生,它就像一个打开家门的钥匙,只要拿着它,就能使用公众平台绝大部分的API功能。因此,对于开发者而言,access_token的使用方式就变得尤其的重要。在日常API接口的运营中,经常遇到各种的疑问:为什么我的access_token突然非法了?为什么刚刚拿到的access_token,用了10min就过期了?对于这些疑问,我们提供出access_token的设计方案,便于开发者对access_token使用方式上的理解。 对于access_token的获取,可以参考公众平台的官方文档:auth.getAccessToken、获取Access token 二、access_token的内部设计 2.1 access_token的时效性 众所周知,access_token是通过appid和appsecret来生成的。内部设计的步骤如下: (1)开发者通过https请求方式: GET https://API.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET,传入appid及apppsecret的参数 (2)公众平台后台会校验appid和哈希(appsecret)是否与存储匹配,若匹配,结合当前时间戳,生成新的access_token。 (3)生成新的access_token的同时,会对老的access_token的过期时间戳更新为当前时间戳。 (4)返回新的access_token给开发者。 这里以图示的方式说明一下,新旧token交替过程: [图片] 从上图需要注意的几点: (1)公众平台存储层只会存储新老两个access_token,意味着假设开发者重复调用3次接口,则会导致最早的access_token立刻失效。 (2)虽然请求新的access_token后,老的access_token过期时间会更新为当前时间,但也不会立刻失效,原理请参考 【2.2 access_token 的逐渐失效性】 (3)出于信息安全考虑,公众平台并不会明文存储appsecret,仅存储appid以及appsecret的哈希值。因此开发者要妥善保管appsecret。当appsecret疑似泄露时,需要及时登录mp.weixin.qq.com重置appsecret。 2.2 access_token 的逐渐失效性 从【access_token的时效性】了解到,当开发者请求获取新的access_token时,老的access_token过期时间会被更新为当前时间,但此时不会立刻失效,因为公众平台会提供【5分钟的新老access_token交替缓冲时间】,因此也称为access_token 的逐渐失效性。 实现的原理是: 1. 由于老的access_token过期时间戳已被刷新,所以在API接口请求期间,带上的access_token解开后,过期时间戳会加上5分钟,然后和当前设备时间进行比对,若超过当前设备时间,判断为失效。 2. 公众平台的设备会保持时钟同步,但设备之间仍然可能会存在1-2分钟的时间差异,所以【5分钟】并非绝对的时间值。当开发者获取到新的access_token后应该尽快切换到新的access_token。 [图片] 从上图需要注意的几点: (1)由于存在设备时间同步的差异,可能会导致开发者遇到拿着老的access_token请求API接口,部分请求成功,部分请求失败的情况,建议开发者获取到新的access_token后尽快使用。 (2)通过理解两个图示,对开发者来说,access_token是相当关键且不能乱调的接口,建议开发者统一管理access_token,以免造成多次请求导致access_token失效。
2021-05-11 - 小程序开发之 web-view 的进阶玩法
背景 半年前写过一个项目,在京东手机的小程序里内嵌老罗的锤子发布会的活动页。前几天老罗又发布了他的加湿器,而这份关于锤子项目的迟到的总结,经过这几天在全网搜索的相关问题来看,依然有必要写一下。 本文主要从 web-view 与 JSSDK 的实现来展开,顺带过一下 web-view 的基础,最后在文末发放一些实用的小糖豆。 ok,废话不多说,开始吧。 web-view 中可用的 JSSDK 接口 1. web-view 的本质 与 JSSDK 我们已知,web-view 是闭环的小程序对外开启的一扇窗户,是小程序承载网页链接的容器。web-view 就像小程序里内置的浏览器内核,可以运行网页,其实 web-view 本质即是微信的 WKWebview 的实现。 区别:小程序框架系统包括两部分,视图层和逻辑层,两者对应的技术实现分别是 webview 和 JSCore;web-view本质上就是一个浏览器,承载网页,包括视图和逻辑实现。 [图片] 微信Webview 不仅应用于小程序的 web-view,也应用于公众号等所有微信里可以打开网页的位置。 微信Webview 不仅集成了普通 webkit 引擎的基础功能,还注入了微信JSBridge(JS-SDK)相关的脚本,提供给开发者更多高效的能力,如:拍照、语音、位置基于手机系统的能力;扫一扫、微信分享、卡券、支付等微信个性化能力。 而基于微信Webview 的 web-view 组件,除开放承载页面的功能外,也被赋予了一些 JS-SDK 的使用能力,尽管有一定的限制,但整理来看也使 web-view 的能力变得强大了。 2. web-view 中可用的 JSSDK 接口 本文从两个维度介绍 JSSDK 的接口。只简单列举几个,更多可支持的接口还请查看web-view的文档。 [图片] 1) 可用的通用JSSDK接口 接口模块 接口说明 接口名称 判断客户端是否支持js checkJSApi 图像接口 拍照或上传 chooseImage 图像接口 预览图片 previewImage 图像接口 上传/下载图片 uploadImage/downloadImage 图像接口 获取本地图片 getLocalImgData 音频接口 开始/停止录音 startRecord/stopRecord 音频接口 播放/暂停/停止语音 playVoice/pauseVoice/stopVoice 地理位置 使用内置地图/获取地理位置 getLocation/openLocation 蓝牙接口 开启/关闭/监听 start/stopSearchBeacons/onSearchBeacons 扫码 微信扫一扫 scanQRCode 卡券 列表/添加/打开 chooseCard/addCard/openCard 长按 小程序圆形码 智能接口 识别音频 translateVoice 2)与小程序相关的接口 除通用的JSSDK接口外,web-view 还支持和小程序跳转相关的接口,比如:navigateTo、redirectTo、switchTab、navigateBack,类似这种。具体写法都是加上 wx.miniProgram. 这样。 此外,关于小程序和web-view 的通信需要请求:wx.miniProgram.postMessage 方法,小程序侧进行侦听即可,具体方法可参看公众号的另一篇文章。 3)这些接口的可用性? 能够运行这些接口的最前提的条件就是,需要在小程序的环境里进行。那如何判断是否在小程序的环境里? 有两种方法: wx.miniProgram.getEnv [代码]wx.miniProgram.getEnv(function(res) { console.log(res.miniprogram) // true }) [代码] window.__wxjs_environment [代码]console.log(window.__wxjs_environment === 'miniprogram') [代码] web-view 如何使用 JSSDK 接口? 了解 web-view 可用的API 后,我们知道,在嵌入的H5 页面里,可以从相机里选择图片,或使用扫一扫的功能,那具体我们该如何实现呢? [图片] 微信公众平台给出了详尽的 JS-SDK 的实现方法,我们这里将几个当年踩过的要点给出。 1)绑定域名。 首先需要登录相关联的公众号,嗯!你没有看错,确实是公众号,(有很多人吐槽此事,但这确实是目前的事实。)登录后进入“公众号设置”-“功能设置”,填写“JS接口安全域名”。 [图片] 这一行为,建立了网页域名和 appId 之间的绑定关系,即,该appId 下可以打开这几个域名白名单里的网页。 同web-view 的业务域名配置一样,也需要将生成的校验文件拷贝到域名指向的 web 服务器的目录下。 2)签名的实现。 [图片] 签名是进入下一步的必要条件,这部分交由后端实现,了解它会提升你们的联调能力。 第一步,获取微信网页授权,拿到access_token值。 接口: https请求方式: [代码]GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET [代码] 参数 是否必须 说明 grant_type 是 获取access_token填写client_credential appid 是 第三方用户唯一凭证 secret 是 第三方用户唯一凭证密钥,即appsecret 公众号和小程序均可以使用 AppID 和 AppSecret 调用本接口来获取access_token。 AppID和AppSecret可在“微信公众平台-开发-基本配置”页中获得(需要已经成为开发者,且帐号没有异常状态)。 需要设置IP白名单:登录“微信公众平台-开发-基本配置”提前将服务器IP地址添加到IP白名单中,点击查看设置方法,否则将无法调用成功。小程序无需配置IP白名单。 入口: [图片] 具体设置: [图片] 第二步,获得jsapi_ticket。 用第一步拿到的access_token 采用http GET方式请求获得jsapi_ticket(有效期7200秒,开发者必须在自己的服务全局缓存jsapi_ticket): [代码]https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi [代码] [代码]{ "errcode":0, "errmsg":"ok", "ticket":"bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA", "expires_in":7200 } [代码] 第三步,生成签名。 初始字段:noncestr(随机字符串), 有效的jsapi_ticket, timestamp(时间戳), url(当前网页的URL,不包含#及其后面部分)。 [代码]noncestr=Wm3WZYTPz0wzccnW jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg timestamp=1414587457 url=http://mp.weixin.qq.com?params=value [代码] 中间过程:对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串。 [代码]jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg&noncestr=Wm3WZYTPz0wzccnW×tamp=1414587457&url=http://mp.weixin.qq.com?params=value [代码] 生成签名:对生成的字符串作sha1加密,字段名和字段值都采用原始值,不进行URL 转义。 [代码]0f9de62fce790f9a083d5c99e95740ceb90c27ed [代码] 3)当前页面注入权限验证配置。 所有需要使用 JSSDK 的页面必须首先注入配置信息,否则无法调用 JSSDK 的 API。在2)中我们有生成的签名和生成签名依赖的时间戳和随机串,这些都是我们进行配置的必要入参。具体如下: [代码]wx.config({ debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 appId: '', // 必填,公众号的唯一标识 timestamp: , // 必填,生成签名的时间戳 nonceStr: '', // 必填,生成签名的随机串 signature: '',// 必填,签名 jsApiList: [] // 必填,需要使用的JS接口列表 }); [代码] 其中 apilist 是本页面里支持使用的 JSSDK 的 API,如果不声明,是不可以被调用使用的。 在下一步4)中会有一个实例。 4)万事俱备之后 config 接口是异步请求的,所以增加 ready 的接口来判定config 是否已经具备;同时,也提供了error 接口来实现 config 失败后的处理。 哈哈,以上基本就是 JSSDK 在小程序 web-view 里的打通实现,因为中间牵扯前后端的实现,看起来篇幅长且凌乱,不知道你还能不能看到这里呢? 如果你是前端童鞋,先跟你报个喜,绑定域名和实现签名这些都不是你需要做的事情,把这些丢给你的后端童鞋,让他们来实现吧。但我还是提个醒,这些我们有必要知道,因为你在打通 web-view 和 JSSDK 时,可能无从下手。 5)一个例子帮你搞定 以下我们用一个例子,把前端方案的实现串起来,给大家一个直观的展示。 引入微信 JSSDK 库: [图片] 请求后端生成签名的接口,获取相关config依赖的字段: [图片] 在ready后,使用 wx.chooseImage() 选择本地相册的图片。 [图片] ok,到此为止,我们基本把 JSSDK 在 web-view 里的应用实现介绍完了。 总的来说,这是一个看起来比较简单,但实现起来可能有很多不可知问题的事情,我们当年苦于不了解需要在公众号下申请各种资源,不知道使用哪个 JSSDK 走了许多弯路,后来还踩过设置IP白名单的问题。这些都还是比较好解决的,本文也基本把这些很详尽的做了描述。在签名的实现中,还会有很多问题,还好我们当时也根据文档附录中的指点一一解除。 长征路漫漫,还好有分享~ 糖豆:web-view 在实际场景中的应用问题 [图片] 在这些和 web-view 相伴相生的日子里,也逐渐总结了一些问题,大致分为了几类,列举如下: 1. 开放能力的问题: 是否开放支付,是否支持直播,是否支持h5里添加分享按钮? 答案否! 插件禁用 webview 组件 个人账号可以测试,无法上线 小程序无法读取 web-view 的 cookie。 小程序不可以触发模板消息 类似以上问题,都可以归结为小程序的 web-view 对 JSSDK 的开放能力,须严格按微信的JSSDK提供的能力检查是否可行。 能否嵌套了其他的第三方网站的页面?答案:一切h5里引入的链接都需要加业务域名。 喜报:支持打开相关联公众号的文章!! web-view 对外部网页的开放能力都是基于业务域名的白名单设置! 感谢开放关联公众号的打开权限,运营小伙伴可以玩起来。 2. 白屏问题? 紧接上文,强化一下业务域名配置问题。很多业务域名配置失败的直接症状就是白屏,然后给一句话,引用非业务域名。问题直接明了!那如何解决? 1)优先查看业务配置是否ok,是否是https等。2)不能解决问题继续查看其他证书,如TSL版本。具体可查阅小程序HTTPS证书的约定。 3. 跳转问题。 首页是web-view 时没有回退按钮?层级过深时,回退过多? 这个是经常被问到的问题,首页web-view 没有回退,官方没有给出很明确的说法;而非首页的 web-view 一旦路由跳转建立起来,web-view 里的路由就默认加入了路由栈中。有什么好办法解决? 这个已跟小程序没关系了,唤醒你在浏览器里的处理吧。 4. 缓存、网页不刷新问题? 这个也可以归结为浏览器里如何处理缓存的问题,类似浏览器网页中的缓存,可以通过时间戳。另外看看是不是你的CDN 缓存了内容? 5. 分享问题? 分享接口拿到的 webViewUrl 只是第一个 url,如果你的页面已经进入到它的子页面中,再次分享时,这时候你分享得到的 url 没有变化。这时候 你需要在子页面中使用 postMessage,捕获到当前页面的路径,传递给小程序。当用户触发分享时,只要读取消息队列最近的一条数据即可。 记得拼接参数时需要encodeURIComponent。 6 web-view 改宽高? 不可以。默认铺满整屏。web-view 和 其他UI组件的关系是互斥的,并且小程序会优先选择展示 web-view。如果你想放多个web-view,很抱歉,它默认只展示第一个web-view 的内容,还会在控制台给个大大的报错。 7 web-view 提示“未绑定网页开发者”? 没有开发该网页的权限!需要在公众号的开发者工具里绑定开发者微信号。 8 web-view 的校验文件是什么格式? 官方回答:是一个含有普通字符串的txt文件,只是一个随机的字符串,与appid无关。放心使用~ 好啦~以上是对 web-view 常见问题的总结,总结不敢保证全对,另外一定是会过时的,所以,这只是你遇到问题的一个思考方向,具体方案还要请参看小程序官方文档。 如果你有更好的方案,欢迎回复我们,超级无敌感谢! 最后,感谢你能够读到这里,也愿你不枉此行。 扩展阅读 [1] 微信JS-SDK说明文档 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115 [2] 微信小程序开放能力-web-view https://developers.weixin.qq.com/miniprogram/dev/component/web-view.html 文章原载于公众号:全栈探索。
2019-02-21 - 头像会变成暂时无法查看
用户头像偶尔会出现“暂时无法查看”,然后多获取几次又正常了,有时获取到的就是“暂时无法查看”,请问有什么办法解决???
2019-03-06 - 业务域名设置--校验文件检查失败自查指引
目前不少开发者在设置业务域名时,发现检查校验文件失败,可先按照如下步骤进行自查: 如果想保存的业务为https://test.com/,下载下来的校验文件为AbC.txt,则需要确保https://test.com/AbC.txt能够访问。 校验文件内容错误。校验文件内容一般是非HTML数据,如果下载下来的校验文件内容为HTML数据,一般为登录态过期。请重新登录小程序下载校验文件。 使用4G网络尝试访问链接,确认自身服务器没有拦截请求(常见于设置了白名单或者防火墙的服务器,需开发者自行确认下) https证书过期。请确保https证书处于有效期内。 使用curl 测试链接,确保curl能够正常访问链接,且curl出来的内容为校验文件内容。 使用time curl https://test.com/abc.txt查看链接时间,建议耗时在1s之内。 请确保url中的文件名与下载下来的文件名大小写一致。如下载的文件是AbC.txt,确保url是https://test.com/AbC.txt,不能是https://test.com/abc.txt 部分用户的服务器配置较陈旧,安全性差(如配置 768位 的 DH),为了保证通信安全,微信后台不支持,请更新服务器配置。 (1)通过https://cloud.tencent.com/product/tools#userDefined12,检测网址是否支持TLS1.2。 (2)可通过工具 https://www.ssllabs.com/ssltest/analyze.html 检查自己的服务器,对该工具标红的各项漏洞逐项修补,建议更新配置直到该工具打分为 C及以上 。 9. 如上述检查都没有问题,请重新下载校验文件重试,确保上传到服务器的文件内容与新下载的文件内容一致。
2018-06-21 - 电脑端,常弹出重新登录窗口?
电脑端弹出登录窗口实际已经是登录状态
2021-07-24 - 一个通用request的封装
小程序内置了[代码]wx.request[代码],用于向后端发送请求,我们先来看看它的文档: wx.request(OBJECT) 发起网络请求。使用前请先阅读说明。 OBJECT参数说明: 参数名 类型 必填 默认值 说明 最低版本 url String 是 - 开发者服务器接口地址 - data Object/String/ArrayBuffer 否 - 请求的参数 - header Object 否 - 设置请求的 header,header 中不能设置 Referer。 - method String 否 GET (需大写)有效值:OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT - dataType String 否 json 如果设为json,会尝试对返回的数据做一次 JSON.parse - responseType String 否 text 设置响应的数据类型。合法值:text、arraybuffer 1.7.0 success Function 否 - 收到开发者服务成功返回的回调函数 - fail Function 否 - 接口调用失败的回调函数 - complete Function 否 - 接口调用结束的回调函数(调用成功、失败都会执行) - success返回参数说明: 参数 类型 说明 最低版本 data Object/String/ArrayBuffer 开发者服务器返回的数据 - statusCode Number 开发者服务器返回的 HTTP 状态码 - header Object 开发者服务器返回的 HTTP Response Header 1.2.0 这里我们主要看两点: 回调函数:success、fail、complete; success的返回参数:data、statusCode、header。 相对于通过入参传回调函数的方式,我更喜欢promise的链式,这是我期望的第一个点;success的返回参数,在实际开发过程中,我只关心data部分,这里可以做一下处理,这是第二点。 promisify 小程序默认支持promise,所以这一点改造还是很简单的: [代码]/** * promise请求 * 参数:参考wx.request * 返回值:[promise]res */ function requestP(options = {}) { const { success, fail, } = options; return new Promise((res, rej) => { wx.request(Object.assign( {}, options, { success: res, fail: rej, }, )); }); } [代码] 这样一来我们就可以使用这个函数来代替wx.request,并且愉快地使用promise链式: [代码]requestP({ url: '/api', data: { name: 'Jack' } }) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); }); [代码] 注意,小程序的promise并没有实现finally,Promise.prototype.finally是undefined,所以complete不能用finally代替。 精简返回值 精简返回值也是很简单的事情,第一直觉是,当请求返回并丢给我一大堆数据时,我直接resolve我要的那一部分数据就好了嘛: [代码]return new Promise((res, rej) => { wx.request(Object.assign( {}, options, { success(r) { res(r.data); // 这里只取data }, fail: rej, }, )); }); [代码] but!这里需要注意,我们仅仅取data部分,这时候默认所有success都是成功的,其实不然,wx.request是一个基础的api,fail只发生在系统和网络层面的失败情况,比如网络丢包、域名解析失败等等,而类似404、500之类的接口状态,依旧是调用success,并体现在[代码]statusCode[代码]上。 从业务上讲,我只想处理json的内容,并对json当中的相关状态进行处理;如果一个接口返回的不是约定好的json,而是类似404、500之类的接口异常,我统一当成接口/网络错误来处理,就像jquery的ajax那样。 也就是说,如果我不对[代码]statusCode[代码]进行区分,那么包括404、500在内的所有请求结果都会走[代码]requestP().then[代码],而不是[代码]requestP().catch[代码]。这显然不是我们熟悉的使用方式。 于是我从jquery的ajax那里抄来了一段代码。。。 [代码]/** * 判断请求状态是否成功 * 参数:http状态码 * 返回值:[Boolen] */ function isHttpSuccess(status) { return status >= 200 && status < 300 || status === 304; } [代码] [代码]isHttpSuccess[代码]用来决定一个http状态码是否判为成功,于是结合[代码]requestP[代码],我们可以这么来用: [代码]return new Promise((res, rej) => { wx.request(Object.assign( {}, options, { success(r) { const isSuccess = isHttpSuccess(r.statusCode); if (isSuccess) { // 成功的请求状态 res(r.data); } else { rej({ msg: `网络错误:${r.statusCode}`, detail: r }); } }, fail: rej, }, )); }); [代码] 这样我们就可以直接resolve返回结果中的data,而对于非成功的http状态码,我们则直接reject一个自定义的error对象,这样就是我们所熟悉的ajax用法了。 登录 我们经常需要识别发起请求的当前用户,在web中这通常是通过请求中携带的cookie实现的,而且对于前端开发者是无感知的;小程序中没有cookie,所以需要主动地去补充相关信息。 首先要做的是:登录。 通过[代码]wx.login[代码]接口我们可以得到一个[代码]code[代码],调用后端登录接口将code传给后端,后端再用code去调用微信的登录接口,换取[代码]sessionKey[代码],最后生成一个[代码]sessionId[代码]返回给前端,这就完成了登录。 [图片] 具体参考微信官方文档:wx.login [代码]const apiUrl = 'https://jack-lo.github.io'; let sessionId = ''; /** * 登录 * 参数:undefined * 返回值:[promise]res */ function login() { return new Promise((res, rej) => { // 微信登录 wx.login({ success(r1) { if (r1.code) { // 获取sessionId requestP({ url: `${apiUrl}/api/login`, data: { code: r1.code, }, method: 'POST' }) .then((r2) => { if (r2.rcode === 0) { const { sessionId } = r2.data; // 保存sessionId sessionId = sessionId; res(r2); } else { rej({ msg: '获取sessionId失败', detail: r2 }); } }) .catch((err) => { rej(err); }); } else { rej({ msg: '获取code失败', detail: r1 }); } }, fail: rej, }); }); } [代码] 好的,我们做好了登录并且顺利获取到了sessionId,接下来是考虑怎么把sessionId通过请求带上去。 sessionId 为了将状态与数据区分开来,我们决定不通过data,而是通过header的方式来携带sessionId,我们对原本的requestP稍稍进行修改,使得它每次调用都自动在header里携带sessionId: [代码]function requestP(options = {}) { const { success, fail, } = options; // 统一注入约定的header let header = Object.assign({ sessionId: sessionId }, options.header); return new Promise((res, rej) => { ... }); } [代码] 好的,现在请求会自动带上sessionId了; 但是,革命尚未完成: 我们什么时候去登录呢?或者说,我们什么时候去获取sessionId? 假如还没登录就发起请求了怎么办呢? 登录过期了怎么办呢? 我设想有这样一个逻辑: 当我发起一个请求的时候,如果这个请求不需要sessionId,则直接发出; 如果这个请求需要携带sessionId,就去检查现在是否有sessionId,有的话直接携带,发起请求; 如果没有,自动去走登录的流程,登录成功,拿到sessionId,再去发送这个请求; 如果有,但是最后请求返回结果是sessionId过期了,那么程序自动走登录的流程,然后再发起一遍。 其实上面的那么多逻辑,中心思想只有一个:都是为了拿到sessionId! 我们需要对请求做一层更高级的封装。 首先我们需要一个函数专门去获取sessionId,它将解决上面提到的2、3点: [代码]/** * 获取sessionId * 参数:undefined * 返回值:[promise]sessionId */ function getSessionId() { return new Promise((res, rej) => { // 本地sessionId缺失,重新登录 if (!sessionId) { login() .then((r1) => { res(r1.data.sessionId); }) .catch(rej); } } else { res(sessionId); } }); } [代码] 好的,接下来我们解决第1、4点,我们先假定:sessionId过期的时候,接口会返回[代码]code=401[代码]。 整合了getSessionId,得到一个更高级的request方法: [代码]/** * ajax高级封装 * 参数:[Object]option = {},参考wx.request; * [Boolen]keepLogin = false * 返回值:[promise]res */ function request(options = {}, keepLogin = true) { if (keepLogin) { return new Promise((res, rej) => { getSessionId() .then((r1) => { // 获取sessionId成功之后,发起请求 requestP(options) .then((r2) => { if (r2.rcode === 401) { // 登录状态无效,则重新走一遍登录流程 // 销毁本地已失效的sessionId sessionId = ''; getSessionId() .then((r3) => { requestP(options) .then(res) .catch(rej); }); } else { res(r2); } }) .catch(rej); }) .catch(rej); }); } else { // 不需要sessionId,直接发起请求 return requestP(options); } } [代码] 留意req的第二参数keepLogin,是为了适配有些接口不需要sessionId,但因为我的业务里大部分接口都需要登录状态,所以我默认值为true。 这差不多就是我们封装request的最终形态了。 并发处理 这里其实我们还需要考虑一个问题,那就是并发。 试想一下,当我们的小程序刚打开的时候,假设页面会同时发出5个请求,而此时没有sessionId,那么,这5个请求按照上面的逻辑,都会先去调用login去登录,于是乎,我们就会发现,登录接口被同步调用了5次!并且后面的调用将导致前面的登录返回的sessionId过期~ 这bug是很严重的,理论上来说,登录我们只需要调用一次,然后一直到过期为止,我们都不需要再去登录一遍了。 ——那么也就是说,同一时间里的所有接口其实只需要登录一次就可以了。 ——也就是说,当有登录的请求发出的时候,其他那些也需要登录状态的接口,不需要再去走登录的流程,而是等待这次登录回来即可,他们共享一次登录操作就可以了! 解决这个问题,我们需要用到队列。 我们修改一下getSessionId这里的逻辑: [代码]const loginQueue = []; let isLoginning = false; /** * 获取sessionId * 参数:undefined * 返回值:[promise]sessionId */ function getSessionId() { return new Promise((res, rej) => { // 本地sessionId缺失,重新登录 if (!sessionId) { loginQueue.push({ res, rej }); if (!isLoginning) { isLoginning = true; login() .then((r1) => { isLoginning = false; while (loginQueue.length) { loginQueue.shift().res(r1); } }) .catch((err) => { isLoginning = false; while (loginQueue.length) { loginQueue.shift().rej(err); } }); } } else { res(sessionId); } }); } [代码] 使用了isLoginning这个变量来充当锁的角色,锁的目的就是当登录正在进行中的时候,告诉程序“我已经在登录了,你先把回调都加队列里去吧”,当登录结束之后,回来将锁解开,把回调全部执行并清空队列。 这样我们就解决了问题,同时提高了性能。 封装 在做完以上工作以后,我们都很清楚的封装结果就是[代码]request[代码],所以我们把request暴露出去就好了: [代码]function request() { ... } module.exports = request; [代码] 这般如此之后,我们使用起来就可以这样子: [代码]const request = require('request.js'); Page({ ready() { // 获取热门列表 request({ url: 'https://jack-lo.github.io/api/hotList', data: { page: 1 } }) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); }); // 获取最新信息 request({ url: 'https://jack-lo.github.io/api/latestList', data: { page: 1 } }) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); }); }, }); [代码] 是不是很方便,可以用promise的方式,又不必关心登录的问题。 然而可达鸭眉头一皱,发现事情并不简单,一个接口有可能在多个地方被多次调用,每次我们都去手写这么一串[代码]url[代码]参数,并不那么方便,有时候还不好找,并且容易出错。 如果能有个地方专门记录这些url就好了;如果每次调用接口,都能像调用一个函数那么简单就好了。 基于这个想法,我们还可以再做一层封装,我们可以把所有的后端接口,都封装成一个方法,调用接口就相对应调用这个方法: [代码]const apiUrl = 'https://jack-lo.github.io'; const req = { // 获取热门列表 getHotList(data) { const url = `${apiUrl}/api/hotList` return request({ url, data }); }, // 获取最新列表 getLatestList(data) { const url = `${apiUrl}/api/latestList` return request({ url, data }); } } module.exports = req; // 注意这里暴露的已经不是request,而是req [代码] 那么我们的调用方式就变成了: [代码]const req = require('request.js'); Page({ ready() { // 获取热门列表 req.getHotList({ page: 1 }) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); }); // 获取最新信息 req.getLatestList({ page: 1 }) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); }); } }); [代码] 这样一来就方便了很多,而且有一个很大的好处,那就是当某个接口的地址需要统一修改的时候,我们只需要对[代码]request.js[代码]进行修改,其他调用的地方都不需要动了。 错误信息的提炼 最后的最后,我们再补充一个可轻可重的点,那就是错误信息的提炼。 当我们在封装这么一个[代码]req[代码]对象的时候,我们的promise曾经reject过很多的错误信息,这些错误信息有可能来自: [代码]wx.request[代码]的fail; 不符合[代码]isHttpSuccess[代码]的网络错误; getSessionId失败; … 等等的一切可能。 这就导致了我们在提炼错误信息的时候陷入困境,到底catch到的会是哪种[代码]error[代码]对象? 这么看你可能不觉得有问题,我们来看看下面的例子: [代码]req.getHotList({ page: 1 }) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); }); [代码] 假如上面的例子中,我想要的不仅仅是[代码]console.log(err)[代码],而是想将对应的错误信息弹窗出来,我应该怎么做? 我们只能将所有可能出现的错误都检查一遍: [代码]req.getHotList({ page: 1 }) .then((res) => { if (res.code !== 0) { // 后端接口报错格式 wx.showModal({ content: res.msg }); } }) .catch((err) => { let msg = '未知错误'; // 文本信息直接使用 if (typeof err === 'string') { msg = err; } // 小程序接口报错 if (err.errMsg) { msg = err.errMsg; } // 自定义接口的报错,比如网络错误 if (err.detail && err.detail.errMsg) { msg = err.detail.errMsg; } // 未知错误 wx.showModal({ content: msg }); }); [代码] 这就有点尴尬了,提炼错误信息的代码量都比业务还多几倍,而且还是每个接口调用都要写一遍~ 为了解决这个问题,我们需要封装一个方法来专门做提炼的工作: [代码]/** * 提炼错误信息 * 参数:err * 返回值:[string]errMsg */ function errPicker(err) { if (typeof err === 'string') { return err; } return err.msg || err.errMsg || (err.detail && err.detail.errMsg) || '未知错误'; } [代码] 那么过程会变成: [代码]req.getHotList({ page: 1 }) .then((res) => { console.log(res); }) .catch((err) => { const msg = req.errPicker(err); // 未知错误 wx.showModal({ content: msg }); }); [代码] 好吧,我们再偷懒一下,把wx.showModal也省去了: [代码]/** * 错误弹窗 */ function showErr(err) { const msg = errPicker(err); console.log(err); wx.showModal({ showCancel: false, content: msg }); } [代码] 最后就变成了: [代码]req.getHotList({ page: 1 }) .then((res) => { console.log(res); }) .catch(req.showErr); [代码] 至此,一个简单的wx.request封装过程便完成了,封装过的[代码]req[代码]比起原来,使用上更加方便,扩展性和可维护性也更好。 结尾 以上内容其实是简化版的[代码]mp-req[代码],介绍了[代码]mp-req[代码]这一工具的实现初衷以及思路,使用[代码]mp-req[代码]来管理接口会更加的便捷,同时[代码]mp-req[代码]也提供了更加丰富的功能,比如插件机制、接口的缓存,以及接口分类等,欢迎大家关注mp-req了解更多内容。 以上最终代码可以在这里获取:req.js。
2020-08-04 - 企业视频号如何开通?
请问微信视频号是否只能通过个人微信开通,企业微信能否开通微信视频号? 如何进行企业微信视频号认证? 认证后能否修改视频号名称? 员工离职之后视频号如何转交给其他人?
2021-01-15 - 当前系统代理不是安全代理是否信任,登录不显示二维码,登录网络连接失败
问题:1、从打开开发者工具就一直弹出当前系统代理不是安全代理是否信任,随意点确定或取消或点右上角 X 关闭都可以正常打开工具使用,在使用过程中仍继续不间断弹框当前系统代理不是安全代理是否信任 尝试1:在电脑左下角开始 ——>运行,输入regedit或按快捷键win+R然后输入regedit,打开注册表编辑器 HKEY_CURRENT_USER/Software/Microsoft/Windows/CurrentVersion/Internet Settings,把里面所有的Proxy开头的项都删除掉,删除成功后,注销或者重启电脑,关闭杀毒软件,打开开发者工具,无果,问题依旧 尝试2:关闭Windows防火墙,问题依旧 尝试3:设置Windows防火墙,把微信开发者工具添加到允许程序通过Windows防火墙列表,问题依旧 尝试4:点开发者工具右上角设置,代理设置,选中第一项:不使用任何代理,勾选后直连网络,问题依旧 [图片] 尝试5:使用emedit工具,点击搜索,在文件中查找,在微信开发者工具的安装目录,搜索"当前系统代理",并没有搜到一个js文件,无法把 !confirm(`当前系统代理不是安全代理,是否信任?`) 修改为false,问题依旧 尝试6:我Win7,64位系统,卸载改变目录重装64位开发者工具wechat_devtools_1.02.1811150_x64,下载安装32位开发者工具wechat_devtools_1.02.1811150_ia32,下载安装上一个版本wechat_devtools_1.02.1810250_x64,均无果,问题依旧,该试的基本都试过了,只差重装操作系统了 [图片] 此问题无法解决,只能忍痛弹框继续使用,使用了几天后,点了一次浏览,没生成二维码,然后点了一次切换账号,新的问题就又出现了(问题2) 问题:2、登录不显示二维码,第一次有二维码,用微信扫描后无反应无法登录,再次打开开发者工具,从此不显示二维码 [图片] 问题:3、从问题2开始,从此打开开发者工具,提示登录网络连接失败,无法登录,网络正常,可以打开浏览器上网,也检查了浏览器代理设置,并没有使用代理 [图片] 以上三个问题花了两天时间各种相关问题查看并尝试,都无效果,只差重装操作系统了 - 预期表现 - 复现路径 - 提供一个最简复现 Demo
2018-11-25 - 微信开发者工具 黑屏的解决方案
最近遇到操蛋的开发者工具下载下来就黑屏。不知道官方怎么想的。有解决方案不嵌入到程序里,让程序员一个个去问,去下载。浪费时间。 下面具体描述,下载了N次,换了N个版本,都是有问题,怎么卸载都不管用,都是出现如下问题,黑屏 问题https://developers.weixin.qq.com/community/develop/doc/000644cdf28218d86a5a4f32c50800 问题https://developers.weixin.qq.com/community/develop/doc/0008c2df4c81c0d6212aec22156800?_at=1589244823904 问题https://developers.weixin.qq.com/community/develop/doc/00026c31d60808724e4ad96fd5d000?_at=1589244837008 以上不止这几个人遇到同样问题,黑屏!!!!!!!!!!!!!! [图片] 其实就是少了一个文件 解决方案: 将这个文件设置为下面贴的代码 C:/User /Administrator/AppData/Local/微信开发者工具/User Data/localstorage_b72da75d79277d2f5f9c30c9177be57e.json { "show": false, "currentCategory": "general", "compiler": { "clusterCompile": false, "autoPreview": false, "autoRemoteDebug": false }, "general": { "openLastModifiedProject": true, "autoPreviewType": "mobile", "autoRemoteDebugType": "mobile", "maxLogLength": 300, "enableNewFW": true, "enableGPU": false, "ignoreUnsafeProxy": false, "locale": "zh", "defaultWorkspace": "/Users/kunlideng/WeChatProjects" }, "appearance": { "theme": "dark", "devtoolsTheme": "dark", "fontFamily": "SF Mono", "fontSize": 12, "lineHeight": 20, "simulatorAlignment": "left" }, "edit": { "tabSize": 2, "insertSpaces": true, "wrap": "on", "minimap": false, "gitIgnoreWindowsReturn": true, "autoTypingsDetectEnabled": true, "alwaysOpenFileInNewTab": false, "autoSave": false, "autoRefresh": false, "saveBeforeCompile": false, "saveBeforePreview": false, "saveBeforeUpload": false }, "proxy": { "proxyType": "SYSTEM", "proxyHost": "127.0.0.1", "proxyPort": "12639" }, "notification": { "bbs": true, "sys": true, "alarm": true }, "security": { "enableServicePort": true, "port": 19195 }, "geo": { "enabled": false, "latitude": 39.92, "longitude": 116.46, "speed": -1, "accuracy": 65, "altitude": 0, "verticalAccuracy": 65, "horizontalAccuracy": 65 }, "shortcuts": { "_editingShortcuts": false, "toggleToolbar": { "modifiers": ["cmd", "shift"], "key": "T" }, "toggleSimulatorWindow": { "modifiers": ["cmd", "alt"], "key": "S" }, "toggleEditorWindow": { "modifiers": ["cmd", "shift"], "key": "E" }, "toggleFileTree": { "modifiers": ["cmd", "shift"], "key": "M" }, "toggleDebugWindow": { "key": "I", "modifiers": ["cmd", "shift"] }, "rebuild": { "key": "B", "modifiers": ["cmd"] }, "format": { "key": "F", "modifiers": ["shift", "alt"] }, "refresh": { "key": "R", "modifiers": ["cmd"] }, "toggleForegroundBackgroundStatus": { "key": "", "modifiers": [] }, "documentationSearch": { "key": "", "modifiers": [] }, "gotoFile": { "key": "P", "modifiers": ["cmd"] }, "gotoRecentFile": { "key": "E", "modifiers": ["cmd"] }, "preview": { "key": "P", "modifiers": ["shift", "cmd"] }, "upload": { "key": "U", "modifiers": ["shift", "cmd"] } }, "syncTime": 1584263702017 } 有的人说没有这个文件 localstorage_b72da75d79277d2f5f9c30c9177be57e.json 那就新建一个txt文件。改名字为这个。降代码复制进去保存即可。 也就是,将localstorage_b72da75d79277d2f5f9c30c9177be57e.json 这个文件放到AppData路径下即可 但是,如果是最新版本的,目录结构可能会是这样 [图片] 所以只能进入 C:\Users\Administrator\AppData\Local\微信开发者工具\User Data\1a695ca2de1a85735f93a43fb366c83f\WeappLocalData 这个路径。 然后添加一个json文件 localstorage_b72da75d79277d2f5f9c30c9177be57e.json 文件的下载地址 https://share.weiyun.com/5cgH1EO
2020-05-12