个人案例
- Fotoo企业版
Fotoo小程序:高效的征集、投稿、评选平台
Fotoo 征集投稿评选扫码体验
- Fotoo征集一下丨征稿投票评选
一个方便照片|短视频征集、投稿评选的小程序
Fotoo征集一下:征集投稿投票评选小程序扫码体验
- 微信小程序长列表卡顿,例如超过100条数据后渲染很慢
使用 scroll-view 组件了,列表数据过多,比如超过100条时候,渲染会很慢
05-25 - 为什么说cps导购运营类小程序违规?
我们提供的是导购类小程序对接的京东等平台,一直运营正常,今天突然说我们违规暂定我们服务
2021-04-18 - 小程序这种针对word、excel预览的功能是怎么实现的?
想要实现这种文档预览效果[图片] 调用wx.openDocument(Object object)只能实现这种效果[图片] 第一种预览word、excel的效果是怎么实现的呢,求各位大神不吝赐教
2020-02-24 - 微信小程序可以作为第三方应用上架企业微信吗?
微信小程序可以作为第三方应用上架企业微信吗?并且调用企业微信的发送消息接口
2020-01-17 - 小程序安全风控接口内测权限申请公告
为提高微信开放平台生态安全性,针对小程序各应用场景中可能存在的恶意注册、营销作弊等黑产风险和安全问题,平台将提供安全风控接口,协助开发者保障小程序正常安全运营。 如需申请安全风控查询接口,请发送邮件至 MiniProgram@tencent.com 进行报名(具体报名信息及格式,详见下文“使用指引”),开通权限后可开始接口调用。 小程序安全风控接口介绍如下: 一、应用场景 营销作弊场景:在首单优惠和特价优惠等营销活动中有效识别刷单、虚假交易、恶意骗保骗补贴等破坏运营秩序和安全的行为。 恶意注册:识别并拦截机器批量注册、垃圾小号、伪造身份等恶意注册行为。 二、解决方案 微信开放平台通过开放API的方式给开发者提供快速查询接口。 实现步骤: 1、开发者调用接口提供信息,具体包括小程序appid、用户openid、操作场景等。 2、平台利用海量数据和安全相关能力,识别访问用户的安全等级和风险场景,并返回给开发者 三、使用指引 1、报名接入 若要使用安全风控查询接口,请先发送邮件至 MiniProgram@tencent.com 进行报名接入。邮件标题格式为“开通安全风控接口——XX小程序”,邮件内容需包含“小程序名称、小程序AppID、小程序服务内容简介、近一周日活跃用户量、预估接口日调用量”等相关信息,供平台侧评估接口权限的开通。 3、用户风险识别接入文档(详见:小程序安全风控接口附件) 根据开发者提交的用户信息数据获取安全等级,查询的用户必须在近30min内有进行小程序访问。调用地址、请求参数及说明详见附件(注:接口需平台侧开通权限后方可使用)。 平台希望能与小程序开发者通力合作,加强小程序风控能力,打造更健康有序的微信开放生态体系。希望各小程序开发者积极行动,通过上述邮件方式联系平台侧报名参与接入。 微信团队 2020年09月04日
2020-09-04 - 问卷/投票/表单工具|互动星球中的这三款工具,究竟有什么区别?
互动星球可为企业机构、社群运营者们提供丰富的圈子运营工具支持,在互动星球中不仅能创建圈子实现内容沉淀,而且还可以使用活动、直播、问卷、投票、表单等运营工具,帮助圈子运营者们提高服务效率,降低管理成本,增加变现渠道,沉淀数字资产。 [图片] 圈子运营工具箱 因问卷、投票、表单这三款功能存在着部分相同的题型,本文将为大家分析一下,到底这三款功能存在有哪些差异?什么情况应该使用哪款工具? 一、使用场景 问卷:问卷功能可适用于调查研究,包括产品调研、用户研究、消费者分析、满意度调查、用户需求调研、服务业、教育业的反馈调查、社会民意等使用场景,都可以通过问卷功能进行实现。 投票:投票功能可适用于给事物打分、岗位竞选、职位竞选、活动竞选、作品评选、产品方案评选、活动作品评选、民意调查选择等场景,拉动圈子成员的积极性,聚集多人去参与投票,增加大家之间的交流,也为圈子增添的人气和活跃度。 表单:表单功能可适用于对数据材料的收集,如登记表、报名表之类,包括报名、登记、邀请、反馈、订单、记录等不同场景都可进行使用。 二、问卷、投票、表单的区别 1、题型区别 问卷可使用的题型:单选题、多选题、图片选择题、填空题、图片填空题、文字投票、图片投票、手机号码、姓名、性别、日期、地理位置、城市、自定义问题描述。 [图片] 问卷可用题型 投票可使用的题型:单选题、多选题、图片选择题、文字投票、图片投票、投票描述、自定义投票选项编辑。 [图片] 投票可用题型 表单可使用的题型:单选题、多选题、填空题、文字投票、自定义问题描述、上传图片类信息、上传附件类信息。 [图片] 表单可用题型 2、功能区别 仅表单功能可添加上传图片类、附件类信息的题型,在数据材料收集方面,可拥有更丰富的题型自定义选择。仅问卷功能可直接添加手机、姓名、性别、日期、位置、城市的题目类型,其中在位置题型中支持添加实时的定位信息,多种题型可满足任意调查场景需求。仅投票功能内可添加投票问题描述,支持上传图片作为描述,描述文字最多可填写300字。表单功能的数据统计中,主要以参与填写的用户为数据展示单位,可点击用户来查看其填写的表单内容,更精准化的进行数据查看。投票功能中,可直接在数据统计中查看具体每项选择的投票人数和具体用户。仅问卷、表单支持一键导出excel名单,投票可直接在数据统计中查看数据详情。三、创建问卷、投票、表单的方法 互动星球中所有的工具都需依据在圈子中进行使用,包括活动管理、直播、问答、表单、问卷、投票等功能,均需要先创建圈子才能配合使用。 问卷、投票、表单这三款功能的创建方法相似,圈主/管理员可在创建的圈子中点击工具箱按钮,进入查看已有的圈子运营工具,可根据上述的使用场景来选择使用问卷、投票或表单功能。 创建投票 [图片] 创建投票 [图片] 创建表单 [图片] 互动星球支持草稿箱功能,在创建问卷、投票、表单时,如果尚不确定其发布时间或里面的内容详情,可先将其保存为草稿,待后续再进行发布。
2020-06-12 - 小程序如何导出excel数据
我在之前的文章中写过,小程序如何批量导入excel数据,具体见下文 小程序如何导入excel数据 https://developers.weixin.qq.com/community/develop/article/doc/00020a18098418896e3aa6d9c56c13 那么今天换个话题,小程序如何导出excel, 该文以云开发为前提,所以需要大家对云开发的云函数、云存储、数据库有所了解 我先说下思路 第一步:创建云函数,由于该场景涉及到excel操作,需要安装node-xlsx类库(node类库) 第二步:把数据库里要导出的数据写到excel里,并且上传,存储到云存储 第三步:通过云文件链接地址下载excel文件 小程序代码片段 https://developers.weixin.qq.com/s/NrvAcKmE7Igf
2020-04-20 - 木鱼小铺正式上线礼品卡功能,助力中小商家社交裂变
最近,木鱼小铺(https://www.muyu007.cn/)正式上线一款重磅营销功能-“礼品卡”功能,说到“礼品卡”小程序,大家都不由自主的想到“星巴克用星说”和“滴滴出行卡”。他们营销模式是通过鼓励用户购买礼品卡向亲朋好友传递心意,拉近与用户之间的距离,同时也是在塑造店铺品牌和提升销量。 [图片]一、功能介绍 礼品卡是一款可以帮助拓展新客户、锁定资金、提升复购率的社交性插件应用。木鱼小铺礼品卡支持批量制卡、自用或转赠好友。该功能支持商家创建不同主题、不同卡面、不同兑换商品的礼品卡用于出售,用户可以购买一张或多张礼品卡,成功购买后,可以一对一社交转赠好友或多张礼品卡同时使用。 [图片] 二、功能应用场景 应用场景:生日祝福、员工福利、节日送礼、商务馈赠等场景。 适用行业:适用于快消品、蛋糕烘焙、鲜花礼品、超市、烟酒、娱乐等行业。 [图片] 三、功能详情 1、自定义创建礼品卡主题,满足商家个性化需求 商家在创建礼品卡时,可以根据自己的业务需求自定义礼品卡主题,商家可选用节日送礼或产品系列推出各种礼品卡,商家可借助不同主题活动来吸引用户进店购买礼品卡,提升销售额。 [图片] 2、先购买后兑换模式,有利于商家锁定资金 商家在设置礼品卡中,礼品卡类型选择“兑换卡”,礼品卡有效期支持永久有效、从购买之日起*日及固定时间三种模式。商家可根据实际业务填写有效期。这种先购买再兑换模式,有利于商家锁定资金。 3、支持无线转赠,有利于商家社交裂变 用户进入商家小程序,依次点击“我的”-“礼品卡中心”,进入“礼品卡中心页面,挑选自己喜欢的主题,购买喜欢的礼品卡,成功购买后,点击“赠送好友“并附上祝福语,编辑完成之后,点击赠送好友,选择微信好友即可完成赠送,好友点击小程序卡片领取礼品卡,如果微信好友有送礼需求,也可以转赠给其他人。 [图片] 4、支持批量制卡,有利于商家回笼资金 商家可以选择“批量创建”或“批量导入”,在创建过程中,商家可以设置礼品卡张数及用户绑定凭证。设置完成,点击保存。用户在“礼品卡中心”,点击“我的”礼品卡”,在我的”礼品卡”页面中绑定礼品卡。 [图片] 批量创建 商家可以批量创建礼品卡,创建完成之后,批量导出礼品卡信息-发给制卡厂商-印刷出实体兑换卡-发放用户-用户在线绑定(凭卡号和密码)进行绑定-下单之后,进行兑换核销。批量创建适用于企业采购、门店售卡、工业食堂饭卡等应用场景。 [图片] 批量导入 商家可以通过批量导入卡号、密码后,这样可以将用户线下场景转移到线上来,用户还可以清晰查看到自己礼品卡使用情况。批量导入适用于商家在线下已积累一批使用过礼品卡。有对应的卡号和密码的场景。 5、支持查看交易记录及数据管理 商家在交易记录中,可以查看用户礼品卡的购买记录及核销记录使用情况,他们都支持筛选查询礼品卡使用情况,方便商家核对。 [图片] 在数据管理中,可以查看总浏览量、总访客量、支付客数等以及各礼品卡销售、支付金额等数据,商家可以根据数据分析,做出相应的调整策略,从而提升业绩。 以上就是本期新上线的礼品卡功能简介,此功能更适用于商家举办营销活动,在功能操作或运营思路有疑问的朋友们,可以给我们留言。
2020-10-13 - 小程序性能优化
小程序性能优化[图片]启动加载优化在小程序启动时,微信会在背后完成几项工作:下载小程序代码包、加载小程序代码包、初始化小程序首页。 初始化小程序环境是微信环境做的工作,我们只需要控制代码包大小,和通过一些相关的缓存策略控制,和资源控制,逻辑控制,分包加载控制来进行启动加载优化。 勾选开发者工具中, 上传时压缩代码(若采用wepy高级版本,自带压缩,请按官网文档采取点击)精简代码,去掉不必要的WXML结构和未使用的WXSS定义。减少在代码包中直接嵌入的资源文件。(比如全国地区库,微信有自带的,在没必要的时候,勿自用自己的库)及时清理无用的资源(js文件、图片、demo页面等)压缩图片,使用适当的图片格式,减少本地图片数量等如果小程序比较复杂,优化后的代码总量可能仍然比较大,此时可以采用分包加载的方式进行优化,分包加载初始化时只加载首评相关、高频访问的资源,其他的按需加载。提前做异步请求,页面最好在onLoad时异步请求数据,不要在onReady时请求启用缓存数据策略,请求时先展示缓存内容,让页面尽快展示,请求到最新数据之后再刷新避免白屏,使用骨架屏等数据通信优化为了提升数据更新的性能,开发者在执行setData调用时,最好遵循以下原则: 不要过于频繁调用setData,应考虑将多次setData合并成一次setData调用;数据通信的性能与数据量正相关,因而如果有一些数据字段不在界面中展示且数据结构比较复杂或包含长字符串,则不应使用setData来设置这些数据;与界面渲染无关的数据最好不要设置在data中,可以考虑设置在page对象的其他字段下。提升数据更新性能方式的代码示例: Page({ onShow: function() { // 不要频繁调用setData this.setData({ a: 1 }) this.setData({ b: 2 }) // 绝大多数时候可优化为 this.setData({ a: 1, b: 2 }) // 不要设置不在界面渲染时使用的数据,并将界面无关的数据放在data外 this.setData({ myData: { a: '这个字符串在WXML中用到了', b: '这个字符串未在WXML中用到,而且它很长…………………………' } }) // 可以优化为 this.setData({ 'myData.a': '这个字符串在WXML中用到了' }) this._myData = { b: '这个字符串未在WXML中用到,而且它很长…………………………' } } }) 事件通信优化视图层会接受用户事件,如点击事件、触摸事件等。当一个用户事件被触发且有相关的事件监听器需要被触发时,视图层会将信息反馈给逻辑层。这个反馈是异步的,会产生延迟,降低延迟的方法有两个: 去掉不必要的事件绑定(WXML中的bind和catch),从而减少通信的数据量和次数;事件绑定时需要传输target和currentTarget的dataset,因而不要在节点的data前缀属性中放置过大的数据。渲染优化页面方法onPageScroll使用, 每次页面滚动都会触发,避免在里面写过于复杂的逻辑 ,特别是一些执行重渲染页面的逻辑在进行视图重渲染的时候,会进行当前节点树与新节点树的比较,去掉不必要设置的数据、减少setData的数据量也有助于提升这一个步骤的性能。
2020-08-20 - 小程序多组件高复杂度实现方案
备注:此方法多适用于多模块高复杂度多插件的页面开发 原理:应用事件总线进行事件传递,并将数据挂载到wx上用于各个组件模块共享 一、事件总线事件总线挂载在wx对象上wx.eventBus派发事件 wx.eventBus.$emit('name',params)监听事件 wx.eventBus.$on('name',fn)示例: [图片] // 派发事件addCart 传递参数为1 wx.eventBus.$emit("addCart", 1) // 监听事件 // 方式1 wx.eventBus.$on("addCart", num => { this.setData({ number: num }); }); // 方式2 this.setNum为methods中的方法 wx.eventBus.$on("addCart", this.setNum).bind(this); 二、共享数据设置共享数据wx.setShareData(data)获取共享数据wx.getShareData()示例: [图片] // 设置共享数据 一般为主页面设置共享数据,组件更新部分数据 wx.setShareData({ data: { spu, sku, watch, postageFee }, chooseSkuInfo, // 选择的sku footerBtnType, pageSetting, closeCommunity: false }); // 获取共享数据 const { footerBtnType, pageSetting, chooseSkuInfo, data: { sku } } = wx.getShareData() // 组件中 更新部分数据 wx.setShareData({ chooseSkuInfo: { ...item, number: 1 } });
2020-08-19 - 微信小程序通过websocket实现实时语音识别
之前在研究百度的实时语音识别,并应用到了微信小程序中,写篇文章分享一下。 先看看完成的效果吧 [图片] 前置条件 申请百度实时语音识别key 百度AI接入指南 创建小程序 设置小程序录音参数 在index.js中输入 [代码] const recorderManager = wx.getRecorderManager() const recorderConfig = { duration: 600000, frameSize: 5, //指定当录音大小达到5KB时触发onFrameRecorded format: 'PCM', //文档中没写这个参数也可以触发onFrameRecorded的回调,不过楼主亲测可以使用 sampleRate: 16000, encodeBitRate: 96000, numberOfChannels: 1 } [代码] 使用websocket连接 [代码] linkSocket() { let _this = this //这里的sn是百度实时语音用于排查日志,这里我图方便就用时间戳了 let sn = new Date().getTime() wx.showLoading({ title: '识别中...' }) recorderManager.start(recorderConfig) //开启链接 wx.connectSocket({ url: 'wss://vop.baidu.com/realtime_asr?sn=' + sn, protocols: ['websocket'], success() { console.log('连接成功') _this.initEventHandle() } }) }, //监听websocket返回的数据 initEventHandle() { let _this = this wx.onSocketMessage((res) => { let result = JSON.parse(res.data.replace('\n','')) if(result.type == 'MID_TEXT'){ _this.setData({ textDis: 'none', value: result.result, }) } if(result.type == 'FIN_TEXT'){ let value = _this.data.text let tranStr = value + result.result _this.setData({ value: '', valueEn: '', textDis: 'block', text: tranStr, }) } }) wx.onSocketOpen(() => //发送数据帧 _this.wsStart() console.log('WebSocket连接打开') }) wx.onSocketError(function (res) { console.log('WebSocket连接打开失败') }) wx.onSocketClose(function (res) { console.log('WebSocket 已关闭!') }) }, [代码] 发送开始、音频数据、结束帧 [代码] wsStart() { let config = { type: "START", data: { appid: XXXXXXXXX,//百度实时语音识别appid appkey: "XXXXXXXXXXXXXXXXXX",//百度实时语音识别key dev_pid: 15372, cuid: "cuid-1", format: "pcm", sample: 16000 } } wx.sendSocketMessage({ data:JSON.stringify(config), success(res){ console.log('发送开始帧成功') } }) }, wsSend(data){ wx.sendSocketMessage({ data:data, success(res){ console.log('发送数据帧成功') } }) }, wsStop(){ let _this = this this.setData({ click: true, }) let config = { type: "FINISH" } wx.hideLoading() recorderManager.stop() wx.sendSocketMessage({ data:JSON.stringify(config), success(res){ console.log('发送结束帧成功') } }) }, [代码] 小程序录音回调 [代码] onShow: function () { let _this = this recorderManager.onFrameRecorded(function (res){ let data = res.frameBuffer _this.wsSend(data) }) recorderManager.onInterruptionBegin(function (res){ console.log('录音中断') _this.wsStopForAcc() }) recorderManager.onStop(function (res){ console.log('录音停止') }) }, wsStopForAcc(){ let _this = this this.setData({ click: true, }) let config = { type: "FINISH" } wx.sendSocketMessage({ data:JSON.stringify(config), success(res){ wx.hideLoading() console.log('发送结束帧成功') } }) }, [代码]
2020-08-20 - 使用MpKit的事件、Mixin、SetData优化、全局拦截等功能增强开发多平台小程序
MpKit与本文章均在不断更新,为保证您获取到的内容是最新的,请关注:https://imingyu.github.io/2020/mpkit/ 前言 近年来,多个公司都开发出了小程序这样的“微应用”方案,其在生态扩展、功能增强等方面都发挥着重要的作用; 作为开发者却要同时面对多个小程序平台,实现相似的功能,如果不考虑使用框架的话,在底层的一些基础技术解决方案上,有没有一个轻量级的选择? 我们在开发一个小程序时,往往会有以下技术需求: 全局事件总线 优化小程序的setData函数 希望将App/Page/Component上的共有功能拆分,并可以有效性的重复利用,甚至组建为App/Page/Component基类 可以全局拦截执行某些操作,如:异常报错、网络请求、功能制止、App/Page/Component的生命周期等 基于以上需求,都是我以往实现过的功能,所以我将我的经验总结成了一个开源项目MpKit,里面就包含对以上需求的功能实现,且不止于此; 项目主页:https://github.com/imingyu/mpkit; 下面我来介绍下它的具体用法和功能列表。 简介 MpKit发布到了NPM平台,以模块化的方式划分为多个包,某些包不止能用在小程序平台,还可以用到h5等平台;某些包却无法在小程序上使用,包列表及相关功能如下: 以下包均支持TypeScript语言 包 适用平台 简介 小程序 H5 Node.js 微信 支付宝 百度 字节跳动 @mpkit/inject ● ● ● ● 提供小程序环境适用的多种实用函数或组件,如setData优化、Mixin、事件总线等。查看文档 @mpkit/set-data ● ● ● ● 小程序setData优化。查看文档 @mpkit/ebus ● ● ● ● ● ● 提供事件触发、监听等功能。查看文档 @mpkit/mixin ● ● ● ● 为小程序提供混入功能。查看文档 @mpkit/util ● ● ● ● 工具函数查看文档 安装 如果你的小程序项目中支持引入npm包,那么你直接根据自己的需要安装对应包即可,如: [代码]npm i @mpkit/ebus -S [代码] 但是如果你是原生开发,不支持引入npm包,你最大的可能需要用到[代码]@mpkit/inject[代码]包,此包中的功能基本包含了其他包的所有功能,且支持按需插件化安装,可节省你的字节空间;使用[代码]@mpkit/inject[代码]: 在磁盘任意位置创建临时目录如[代码]xx/temp[代码]; 在此目录中执行命令: [代码]npm i @mpkit/inject [代码] 找到[代码]xx/temp/node_modules/@mpkit/inject[代码]下的[代码]dist[代码]目录,将其下内容拷贝到你的小程序项目目录下; 找到[代码]dist/config.js[代码]文件,将你需要的功能插件引入,如: [代码]// 提供事件类功能 import { plugin as EbusPlugin } from './plugins/ebus'; // 提供混入功能 import { plugin as MixinPlugin } from './plugins/mixin'; // 提供setData优化 import { plugin as SetDataPlugin } from './plugins/set-data'; var config = { // 是否重写全局变量 rewrite: { App: false, // 重写全局变量App为 plugins/mixin 中的MkApp 即 MpKit.App Page: false, // 重写全局变量Page为 plugins/mixin 中的MkPage 即 MpKit.Page Component: false, // 重写全局变量Component为 plugins/mixin 中的MkComponent 即 MpKit.Component Api: false, // 重写全局api变量wx/my/tt为 plugins/mixin 中的MkApi 即 MpKit.Api setData: false // 重写Page/Component中的setData为优化后的setData 即 MpKit.setData }, plugins: [ EbusPlugin, MixinPlugin, SetDataPlugin ] }; export default config; [代码] 如果配置了[代码]config.rewrite[代码]项,请在小程序项目的[代码]app.js[代码]的第一行处引入[代码]@mpkit/inject/dist[代码]中的[代码]index.js[代码]文件,否则无法实现全局变量重写;如果没有配置该项,则需要在使用时引入,如: [代码]import MpKit from '@mpkit/inject/dist/index'; MpKit.on(...); [代码] 安装完成。 小提示:不需要的插件js文件可以直接删掉,插件不直接相互依赖。 使用 这里着重介绍[代码]@mpkit/inject[代码]包的使用方式和细节,其他包可自行参考对应文档; [代码]@mpkit/inject[代码]包提供下列功能, 方法/变量 作用 依赖插件 [代码]on(eventName:string, handler:Function)[代码] 为某全局事件添加监听函数 ebus [代码]off(eventName:string, handler:Function)[代码] 为某全局事件移除监听函数 ebus [代码]emit(eventName:string, data:any)[代码] 触发某全局事件并传递数据 ebus [代码]App(...mixins:MpAppSpec[]) : MpAppSpec[代码] 接收多个对象,对象结构与小程序[代码]App[代码]函数接收的对象结构一致,并将这些对象合并,返回一个新对象,包含所有对象的功能和数据,合并策略下文描述。 mixin [代码]Page(...mixins:MpPageSpec[]) : MpPageSpec[代码] 接收多个对象,对象结构与小程序[代码]Page[代码]函数接收的对象结构一致,并将这些对象合并,返回一个新对象,包含所有对象的功能和数据,合并策略下文描述。 mixin [代码]Component(...mixins:MpComponentSpec[]) : MpComponentSpec[代码] 接收多个对象,对象结构与小程序[代码]Component[代码]函数接收的对象结构一致,并将这些对象合并,返回一个新对象,包含所有对象的功能和数据,合并策略下文描述。 mixin [代码]Api[代码] 与小程序上的[代码]wx[代码] [代码]my[代码] [代码]swan[代码] [代码]tt[代码]等对象的属性和方法一致,只不过在其方法上添加了钩子函数,方便拦截处理 mixin [代码]MixinStore.addHook(type:string, hook:MpMethodHook)[代码] [代码]MpKit[代码]中内置了很多全局钩子函数,方可实现了全局拦截,setData重写等功能,而如果你也想在全局添加自己的钩子函数,那么可以调用此函数 mixin [代码]setData(view:any, data:any, callback:Function) : Promise[代码] 以优化的方式向某个Page/Component设置数据,仅设置变化的数据,并以Promise的方式返回diff后的数据结果 set-data 依赖 从上面的功能列表中可以看到,某些方法或变量是依赖插件的,如果没有安装相关插件,则无法使用对用方法; App/Page/Component 当使用[代码]MpKit.App/Page/Component[代码]时,可传递多个对象,如: [代码]import MpKit from '@mpkit/inject/dist/index'; // 如果在config中配置了rewrite.App=true,则调用App等同于调用了[未重写的App(MpKit.App(...mixins))] App(MpKit.App({ globalData: { name: 'Tom', age: 10 }, onShow() { console.log('onShow1') }, add(a, b) { return a + b; } }, { globalData: { age: 20 }, onShow() { console.log(`onShow2, ${this.add(2, 4)}`); console.log(this.globalData); } })); // 输出:onShow1 // 输出:onShow2, 6 // 输出:{ name: 'Tom', age: 20 } Component(MpKit.Component({ data: { name: 'Alice', products: [ { name: '苹果', price: 6 }, { name: '香蕉', price: 5 } ] }, created() { console.log('created1') }, methods: { sayhi() { console.log(`hi ${this.data.name}`); } } }, { data: { products: [ { price: 8 } ] }, created() { console.log('created2'); this.sayhi(); console.log(this.data.list); }, methods: { sayhi() { console.log(`你好 ${this.data.name}`); } } })); // 输出:created1 // 输出:created2 // 输出:hi Alice // 输出:你好 Alice // 输出:[ { name: '苹果', price: 8 }, { name: '香蕉', price: 5 } ] [代码] 合并策略 从上面的例子可以看出合并策略是: 属性进行深度合并 方法会保留所有mixin中的方法体,按照顺序全部执行 钩子函数 为可以进行全局拦截[代码]App/Page/Component/Api[代码]上的方法,MpKit做了钩子函数的机制,具体为: 每个方法执行前会调用[代码]before[代码]钩子 如果[代码]before[代码]钩子函数存在,且有返回值(如果有多个[代码]before[代码]钩子则取最后一个不为[代码]undefined|true[代码]的结果)时 对于[代码]App/Page/Component[代码]如果返回值为[代码]false[代码],则不会继续向下执行 对于[代码]Api[代码]如果返回值为[代码]false[代码],则不会继续向下执行;同时如果返回值不为[代码]true[代码]和[代码]undefined[代码]时,会直接将结果返回出去,且不会继续向下执行 然后执行[代码]方法体[代码],如果有多个,则依次全部执行,并返回最后一个不为空的结果 执行[代码]after[代码]钩子,并传入[代码]方法体[代码]的执行结果 如果是[代码]Api[代码]上的异步方法,还会根据结果回调(success|fail)在执行[代码]complete[代码]钩子 MpKit内置了很多钩子,用于全局事件触发、方法重写等,同时你可以添加自己的钩子函数,调用: [代码]MpKit.MixinStore.addHook(type:MpViewType.App|MpViewType.Page|MpViewType.Component|'Api', hook:MpMethodHook)[代码]可为 App/Page/Component/Api 添加全局钩子函数;[代码]MpMethodHook[代码]的定义如下: [代码]interface MpMethodHookLike { before?( methodName: string, methodArgs: any[], methodHandler: Function, funId?: string ); after?( methodName: string, methodArgs: any[], methodResult: any, funId?: string ); catch?( methodName: string, methodArgs: any[], error: Error, errType?: string, funId?: string ); complete?( methodName: string, methodArgs: any[], res: any, success?: boolean, funId?: string ); } interface MpMethodHook extends MpMethodHookLike { [prop: string]: Function | MpMethodHookLike; } [代码] 示例1: [代码]import MpKit from "@mpkit/inject/dist/index"; import { MpViewType } from "@mpkit/inject/dist/types"; MpKit.MixinStore.addHook(MpViewType.App, { before(methodName, methodArgs) { console.log(`before methodName=${methodName}`); }, after(methodName, methodArgs, methodResult) { console.log(`after methodName=${methodName}, ${methodResult}`); }, catch(methodName, methodArgs, error) { console.log(`catch err=${error.message}`); }, }); App( MpKit.App({ onLaunch() { this.add(1, 2); }, onShow() { throw new Error("test"); }, add(a, b) { return a + b; }, }) ); // 输出:before methodName=onLaunch // 输出:before methodName=add // 输出:after methodName=add, 2 // 输出:after methodName=onLaunch, // 输出:before methodName=onShow, // 输出:catch err=test MpKit.MixinStore.addHook("Api", { before(methodName, methodArgs, methodHandler, funId) { console.log(`before api=${methodName}`); }, after(methodName, methodArgs, methodResult, funId) { console.log(`after api=${methodName}, ${methodResult}`); }, complete(methodName, methodArgs, res, isSuccess, funId) { console.log(`complete api=${methodName}, ${isSuccess}, ${res}`); }, }); MpKit.Api.request({ url: "...", }); // 输出:before api=request // 输出:after api=request, [RequestTask Object] // 假设请求成功且返回字符串“1”,则输出:complete api=request, true, 1 // 假设请求失败,则输出:complete api=request, false, { errMsg:'...' } [代码] 示例2:当在[代码]before[代码]钩子中返回[代码]false[代码]或具体值时: [代码]MpKit.MixinStore.addHook(MpViewType.App, { onShow: { before(methodName, methodArgs) { console.log("hook onShow"); return false; }, }, }); App( MpKit.App({ onLaunch() {}, onShow() { console.log("self onShow"); }, }) ); // 仅输出:hook onShow const store = {}; MpKit.MixinStore.addHook("Api", { before(methodName, methodArgs, methodHandler, funId) { console.log(`before methodName=${methodName}`); if (methodName === "setStorageSync") { store[methodArgs[0]] = methodArgs[1]; } if (methodName === "getStorageSync") { // 并不会真正执行(wx|my|tt|..).getStorageSync return store[methodArgs[0]]; } }, after(methodName) { console.log(`after methodName=${methodName}`); }, }); MpKit.Api.setStorageSync("name", "Tom"); const name = MpKit.Api.getStorageSync("name"); console.log(name === store.name); // 输出:before methodName=setStorageSync // 输出:after methodName=setStorageSync // 输出:before methodName=getStorageSync // 输出:true [代码] 结语 希望MpKit可以为你带来便捷愉快的开发体验,祝大家工作顺心,家庭美满,加油! 可关注作者的其他开源项目: 小松鼠:适用于JavaScript平台的数据上报工具 小程序调试辅助工具:小程序控制台调试辅助工具
2020-08-20 - 小程序数据过大,导致界面卡顿问题漫谈
本文背景其实小程序数据过大导致卡顿也算是老生常谈的问题了,今天在社区翻看帖子,看到这个帖子的方案给我了一点思考,是不是有新的解决方案 说新的解决方案是因为当前我对数据过大已经储备了优秀的可称得上原创的完美的解决方案,而且在生产发布的小程序经得住考验,看到这么多赞美的修饰词就知道,提起这个解决方案就难以掩饰我内心的喜悦 本文内容在下面参考的帖子里面,社区的同学提到这个方案,该方案是指,虽然在数组维度上没有降低,但是数组的大小确确实实减小了,而通过官方的文档可以看到,setData最后卡顿还是由于逻辑层和渲染层之间通信造成的,当然如果通信传递的数据少了,速度肯定是更快的。 具体官方文档请参考 https://developers.weixin.qq.com/miniprogram/dev/reference/api/Page.html https://developers.weixin.qq.com/miniprogram/dev/framework/performance/tips.html https://developers.weixin.qq.com/miniprogram/dev/framework/audits/performance.html f [图片] f [图片] f 本文参考setData渲染数据量大怎么处理?? - 微信开放社区 https://developers.weixin.qq.com/community/develop/doc/00006aca4d4f08397b491227851800 本文总结本文通过社区某个帖子对某个问题的解答,引起我对数据量大加载卡顿问题的思考,进而提出是不是可以将这个方案进行延伸,这个有待在真实场景结合业务来实现, 后续会补充。
2020-08-12 - 除了广告,小程序还有其他变现方式吗?
我们团队开发了一个外卖红包插件,集成美团、饿了么、肯德基、瑞幸等红包,可用在小程序,向我们申请使用就可以。 原本是在自己矩阵自己用的,发现居然还挺不错,因为使用频次高。给了用户福利,提升了他们的活跃,还可以有收入。所以开放出来给大家试试~支持CPS分佣,一起赚钱!有兴趣可以私聊,或在小程序平台搜索插件“惊喜外卖券”申请试试 [图片]
2020-08-12 - 同一页面存在多个video时,video无法正常播放一直在加载转圈
不建议同个页面使用多个video组件,建议不超过3个video,如果要实现video列表功能,请进行优化(image列表,选中时将image替换成video)
2019-08-29 - 非常简单的长列表(无限上拉触底加载 onReachBottom)实现方案
我们知道小程序针对长列表有两个硬杠杠,一旦越界直接给白屏: 1、setData的数组数据不能超过1M。 2、DOM数不能太多,具体数据未知。 官方这样处理也不无道理,太长了本身性能确实也有问题。所以长列表一定要人为干预处理,不处理一直上拉加载肯定是不行的。 目前主流的处理方法有三种: 1、二维数组,就是把数据改为二维的,每一个分页数据作为一个一维数组的元素。这样处理,只解决了setData的问题,DOM的问题并未解决。并且把本来是一维的数据强行二维化,在很多逻辑处理上变得复杂。 2、官方提供了一个扩展组件recycle-view,但它要求item等高,存在局限性。https://developers.weixin.qq.com/miniprogram/dev/extended/component-plus/recycle-view.html 3、自行搭建骨架屏,类似于方案2的自研版本,根据自己的实际需要编写代码。实现成本非常高。 实现方案如下(太简单了,不提供代码示例): 1、思路:只保留最新的n页数据进行setData,每新加载一页数据,就舍弃最前面一页的数据。同时把第1页的数据保存起来,监听onPageScroll,如果发现用户拉回到了页面顶部,则舍弃所有数据,把第1页的数据setData回来。 2、举个例子:假设n=5,那么当加载了第6页数据时,第6页数据合并到数组尾部,把数组头部的第1页数据去掉,让setData的数据始终保持5页。这里有个细节要处理好,就是去除数据的时候先setData一次,新数据加载合并后,再setData一次,这样可以保证用户的scrollTop不会走位,停留在最新一条数据那个位置。 3、这个方案也存在一些弊端,看你实际项目中能否接受,主要有两个问题:a、用户如果倒着往回逐条浏览,体验是不连续的,因为中间一段我们已经舍弃掉了,如果拉到页面顶部时,将会出现直接回到第一条数据。b、去除数据的setData操作时,存在一定程度的闪屏现象。(针对问题a,应该可以解决,无非就是把数据再逐页塞回来,而不是像我的方案简单粗暴的回到第一页数据,如果项目有需要可以自行尝试。) 4、适用范围:比较适合信息处理类应用,比如后台管理系统。这类应用,往往头几屏内容就能找到信息,或者借助搜索,比较少会拉很多屏。而且往往处理完毕时是直接回到顶部的,不会逐条翻回去。所以这类应用只要保证不出白屏,一些小概率场景下存在一些几乎可以忽略的体验小瑕疵可以接受。信息浏览类应用,比如新闻应用,往往都是长列表浏览,小瑕疵就不一定能接受。
2020-10-28 - 小程序1rpx边框不完美解决方案
在小程序开发中,1rpx边框随处可见, 像上图UI给的设计稿,如果只是简单使用[代码]border: 1rpx solid red;[代码]的话,在不同的机型上会有不同的表现 [图片] 表现IOS 机型上[图片] Android机型上[图片] 由图片可以看出, IOS机型上会有边框缺失(然而经常出现缺不能稳定复现), 而Android机型上边框比较粗 原因上面这两种表现形式很难联系到一起 首先先看IOS边框缺失的问题,借鉴网络上前辈们的经验 当父元素的高度为奇数,容易出现上下边框缺失,同理宽度为奇数,容易出现左右边框缺失解决办法是在边框内部添加一个1rpx的元素或者伪元素, 撑开内部使父元素的宽高是偶数。 然而我们发现这种方案在Iphone 6等2倍屏可以生效, 但放在如Iphone X等3倍屏下面就很飘了, 还是经常会出现边框缺失的情况, 这种情况下再去把父元素改为2和3共同的倍数就非常不现实了。 再回过头看导致边框缺失的具体原因是啥。 在这之前需要了解下高分屏的物理像素和虚拟像素的概念 简单来说物理像素是设备的实际像素 虚拟像素是设备的坐标点, 可以简单理解为css像素 而rpx类似rem,渲染后实际转换成px之后可能存在小数,在不同的设备上多多少少会存在渲染的问题。而1rpx的问题就更加明显,因为不足1个物理像素的话,在IOS会进行四舍五入,而安卓好像统一向上取整,这也是上面两种设备表现不同的原因。 解决方法我们采用的方法是采用translate:scale(0.5)的方法对边框进行缩放 具体的代码如下 .border1rpx, .border1rpx_before{ position: relative; border-width: 0rpx !important; padding: 0.5rpx; z-index: 0; } .border1rpx::after, .border1rpx_before::before{ content: ""; border-style: inherit; border-color: inherit; border-radius: inherit; box-sizing: border-box !important; position: absolute; border-width: 2rpx !important; left: 0; top: 0; width: 200% !important; height: 200% !important; transform-origin: 0 0; transform: scale(0.5) !important; z-index: -1; } .border1rpx-full { margin: -1rpx; } 给.border1rpx的元素设置边框宽度为0给::after伪元素宽高为两倍,边框设置2rpx,边框其他样式继承元素的设置然后再缩放0.5来达到边框为1rpx的效果 用法基础用法给相应的元素添加border1rpx的class即可, (.borde1rpx说:我们不生产边框,只是边框的搬运工,要显示边框样式的话还需要在元素上自行设置) 圆角边框圆角边框需要自行设置相应伪元素::before 或 ::after的border-raduis值为预期的2倍, 如原本想要设置10rpx的圆角,需要设置[代码].xxx::after{border-raduis: 20rpx;}[代码] 边框内部填充由于设计原因,目标元素会留1rpx的padding用于显示伪元素的边框,如果内部元素是填充的,正常会看到填充元素和目标元素有小部分间隙,此时需要给填充元素添加.border1rpx_full来解决 注意点此方案默认使用::after伪元素实现边框,如果目标元素的after被占用(如iconfont),请使用[代码].border1rpx_before[代码]如单独设置边框(如上边框), [代码]border: 1rpx solid red;border-width: 1rpx 0 0 0;[代码]不能被正确继承,请使用简写[代码]border-top: 1rpx solid red;[代码]由于设计原因,目标元素请最少设置1rpx的padding用于显示边框,(上面的样式已经有了默认的padding,不写也可以, 只是不要用padding:0覆盖)请自行测试点击功能是否正常,防止层级关系导致元素区域被伪元素覆盖
2020-07-23 - 关于解决video-swiper组件的几个坑
1.不能从第一个视频播放 解决思路:在获取到数据后拿到第一条数据使用unshift放到数组的第一条(笨办法) 2.播放不到最后一个视频 解决思路:这个问题会在视频长度为3、6、9、12、下会出现,以此类推,相信大家看到这个长度就会有解决办法了吧 3.在播放到最后会重复播放倒数第二条 解决思路:这个问题会在视频长度为4、7、10、13、下会出现,以此类推,一开始还给数组复制了那个是不算在这个长度里的,这个长度最好是从后端拿,下面就贴代码 由于时间原因,解释的不太详细,直接贴图了,研究过源码里的算法一看就懂了,希望能帮助到大家,觉得还行给个赞以示鼓励 [图片] animationfinish: function animationfinish(e) { var _data = this.data, _last = _data._last, _change = _data._change, curQueue = _data.curQueue, prevQueue = _data.prevQueue, nextQueue = _data.nextQueue, total = _data.total; var current = e.detail.current; var diff = current - _last; if (diff === 0) return; this.data._last = current; this.playCurrent(current); this.triggerEvent('change', { activeId: curQueue[current].id}); var direction = diff === 1 || diff === -2 ? 'up' : 'down'; if (direction === 'up') { //判断下面是否有视频 if (this.data._invalidDown === 0) { //当前索引 var change = (_change + 1) % 3; //加入下一个视频 var add = nextQueue.shift(); //移除上一个视频 var remove = curQueue[change]; if (add) { prevQueue.push(remove); curQueue[change] = add; this.data._change = change; if((total%3) === 0 && nextQueue.length === 0) { curQueue[3] = add } else if((total%3) === 1 && nextQueue.length === 0){ this.setData({ _pop : curQueue.pop() }) } } else { this.data._invalidUp += 1; } } else { this.data._invalidDown -= 1; } } if (direction === 'down') { if (this.data._invalidUp === 0) { var _change2 = _change; var _remove = curQueue[_change2]; var _add = prevQueue.pop(); if (_add) { curQueue[_change2] = _add; nextQueue.unshift(_remove); this.data._change = (_change2 - 1 + 3) % 3; } else { this.data._invalidDown += 1; } } else { if((total%3) === 0 && curQueue.length === 4) { curQueue.pop() } else if((total%3) === 1 && nextQueue.length === 0){ curQueue.push(this.data._pop); } this.data._invalidUp -= 1; } } var circular = true; if (nextQueue.length === 0 && current !== 0) { circular = false; } if (prevQueue.length === 0 && current !== 2) { circular = false; } this.setData({ curQueue: curQueue, circular: circular }); // console.log(1, curQueue) // console.log(2, prevQueue) // console.log(3, nextQueue) // console.log('--------------', _change) },
2020-07-24 - 数据分析&标注,让你的机器人更聪明!
前两期推送我们介绍了智能客服转人工的服务升级,客服小程序的上线使随时随地回复用户留言更加便利。那么如何看到机器人使用的整体情况呢?如何衡量我们工作的准确性呢?如何评判我们服务能力的质量呢? 让数据说话 统计模块是平台为开发者跟踪观测已发布技能的服务能力所提供的数据分析后台,通过数据反馈,帮助开发者进一步管理、优化和创新技能。根据对技能不同关注点、不同时间范围的需要,开发者可以查看或下载基础概况、技能、未命中话术和模型标注的数据统计情况。 [图片] [图片] 基础概况 基础概况主要收录了三项数据:会话数据、用户数据、命中率。 会话数据 从用户向公众号/小程序发出请求到退出对话框计算为一次会话,展示昨日日会话数,并可按时间维度展示日会话数波动情况。 用户数据 统计了一段时间内使用技能的粉丝数量,展示昨日的用户数量,并可按时间维度展示每日用户数量波动情况。 命中率 统计了一段时间内技能整体命中情况,并展示未命中话术与未命中话术占比。 [图片] 技能详情这里收录了该机器人的技能总数,同时统计了该机器人不同技能的命中次数,以及各意图的命中情况;点击“技能名称”,还可调起浮窗,查看各技能详情,以折线图形式直观展示近期命中情况; [图片] 未命中话术当机器人回复为默认回答时,即统计为未命中话术; 在这里可查看未命中话术中的具体 query 及该 query 未命中次数,同时统计了未命中话术总数;针对未命中的query是有聚类功能的,点击可以查看此类query中的所有未命中话术,进而帮助开发者更好的完善机器人的语料库。 [图片] 模型标注模型标注包括“用户问法和命中回答”手动标注部分和准确率、召回率、F1、AUC 四项数据的展示。 用户问法和命中回答: 用户问法(人物头像 icon):用户真实的 query; 命中回答(机器人头像 icon):模型中命中的标准问题; 在这里可对机器人与用户的对话进行正确与错误的标注,通过手动标注用户真实 query 与命中 question 是否一致,从而优化机器人模型,提高机器人回答正确率。 [图片] 准确率: 准确率(P) = 标注的正确信息条数 / 标注的所有信息条数。 召回率: 召回率(R)= 标注的正确信息条数 / 样本中所有信息条数。 F1: 准确率和召回率的调和平均数,F1 = (2 _ P _ R) / (P + R)。 AUC(Area Under Curve): 代表不同类别的区分度,AUC 值越大代表标注正确的概率越大。 [图片] 同步移动端 除了PC端,数据分析功能也同步到了OpenAI客服小程序,方便用户在移动端进行数据的监测。 [图片]
2020-04-29 - 一个类似探探的高性能卡片滑动组件
cardSwipe - 小程序卡片滑动组件 介绍 此组件是使用原生微信小程序代码开发的一款高性能的卡片滑动组件,无外部依赖,导入即可使用。其主要功能效果类似探探的卡片滑动,支持循环,新增,删除,以及替换卡片。 [图片] 用法 获取: [代码]git clone https://github.com/1esse/cardSwipe.git [代码] 相关文件介绍: /components /card /cardSwipe /pages /index 其中,components文件夹下的card组件是cardSwipe组件的抽象节点,放置卡片内容,需要调用者自己实现。而cardSwipe组件为卡片功能的具体实现。pages下的index为调用组件的页面,可供参考。 功能介绍 亮点: 支持热循环(循环与不循环动态切换),动态新增,动态删除以及动态替换卡片 卡片的wxml节点数不受卡片列表的大小影响,只等于卡片展示数,如果每次只展示三张卡片,那么卡片所代表的节点数只有三个 支持调节各种属性(滑动速度,旋转角度,卡片展示数…等等) 节点数少,可配置属性多,自由化程度高,容易嵌入各种页面且性能高 实现方式: 循环/不循环: 属性circling(true/false)控制 新增: 向卡片的循环数组中添加(不推荐新增,具体原因后面分析。硬要新增的话,如果卡片列表不大,并且需要新增多张卡片,可以直接将数据push到卡片列表中然后setData整个数组。如果是每次只增加一张卡片,推荐使用下面这种方式,以数据路径的形式赋值) [代码]this.setData({ [`cards[${cards.length}]`]: { title: `新增卡片${cards.length + 1}`, // ... } }) [代码] 删除: [代码]// removeIndex: Number,需要删除的卡片的索引,将删除的卡片的值设置为null // removed_cards: Array,收集已删除的卡片的索引,每次删除卡片都需要更新 this.setData({ [`cards[${removeIndex}]`]: null, removed_cards }) [代码] 替换: [代码]// replaceIndex:Number,需要替换的卡片的索引 // removed_cards: Array,收集已删除的卡片的索引,如果replaceIndex的卡片是已删除的卡片的话,需要将该卡片索引移出removed_cards this.setData({ [`cards[${replaceIndex}]`]: { title: `替换卡片${replaceIndex}`, // ... } // removed_cards }) [代码] 注意:删除和替换操作都需要同步removed_cards why?为什么使用removed_cards而不直接删除数组中的元素 由于小程序的机制,逻辑层和视图层的通信需要使用setData来完成。这对大数组以及对象特别不友好。如果setData的数据太多,会导致通信时间过长。又碰巧数组删除元素的操作无法通过数据路径的形式给出,这会导致每次删除都需要重新setData整张卡片列表,如果列表数据过大,将会引发性能问题。 删除的时候,如果删除的卡片索引在当前索引之前,那么当前索引所代表的卡片将会是原来的下一张,导致混乱。 保留删除元素,为卡片列表的替换以及删除提供更方便,快捷,稳定的操作。 优化 由于组件支持动态的删除以及替换,这使得我们可以以很小的卡片列表来展示超多(or 无限)的卡片 场景1:实现一个超多的卡片展示(比如1000张) 实现思路:以分页的形式每次从后台获取数据,先获取第一页和第二页的数据。在逻辑层(js)创建一个卡片列表,把第一页数据赋值给它,用于跟视图层(wxml)通信。开启循环,用户每滑动一次,将划过的卡片替换成第二页相同索引的卡片,第一页卡片全部划过,第二页的内容也已经同步替换完毕,再次请求后台,获取第三页的内容,以此类推。到最后一页的时候,当前索引为0时,关闭循环,删除最后一页替换不到的上一页剩余的卡片 场景2:实现一个无限循环的卡片 实现思路:类似场景1。不关闭循环。 为什么不建议新增卡片 新增卡片会增加卡片列表的长度,由于每次滑动都要重新计算卡片列表中所有卡片的状态,卡片列表越大,预示着每次滑动卡片需要计算状态的卡片越多,越消耗性能。在完全可以开启循环然后用替换卡片操作代替的情况下,不推荐新增卡片。建议卡片列表大小保持在10以内以保证最佳性能。 以下为卡片列表大小在每次滑动时对性能的影响(指再次渲染耗时) 注:不同手机可能结果不同,但是耗时差距的原因是一样的 耗时(ms/毫秒) 10张卡片 100张卡片 1000张卡片 再次渲染1 10 12 116 再次渲染2 12 10 87 再次渲染3 17 16 145 再次渲染4 9 16 112 再次渲染5 9 18 90 平均 11.4 14.4 110
2020-04-20 - 自定义客服 消息卡片
小程序如何合理合规引导用户关注公众号或者添加客服微信点击客服,出现"你可能发送的图片"或者"你可能发送的小程序" 在之前的文章中我写过,如果通过小程序合理合规引导用户关注公众号,具体见下文 https://developers.weixin.qq.com/community/develop/article/doc/000208cb3fc438638ae9fd6d451013 其中提到一种方式就是客服消息, 自定义卡片消息交互设计示例 具体示例图如下所示: 以下截图来自小程序抽奖助手 [图片] [图片] [图片] [图片] 自定义卡片消息代码片段 https://developers.weixin.qq.com/miniprogram/dev/component/button.html 该技术是自定义客服卡片,具体代码可以参考 欢迎使用代码片段,可在控制台查看代码片段的说明和文档 联系客服 https://developers.weixin.qq.com/s/NHTz9tml76c3 [图片]
2020-03-14 - 小程序在国际化上遇到的问题以及解决方案
对于像我这样少数民族开发者来说,经常会遇到一些项目要求多语言、国家化。正好最近有一个项目,需要国语和维吾尔语之间进行切换。 根据微信官方给出的国际化工具类库很快做到了国际化。但是我们这个项目有tabBar,而且,tabBar 需要我们自定义,因为手机自带的字体太过于难看(对于维吾尔文来说,系统自带的字体太难看了)。而且tabBar原生不支持指定font-face属性。几乎所有的原生组件都无法指定font-face属性。所以我们只能自定义tabBar了。有点扯远了,回归问题本身。 当tabBar进行自定义之后,国际化类库不好使了。页面其他位置的国际化切换的都很流畅,唯独tabBar就没生效。也不知道是什么原因。最终通过国际化类库提供的 语言切换时触发的回调函数哪儿做到了同步切换。免得大家以后多走弯路,我把本次经验分享出来了。如果有什么不对的,希望不要骂我,我也是一个学者。谢谢各路神仙听我胡扯! ready(){ // 页面加载完毕后更新一下tabbar列表数据 this.updateTabList(); // 切换语言是再次进行更新tabbar列表数据 this.onLocaleChange(()=>{ this.updateTabList(); }) }
2020-03-06 - 全平台(Vue、React、微信小程序)任意角度旋转 图片裁剪组件
SimpleCrop全网唯一支持裁剪图片任意角度旋转、交互体验媲美原生客户端的全平台图片裁剪组件。 Github 地址:https://github.com/newbieYoung/Simple-Crop 特性及优势和目前流行的图片裁剪组件相比,其优势在于以下几点: 裁剪图片支持任意角度旋转;支持 Script 标签、微信小程序、React、Vue;支持移动和 PC 设备;支持边界判断、当裁剪框里出现空白时,图片自动吸附至完全填满裁剪框;移动端缩放以双指中心为基准点;交互体验媲美原生客户端。示例微信小程序示例[图片] 移动端示例[图片] 左侧是 IOS 系统相册中原生的图片裁剪功能,右侧为 SimpleCrop 移动端示例。 可以扫描二维码体验: [图片] 或者访问以下链接: https://newbieyoung.github.io/Simple-Crop/examples/test-2.html PC 示例[图片] 链接如下: https://newbieyoung.github.io/Simple-Crop/examples/test-1.html 安装npm install simple-crop 用法Script 用法微信小程序用法React 用法Vue 用法开源许可协议MIT License. 原理及实现[代码]全平台(Vue、React、微信小程序)任意角度旋转 图片裁剪组件[代码] https://newbieweb.lione.me/2019/05/16/simple-crop/
2020-03-04 - 微信内部 H5跳转小程序的几种方式
昨天半夜被头儿一阵急匆匆的电话叫醒 最近由于疫情,截止今天(复工的第三周了),小伙伴们都在家办(划)公(水),有些需求调研可能都是头儿独自来撑着,头儿自报家门后问我,网页里面能不能直接打开小程序或者网页分享到朋友圈然后打开小程序,我不好掐指算,头儿调研的功能,但是很肯定的告诉头儿,不行,不行,你别胡来。 当然我给头儿的回复是肯定不可以这么操作,从技术角度是办不到的。 言归正传,那么从微信H5打开小程序有几种实现方式呢?目前我想到有两种,如果还有其他方式,可以评论区留言 1、微信H5内部,放一张小程序码 图片,然后提示用户长按小程序码图片体验小程序 2、微信H5提供链接,跳转到公众号MP文章,也就是公众号内部文章,这个时候由于公众号是可以通过下面截图中文字、图片、小程序卡片、小程序码四种方式唤醒小程序的。 [图片] 占位、 如果还有其他技术可行性方案,欢迎大家在评论区留言,非常感谢。
2020-02-18 - 一个小小的优化,能让你的小程序瘦身10%
我司一直专注于微信小程序(以下简称小程序)开发,可以说是重仓押注在小程序上。 但由于小程序的大小有严格的限制(单个分包/主包大小不能超过2M)。 而我们的业务又相对比较复杂,因此常常会突破小程序的大小限制。因此,我们就不得不思考如何优化小程序的大小。 暴力方式 要优化小程序的大小,最好(最暴力)的方式就是删页面。 这样来,即高效执行起来也简单:统计下所有页面的PV、UV,将一些不活跃的页面移除就完事了。 但是,本文并不是要讲如何移除页面,因为这没什么好讲的。 分析 讲本文的优化方式之前,先分析一下小程序一般都由哪些文件组成的。 一般都是由以下几种文件组成: [代码].js[代码] 逻辑文件 [代码].wxml[代码] 页面结构文件 [代码].wxss[代码] 样式文件 [代码].json[代码] 配置文件 也许你会将一些image放在小程序里,一般建议放较小且少量的image,其他都使用网络图片 其中,由于[代码]JavaScript[代码]有一定的兼容问题需要处理,因此在打包和上传小程序时,开发者工具会对[代码]JavaScript[代码]进行[代码]babel[代码]编译处理,故这块可优化的空间比较有限。 而[代码]JSON[代码]的大小都比较小,且格式较为固定,也没什么可优化的地方。 接下来就是本文要重点说到的[代码]WXML[代码]了,一般[代码]WXSS[代码]都是和[代码]WXML[代码]配套使用的。这两者占小程序的大小比例也比较高,可优化空间非常大,可优化的思路也非常多。本文先讲一下[代码]WXML[代码]的一个优化技巧。 试验 其实,小程序最终的执行都是以WEB的形式完成的。因此[代码]WXML[代码]可以理解成类似于[代码]VUE[代码]的语法糖,最终都是要编译成[代码]HTML[代码]的。 所以,想要压缩[代码]WXML[代码]代码,就可以参考[代码]HTML[代码]的压缩方式。比如移除多余的空格。 我立马做了个试验,将[代码]WXML[代码]中的部分的空格移除之后,再使用开发者工具上传,发现小程序的大小真的发生了变化,变得更小了。因此可以得出结论,移除[代码]WXML[代码]中的空格是可行的压缩思路。 自动化 既然移除空格是可以减小小程序代码体积的,那么如何实现自动化移除的。 首先我想到的是,利用巨人的肩膀:[代码]htmlparser2[代码]。通过语法分析器,识别[代码]WXML[代码]的空格,并一举歼灭。 绝大多数情况下,这个做法是可行的。但是有一种情况,会导致[代码]parser[代码]识别出错:[代码]WXML[代码]中出现[代码]{{ }}[代码],且使用了[代码]<[代码]。 因此需要特制一个识别[代码]WXML[代码]语法的[代码]parser[代码]。 由于这样的parser比较简单,因此我就自己上手写了一个:wxml-parser 实践 通过上述我写的parser,写了一个简单的minifier:wxml-minifier 安装 [代码]npm i -D wxml-minifier [代码] 使用 [代码]let minifier = require('wxml-minifier') let fs = require('fs') let resource = fs.readSync('./app.wxml') // 假设输入为:<view class="home" ></view> <!-- test --> let result = minifier(resource) console.log(result) // <view class="home"></view> [代码] 总结 通过将[代码]WXML[代码]中多余的空格移除,可以将小程序的代码减小大概10%。 其实,从这个角度可以发现,开发者工具在上传[代码]WXML[代码]时,是没有做任何处理的。因此对于HTML的任何压缩方式都可以在[代码]WXML[代码]上使用。当然这也是后续我的[代码]wxml-parser[代码]持续更新迭代的方向。 不知道为什么微信官方在开发者工具上传代码时,不进行简单的简化处理。如果你有答案的话,欢迎在评论中给我回复! 如果觉得对你有用,希望给我一个star,感谢!
2020-01-21 - 借助nginx快速搭建一个简单的https
目前尝试过基于tomcat和nginx来搭建https,总体来说感觉nginx会更灵活,所以做个笔记 1、购买SSL证书,从各大证书平台购买,并且要和你的域名进行绑定。 由于每个平台都是大同小异的,购买过程就忽略了,商家指引步骤都很详细在证书管理控制台中申请,并与域名绑定(目前发现免费证书不支持绑定二级域名,其他证书未知)提交审核后一般几分钟就可以认证通过,点击部署到对应的云服务器,下载nginx证书文件2、nginx配置(假如证书文件名是a.pem,私钥文件是a.key) 在Nginx的安装目录下创建cert目录,并且将下载的文件解压后拷贝到cert目录中。如果申请证书时是自己创建的CSR文件,请将对应的私钥文件放到cert目录下并且命名为a.key 打开 Nginx 安装目录下 conf 目录中的 nginx.conf 文件。添加HTTPS server相关配置,默认配置文件会有一个注释掉的代码块,改成这样#复制某云的官方配置进行修改 server { listen 443 ssl; server_name xxx.myserver.xxx; ssl_certificate ../cert/a.pem; # 对照自己存放秘钥文件的位置,不一定要在这里 ssl_certificate_key ../cert/a.key; # 对照自己存放秘钥文件的位置,不一定要在这里 ssl_session_timeout 5m; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; # 不重要的文件资源 location /myfiles { alias /var/apps/nginx/files/myapp/; } # 代理443端口对应自己服务器的api地址,可以个性化地结合后端安全框架灵活管理api location / { proxy_pass http://xxx.myserver.xxx:9090; } # 如果还需要其他辅助api也可以继续添加配置 location /py { proxy_pass http://xxx.myserver.xxx:7001; } # 同上 location /myapp{ proxy_pass http://xxx.myserver.xxx:9090/myapp; } # 后台系统界面配置 location /admin { root html/myapp-web; # nginx的web文件习惯放在/html下 index index.html; # 在使用BrowserHistory打包的前后端分离单页面web应用时,需要此配置将路由 fallback 到 index.html,以免刷新页面跑偏 try_files $uri $uri/ /index.html; } } 配置写好保存,启动nginx。测试一个/nginx/files/myapp/下的静态资源文件或任意一个api是否能跑通例如 原api为:http://xxx.myserver.xxx:9090/test 改为:https://xxx.myserver.xxx/test 至此结束,欢迎补充
2020-01-16