- 服务号今年付费300认证通过后,为什么小程序还得二次年审?缴费?
服务号今年付费300认证通过后,为什么小程序还得二次年审?缴费?,我记得往年只需要付费主体公众号就行,小程序直接服用主体公众号就行,不用二次付费,我今年付费主体公众号后,小程序还是提示付费?有啥办法不?
02-07 - 添加页面安全水印
背景 小程序需要做一个内部工具,但敏感信息为了防止工作人员随意截屏转发,需要在页面添加水印标识员工工号 实现 wxml: [代码]<view class="watermark"> <block wx:for="{{30}}"> <view class="watermark-row"> <span class="watermark-col" wx:for="{{5}}">{{userId}}</span> </view> </block> </view> [代码] css: [代码].watermark { position: fixed; width: 200vw; height: 150vh; top: -20vw; left: -50vw; color: gray; font-size: 14px; opacity: 0.1; z-index: 1000000; // 放在顶层 pointer-events: none; // 点击穿透,不影响页面交互 transform: rotate(-20deg); // 水印倾斜角度 } .watermark-col { display: inline-block; padding: 50rpx 40rpx; } .watermark-row { white-space: nowrap; } .watermark-row:nth-child(2n+1) { transform: translateX(10%); // 奇偶数行水印错开 } [代码] 效果 [图片] TIPS 可以封装成组件在需要的页面引用
2023-09-12 - 小程序异常监控收集
前言你是否经常碰到业务反馈,线上的小程序某个页面打不开了,订单没法结算了,但是你当时测试的时候都是好好的。 由于线上环境复杂,一些问题只会在特定网络环境或者设备上发生,对于这类问题,异常信息的收集就显得格外重要了,我们不但希望收集错误的堆栈信息,还需要用户操作流程,设备信息等,以便复现错误。 简单收集小程序App()生命周期里提供了onError函数,可以通过在onError里收集异常信息 App({ // 监听错误 onError: function (err) { // 上报错误 wx.request({ url: "https://url", // 自行定义报告服务器 method: "POST", errMsg: err }) } }) 用户操作路径收集一些较隐蔽的错误如果只有错误栈信息,排查起来会比较难,如果有用户操作的路径,在排查时就方便多了。 方法一:暴力打点方法收集 优点:简单直接 缺点:污染业务代码,造成较多垃圾代码 方法二:函数劫持(推荐使用) 需要在App函数中的onLaunch、onShow、onHide生命周期插入监控代码,我们通过重写App生命周期函数来实现。 App = function(app) { ["onLaunch", "onShow", "onHide"].forEach(methodName => { app[methodName] = function(options) { // 构造访问日志对象 var breadcrumb = { type: "function", time: utils.now(), belong: "App", // 来源 method: methodName, path: options && options.path, // 页面路径 query: options && options.query, // 页面参数 scene: options && options.scene // 场景编号 }; self.pushToBreadcrumb(breadcrumb); // 把执行对象加入到面包屑中 }) }但是这样写,会把用户自定义的内容给覆盖掉,所以我们还需要把用户定义的函数和监控代码合并。 var originApp = App // 保存原对象 App = function(app) { // .... 此处省略监控代码 // .... 此处省略监控代码 originApp(app) // 执行用户定义的方法 }记录结果 可以从下面的json看出,用户到了detail页面,执行了onLoad => getDetail => onReady => buy 当执行buy方法的时候报错。 [{"method":"onLoad","route":"pages/film/detail","options":{"id":"4206"}}, {"method":"getDetail","route":"pages/film/detail","options":{"id":"4206"}}, {"method":"onReady","route":"pages/film/detail","options":{"id":"4206"}},{"method":"buy","route":"pages/film/detail","options":{"id":"4206"}}] 上报策略考虑到在大型应用中,日志量比较大,我们采取抽样,合并,过滤三个方法减少日志的输出,代码实现可以参考lib/report.js 代码组织项目使用rollup作为构建工作,实现ES6转ES5,模块加载功能。 项目目录如下: config.js // 配置文件 core.js // 劫持小程序核心代码 events.js // 监听自定义事件 report.js // 上报类 utils.js // 工具类 🌟喜欢的点个star:https://github.com/zhengguorong/xbossdebug-wechat
2018-06-06 - 第三方注册的小程序可以直接开通云开发-无限制
亲测,开发工具版本:1.05.2102242,通过第三方注册的小程序可以直接开通云开发,手机号还是需要绑定的。但是,是突破以前的5个限制。 我的情况:目前有72个小程序,我自己的手机号100%已经绑定5个小程序了,所以我的手机号是无法再次绑定小程序的。 下面是体验流程: 1、下载最新版开发工具:https://developers.weixin.qq.com/miniprogram/dev/devtools/nightly.html [图片] 2、创建云开发项目 [图片] 3、开通云开发,会提示发送到管理员微信绑定手机号 [图片] 4、微信公众平台会直接给通知,点击进去即可 [图片] 5、直接绑定手机号,我用的手机号已经绑定了5个小程序 [图片] 5、绑定成功 [图片] 6、开通后需要关闭开发工具重新打开才能开通,要不然还是提示第三步的发送到管理员微信绑定手机号 7、看完有帮助的话,知道干嘛吧?
2021-02-25 - canvas生成海报图、分享
首先话不多说元素样式走起来 canvas宽高由js变量动态定义 html <canvas class=‘canvas’ canvas-id=“shareCanvas” style=“width:{{canvasWidth}}px;height:{{canvasHeight}}px”></canvas> css .canvas{ margin: 0 auto; letter-spacing: 2rpx; //画布上文字间距 我实在不知道canvas api怎么控制字间距 margin-top: 10%; } 然后绘制canvas代码 canvasImg:function(){ [代码]var that = this; wx.showLoading({ title: '图片正在生成' }); //拿到用户名称和用户头像 name ,img var name = app.header.userInfo.nickName; var img = app.header.userInfo.avatarUrl.replace("http:", "https:"); //请求后台接口拿到小程序码临时url //这里是我们后台根据我传递的页面路径和标识获取对应小程序的小程序码 返回一个图片临时缓存的url wx.request({ url: app.data.proxy + '/miniprogram/getUnlimited', data: { type: app.data.pdd, page:'pages/index/index' }, success(res) { var image = res.data.result; //拿到临时url 使用getImageInfo缓存到本地 wx.getImageInfo({ src:image, success(res){ //liload 小程序码本地缓存地址 var liload = res; //获取用户设备宽高 wx.getImageInfo({ src: img, success(res) { var width,height; wx.getSystemInfo({ success(res) { width = res.screenWidth * 0.6; height = Math.round(width * 1066 / 600); that.setData({ canvasWidth: width, canvasHeight: height }); } }); var x = width/750; //设置相对canvas自适应根元素大小 const ctx = wx.createCanvasContext('shareCanvas'); ctx.drawImage('../img/pinduoduo.jpg', 0, 0, 500*x, 812*x); ctx.save(); ctx.setTextAlign('center'); // 文字居中 ctx.setFillStyle('#fff'); // 文字颜色:黑色 ctx.setFontSize(16); // 文字字号 ctx.fillText(name, 250*x, 250*x); //名字 ctx.setFontSize(14); // 文字字号 ctx.fillText('邀请你使用【拼拼宝盒】', 250*x, 300*x); ctx.save(); //圆形头像框 ctx.setStrokeStyle('rgba(0,0,0,.2)'); ctx.arc(250 * x, 140 * x, 60 * x, 0, 2 * Math.PI); ctx.setStrokeStyle('rgba(0,0,0,.2)'); ctx.arc(250 * x, 460 * x, 120 * x, 0, 2 * Math.PI); ctx.fill('#fff'); //小程序码 ctx.clip(); ctx.drawImage(liload.path, 150*x, 360*x, 200*x, 200*x); //头像 ctx.clip(); ctx.drawImage(res.path, 190*x, 80*x, 120*x, 120*x); ctx.save(); ctx.stroke(); ctx.draw(); wx.hideLoading(); } }); } }) } }); [代码] } 绘制完成 [图片] 保存事件 这里首先用getSetting检测用户有没有开启相册权限 有的话直接保存 没有的话弹到权限页面让用户授权 btnTap:function () { [代码]var that = this; wx.showLoading({ title: '正在保存', mask: true, }); wx.getSetting({ success(res) { if (res.authSetting['scope.writePhotosAlbum']) { that.saveImg(); } else if (res.authSetting['scope.writePhotosAlbum'] === undefined) { wx.authorize({ scope: 'scope.writePhotosAlbum', success() { that.saveImg(); }, fail() { wx.showToast({ title: '您没有授权,无法保存到相册', icon: 'none' }) } }) } else { wx.openSetting({ success(res) { if (res.authSetting['scope.writePhotosAlbum']) { that.saveImg(); } else { wx.showToast({ title: '您没有授权,无法保存到相册', icon: 'none' }) } } }) } } }) [代码] } 用户有授权调用保存图片API 保存图片到手机 saveImg:function(){ [代码] wx.canvasToTempFilePath({ canvasId: 'shareCanvas', success: function (res) { wx.hideLoading(); var tempFilePath = res.tempFilePath; wx.saveImageToPhotosAlbum({ filePath: tempFilePath, success(res) { wx.showModal({ content: '图片已保存到相册,赶紧晒一下吧~', showCancel: false, confirmText: '好的', confirmColor: '#333', success: function (res) { if (res.confirm) { var arr = []; arr.push(tempFilePath); //保存后全屏显示 wx.previewImage({ urls: arr, current: arr }); } }, fail: function (res) { } }) }, fail: function (res) { wx.showToast({ title: '没有相册权限', icon: 'none', duration: 2000 }); } }) } }); [代码] } 好了 就这么多了 第一次发帖 有不足之处望各路大佬见谅、指出不胜感激 附代码片段:https://developers.weixin.qq.com/s/8Z8oXbm17ojh
2020-07-28 - 浅谈微信抽奖小程序!
说抽奖小程序之前,先整体了解下抽奖这一核心。从以下六方面分析 抽奖心理:抽奖在想什么? 抽奖假象:越多越多? 抽奖玩法:抽奖本质,能不能解决? 抽奖变现:或许天上真的会掉馅饼,但还是钱好 抽奖机会:还有没有必要做一个抽奖小程序? 抽奖意义:我们有一个锦鲤梦,游戏人生 抽奖是一个利他利己的行为,一来增加自己的中奖率,二来传播也为他人制造中奖机会。通过人与人之间的信任关系,间接为发起人活动背书,随之品牌营销,低成本的流量曝光。 哪里有福利,哪里有免费,哪里就有流量! 谁都可以参与,谁都可以发起。 一、抽奖心理你是否懂参与者,他们分别的诉求是什么? ▍抽奖者:我能中?有多少人参与了,什么时候开奖,及时通知开奖结果,活动真实性?简单易懂,奖品吸引力。 ▍发起者:怎么玩?才能更好的推广自己/公众号增粉/品牌服务/产品特性等,用户精不精准? 先明确身份(普通玩家,品牌方,运营,线下商家等) 再次挖掘需求(打品牌广告,提升销售?),工具是否支持 设置门槛,扩大影响力,ROI 上手教程(需求场景解决方案,与产品匹配) [图片] 二、抽奖假象▍抽奖越多越好? 听说现在有几百款抽奖小程序了,生存状况如何?先说出3个,有何差异? ▍玩法越多越好? 看到复杂的玩法,一方面难受,一方面欣慰 难受,大概率不会去玩 欣慰,营销玩法还挺丰富的,需求与利益结合 ▍功能越多越好? 如何把用户“困”在小程序,是对“美好生活”的向往? 后来才明白一句“你说你,想要逃,偏偏注定要……” 化简为繁,一蹴而就,谁在可歌可泣! 三、抽奖玩法简单来说,大的方向也只需要解决这三步。 提供奖品 开奖条件 发放奖品 [图片] ▍提供奖品: 实物,红包,知识付费等。 ▍开奖条件: 默认到达时间自动开奖,其次按人数,即开即中。 弱化开奖条件,因为一般都是按时间开奖。为用户提供最佳的解决方法,而非,先熟悉整个游戏规则,展示全部条件,全部玩法等。 ▍发奖方式: 发起者联系:让中奖人填写地址,电话,微信号等,适合快递实物,或添加中奖人再说明。 中奖人联系:留下发起人的手机,微信,二维码等,适合发放优惠券,红包等虚拟物品。 延伸: 单独自定义给中奖者一句话 一般平台自带文案,如果发起者还能单独设置一句,相当于多了一次互动营销机会,凸显人格化。 延长中奖人填写中奖信息时间 比如遇到节假日,参与人中奖人太多等。 ▍分享按钮: 是否允许参与者分享本次抽奖活动。之前「抽奖助手」把这个功能放在了“更多功能”里面,默认不开启,有的小程序选择了默认开启,我更倾向于前者。 在心理上,用户并不一定需要他人随意分享,他的赞助奖品。 在操作上,手动开启代表用户默认同意,手动关闭代表防止止损。 所以,应该把选择选择权交给用户,而不是替用户选择。让用户的利益与平台背后的利益一起成长,也多了裂变小程序的条件,多赢。在用户的关系链分享,垂直密集,越是喜欢抽奖的人,越会分享给抽奖的人,相互吸引。 有前置这个功能的必要。 ▍其他玩法: 线下活动/线上直播/年会抽奖玩法: 突出:公司,logo,主题,倒序抽奖 担心:繁琐,手忙脚乱,冷场,多人操作 在线互动——投屏,弹幕,背景音乐,活跃氛围 后台规则——简单,倒序(手动逐个开奖),重抽 延伸:摇一摇功能(摇越快,中奖率越高) 上滑抽奖:提示上拉,看下个奖品(固定栏展示:不断陷入) 参与条件:如性别,地区,(实名),达到一定抽奖次数,输入指定口令参与等 头像玩法: 列表:头像,昵称,码,中奖概率,排名(突出榜单,中奖概率) 宫格:头像,更多(一般都是这种,突出活动受欢迎) ▍小插曲:关于抽奖背景图片 如果平台能针对节假日提供2-3张背景图,不是更有助于发起活动吗?在每次小程序迭代上新,去旧图片。 这个小设计,花费不大,又让用户觉得产品很用心。 [图片] 四、抽奖变现自有广告:上首页(类似每日福利,自助福利) 增值服务:付费版(去除广告,突破参与人数限制,公号仅粉丝参与等) 电商合作:合作电商(购买商城东西当抽奖礼物),第三方电商优惠券导购等 互选广告:保证小程序内广告的调性,视觉统一性,比如开学期某当的的图书满减活动,做用户尽可能有兴趣的广告,增加了广告主的曝光/跳转/转化,成为平台的案例级,再次吸引广告主。位置:banner,开奖后短文案广告,商城广告等 官方广告:如先看视频后抽奖等 五、抽奖机会说抽奖就想到……那其他抽奖小程序还有机会吗? 头部小程序在教育你,我(品牌)等于抽奖(品类),稳固自己的地位,防守 做不了头部,后来者怎么办? 占据特性 垂直聚焦 开创品类 微商,公众号主,电商,专属游戏周边,线下门店等成了多款抽奖小程序的突破口。 不同人群的喜好,行为,用户社交关系链的特征不一样,复制之前的成功模式到自己的行业,有一定的借鉴意义。 谁能比较优雅、灵活地解决掉对应场景存在的问题,谁能满足差异化需求,打消参与顾虑,自然流量/广告主纷涌而至。 以下举3个例子 01. 点赞抽奖 公众号粉丝互动:点赞就能参与,抽红包 行业KOL & 节点效应:或许你喜欢的公号发起了一个活动,因为他的背书,而积极参与。如何帮助公众号主完成与粉丝的互动,做成案例,由此扩散,吸引一个又一个的公众号主,重合度引爆。这种推广方式,好过工具方主动介绍。 [图片] 02.锦鲤圈 京东平台导流:店铺抽奖 与 用户跳转到“京东购物”小程序店铺提升中奖率 通过锦鲤奖+人气奖,双福利邀好友,关注店铺,浏览该店铺商品,提升中奖率,多入口跳转到京东店铺。(方法很京东,也有小程序通过试玩应用,签到等任务来提升抽奖币,限定消耗抽奖币参与,换购等电商) [图片] 03.华硕+ 品牌宣传,拉新会员,导流门店 成为会员无门槛抽奖。导流到线下门店(线下门店优惠券),门店自助发布活动管理等 [图片] 不同的产品决定了其走向,有的是平台主导权,有的擅长调性,研究人性,有的擅长运营增长,有的正利用不公平,谁是分享王福利就向你倾斜。 抽奖再怎么玩法多样,用户有他自己的判断,同化的用户留下来! 六、抽奖意义如果说抽奖有什么社会意义,解决供需双方之间的需求。在诞生的那一刻,给我们带来短期或长期的价值,降低推广成本,短时间获取用户,尽管用户用完就跑…… 不管我们做什么,要提供给用户价值和好玩的东西,做对小程序本身有价值的事情。 感谢围观小程序测评系列《抽奖小程序》,作为小程序测评师,希望给大家带来一些启发,一起推送小程序更好地发展。
2019-09-04 - 基于小程序·云开发构建高考查分小程序丨实战
2019高考报名人数达到了 1031 万的新高,作为一名三年前参考高考的准程序猿,赶在高考前,加班加点从零开始做了一款高考查分小程序,算是一名老学长送给学弟学妹们的高考礼。上线仅 1 个月,用户数就突破了 1k,关于小程序的介绍就不多说了,可以去搜【历年高考分数线查询】体验,今天主要谈谈技术原理和实现细节。 [图片] 数据来源 小程序后台共收录近 30w 条数据,包含 2008-2017 年所有重点高校的各个批次的文理分科录取分数线以及 2008-2018 所有采用新课标一卷、新课标二卷、新课标三卷以及部分自主命题省份的从提前批到高职专科批的录取分数线,勉强称得上内容翔实。 [图片] 所有数据均采集自各大院校和各高考相关网站,由于数据量巨大,为提高速度,使用了 concurrent.futures (需要 Python3.5+) 模块里的 ThreadPoolExecutor 来构建线程池来并发执行多任务。 数据库采用的是 PgSQL,一款号称世界上最强大的开源数据库产品,所有数据均存在新建的 gaokao 数据库中,其下有两个表,university(院校的录取分)和 province(省份的批次线) university 表说明 字段 解释 name 院校名称 stu_loc 生源地 stu_wl 文理科 pc 录取批次 year 年份 score 录取平均分 province 表说明 字段 解释 year 年份 stu_loc 考生所在地 stu_wl 文理科 pc 批次 control 本批次最低控制线 30w 的数据量,多个站点,并发爬取,数据冲突是不可避免地,在执行插入之前,首先过滤掉残缺不全的数据,比如在插入 university 表时某条数据缺少 pc 字段,那么这条记录就应该被舍弃,最严重的是数据重复,我采用的解决办法是:先查询待插入的数据是否已经存在, university 表的主码是(name,stu,stu_wl,pc,year),因为现实约束一个院校只能在一个年份在一个类别一个批次只能有一个录取平均分,如果不存在,才执行最后的插入,并 commit 提交事务。 后台搭建 在 30w 条数据拿到后,我打算后台采用 Flask+PgSQL 的模式实现,甚至在后台在阿里云服务器部署好,小程序端在开发者工具联调通过之后,小程序上线遇到到一个大麻烦,因为小程序要求线上运行不能通过 ip 地址访问后台,必须通过备案的域名访问,域名购买一个倒不麻烦,只是域名备案比较耗时间,需要一周多时间,而当时距离高考也就不到 5 天,在手足无措之时,无意间看到小程序云开发,关于小程序云开发,官网的介绍是: 开发者可以使用云开发开发微信小程序、小游戏,无需搭建服务器,即可使用云端能力。 云开发为开发者提供完整的原生云端支持和微信服务支持,弱化后端和运维概念,无需搭建服务器,使用平台提供的 API 进行核心业务开发,即可实现快速上线和迭代,同时这一能力,同开发者已经使用的云服务相互兼容,并不互斥。 也就是说,只要把数据导入小程序自带的后台,就能通过小程序平台的 API 访问到这些数据,以前了解过第三方的 LeanCloud云 和 Bomb 云,没想到小程序现在集成了这些功能,不得不佩服一下腾讯。 也就是,接下来的后台的工作是主要是导入数据,查询小程序后台可知,后台支持导入 json 或者 csv 格式的数据。于是我就写了个脚本,把数据从本地数据库导出到 json 文件中: [代码]import psycopg2 import json # 连接 pgsql 数据库,为保证隐私,密码已隐藏 conn = psycopg2.connect(database="gaokao", user="postgres", password="*******", host="127.0.0.1", port="5432") cur = conn.cursor() cur.execute('select stu_loc,year,stu_wl,pc,control from province') result = [] query_res = cur.fetchall() for i in query_res: item = {} item['stu_loc'] = i[0] item['year'] = i[1] item['wl'] = i[2] item['pc'] = i[3] item['score'] = i[4] result.append(item) # indent=2 控制 json 格式的缩进 # ensure_ascii 控制中文的正常显示 with open("province.json", 'w', encoding="utf-8") as f: f.write(json.dumps(result, indent=2, ensure_ascii=False)) [代码] 这里还有有个坑需要说明一下,小程序后台要求的 json 格式和我们平常意义上的 json 格式还有点区别,首先,json 的所有内容不能被 [ 和 ] 包括起来,而且每个被 {} 所包括得数据项之间不能有逗号。 [图片] 选用 notepad++ 打开原来的 json 文件,使用替换功能就能解决,把 [ 和 ] 替换成空格,把 },替换成 } 即可。 修改之后,在小程序后台通过导入该 json 文件,后台搭建就基本完成了。 小程序端编写 关于小程序端的编写,我主要谈谈两点经验,第一是页面的编写,比如下面这个界面。 [图片] 最开始想实现这样的效果,完全没有思路,最后在从自定义模态弹窗那得到了思路,一开始地区院校这个下拉框对应的布局是隐藏的,在 wxml 文件中通过 hidden=true 控制,一点击 地区/院校 下拉框,就把 hidden 置为 false,如果开始有其他下拉框对应的布局的 hidden 属性是 false 的话,同时要把这些布局的 hidden 属性置为 true 来隐藏其他布局,当然,这里的 true or false 需要在 js 里通过 setData() 动态修改,把修改后的数据从数据层渲染到视图层。 第二是关于小程序云开发的原生 Bug,查询后台时一次只能最多查询到 20 条数据,要实现一次得到所有匹配的结果,需要解决两个问题,第一个问题很自然而然就能想到,第一次查到 20 条数据后,第二次跳过前 20 条再取 20 条,第三次跳过前 40 条再取 20 条,以此类推;还有一个更为致命的问题,查询后台的 API 获取结果的回调函数的 异步 的,也就是说,为了保证获得完整数据,第二次查询需要写在第一次查询的回调里,第三次查询需要写在第二次查询的回调里,而且你还不能显式地知道要查询多少次,需要写多少层这样的嵌套,以及烦人的同名变量覆盖问题,这就是所谓的 异步地狱。为了解决这个问题,需要我们编写代码把这个异步方法转成同步的,具体做法是: 先在所要添加功能的js页面中导入 runtime.js 文件,同时把runtime.js文件放入相应文件夹 ; const regeneratorRuntime = require("…/runtime"); runtime.js 下载地址:https://github.com/inspurer/CampusPunchcard/blob/master/runtime.js 同时模仿下例代码完成业务逻辑: [代码]// 查询可能较慢,最好加入加载动画 wx.showLoading({ title: '加载中', }) const countResult = await db.collection('province').where({ stu_loc: name, pc: pici, }).count() const total = countResult.total //计算需分几次取 const batchTimes = Math.ceil(total / MAX_LIMIT) // 承载所有读操作的 promise 的数组 //初次循环获取云端数据库的分次数的promise数组 for (let i = 0; i < batchTimes; i++) { const promise = await db.collection('province').where({ stu_loc: name, pc: pici, }).skip(i * MAX_LIMIT).limit(MAX_LIMIT).get() //二次循环根据获取的promise数组的数据长度获取全部数据push到newResult数组中 for (let j = 0; j < promise.data.length; j++) { var item = {}; item.code = i * MAX_LIMIT + j; item.name = promise.data[j].stu_loc; item.year = promise.data[j].year; item.wl = promise.data[j].wl; item.pc = promise.data[j].pc; item.score = promise.data[j].score; console.table(promise.data) newResult.push(item) } } if (newResult.length != 0) { that.setData({ hasdataFlag: true, resultData: newResult }) } else { that.setData({ hasdataFlag: false, resultData: newResult }) } // 隐藏加载动画 wx.hideLoading() [代码] 以上就是我本次开发的一些心得体会,欢迎批评指正。 源码地址: https://github.com/TencentCloudBase/Good-practice-tutorial-recommended 联系我们 更多云开发使用技巧及 Serverless 行业动态,扫码关注我们~ [图片]
2019-09-06 - 微信开发者工具 1.02.1909051 RC 更新说明
下载地址 Windows 64 、 Windows 32 、 macOS已知问题 本地预览正常,真机预览空白;尝试关闭 项目详情 - 本地设置 - 代码保护;将在最近的 nightly 和 RC 版本修复 1. 本地编译时进行合并编译本地编译时使用合并编译可以加快小程序加载的速度, 通过 [代码]项目详情 - 本地设置 - 本地编译时进行合并编译[代码] 可以打开此功能 [图片] 使用合并编译后,主包的 js 文件会被打包成一个 [代码]__APP__/appservice.js[代码] 文件 [图片] 合并编译对于错误调试暂时还是不友好的,异步报错从控制台中点击会先跳转到合并后的文件,而如果合并后的文件过大会导致调试器卡顿 2. PC 微信开发版小程序自动预览PC 微信内测版 支持小程序 开发者工具 [代码]设置 - 通用设置 - 启用 PC 端自动预览[代码],可以将开发版小程序通过自动预览推送到同登录态的 PC 微信上 [图片] 3. 自动真机调试增加自动真机调试功能,减少真机调试扫码的交互 [图片] 4. 使用测试号进行多帐号调试在 [代码]菜单 - 工具 - 多帐号调试[代码],可以打开多帐号调试帐号管理面板 本次为所有 appid 添加了 4 个测试帐号,可以使用测试号进行多帐号调试 [图片] 5. 支持多线程 [代码]worker[代码] 的单步调试本次更新优化了多线程 [代码]worker[代码] 的编译和代码加载的方式,从而支持了单步调试 [图片] 6. 公众号网页调试增加 url 收藏功能之前只有通过地址栏历史记录的匹配交互来快速调试常用页面,本次新增了收藏功能,更加便利 [图片] 7. 云开发套餐支持代金券支付在云开发控制台支付套餐时,可以选择使用代金券进行支付 [图片] 8. 新增周期性更新调试拉取周期性数据由于微信客户端每隔 12 个小时才会发起一次请求,调试周期性更新功能会显得不太方便。 目前新增能够在开发者工具上调试整个流程,操作路径为点击菜单 [代码]工具 -> 拉取周期性缓存数据[代码], 点击后开发者工具会立即向配置的数据下载地址请求数据,如下图所示: [图片] 清除周期性数据如果需要清除工具缓存的周期性数据,可以通过点击工具栏的 [代码]清除数据缓存[代码] 或者 [代码]全部清除[代码] 来进行清除。 9. 支持小游戏关系链互动数据开发在小游戏项目 [代码]project.config.json[代码] 中指定 [代码]jsserverRoot[代码] 为项目内目录,可以在该目录下进行小游戏关系链互动数据开发,右键支持直接上传到开发环境或正式环境,或者对比文件 [图片] 10. 小游戏节点审查插件通过 [代码]菜单 - 工具 - 插件[代码] 打开插件面板,可以添加小游戏节点审查调试器插件 [图片] 添加插件后重启工具,选择 [代码]Game Inspector[代码] 调试器面板,支持使用主流游戏引擎的小游戏获取当前游戏场景下的节点树信息 [图片]
2019-09-09 - 小程序之「 navigateBack 」公共页面案例
navigateBack的用法 [代码] wx.navigateBack({ delta: backSize // backsize表示关闭几页,从页面栈中移除几个(包含本页面) }) [代码] 项目中的需求 「 需求 」:做一个公共结果页,从其他地方跳转到这个页面,最后点击"完成按钮或者右上角返回"后,回到想回到的页面,并且控制目标页面刷新还是不刷新。 「 分析 」:想实现回到想回到的页面,通过对页面栈的管理来实现。 「 举例 」:页面栈中有1-2-3-4-5,5是公用页面,现在想跳到1,那么就可以通过navigateBack的delta值等于4来控制回到1,我们就动态改变delta的值实现动态跳转,navigateBack会让页面出栈,这样就很好地维护了页面栈。 具体思路 通过getCurrentPages( )能够拿到当前小程序页面栈的数组,数组中有个key为route,该值即是栈里页面地址。要跳转到1,4传值给5,5中通过遍历页面栈数组,判断传过来的值是否等于页面栈中的route值,相等的话,记录index,这样即可完成。 最终效果 无论是触发了ios滑屏退出、android物理按钮返回、小程序左上角返回按钮,都会回到目标页面。 但是,经真机测试后发现。点击左上角返回、手机物理返回键、滑屏返回到目标页面,效果上会关闭两个页面。但最终也可以回到目标页面。只能说功能达到了,效果上没那么完美。这没办法。毕竟页面卸载的处理本身就是一个伪处理-_- 代码说话 这是项目中用到的公用页面,就是通过navigateBack来控制跳转 注释写得很清楚了 想要更清晰地了解页面栈是怎样的,就打断点看一看 有问题欢迎留言讨论😄 [代码]Page({ data: { isInvoke: false,// 是否调用过按钮 isUnload: true,// 是否是卸载的生命周期 resultImg: "", // 图标(页面内使用) result: { title: "",// 必传(页面标题) type: 1,// 必传(0:失败 1:成功) url: "", // 必传(跳转的url),传空或者不在页面栈中,跳转到首页或指定页面 // 选传(上面文案布局,成功默认是:成功,失败默认:失败) resultUp: "", // 选传(下面文案布局,不传默认是空 resultDown: "", // 选传(按钮名称,成功默认:完成,失败默认:重试) btnName: "", // 选传(是否刷新目标页,默认刷新) isRefresh: true, } }, /** 页面初始化 */ onLoad: function (options) { }, onShow: function () { this.initData(); }, /** 页面卸载*/ onUnload: function () { // 如果点击了按钮,就不再调用它 if (!this.data.isInvoke) { this.data.isUnload = true this.targetJump() } }, /** 初始化值 */ initData: function () { var result = wx.getStorageSync("result") if (result) { // 页面标题 wx.setNavigationBarTitle({ title: result.title }) var type = result.type this.data.result.url = result.url if (typeof (result.isRefresh) != "undefined") { this.data.result.isRefresh = result.isRefresh } if (type) { // 成功 result.btnName = result.btnName ? result.btnName : "完成" this.data.resultImg = "/resources/success.png" result.resultUp = result.resultUp ? result.resultUp : "成功" } else {// 失败 result.btnName = result.btnName ? result.btnName : "重试" this.data.resultImg = "/resources/failed.png" result.resultUp = result.resultUp ? result.resultUp : "失败" } var btnName = "result.btnName" var resultUp = "result.resultUp" var resultDown = "result.resultDown" this.setData({ resultImg: this.data.resultImg, [resultUp]: result.resultUp, [resultDown]: result.resultDown ? result.resultDown : "", [btnName]: result.btnName }) } }, /** 按钮点击事件 */ btnClick: function () { this.data.isInvoke = true this.data.isUnload = false this.targetJump(); }, /** 目标页面跳转 */ targetJump: function () { // 清除缓存 wx.removeStorageSync("result") var pages = getCurrentPages() // 如果url为空或者不在页面栈中,返回首页 var backSize = 100 var target console.log(pages) for (var i = 0;i < pages.length;i++) { if (this.data.result.url.substring(1) == pages[i].route) { // 自身也在栈中,所以要-1 backSize = pages.length - i - 1; target = pages[i] console.log("target", target) break } } console.log("backSize", backSize) if (backSize == 100) {// 首页或者指定页面 wx.reLaunch({ url: this.data.result.url, }) return } if (this.data.result.isRefresh) { // 刷新目标页面 target.onLoad() } if (this.data.isUnload) {// 用户点击了返回键 // 如果目标页面在页面栈中倒数第二个位置,返回 if (backSize == 1) { return } // 如果只是调用了小程序返回键,因为back键默认会关掉一页 backSize = backSize - 1 } wx.navigateBack({ delta: backSize }) } }) [代码] 以上
2019-05-28 - 减少重复造轮子,开源微信小程序商城(前后端开源:uniapp+Java),秒杀、优惠券、多商户、直播卖货、分销等功能。
注意 Entity里不是缺少get、set方法,Eclipse、IDEA请先安装lombok插件 使用须知 ✅允许 个人学习使用 允许用于学习、毕设等 允许进行商业使用,请自觉遵守使用协议,如需要商业使用推荐购买商业版(进群联系群主) 请遵守 Apache License2.0 协议,再次开源请注明出处 推荐Watch、Star项目,获取项目第一时间更新,同时也是对项目最好的支持 如何交流、反馈、参与贡献? 官方社区:http://fly2you.cn gitee仓库:https://gitee.com/fuyang_lipengjun/platform github仓库:https://github.com/lipengjun92/platform-wxshop 官方QQ群: https://qm.qq.com/cgi-bin/qm/qr?k=HNLRmaIdvnj2e_TGkMspORvIn-AHNZCb&jump_from=webapi”>img border=“0” src="//pub.idqqimg.com/wpa/images/group.png" alt=“微同科技 ①群” title=“微同科技 ①群”> https://qm.qq.com/cgi-bin/qm/qr?k=4i3Z9xgp7SlPnk_X1v0TWToSOoT_gJMz&jump_from=webapi”>img border=“0” src="//pub.idqqimg.com/wpa/images/group.png" alt=“微同科技 ②群” title=“微同科技 ②群”> https://qm.qq.com/cgi-bin/qm/qr?k=hQLMx7vYLfP_C-d2-yP_udx1yciJXfHC&jump_from=webapi”>img border=“0” src="//pub.idqqimg.com/wpa/images/group.png" alt=“微同科技 ③群” title=“微同科技 ③群”> Watch、Star项目,进群联系群主获取《微同开源商城启动部署手册.docx》 技术讨论、二次开发等咨询、问题和建议,请移步到官方社区,我会在第一时间进行解答和回复! 微信扫码并关注公众号,获得项目最新动态及更新提醒 [图片] 开发计划 1 使用uniapp重构小程序端所有页面 2 修复所以已知bug,使用遇到bug请给我们提交issues 3 后台使用SpringBoot重构 4 适配H5、IOS、Android端 5 接入支付宝支付 6 出配套视频教程 微同商城商业版 小程序 [图片] H5 IOS Android 商业版与开源版差异 微信小程序商城(Java版) 获得荣誉 GVP [图片] 新手必看启动教程 https://www.bilibili.com/video/av66149752 微同商城开源版体验: [图片] git:https://gitee.com/fuyang_lipengjun/platform 代码生成工具IDEA插件 git:https://gitee.com/fuyang_lipengjun/platform-gen 技术选型 1 后端使用技术 1.1 springframework4.3.7.RELEASE 1.2 mybatis3.1.0、MyBatis-Plus 3.1.0 1.3 shiro1.3.2 1.4 servlet3.1.0 1.5 druid1.0.28 1.6 slf4j1.7.19 1.7 fastjson1.2.30 1.8 poi3.15 1.9 velocity1.7 1.10 quartz2.2.3 1.11 mysql5.1.39 1.12 swagger2.4 1.13 j2cache2.3.22-release 1.14 weixin-java-mp3.2.0 1.15 MybatisPlus3.1.0 1.16 lombok 2 前端使用技术 2.1 Vue2.5.1 2.2 iview 2.3 layer3.0.3 2.4 jquery2.2.4 2.5 bootstrap3.3.7 2.6 jqgrid5.1.1 2.7 ztree3.5.26 2.8 froala_editor1.2.2 项目结构 [代码]platform |--platform-admin 后台管理 |--platform-api 微信小程序商城api接口 |--platform-common 公共模块 |--platform-framework 系统WEB合并,请打包发布此项目 |--platform-gen 代码生成 |--platform-mp 微信公众号模块 |--platform-schedule 定时任务 |--platform-shop 商城后台管理 |--uni-color-ui uni-mall商城参考项目 |--uni-mall 移动端商城 |--wx-mall 微信小程序商城 [代码] 实现功能 一:会员管理 a 会员管理 b 会员等级 c 收货地址管理 d 会员优惠劵 e 会员收藏 f 会员足迹 g 搜索历史 h 购物车 二:商城配置 a 区域配置 b 商品属性种类 c 品牌制造商 d 商品规格 e 订单管理 f 商品类型 g 渠道管理 h 商品问答 i 反馈 j 关键词 三:商品编辑 a 所有商品 b 用户评论 c 产品设置 d 商品规格 e 商品回收站 四:推广管理 a 广告列表 b 广告位置 c 优惠劵管理 d 专题管理 e 专题分类 五:订单管理 a 所有订单管理 六:系统管理 a 管理员列表 b 角色管理 c 菜单管理 d SQL监控 e 定时任务 f 参数管理 g 代码生成器 h 系统日志 i 文件上传 j 通用字典表 七:短信服务平台 a 配置短信平台账户信息 b 向外提供发送短信接口: [代码]http://域名:端口/api/sendSms?mobile=13000000000,15209831990&content=发送的短信内容 安全起见,需配置有效IP地址。platform.properties -> sms.validIp [代码] 安装教程 配置环境(推荐jdk1.8、maven3.3、tomcat8、mysql5.7、redis4.0.1) 创建数据库 依次初始化sql脚本 /_sql/platform.sql /_sql/sys_region.sql 导入项目到IDE中 导入支付证书至/platform-shop/src/main/resources/cert/目录下(申请商户号、开通微信支付、下载支付证书) 修改配置文件 /platform-admin/src/main/resources/dev/platform.properties jdbc.url jdbc.username jdbc.password wx.appId wx.secret wx.mchId wx.paySignKey wx.notifyUrl sms.validIp mp.appId mp.secret mp.token mp.aesKey 修改配置文件 /platform-admin/src/main/resources/j2cache.properties redis.hosts redis.password 启动redis服务 启动后台项目(参照开发文档) 打开微信开发者工具 导入 /wx-mall填写appId 修改 /wx-mall/config/api.js里API_BASE_URL的值 使用eclipse启动项目后默认访问路径 http://localhost:8080/platform-framework 使用idea启动项目后默认访问路径 http://localhost:8080 页面展示 登录页面 [图片] 首页 [图片] 发送短信 [图片] 捐赠 [图片] 小程序首页 [图片] 专题 [图片] 分类 [图片] 购物车 [图片] 登录授权 [图片] 优惠券 [图片] 小程序并联手机 [图片] uniapp版本 [图片] [图片] [图片] [图片] [图片] [图片] 开发文档目录 a name=“doc” href=“http://fly2you.cn/guide/index” target="_blank">[图片]
2020-08-11 - 采用云开发的一款图像识别AI -- 开源
SEA-AI >>>> star 模板使用云开发实现,接入百度AI平台API图像识别系统,无需另外搭建服务器,只需修改文件内配置项 识别前 接入百度AI 图片审核,假如对社会有影响或有害的,直接返回失败 识别后可点击查看相应图片获取百度百科中更关键的内容(云函数爬虫百科内容) 项目介绍 一款方便快捷识别AI,可根据您拍摄或相册中照片识别出您所需要知道的物种(植物,动物,图文,菜品类型),相关知识,帮助您了解该物种,打开新世界! [图片] 特别鸣谢 UI设计师 - Rolland罗兰 欢迎各大公众号关联 小程序APPID - wx162fc6bf32f43f7c 效果预览 [图片][图片][图片][图片] 学习本项目 整套前端使用 Wepy 开发,提倡前端组件化工程化,高效的完成前端项目。 使用说明 申请百度AI 获取Appid,secret 找到文件内的appid,secret , 修改为你微信小程序的appid以及secret 安装使用 安装(更新) wepy 命令行工具。 [代码]npm install wepy-cli -g [代码] 安装依赖 [代码]cd sea-ai npm install [代码] 开发实时编译 [代码]npm run dev [代码] 开发者工具导入项目 使用[代码]微信开发者工具[代码]新建项目,本地开发选择项目根目录,会自动导入项目配置。 上传安装云函数 开发者工具中找到云函数目录上传并部署:云端安装依赖(不上传node_modeles) setBaiduToken 需上传触发器(定时器) 每十五天更新一次token 添加数据库字段 [代码]identification-record token baidu-token user [代码]
2019-05-27 - 如何快速实现运营平台可配置化
「如果能快速产出不同类型的活动且这个过程不需要开发参与,完全由运营或PM独立完成小程序运营活动的创建,是运营PM与开发共同的愿望。」 近年小程序逐渐流行起来,各公司为吸引更多用户来使用其公司小程序,越来越多运营活动的开发成为常见的场景和趋势。 作为第一批开发微信小程序的我司——猫眼电影,如今用户量与日俱增,基于小程序运营活动的需求也随之增长。 『 以下对话改编自真实案例 』 运营&PM:春节快要到了,我们能不能复用一下之前七夕的活动?这次活动页面的颜色,头图,tab文案,提示文案等统统都要换。还有我们希望改版一下这里的排版,再加一个xx功能,首页再加一个xx提醒,这个活动之前做过,这次我们复用的话很快吧?明天能上线吗? 前端开发:…这个活动之前没有说过要复用,是一次性的,我们如果这次还想上这个活动,需要前端来改动代码,将你说的与之前活动不同的地方(颜色,头图,文案等等)在代码中更换掉。你说的xx功能和这个xx提醒是需要后端开发来支持的,我们相当于是在原有页面的基础上二次开发,而且后端也有开发量,需要拉上后端一起来评估一下,明天上线是不可能的! 运营&PM:这个需求很简单,怎么实现我不管,明天上线! 前端开发:… [图片] 『 活动复用之痛 』 之前某些一次性的活动,由于效果较好,会被要求再次复用。 「如果每次都在原有活动基础上改动代码及一些配置并重新上线显然是浪费人力且不可持续的」而且事实证明,每次这种被要求再次上线的活动,除了改动一些本次活动相关的图片文案之外,往往还需要增加额外一些“简单”优化和功能。 [图片] 「为了复用这种活动,活动模版化便被提上了日程。」 如果采用传统的解决方案:在已有B端系统,添加该活动配置项,同时需要后端开发接口将活动配置项存在数据库中,而依赖后端会有一系列问题产生。 1. 后端资源紧缺,时间成本高 这种与主流程不太相关的需求通常优先级比较低,如果后端无法及时配合上线时间很难保证。前后端联调的时间成本也是比较高的。 2. 不灵活 前后端PM需要沟通协调配置项的问题,一旦确定可配置项都有哪些,表结构确定之后就难再轻易改动,如果有需求的变更需要PM和前后端讨论并周知到前后端相关开发人员,改数据库改前后端代码,费人力费时间。 3. 需求与后端关系不大 活动模版化存储的活动配置项数据与后端其他逻辑几乎无关联,只是为了配合前端而做的简单存储,存储在后端就意味着后端要为前端提供增删改查的接口。后端心理上也比较排斥这种与后端其他业务逻辑毫无关联只是为配合前端而做的需求。 [图片] 『 如何用云开发解决活动复用之痛 』 为解决活动复用之痛,正值小程序云开发刚刚推出,这种serverless的开发模式正好是我们做活动复用所需要的,何不用云开发来帮助我们解决这个问题呢? 于是,我们创建了一个「小程序运营工具」(代号:唐图)的后台管理系统项目。 「运营可以通过唐图进行活动数据、状态管理,如:新建、编辑、查看、删除、上线、下线、置顶、设为模版等操作」 前端开发根据不同的活动类型为运营提供不同活动模版(目前为前端根据运营PM的需要设计可配置项模版的表结构并存储在云开发的云数据库中,之后计划开发为运营提供可视化配置可通过拖拽模版组件动态生成活动模版,同时活动数据的编辑也将提供可视化编辑功能)。 唐图产生的活动数据、活动模版数据、权限/身份数据等涉及到图片文件与文字信息的存取,因此使用了小程序·云开发的「数据库」能力与「存储」能力,使用小程序·云开发的Node端SDK支持该后台系统。 『 小程序端的模版化 』 「小程序运营工具」中产生的每个活动数据,都有活动类型与活动id标识,小程序端访问该活动时带上必要参数在小程序端访问云开发的云数据库拿到对应活动配置数据来渲染页面,即实现了使用一套模板创建不同活动的目的。 [图片] 『 问题、思考与解决方案 』 当然在通过小程序·云开发实现唐图使用的过程中,我们遇到了一些值得思考的问题。以下给大家分享一下我们的经验: 1. 不同环境数据存取策略 当开发一个新活动模版化时,小程序开发版将使用云开发test环境,线上版将使用云开发prod环境。 1.1 数据的存取 唐图线上的活动配置项由运营来配置,如果线上配置的活动配置只在prod环境存储的话,意味着小程序开发版将拿不到活动配置项数据,然而开发需要验证不可能等到上线之后。所以唐图中保存策略在考虑后确定为:会将数据同时保存到两个不同环境中,这样线上线下的配置项数据可保证为一致利于开发与测试。 1.2 图片的存储与使用 唐图中很多活动配置项为图片文件,上传文件即时上传并返回fileid。通过fileid拿到图片的链接,存储配置项字段时将包含图片信息的object(fileid和url)存为value。 小程序云中如果权限是私有的,url会是临时的url,如果权限是公开的(所有用户可读)url不变。我们的活动配置权限设置为公开数据,url不变,所以此处我们将图片url直接存入数据库中以供小程序使用 [图片] 「小程序云上的存储管理也是按照环境隔离的。」如果也按照数据的存取策略来执行,将同一份数据上传到不同环境,图片数据会变得很冗余,且没有必要,所以决定上传图片只存储到prod环境,返回的链接保存在两个不同环境的数据中。 调用上传文件接口之前必须要转译一下文件名,因为文件名将直接作为图片链接的一部分。如果文件名包含汉字或特殊字符且没有被转译可能导致上传失败,或是生成的链接不可用(如果该图片是分享给好友时的分享图,带有汉字链接的图片将在分享的时候不可用)。 在上传之前先查找一下有无与当前文件名相同的文件,如果有需将该文件拼接一些随机字符串再上传。或不查找文件,直接用随机字符串替换掉之前的文件名。(但我们需求场景该文件名也许有含义所以没有直接替换掉)。 2. 使用短链接生成猫眼小程序码 在活动可定制化之后,运营可以自己生成活动了!但是生成完活动之后,运营同学需要投放该活动的入口,不清楚当前活动的链接地址与对应的小程序码,还需要找前端同学提供。 我们预料到了这种情况在唐图事先添加了个可查看链接和小程序码的功能。 使用的是getWXACodeUnlimit api生成小程序码,这种小程序码的优点是永久有效、数量暂无限制,但是所传递的参数scene最大为32个可见字符,scene参数需要传递的信息至少包括活动id与活动type,当时实现该功能时小程序云数据库默认的_id标识长度为20位,参数长度总和勉强没超限,但是也只能支持扫码进入当前活动页,如果想实现先跳转到猫眼小程序首页再跳转到活动页(目的是希望用户可以返回到小程序首页)这种需求就显得力不从心。 为解决这个问题,我们利用云开发的云数据库,存储了活动链接(长链接)并返回新增这条数据后小程序云生成的唯一标识_id,只将_id作为参数当作scene字段的值。 存储在云数据库中的数据如下: [图片] 但不久后就发现出现了新的问题,通过唐图调用小程序云的Node端SDK存储长链接返回的唯一标识_id从原来的20位变成了32位!我们才意识到原来这个默认_id位数是有可能会变化的,是不可以依赖默认_id作为该场景下(严格要求位数)使用的。我们通过在Node端生成随机字符串并在新建数据时将该随机字符串指定给_id而不是使用默认的_id。 [图片] 这个功能非常实用,上线后,运营再也不用来问前端:“xx情况下的路径应该填啥”“求生成一个小程序码”。 『 云开发让运营活动需求不再难以实现 』 目前已实现最常用活动模版化,并自唐图一期上线以来(2月初上线)至今(5月初)已支持5个线上活动,收益显著。 对于运营来讲 「想上就上」同类型的活动上线不再需要开发,创建一个活动的复杂度降低,效率大幅提高。 「想改就改」运营可随心所欲通过唐图新建并修改活动数据。 「想在哪儿上就在哪儿上」运营可以通过唐图查看当前活动的小程序链接及当前活动的小程序码(用于入口投放) [图片] 对于开发来讲 「前端开发启动活动模版化不再依赖后端,面对模版化时随时可能被新加入的新字段或新功能,也能从容应对」小程序·云开发的云数据库基于mongo,自己就能改表结构而且代价不大,如果改动频繁也可以自己先mock一个json,等稳定了再将json文件上传到数据库,so easy!妈妈再也不用担心我的加班! 「后端开发摆脱苦海」后端不再需要配合前端做这些改来改去无聊的存储工作,有时间去做更重要的事了! 「解放QA」模版化后的活动,只需要模版化后的第一个活动需要测试,以后就不需要再无休止的测试同个活动啦! 模版化后的活动无形中限制了运营针对每次活动的「定制化的」「仅使用一次」的修改,如果改模版将更加慎重的考虑今后的复用性,减少了脑洞大开或抽风的奇葩需求产生的概率! [图片] 借力云开发,猫眼在活动模版化和可定制化方面已经初见成效,小程序方面抽象出独立的活动插件项目并在小程序插件中使用云开发来完善我们的活动可定制化项目也已在规划中。也希望云开发能推出更多好用的服务,服务更多的前端开发人员。
2019-05-28 - U计划入选项目【清艺管家】开发经历分享
产品简介 清艺管家是我们为学校的艺教中心开发的小程序,旨在用小程序这种更方便的方式让同学们预约使用上我们学校的琴房,也能让琴房利用率更高、管理更规范。因为是给清华大学艺术教育中心使用的,并取"轻易"谐音,所以给这个小程序取名叫清艺管家。 目前正在校内办程序上线的相关手续。 产品设计思路 需求背景 到目前为止,在我们学校使用琴房需要经历以下的流程: [图片] 其中,“办理琴卡"和"打孔"都需要去现场找老师进行办理,且办卡处和琴房不在同一个地方。可以发现这样的琴房使用流程是低效且难以管理的,暂且不论同学在不知道"是否有琴房可供使用"的情况下到现场很可能发现"自己有空的时间没有空闲的琴房”。另外,办卡对于短期使用琴房的同学来说有些不太合理。 通过与艺教中心的老师沟通,我们得知他们目前管理琴房使用记录是纯手工进行,即一名同学使用了一次则写一行记录,记录整理汇总的难度较大。 基于以上旧有预约制度的不便利,我们希望开发“清艺管家”这个系统,此系统能够使用微信小程序向用户显示琴房信息和预约情况,处理用户的预约请求,并让管理老师在现场能够完成预约者身份的验证。 产品定位 通过问卷调研需求、撰写用户故事等方法,我们获取了"用户们"对新系统的大致需求,也给了我们的产品一个较为合适的定位: 对于用户端,微信小程序拥有的很多方便的特性,避免使用浏览器,APP等,只在微信一个应用内就能完成预约过程。所以对于用户端我们优先考虑使用微信小程序。 对于管理端,使用网页的方式呈现,能够尽量呈现多的信息,方便老师进行琴房管理。 目标用户:清华师生及校外可能使用到琴房的人 简介:提供预约琴房服务 产品理念:交互流畅,操作简单,定制化(按时提醒) 主要功能:提供查看使用情况,预约,缴费,查询记录等功能 卖点:简化预约-付款-签到流程,提高管理方管理效率,提升琴房预约系统用户的用户体验 产品设计 以下4幅图分别是我们对"清艺管家"系统管理端、小程序端模块设计和系统设计。 管理端设计 [图片] 小程序端整体设计 [图片] 小程序端各模块设计 [图片] 系统设计 [图片] 原型设计 首先,我们使用墨刀作为我们的原型设计工具,因为免费而且上手难度不高。其次,我们都没有美术或者设计功底,所以没有打算把原型做的多么精致,只是希望能够把原型界面做得简单明了,并且指导之后的前端开发就好了。 我们选择了紫色(因为清华是紫色的)作为我们的主题色,针对一些优先级最高的功能进行了界面设计,下面是我们初期的原型设计。 预约流程 [图片] 订单流程 [图片] 个人界面 [图片] 墨刀原型设计链接 我们真正开发出来的小程序和原型设计相差还是很大的,这个原型设计也是比较粗糙,干脆把链接贴出来: https://free.modao.cc/app/0cfcc5536c6a9ce8fbd81a0ee793120a981ce514#screen=s5A3CC545541540988107569 开发心得 一定要经常与需求方(对于我们来说是艺教中心)沟通需求,因为仅靠前期大致的需求调研往往是不能够全面知悉的,最好一边设计或者开发一边与需求方明确具体需求。 有的时候开发团队闷声开发,奋战几周出来一个产品,会发现与客户想要的完全不是一个东西; 开发过程中可能会有灵光一闪,把新的想法告诉客户之后客户可能会发现这正是他想要的; 敏捷开发正是以用户的需求进化为核心,采用迭代、循序渐进的方法进行软件开发,现在社会节奏快,客户需求变化也快,我们不能也不应该要求客户一开始就板上钉钉地给出最终方案。 由于是在学期里为学校的机构开发应用,学习压力还是有的,这要求我们小组四个人都明确自己的分工,在确定的时间节点之前保质保量地完成开发和测试工作。我们建议类似的团队在以后的开发中尽量一起开发,虽然可能开发的模块不一样,对开发工作没有直接帮助,但是几个人坐在一块交流更加顺畅,对彼此进度也更加了解,这在团队开发中是很重要的,相当于让彼此都安心吧。(其实是相互鼓励,然后还能约饭哈哈哈~ 要利用好 github 等代码托管仓库进行团队成员工作的分配和协调,作为组长可以多布置 Issue ,如果有人领锅则好,没人领锅就强行甩锅,这样工单管理也比较系统,日后回顾会有成就感。比如下面是我们的 Issue 和 Project 面板: [图片] [图片] 人越多的项目,越有使用CI的必要。有项目经验的同学可能有体会:自己开发了一两天,从远端把代码 Fetch 回来之后发现原来 Work 的代码现在不 Work 了,仔细一看发现是队友写的代码有问题,但是你和队友分别负责两个模块,你不想看也看不懂他写的代码,但是他往主分支里推了没有检验过的代码。这是相当难受的,但是如果配好CI,就可以很快的在这个时候发现问题,并告诉你的队友说:“你的代码都还没 Work 也敢往 master 推?”于是晚饭就有了。 [图片]
2019-05-29 - Painter 一款轻量级的小程序海报生成组件
生成海报相信大家有的人都做过,但是canvas绘图的坑太多。大家可以试试这个组件。然后附上楼下大哥做的可视化拖拽生成painter代码的工具:链接地址https://developers.weixin.qq.com/community/develop/article/doc/000e222d9bcc305c5739c718d56813
2019-09-27 - [填坑手册]小程序Canvas生成海报(一)--完整流程
[图片] 海报生成示例 最近智酷君在做[小程序]canvas生成海报的项目中遇到一些棘手的问题,在网上查阅了各种资料,也踩扁了各种坑,智酷君希望把这些“填坑”经验整理一下分享出来,避免后来的兄弟重复“掉坑”。 [图片] 原型图 这是一个大致的原型图,下面来看下如何制作这个海报,以及整体的思路。 [图片] 海报生成流程 [代码片段]Canvas生成海报实战demo demo的微信路径:https://developers.weixin.qq.com/s/Q74OU3m57c9x demo的ID:Q74OU3m57c9x 如果你装了IDE工具,可以直接访问上面的demo路径 通过代码片段将demo的ID输入进去也可添加: [图片] [图片] 下面分享下主要的代码内容和“填坑现场”: 一、添加字体 https://developers.weixin.qq.com/miniprogram/dev/api/canvas/font.html [代码]canvasContext.font = value //示例 ctx.font = `normal bold 20px sans-serif`//设置字体大小,默认10 ctx.setTextAlign('left'); ctx.setTextBaseline("top"); ctx.fillText("《智酷方程式》专注研究和分享前端技术", 50, 15, 250)//绘制文本 [代码] 符合 CSS font 语法的 DOMString 字符串,至少需要提供字体大小和字体族名。默认值为 10px sans-serif 文字过长在canvas下换行问题处理(最多两行,超过“…”代替) [代码]ctx.setTextAlign('left'); ctx.setFillStyle('#000');//文字颜色:默认黑色 ctx.font = `normal bold 18px sans-serif`//设置字体大小,默认10 let canvasTitleArray = canvasTitle.split(""); let firstTitle = ""; //第一行字 let secondTitle = ""; //第二行字 for (let i = 0; i < canvasTitleArray.length; i++) { let element = canvasTitleArray[i]; let firstWidth = ctx.measureText(firstTitle).width; //console.log(ctx.measureText(firstTitle).width); if (firstWidth > 260) { let secondWidth = ctx.measureText(secondTitle).width; //第二行字数超过,变为... if (secondWidth > 260) { secondTitle += "..."; break; } else { secondTitle += element; } } else { firstTitle += element; } } //第一行文字 ctx.fillText(firstTitle, 20, 278, 280)//绘制文本 //第二行问题 if (secondTitle) { ctx.fillText(secondTitle, 20, 300, 280)//绘制文本 } [代码] 通过 ctx.measureText 这个方法可以判断文字的宽度,然后进行切割。 (一行字允许宽度为280时,判断需要写小点,比如260) 二、获取临时地址并设置图片 [代码]let mainImg = "https://demo.com/url.jpg"; wx.getImageInfo({ src: mainImg,//服务器返回的图片地址 success: function (res) { //处理图片纵横比例过大或者过小的问题!!! let h = res.height; let w = res.width; let setHeight = 280, //默认源图截取的区域 setWidth = 220; //默认源图截取的区域 if (w / h > 1.5) { setHeight = h; setWidth = parseInt(280 / 220 * h); } else if (w / h < 1) { setWidth = w; setHeight = parseInt(220 / 280 * w); } else { setHeight = h; setWidth = w; }; console.log(setWidth, setHeight) ctx.drawImage(res.path, 0, 0, setWidth, setHeight, 20, 50, 280, 220); ctx.draw(true); }, fail: function (res) { //失败回调 } }); [代码] 在开发过程中如果封面图无法按照约定的比例(280x220)给到: 那么我们就需要处理默认封面图过大或者过小的问题,大致思路是:代码中通过比较纵横比(280/220=1.27)正比例放大或者缩小原图,然后从左上切割,竟可能保证过高的图是宽度100%,过宽的图是高度100%。 在canvas中draw图片,必须是一个(相对)本地路径,我们可以通过将图片保存在本地后生成的临时路径。 微信官方提供两个API: wx.downloadFile(OBJECT)和wx.getImageInfo(OBJECT)。都需先配置download域名才能生效。 三、裁切“圆形”头像画图 [代码]ctx.save(); //保存画图板 ctx.beginPath()//开始创建一个路径 ctx.arc(35, 25, 15, 0, 2 * Math.PI, false)//画一个圆形裁剪区域 ctx.clip()//裁剪 ctx.closePath(); ctx.drawImage(headImageLocal, 20, 10, 30, 30); ctx.draw(true); ctx.restore()//恢复之前保存的绘图上下文 [代码] 使用图形上下文的不带参数的clip()方法来实现Canvas的图像裁剪功能。该方法使用路径来对Canvas话不设置一个裁剪区域。因此,必须先创建好路径。创建完整后,调用clip()方法来设置裁剪区域。 需要注意的是裁剪是对画布进行的,裁切后的画布不能恢复到原来的大小,也就是说画布是越切越小的,要想保证最后仍然能在canvas最初定义的大小下绘图需要注意save()和restore()。画布是先裁切完了再进行绘图。并不一定非要是图片,路径也可以放进去~ 小程序 canvas 裁切BUG [代码]ctx.setFillStyle("#fff"); ctx.fillRect(0, 0, 320, 500); //第一个填充矩形 wx.downloadFile({ url: headUri, success(res) { ctx.beginPath() ctx.arc(50, 50, 25, 0, 2 * Math.PI) ctx.clip() ctx.drawImage(res.tempFilePath, 25, 25); //第二个填充图片 ctx.draw() ctx.restore() ctx.setFillStyle("#fff"); ctx.fillRect(0, 0, 320, 500); ctx.draw(true) ctx.restore() } }) [代码] clip裁切这个功能,如果有超过一张图片/背景叠加,则裁切效果失效。 错误参考:http://html51.com/info-38753-1/ 四、将canvas导出成虚拟地址 [代码]wx.canvasToTempFilePath({ fileType: 'jpg', canvasId: 'customCanvas', success: (res) => { console.log(res.tempFilePath) //为canvas的虚拟地址 } }) res: { errMsg: "canvasToTempFilePath:ok", tempFilePath: "http://tmp/wx02935bb29080a7b4.o6zAJswFAuZuKQ5NZfPr….cGnD1a02PlVC0b3284be3a41d08986c2477579a5fd8e.jpg" } [代码] 这里需要把canvas里面的内容,导出成一个临时地址才能保存在相册,比如: http://tmp/wx02935bb29080a7b4.o6zAJswFAuZuKQ5NZfPr5UfJVR4k.cGnD1a02PlVC0b3284be3a41d08986c2477579a5fd8e.jpg 五、询问并获取访问手机本地相册权限 [代码]wx.getSetting({ success(res) { console.log(res) if (!res.authSetting['scope.writePhotosAlbum']) { //判断权限 wx.authorize({ //获取权限 scope: 'scope.writePhotosAlbum', success() { console.log('授权成功') //转化路径 self.saveImg(); } }) } else { self.saveImg(); } } }) [代码] 判断是否有访问相册的权限,如果没有,则请求权限。 六、保存到用户手机本地相册 [代码]wx.saveImageToPhotosAlbum({ filePath: res.tempFilePath, success: function (data) { wx.showToast({ title: '保存到系统相册成功', icon: 'success', duration: 2000 }) }, fail: function (err) { console.log(err); if (err.errMsg === "saveImageToPhotosAlbum:fail auth deny") { console.log("当初用户拒绝,再次发起授权") wx.openSetting({ success(settingdata) { console.log(settingdata) if (settingdata.authSetting['scope.writePhotosAlbum']) { console.log('获取权限成功,给出再次点击图片保存到相册的提示。') } else { console.log('获取权限失败,给出不给权限就无法正常使用的提示') } } }) } else { wx.showToast({ title: '保存失败', icon: 'none' }); } }, complete(res) { console.log(res); } }) [代码] 保存到本地需要一定的时间,需要加一个loading的状态。 七、关于组件中引用canvas [代码]let ctx = wx.createCanvasContext('posterCanvas',this); //需要加this [代码] 在components中canvas无法选中的问题: 在components自定义组件下,当前组件实例的this,表示在这个自定义组件下查找拥有 canvas-id 的 <canvas> ,如果省略则不在任何自定义组件内查找。
2021-09-13 - 小程序里的文件何去何从?
downloadFile;getFileSystemManager;openDocument 这三个是比较常用的文件相关api,不管是下载到临时路径、小程序隔离存储、直接打开,我们需要的不仅仅是只在自己的微信上通过小程序查看。 我们期望的是交互,是用户和我们的小程序形成一个生态圈,而不是让用户只在一个小小的小程序里边。 但是我们开发者是真的搞不懂微信相关到底是有什么顾虑,只是在自己一个小小的圈子里。再者说,我通过文件接口把文件下载然后打开之后,顺理成章的,肯定是转发给我的好友,但是这么自然的一种操作趋势还能被微信相关产品经理给生生的打断。 没有分享,没有转发,没有交互,我要downloadFile;getFileSystemManager;openDocument这三个接口有什么用,自己做着玩儿? 麻烦微信相关产品不要再孤芳自赏,怡然自得的把自己关在小程序这个小闭环里了。 最后贴上自己的代码,烦请微信官方考虑一下我们开发者的需求,不要只顾自己开心,好吧? [代码]const file_manager = wx.getFileSystemManager();[代码][代码]const file_dir = wx.env.USER_DATA_PATH + [代码][代码]"/a"[代码][代码];[代码] [代码]file_manager.access({[代码][代码] [代码][代码]path: file_dir,[代码][代码] [代码][代码]complete:[代码][代码]function[代码][代码]( result ) {[代码][代码] [代码][代码]let code_msg = result.errMsg;[代码][代码] [代码][代码]if[代码] [代码](code_msg.indexOf( [代码][代码]'ok'[代码] [代码]) == -1 ) {[代码][代码] [代码][代码]file_manager.mkdir({[代码][代码] [代码][代码]dirPath: file_dir,[代码][代码] [代码][代码]recursive: [代码][代码]true[代码][代码],[代码][代码] [代码][代码]complete: [代码][代码]function[代码] [代码](result) {[代码][代码] [代码][代码]console.log( result );[代码][代码] [代码][代码]}[代码][代码] [代码][代码]});[代码][代码] [代码][代码]}[代码][代码] [代码][代码]}[代码][代码]});[代码] [代码]wx.downloadFile({[代码][代码] [代码][代码]url:[代码][代码]'https://ncstatic.clewm.net/rsrc/2019/0410/15/1cad975df1a49179a2a4a46d7e7cbec0.pdf'[代码][代码],[代码][代码] [代码][代码]complete: [代码][代码]function[代码][代码](result) {[代码][代码] [代码][代码]if[代码] [代码]( result.statusCode != 200 ) {[代码][代码] [代码][代码]return[代码][代码];[代码][代码] [代码][代码]}[代码] [代码] [代码][代码]file_manager.saveFile({[代码][代码] [代码][代码]tempFilePath: result.tempFilePath,[代码][代码] [代码][代码]filePath:file_dir+[代码][代码]'/a.pdf'[代码][代码],[代码][代码] [代码][代码]complete:[代码][代码]function[代码][代码]( result ) {[代码][代码] [代码][代码]// 下载失败[代码][代码] [代码][代码]let code_msg = result.errMsg;[代码][代码] [代码][代码]if[代码] [代码](code_msg.indexOf([代码][代码]'ok'[代码][代码]) == -1) {[代码][代码] [代码][代码]return[代码][代码];[代码][代码] [代码][代码]}[代码][代码] [代码][代码]let saved_file_path = result.savedFilePath;[代码] [代码] [代码][代码]wx.openDocument({[代码][代码] [代码][代码]filePath: saved_file_path,[代码][代码] [代码][代码]fileType: [代码][代码]'pdf'[代码][代码],[代码][代码] [代码][代码]complete: [代码][代码]function[代码] [代码](result) {[代码][代码] [代码][代码]console.log(result);[代码][代码] [代码][代码]}[代码][代码] [代码][代码]});[代码][代码] [代码][代码]// console.log( saved_file_path );[代码][代码] [代码][代码]// wx.chooseMessageFile({[代码][代码] [代码][代码]// count:1,[代码][代码] [代码][代码]// type: 'all',[代码][代码] [代码][代码]// success(res) {[代码][代码] [代码][代码]// // tempFilePath可以作为img标签的src属性显示图片[代码][代码] [代码][代码]// console.log(res );[代码][代码] [代码][代码]// }[代码][代码] [代码][代码]// });[代码][代码] [代码][代码]}[代码][代码] [代码][代码]});[代码][代码] [代码][代码]}[代码][代码]});[代码]
2019-04-10 - 「小程序·云开发」资源配额调整和功能更新
各位开发者: 大家好 为了让开发者能够更方便的使用小程序·云开发。我们对云开发提供的基础资源配额进行了调整,具体调整内容包括: 存储上传次数由 2 万/天调整为 60 万/月 存储下载次数由 5 万/天调整为150 万/月 去除数据库 QPS 限制 数据库单集合索引调整为系统参数限制,由原有 10 个调整为 20 个 云函数数量调整为系统参数限制,由原有 20 个调整为 50 个 增加数据库流量说明,单次出包大小为 16 M 其余参数不做调整。调整后的配额信息可参考 小程序·云开发配额。 同时,由于近期小程序·云开发将上线付费功能(付费功能针对非基础资源配额,基础资源配额仍可免费使用)。为了给开发者更充足的时间进行调整,对于截止 2019-05-17 日前通过邮件申请调整的配额(非基础资源配额)的截止日期统一延长至 2019-06-30 。且对于已申请过配额调整的小程序帐号: 存储上传次数和存储下载次数调整为按月计算。如之前存储上传次数为 10 万/天,则调整后为 300 万/月 去掉数据库 QPS 限制 数据库单集合索引调整为系统参数限制,由原有 10 个调整为 20 个 云函数数量调整为系统参数限制,由原有 20 个调整为 50 个 增加数据库流量说明,单次出包大小为 16 M 云开发控制台新增详细的资源使用量统计 为了方便开发者了解资源使用情况,我们在云开发控制台的资源使用中增加了详细的资源使用量统计数据以及资源生命周期。开发者可通过下载最新 RC Build 版的开发者工具进行功能体验。 [图片] 小程序·云开发新增 HTTP API 为了进一步降低开发门槛并解决数据孤岛问题,小程序·云开发新增 HTTP API 支持。HTTP API 提供了小程序外访问云开发资源的能力,使用 HTTP API 开发者可在已有服务器上访问云资源,实现与云开发资源的互通。具体使用方法请参考 小程序·云开发HTTP API文档。 云调用支持开放数据调用 为了方便开发者更快速的进行功能迭代,云调用支持开放数据调用。对返回敏感开放数据的小程序端接口,从基础库 2.7.0 起,如果小程序已开通云开发,则可在开放数据接口的返回值中获取到唯一对应敏感开放数据的 cloudID,通过云调用可以直接获取到开放数据,具体使用方法请参考 云调用直接获取开放数据。 微信团队 2019.05.17
2019-05-17 - 各位保护好代码
微信小游戏可以被反编译的问题怎么解决. 和小程序一样,单机版的小游戏可以被直接反编译,并且上架. 这里给出两个游戏的试玩视频,一个是我的,一个是别人的 引力怪[我的] https://v.youku.com/v_show/id_XMzc0NDE4OTIyMA==.html?spm=a2h3j.8428770.3416059.1 观看密码:123456 无尽黑洞[盗版]试玩视频: https://v.youku.com/v_show/id_XMzc0NDE5MDY1Ng==.html?spm=a2h3j.8428770.3416059.1 观看密码:123456 附上一个横向对比图: [图片] 两次投诉结果: [图片]
2018-07-26 - 如何打造一份0 bug 的js代码!
说到程序开发,bug总是如影随形,开发过程中50%的时间在debug,30%是在修之前发布了的bug,毫不奇怪。 如何把bug见到最少,甚至是0 bug呢?看似遥不可及,但实际是可以追求的,方法就是完整科学的测试,事实上,测试和使用是证明代码没bug的唯一方式。 测试又分为白盒测试跟黑盒测试,一般来说,产品上线前经过测试同学测试做的功能测试都是黑盒测试,毕竟测试同学不可能了解所有的代码,而能做白盒测试的,都是最了解代码的人,也就是写代码的程序员。而只有最了解代码的人也才能写出最完善的测试用例。而单元测试就是白盒测试里面最重要的方法之一。 很多程序员,特别是前端程序员是没有写单元测试的习惯。诚然,写单元测试其实是比较费劲的事情,特别在前端领域,涉及大量ui及交互操作,写单元测试尤为困难。但是近年来js模块化越演越烈,模块化的同时也使得写单元测试变得更容易了。 本文就致力于探讨在当前的开发环境下做单元测试的一些实践方法。 先介绍下被测试的对象 —— 一个js工具库,这个公共库有几个比较重要的标签:ES6,移动端,浏览器端。 从最简单的讲起吧,js的测试框架其实不算少,比较有名的有mocha,Jasmine,Jest等,基本用法都比较简单明了,看看官方文档就大概能写出来一些测试用例了。下面我主要使用比较强大mocha来作为主要的测试框架。 下面是我项目中的一份test.js: [代码]import * as lib from './index'; import chai from 'chai'; let expect = chai.expect; describe('testcase',function(){ it('single one',function(){ let a = 'aer'; expect(a).to.be.a('string'); }); it('test /common/getUrlParam',function(){ let ret = lib.common.utils.getUrlParam('a','http://vip.qq.com?a=1'); expect(ret).to.equal('1'); ret = lib.common.utils.getUrlParam('a','http://vip.qq.com#a=1'); expect(ret).to.equal('1'); ret = lib.common.utils.getUrlParam('a','https://vip.qq.com?a=1'); expect(ret).to.equal('1'); ret = lib.common.utils.getUrlParam('b','http://vip.qq.com?a=1&b=2'); expect(ret).to.equal('2'); ret = lib.common.utils.getUrlParam('a','http://vip.qq.com?a=1#a=2'); expect(ret).to.equal('2'); }); }); [代码] ES6 这份test.js包含两个测试用例,第一用例是用于测试的,单纯试用下测试框架跟断言库的功能,第二个用例是对库文件中一个模块的一个方法的测试用例,估计前端大佬们看看方法名大概都能看懂是什么东西,我就不多说了。写好test.js后就跑了试试看,运行mocha,然后马上就报错了: [代码]JUSTYNCHEN-MC0:gxh-lib-es6 justynchen$ ./node_modules/.bin/mocha /path/to/your/project/somelib/test.js:1 (function (exports, require, module, __filename, __dirname) { import * as lib from './index'; ^ SyntaxError: Unexpected token * at new Script (vm.js:79:7) at createScript (vm.js:251:10) at Object.runInThisContext (vm.js:303:10) at Module._compile (internal/modules/cjs/loader.js:656:28) ... [代码] 我当前的node版本是v10.13.0(对,近日node的LTS版本已经升级到10.x了,没升的同学赶紧玩玩吧),按理说是默认支持import的。仔细一看报错,原来mocha不是直接执行test.js,而是把test.js的内容放到了一个沙箱里面执行的。那就有点蛋疼了,就算node新版本已经支持了也没法直接使用。第一想法就是先把代码编译了,然后再做测试,可是测试的代码都是编译后的代码,就算测试出什么问题,还要经过sourcemap才能找到源码中出错的位置,想想都蛋疼。 官方当然是不会这么蠢的,稍微找了下官方的方案,不难找到对es6的支持。babel提供了一个register,给到不同的应用去做转换,mocha同样也可以使用这个register先转换然后再跑。命令就变成了这个: [代码]mocha --require babel-core/register [代码] 同时,package.json里面的选项也要加上babel的选项: [代码]"babel": { "presets": [ "stage-3", "latest" ], }, [代码] 当然,相关的包(babel,babel-core)也要同时装上,大家都懂的后面我就不提了,缺啥装啥就对了,后面提到的工具如没特别说明都是指npm包。 到这里相关的资料还比较好找,接下来就是干货了。 browser 运行上面改造过的mocha命令,是不是就ok了呢?当然没那么顺利,这里遇到了第二个坑: [代码]./node_modules/.bin/mocha --require babel-core/register /path/to/your/project/somelib/common/cache.js:15 var storage = window[storageType]; ^ ReferenceError: window is not defined at initStorage (/Users/justynchen/....../cache.js:10:16) at Object.<anonymous> (/Users/justynchen/....../cache.js:41:16) at Module._compile (internal/modules/cjs/loader.js:688:30) [代码] 前面也说了这份库是给移动端浏览器用的,其中就免不了使用一些浏览器的API,这些API在node里面都是不存在的。解决方案有两个: 直接不测试使用了浏览器API的代码,使用前先做检测并return掉。 找一个模拟浏览器的环境,让浏览器的API也能正常执行。 方案一是我们不愿意看到的,特别是一份浏览器用的库,不测试浏览器相关的特性那跟咸鱼有什么区别【手动狗头】。那就按方案二的思路想走吧,想到node模拟浏览器的环境,脑中浮现的第一个名词估计大部分人跟我都一样 —— electron。作为业界最著名的“没有界面的浏览器”,用在这里再合适不过了。但是该怎么用呢,稍作搜索,果然已经有前人做了相应的工作,有一个electron-mocha的工具刚好就是把这两个东西合了起来。 然后命令就变成了这样: [代码]electron-mocha --renderer --require babel-core/register [代码] 然后终于得到了我们想要的结果: [代码]JUSTYNCHEN-MC0:somelib justynchen$ ./node_modules/.bin/electron-mocha --renderer --require babel-core/register testcase ✓ single one ✓ test /common/getUrlParam 2 passing (37ms) [代码] 完美~(请自动脑补金星脸) 可是,就这么完了是否有点意犹未尽? 是的,就是缺了点什么东西,说好的0 bug呢,写了测试用例就能保证0 bug了么?肯定不是的,如果有的地方就是有bug,只是用例没写好,并没有覆盖到有问题的地方怎么办?只有写“全”了的测试用例才能保证0 bug。如何确保用例写全了呢?请往下看。 代码测试覆盖率 这里引入一个概念,叫代码测试覆盖率,大概意思就是说,你的测试用例到底覆盖了多少的代码。理想情况下,肯定只有100%覆盖所有代码的用例,才能说自己经过测试的代码是0 bug的,当然现实中100%总是很难的,一般覆盖到90%以上已经是比较理想的情况了。 JS也有统计代码测试覆盖率的库 —— Istanbul。这库名也很有意思,库名直译是伊斯坦布尔,没错,就是那个正常中国人可能名字都没听说过的中东城市。这个地方有个特产是毯子,然后这个库的作者就想,覆盖就是毯子该做的事情嘛,脑洞一开就把库名起作Istanbul了。 继续“稍微读下文档”,哦,原来这个库有一个命令行工具,nyc,装上然后放到执行命令的前面就能做覆盖率统计了。然后就有了下面的命令 [代码]nyc --reporter=lcov --reporter=text electron-mocha --renderer --require babel-core/register [代码] 其中reporter是定制化报告的内容,默认是text,lcov就是生成一个网页版的覆盖率报告。 然而,跑完之后是酱婶儿的 [代码]JUSTYNCHEN-MC0:somelib justynchen$ ./node_modules/.bin/nyc ./node_modules/.bin/electron-mocha --renderer --require babel-core/register testcase ✓ single one ✓ test /common/getUrlParam ✓ aidMaker test 3 passing (18ms) ----------|----------|----------|----------|----------|-------------------| File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | ----------|----------|----------|----------|----------|-------------------| All files | 0 | 0 | 0 | 0 | | ----------|----------|----------|----------|----------|-------------------| [代码] 苍天大地啊,为啥啥都没有。。。。 这里就又开始苦逼的查资料环节,不得不吐槽一下,这个资料是真不好查,中文资料没有就算了,反正早习惯了,文档翻遍了还是没有。。。那就过分了。然后查了下别的资料,基本都是mocha跟Istanbul一起用的,也没有electron-mocha相关的。 最后还是找到了github的issue里面,果然有人是跟我有类似问题,找了几个提了没啥回音的,终于找到一个maintainer的回复。里面指向了一个插件 —— babel-plugin-istanbul。 皇天不负有心人,得益于之前已经引入了babel,这里只要加上这个插件就ok了,照例先装包,package.json里面的babel配置加上这个参数 [代码]"babel": { "presets": [ "stage-3", "latest" ], "env": { "test": { "plugins": [ "istanbul" ] } } }, [代码] 然后按照插件的README改一下命令,最终得到了这个 [代码]cross-env NODE_ENV=test nyc --reporter=lcov --reporter=text electron-mocha --renderer --require babel-core/register [代码] 然后一跑。。。发现,x你x的。。。跟前面的输出一毛一样,啥的没有!!! 冷静一下,再回头看看maintainer回复的原话 Basically, yes. Note that if you’re using babel already you can now just use the excellent istanbul-plugin to instrument your code. If you do this, you really only need to write out the __coverage__ object after the tests have run. (I write them out for both renderer and main thread tests, then combine them using nyc report from the command line). 里面提到了一个奇怪的参数__coverage__,又查一波资料,在这个项目的某些commit comment里面看到这个__coverage__的蛛丝马迹,这东西好像是一个全局参数,那这个参数有啥用咧?管他有用没用,打出来看看再说,然后就在test.js里面吧这个参数打了下,发现,卧槽,还真有,而且里面不就是覆盖率的数据么???? 嗯,有数据。。。怎么生成报告呢?作者写的语焉不详。。。啥叫write out这个参数然后配合nyc report命令就能用了,write到啥地方啊,咋配合啊!!! 这个时候就想到了Istanbul的一些特性,其实它是会在测试后生成一个.nyc_output的文件夹的,打开一看,里面不就是一些json么!那是不是直接write进去就好了呢?文件该叫啥名字咧,原来的文件都是hash命名的,这hash哪来的呀。不管了写了再说,然后得到如下test.js [代码]import * as lib from './index'; import chai from 'chai'; import fs from 'fs'; let expect = chai.expect; describe('testcase',function(){ it('single one',function(){ let a = 'aer'; expect(a).to.be.a('string'); }); it('test /common/getUrlParam',function(){ let ret = lib.common.utils.getUrlParam('a','http://vip.qq.com?a=1'); expect(ret).to.equal('1'); ret = lib.common.utils.getUrlParam('a','http://vip.qq.com#a=1'); expect(ret).to.equal('1'); ret = lib.common.utils.getUrlParam('a','https://vip.qq.com?a=1'); expect(ret).to.equal('1'); ret = lib.common.utils.getUrlParam('b','http://vip.qq.com?a=1&b=2'); expect(ret).to.equal('2'); ret = lib.common.utils.getUrlParam('a','http://vip.qq.com?a=1#a=2'); expect(ret).to.equal('2'); }); after(function() { fs.writeFileSync('./.nyc_output/coverage.json',JSON.stringify(__coverage__)); }); }); [代码] 终于生效了 [代码]JUSTYNCHEN-MC0:somelib justynchen$ tnpm run test > @tencent/somelib@1.0.1 test /path/to/your/project/..... > cross-env NODE_ENV=test nyc --reporter=lcov --reporter=text electron-mocha --renderer --require babel-core/register testcase ✓ single one ✓ test /common/getUrlParam 3 passing (26ms) ----------------------|----------|----------|----------|----------|-------------------| File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | ----------------------|----------|----------|----------|----------|-------------------| All files | 5.96 | 3.3 | 3.6 | 6.02 | | gxh-lib-es6 | 0 | 0 | 0 | 0 | | index.js | 0 | 0 | 0 | 0 | | gxh-lib-es6/business | 16.89 | 8.84 | 3.45 | 16.89 | | aid-maker.js | 80 | 66.67 | 100 | 80 | 325,340,341,344 | cgi-handler.js | 0 | 0 | 0 | 0 |... 57,159,160,161 | index.js | 0 | 0 | 0 | 0 | | pay.js | 0 | 0 | 0 | 0 |... 86,88,89,91,97 | .... [代码] 至此,终于可以说出那句 完美~ 附录 整体架构图 [图片] 后记:整个单元测试的技术其实都没什么困难的,基本上都有库可以用,主要把时间都花在了查询资料上面。写本文的时候好像是遇到问题马上就找到了解决方案,其实真实情况是,几乎遇到每个坑都会试了至少一两个走不通的方案,最后才找到正确的方案的,所以对于不经常关注社区的人来说,单靠文档是很难解决所有的问题的。单从前端领域来看,前端的技术日新月异,再完善的文档都很快会跟不上发展的步伐,还是要靠多关注社区的动向,甚至多参与社区的讨论和建设才不至于在需要用某些技术的时候无从下手。
2019-03-29 - 有赞美业店铺装修前端解决方案
一、背景介绍 做过电商项目的同学都知道,店铺装修是电商系统必备的一个功能,在某些场景下,可能是广告页制作、活动页制作、微页面制作,但基本功能都是类似的。所谓店铺装修,就是用户可以在 PC 端进行移动页面的制作,只需要通过简单的拖拽就可以实现页面的编辑,属于用户高度自定义的功能。最终编辑的结果,可以在 H5、小程序进行展示推广。 有赞美业是一套美业行业的 SaaS 系统,为美业行业提供信息化和互联网化解决方案。有赞美业本身提供了店铺装修的功能,方便用户自定义网店展示内容,下面是有赞美业店铺装修功能的截图: [图片] 上面的图片是 PC 端的界面,下面两张图分别是 H5 和小程序的最终展示效果。可以简单地看到,PC 端主要做页面的编辑和预览功能,包括了丰富的业务组件和详细的自定义选项;H5 和小程序则承载了最终的展示功能。 再看看有赞美业当前的技术基本面:目前我们的 PC 端是基于 React 的技术栈,H5 端是基于 Vue 的技术栈,小程序是微信原生开发模式。 在这个基础上,如果要做技术设计,我们可以从以下几个角度考虑: 三端的视图层都是数据驱动类型,如何管理各端的数据流程? 三个端三种不同技术栈,业务中却存在相同的内容,是否存在代码复用的可能? PC 最终生成的数据,需要与 H5、小程序共享,三端共用一套数据,应该通过什么形式来做三端数据的规范管理? 在扩展性上,怎么低成本地支持后续更多组件的业务加入? 二、方案设计 所以我们针对有赞美业的技术基本面,设计了一个方案来解决以上几个问题。 首先摆出一张架构图: [图片] 2.1 数据驱动 首先关注 CustomPage 组件,这是整个店铺装修的总控制台,内部维护三个主要组件 PageLeft、 PageView 和 PageRight,分别对应上面提到的 PC 端3个模块。 为了使数据共享,CustomPage 通过 React context 维护了一个”作用域“,提供了内部三个组件共享的“数据源”。 PageLeft 、 PageRight 分别是左侧组件和右侧编辑组件,共享 context.page 数据,数据变更则通过 context.pageChange 传递。整个过程大致用代码表示如下: [代码]// CustomerPage class CustomerPage extends React.Component { static childContextTypes = { page: PropTypes.object.isRequired, pageChange: PropTypes.func.isRequired, activeIndex: PropTypes.number.isRequired, }; getChildContext() { const { pageInfo, pageLayout } = this.state; return { page: { pageInfo, pageLayout }, pageChange: this.pageChange || (() => void 0), activeIndex: pageLayout.findIndex(block => block.active), }; } render() { return ( <div> <PageLeft /> <PageView /> <PageRight /> </div> ); } } // PageLeft class PageLeft extends Component { static contextTypes = { page: PropTypes.object.isRequired, pageChange: PropTypes.func.isRequired, activeIndex: PropTypes.number.isRequired, }; render() {...} } // PageRight class PageRight extends Component { static contextTypes = { page: PropTypes.object.isRequired, pageChange: PropTypes.func.isRequired, activeIndex: PropTypes.number.isRequired, }; render() {...} } [代码] 至于 H5 端,可以利用 Vue 的动态组件完成业务组件的动态化,这种异步组件的方式提供了极大的灵活性,非常适合店铺装修的场景。 [代码]<div v-for="item in components"> <component :is="item.component" :options="convertOptions(item.options)" :isEdit="true"> </component> </div> [代码] 小程序因为没有动态组件的概念,所以只能通过 if else 的面条代码来实现这个功能。更深入的考虑复用的话,目前社区有开源的工具实现 Vue 和小程序之间的转换,可能可以帮助我们做的更多,但这里就不展开讨论了。 PC 编辑生成数据,最终会与 H5、小程序共享,所以协商好数据格式和字段含义很重要。为了解决这个问题,我们抽取了一个npm包,专门管理3端数据统一的问题。这个包描述了每个组件的字段格式和含义,各端在实现中,只需要根据字段描述进行对应的样式开发就可以了,这样也就解决了我们说的扩展性的问题。后续如果需要增加新的业务组件,只需要协商好并升级新的npm包,就能做到3端的数据统一。 [代码]/** * 显示位置 */ export const position = { LEFT: 0, CENTER: 1, RIGHT: 2, }; export const positionMap = [{ value: position.LEFT, name: '居左', }, { value: position.CENTER, name: '居中', }, { value: position.RIGHT, name: '居右', }]; [代码] 2.2 跨端复用 PageView 是预览组件,是这个设计的核心。按照最直接的思路,我们可能会用 React 把所有业务组件都实现一遍,然后把数据排列展示的逻辑实现一遍;再在 H5 和小程序把所有组件实现一遍,数据排列展示的逻辑也实现一遍。但是考虑到代码复用性,我们是不是可以做一些“偷懒”? 如果不考虑小程序的话,我们知道 PC 和 H5 都是基于 dom 的样式实现,逻辑也都是 js 代码,两端都实现一遍的话肯定做了很多重复的工作。所以为了达到样式和逻辑复用的能力,我们想了一个方法,就是通过 iframe 嵌套 H5 的页面,通过 postmessage 来做数据交互,这样就实现了用 H5 来充当预览组件,那么 PC 和 H5 的代码就只有一套了。按照这个实现思路,PageView 组件可以实现成下面这样: [代码]class PageView extends Component { render() { const { page = {} } = this.props; const { pageInfo = {}, pageLayout = [] } = page; const { loading } = this.state; return ( <div className={style}> <iframe title={pageInfo.title} src={this.previewUrl} frameBorder="0" allowFullScreen="true" width="100%" height={601} ref={(elem) => { this.iframeElem = elem; }} /> </div>); } } [代码] PageView 代码很简单,就是内嵌 iframe,其余的工作都交给 H5。H5 将拿到的数据,按照规范转换成对应的组件数组展示: [代码]<template> <div> <component v-for="(item, index) in components" :is="item.component" :options="item.options" :isEdit="false"> </component> </div> </template> <script> computed: { components() { return mapToComponents(this.list); }, }, </script> [代码] 因为有了 iframe ,还需要利用 postmessage 进行跨源通信,为了方便使用,我们做了一层封装(代码参考自有赞餐饮): [代码]export default class Messager { constructor(win, targetOrigin) { this.win = win; this.targetOrigin = targetOrigin; this.actions = {}; window.addEventListener('message', this.handleMessageListener, false); } handleMessageListener = (event) => { // 我们能相信信息的发送者吗? (也许这个发送者和我们最初打开的不是同一个页面). if (event.origin !== this.targetOrigin) { console.warn(`${event.origin}不对应源${this.targetOrigin}`); return; } if (!event.data || !event.data.type) { return; } const { type } = event.data; if (!this.actions[type]) { console.warn(`${type}: missing listener`); return; } this.actions[type](event.data.value); }; on = (type, cb) => { this.actions[type] = cb; return this; }; emit = (type, value) => { this.win.postMessage({ type, value, }, this.targetOrigin); return this; }; destroy() { window.removeEventListener('message', this.handleMessageListener); } } [代码] 在此基础上,业务方就只需要关注消息的处理,例如 H5 组件接收来自 PC 的数据更新可以这样用: [代码]this.messager = new Messager(window.parent, `${window.location.protocol}//mei.youzan.com`); this.messager.on('pageChangeFromReact', (data) => { ... }); [代码] 这样通过两端协商的事件,各自进行业务逻辑处理就可以了。 这里有个细节需要处理,因为预览视图高度会动态变化,PC 需要控制外部视图高度,所以也需要有动态获取预览视图高度的机制。 [代码]// vue script updated() { this.$nextTick(() => { const list = document.querySelectorAll('.preview .drag-box'); let total = 0; list.forEach((item) => { total += item.clientHeight; }); this.messager.emit('vueStyleChange', { height: total }); } } // react script this.messsager.on('vueStyleChange', (value) => { const { height } = value; height && (this.iframeElem.style.height = `${height}px`); }); [代码] 2.3 拖拽实现 拖拽功能是通过 HTML5 drag & drop api 实现的,在这次需求中,主要是为了实现拖动过程中组件能够动态排序的效果。这里有几个关键点,实现起来可能会花费一些功夫: 向上向下拖动过程中视图自动滚动 拖拽结果同步数据变更 适当的动画效果 目前社区有很多成熟的拖拽相关的库,我们选用了vuedraggable。原因也很简单,一方面是避免重复造轮子,另一方面就是它很好的解决了我们上面提到的几个问题。 vuedraggable 封装的很好,使用起来就很简单了,把我们前面提到的动态组件再封装一层 draggable 组件: [代码]<draggable v-model="list" :options="sortOptions" @start="onDragStart" @end="onDragEnd" class="preview" :class="{dragging: dragging}"> <div> <component v-for="(item, index) in components" :is="item.component" :options="item.options" :isEdit="false"> </component> </div> </draggable> const sortOptions = { animation: 150, ghostClass: 'sortable-ghost', chosenClass: 'sortable-chosen', dragClass: 'sortable-drag', }; // vue script computed: { list: { get() { return get(this.designData, 'pageLayout') || []; }, set(value) { this.designData.pageLayout = value; this.notifyReact(); }, }, components() { return mapToComponents(this.list); }, }, [代码] 三、总结 到这里,所有设计都完成了。总结一下就是:PC 端组件间主要通过 React context 来做数据的共享;H5 和 小程序则是通过数据映射对应的组件数组来实现展示;核心要点则是通过 iframe 来达到样式逻辑的复用;另外可以通过第三方npm包来做数据规范的统一。 当然除了基本架构以外,还会有很多技术细节需要处理,比如需要保证预览组件不可点击等,这些则需要在实际开发中具体处理。
2019-03-01 - 我是如何从零开始写出一个微信小程序的
话不多说,直接开干 工具:Bmob后端云 新建小程序项目 一、新建项目选择小程序项目,选择代码存放的硬盘路径,填入你的小程序 AppID,给你的项目起一个好听的名字,最后,点击确定,你就得到了你的小程序了开发者工具传送门 目录结构 page index index.js index.wxml index.wxss logs logs.js logs.json logs.wxml logs.wxss utils util.js app.js app.json app.wxss project.config.json 下载和安装BmobSDK 一、把"bmob-min.js"和"underscore.js"放到相应的文件,例如放到utils中 二、接着是在app.js中加入下面两行代码进行全局初始化 [代码]const Bmob = require('utils/bmob.js'); Bmob.initialize("你的Application ID", "你的REST API Key"); [代码] 数据库的搭建 一、创建一个名字为[代码]detail[代码]的表,然后点击添加列创建3个字段,一个一个的添加 [代码]title[代码]字段,[代码]String[代码] 类型,用于存放文章的标题 [代码]image[代码]字段,[代码]String[代码] 类型,用于存放文章的图片 [代码]detail[代码]字段 [代码]String[代码]类型,用于存放文章的正文 然后添加一些数据进行测试 列表页面实现 一、去到index.js中 [代码]Ctrl + A[代码]然后按[代码]Delete[代码]清空这个页面,然后自己来写逻辑吧,首先我们需要引入[代码]bmob.js[代码] 然后在[代码]onLoad[代码]小程序生命周期中去请求[代码]detail[代码]表中的数据,让bmob和小程序完成第一次交互 [代码]//index.js //获取应用实例 const Bmob = require('../../utils/bmob.js'); //每个需要使用到bmob的页面都需要引入这个js Page({ onLoad() { let Diary = Bmob.Object.extend("detail"); //引入detail这张表 let query = new Bmob.Query(Diary); query.find({ success: (results) => { console.log(results)//打印数据 }, error(error) { console.log(`查询失败: ${error.code } ${error.message}`); } }); }, }) [代码] 这里完成了一次对bmob的数据查询,bmob文档传送门, 这个查询默认返回10条记录。当数据多了之后,一次查询很多数据,这样是不友好的,并不是说bmob查询数据慢,而是指如果将来你的用户在网速比较慢的情况使用你的小程序,请求数据等待时间过长,这个等待的过程也许会导致用户不再使用你的小程序。所以我们需要优化用户体验。那么将代码改造成一上拉加载更多。 [代码]//index.js //获取应用实例 const app = getApp(); const Bmob = require('../../utils/bmob.js'); //每个需要使用到bmob的页面都需要引入这个js Page({ data: { detail:[], //页面数据 pagination:0, //页码 pageSize: 4, //每页数据 nodata:true //无数据 }, onLoad() { //初始页面第一次获取页面数据 this.getData(); }, getData(){ let Diary = Bmob.Object.extend("detail"); //引入detail这张表 let query = new Bmob.Query(Diary); query.limit(this.data.pageSize); //返回n条数据 query.skip(this.data.pageSize * this.data.pagination); //分页查询 query.descending('createdAt'); //已created列倒序 query.find({ success: (results) => { let data = []; //将得到的数据存入数组 for (let object of results) { data.push({ id: object.id, title: object.get('title'), image: object.get('image'), detail: object.get('detail'), createdAt: app.timeago(object.createdAt) //调用timeagoJs把日期转成N天前格式 }) } //判断是否有数据返回 if(data.length){ let detail = this.data.detail; //得到页面上已经存在的数据(数组) let pagination = this.data.pagination; //获取当前分页(第几页) detail.push.apply(detail, data); //将页面上面的数组和最新获取到的数组进行合并 pagination = pagination ? pagination+1 : 1; //此处用于判断是首次渲染数据还是下拉加载渲染数据 //更新数据 this.setData({ detail: detail, pagination: pagination }) }else{ //没有返回数据,页面不再加载数据 this.setData({ nodata: false }) } }, error(error) { console.log(`查询失败: ${error.code } ${error.message}`); } }); }, router(e) { //跳转至文章详情页 const id = e.currentTarget.dataset.id wx.navigateTo({ url: `../detail/detail?id=${id}` }) }, onReachBottom(){ //下拉触底加载更多数据 this.getData(); } }) [代码] 上述代码中在日期处使用了[代码]timeagoJs[代码]下载地址,下载[代码]timeagoJs[代码]放到[代码]util[代码]文件夹中,然后在app.js中引入。 [代码]//app.js const Bmob = require('./utils/bmob.js') const timeago = require("./utils/timeago.min.js"); Bmob.initialize("你的Application ID", "你的REST API Key"); App({ timeago(date){ return new timeago().format(date, 'zh_CN') } }) [代码] 二、完成了列表页逻辑层之后,去到index.wxml编写视图层,视图层就简单许多了,得到的数据是一个数组,数组里面是一个json,用[代码]wx:for[代码]方法把它渲染出来就好了 [代码]<!--index.wxml--> <view class='container'> <view class='pane' wx:for='{{detail}}' wx:key="index" data-id='{{item.id}}' bindtap='router'> <image src='{{item.image}}'mode='aspectFill'></image> <view class='content'> <text class='title'>{{ item.title }}</text> <text class='date'>{{ item.createdAt }}</text> </view> </view> <view wx:if='{{!nodata}}' class='nodata'>没有更多数据了</view> </view> [代码] 三、来对页面进行一些美化,编写一样样式吧。毕竟这是一个看脸的社会 [代码]/**index.wxss**/ .container{ padding: 30rpx;} .pane{ width: 100%; margin-bottom:30rpx; border-radius: 5rpx; overflow: hidden; box-shadow: 0 0 10rpx rgba(0, 0, 0, .1) } .pane image{ width: 100%; height: 240rpx; display: block;} .pane .content{ background-color: #FFF; padding: 20rpx;} .pane .title{ display: block; font-size: 30rpx; font-weight: bold; margin-bottom: 20rpx;} .pane .date{ display: block; font-size: 24rpx; color: #999} .nodata{ text-align: center; font-size: 24rpx; color: #999} [代码] 如果你不喜欢写这些ui布局,或者前端ui,css比较差,可以直接用别人写好的现成的样式传送门。 以上列表页面算是完成了。此时点击页面的时候,应该会报错,提示detail页面未配置,那来到app.json里面配置一下detail这个页面。文章的id已经传过来了,文章的详情页就当是自己的一个小练习,熟悉bmob吧。 欢迎沟通: Q群:372103594 个人QQ:2967459363
2019-03-05 - 【优化】小程序优化-代码篇
本文主要是从代码方面跟大家分享我自己在开发小程序的一些做法,希望能帮到一些同学。 前言 不知道大家有没有这种体会,刚到公司时,领导要你维护之前别人写的代码,你看着别人写的代码陷入了深深的思考:“这谁写的代码,这么残忍” [图片] 俗话说“不怕自己写代码,就怕改别人的代码”,一言不和就改到你吐血,所以为了别人好,也为了自己好,代码规范,从我做起。 项目目录结构 在开发之前,首先要明确你要做什么,不要一上来就是干,咱们先把项目结构搭好。一般来说,开发工具初始化的项目基本可以满足需求,如果你的项目比较复杂又有一定的结构的话就要考虑分好目录结构了,我的做法如下图: [图片] component文件夹是放自定义组件的 pages放页面 public放公共资源如样式表和公共图标 units放各种公共api文件和封装的一些js文件 config.js是配置文件 这么分已经足以满足我的需求,你可以根据自己的项目灵活拆分。 配置文件 我的项目中有个config.js,这个文件是用来配置项目中要用到的一些接口和其它私有字段,我们知道在开发时通常会有测试环境和正式环境,而测试环境跟正式环境的域名可能会不一样,如果不做好配置的话直接写死接口那等到上线的时候一个个改会非常麻烦,所以做好配置是必需的,文件大致如下: [图片] 首先是定义域名,然后在config对象里定义接口名称,getAPI(key)是获取接口方法,最后通过module暴露出去就可以了.引用的时候只要在页面引入 import domain from ‘…/…/config’;,然后wx.request的时候url的获取方式是domain.getAPI(’’) 代码健壮性、容错性 例子 代码的健壮性、容错性也是我们应该要考虑的一点,移动端的项目不像pc端的网络那么稳定,很多时候网络一不稳定就决定我们的项目是否能正常运行,而一个好的项目就一定要有良好的容错性,就是说在网络异常或其它因素导致我们的项目不能运行时程序要有一个友好的反馈,下面是一个网络请求的例子: [图片] 相信多数人请求的方式是这样,包括我以前刚接触小程序的时候也是这样写,这样写不是说不好,而是不太严谨,如果能够正常获取数据那还好,但是一旦请求出现错误那程序可以到此就没法运行下去了,有些比较好的会加上faill失败回调,但也只是请求失败时的判断,在请求成功到获取数据的这段流程内其实是还有一些需要我们判断的,一般我的做法是这样: [图片] 在请求成功后小程序会进行如下判断: 判断是否返回200,是则进行一下步操作,否则抛出错误 判断数据结构是否完整,是则进行一下步操作,否则抛出错误 然后就可以在页面根据情况进行相应的操作了。 定制错误提示码 可以看到上面的截图的错误打印后面会带一个gde0或gde1的英文代码,这个代码是干嘛用的呢,其实是用来报障的,当我们的小程序上线后可能会遇到一些用户发来的报障,一般是通过截图发给我们,之前没有做错误提示码的时候可能只是根据一句错误提示来定位错误,但是很多时候误提示语都是一样的,我们根本不知道是哪里错了,这样一来就不能很快的定位的错误,所以加上这样一个提示码,到时用户一发截图来,我们只要根据这个错误码就能很快的定位错误并解决了,错误提示码建议命名如下: 不宜过长,3个字母左右 唯一性 意义明确 像上面gde表示获取草稿失败,后面加上数字表示是哪一步出错。 模块化 我们组内的大神说过, 模块化的意义在义分治,不在于复用。 之前我以为模块化只是为了可以复用,其实不然,无论模块多么小也是可以模块化,哪怕只是一个简单的样式也一样,并是不为了复用,而是管理起来方便。 很多同学经常将一些公共的样式事js放在app.wxss和app.js里以便调用,这样做其实有一个坏处,就是维护性比较差,如果是比较小的项目还好,项目一大问题就来了。而且项目是会迭代的,不可能总是一个人开发,可能后面会交接给其他人开发,所以会造成的问题就是: app.wxss和app.js里的内容只会越来越多,因为别人不确定哪些是没用的也不敢删,只能往里加东西,造成文件臃肿,不利于维护。 app.wxss和app.js对于每个页面都有效,可读性方面比较差。 所以模块化的意义就出来了,将公共的部分进行模块化统一管理,也便于维护。 样式模块化 公共样式根据上面的目录结构我是放在public里的css里,每个文件命名好说明是哪个部分的模块化,比如下面这个就表示一个按钮的模块化 [图片] 前面说过模块化不在于大小,就算只是一个简单的样式也可以进行模块化,只要在用到的地方import一下就行了,就知道哪里有用到,哪里没有用到,清晰明了。 js模块化 js模块化这里分为两个部分的模块化,一部分是公共js的模块化,另一部分是页面js的模块化即业务与数据的拆分。 公共js模块化 比较常用的公共js有微信登录,弹窗,请求等,一般我是放在units文件夹里,这里经微信弹窗api为例: [图片] 如图是在小程序中经常会用到的弹窗提示,这里进行封装,定义变量,只要在页面中引入就能直接调用了,不用每次都写一大串。比如在请求的时候是这样用的 [图片] toast()就是封装的弹窗api,这样看起来是不是清爽多了! 业务与数据模块化 业务与数据模块化就是指业务和数据分开,互不影响,业务只负责业务,数据只负责数据,可以看到页面会比普通的页面多了一个api.js [图片] 这个文件主要就是用来获取数据的,而index.js主要用来处理数据,这样分工明确,相比以往获取数据和处理数据都在一个页面要好很多,而且我这里获取数据是返回一个promise对象的,也方便处理一些异步操作。 组件化 组件化相信大家都不陌生了,自从小程序支持自定义组件,可以说是大大地提高了开发效率,我们可以将一些公共的部分进行组件化,这部分就不详细介绍,大家可以去看文档。组件化对于我们的项目来说有很大的好处,而且组件化的可移植性强,从一个项目复用到另一个项目基本不需要做什么改动。 总结 这篇文章通过我自己的一些经验来给大家介绍如何优化自己的代码,主要有以下几点 分好项目目录结构 做好接口配置文件 代码健壮性、容错性的处理 定制错误提示码方便定位错误 样式模块化和js模块化 组件化 最后放上项目目录结构的代码片段,大家可以研究一下,有问题一起探讨:https://developers.weixin.qq.com/s/1uVHRDmT7j6l
2019-03-07 - 我的公众号怎么搜索不了
我的公众号怎么搜索不了 怎么操作
2018-10-23 - 2.3.1小程序闪退
2.3.1大约是什么时候上线的?从前两天开始 就有好几个客户反馈小程序闪退的问题,然后我们尝试了把小程序会退到1个月之前的版本,还是会出现闪退;而且更严重 出现问题的都是sdk2.3.1版本
2018-10-23 - 小程序 bug 集中营
https://github.com/LiangJunrong/document-library/blob/master/other-library/WeChatApplet/WeChatAppletBug.md 原文如上,是使用 markdown 语法写到 GitHub上的,这里使用富文本编辑器,我就不多发了,小伙伴们可以点击去看看,有问题的可以提到 1741020489,欢迎你的留言探讨~
2018-10-13 - 我的第一个小程序,今天审核通过啦,哈哈哈
用了好几个晚上,每晚熬到两点多,昨晚晚上2点多提交审核的,今天下午3点多审核通通过了, 从0到1,终于把这个我一直心心念的,想要开发的小程序搞上线了,收到审核通过的信息,真的好激动啊。 下面是小程序码,欢迎体验哈! [图片]
2018-10-16 - 汇总微信开发涉及到的官方平台及其功能介绍
1、微信公众平台,访问地址:https://mp.weixin.qq.com 公众平台用于申请注册组织类型的服务号、个人和组织类型的订阅号、个人和组织类型的小程序/小游戏账号,登录已经申请的账号以及查看相关小程序/小游戏开发文档(https://developers.weixin.qq.com/miniprogram/dev/index.html),也可以由微信公众平台直接进入微信开发者社区PC端(https://developers.weixin.qq.com) 2、微信开放平台,访问地址:https://open.weixin.qq.com 开放平台可以实现app对接微信登录授权;app与公众号、小程序的绑定,三者绑定后union ID可以帮助开发者区分系统用户唯一性;认证成为第三方开发者,所以微信开放平台也成为第三方开发者平台,成为第三方开发者之后,企业可以给其他企业提供公众号和小程序的模版服务。 关于App如何对接微信登录授权、网页授权、公众号二次开发、第三方开发者接入流程和技术指引可以查看开发文档(https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&lang=zh_CN) 3、微信商户平台,访问地址:https://pay.weixin.qq.com 商户平台主要用于企业用户接入微信支付相关的服务,这里可以在微信认证后注册微信商户账号、查看微信支付相关的交易记录、提现、充值、企业付款等功能接入。也可以注册成为微信支付的服务商,为更多小商家提供微信支付服务。 涉及到app接入微信支付、公众号/小程序接入支付、网站微信付款等的接入流程和技术指引可以查看开发文档(https://pay.weixin.qq.com/wiki/doc/api/index.html) 4、企业微信,访问地址:https://work.weixin.qq.com 企业微信是从微信公众平台独立出来的企业管理协同工具,目前在微信公众平台登录页可以看到入口,自己也有独立的入驻登录入口。目前已经实现了企业微信与微信消息互通、企业微信支持小程序、支持微信支付等功能。技术服务商可以为企业微信提供一些列企业应用。
2018-10-17 - 点击广告也需要确认才可以跳转?
用户自己跳转的小程序需要确认这个能理解,刚发现居然点广告也需要再次确认才可以跳转了, 这样流量主还怎么玩。。 [图片]
2018-10-17 - 第三方平台下,取到的小程序信息里没有nickName和headImg
第三方平台下,取到的小程序信息里没有nickName和headImg。也取不到分类信息。 [图片] 同一个第三方平台下,其他的小程序是可以的。下边的两个不行。 appid:wx422b143e8f301a55 appid:wx422b143e8f301a55
2018-10-17