个人案例
- 商家接入微信支付后,如何对用户开通连续包月的扣款服务?
如题,申请开通连续包月功能需要达成什么条件?如何申请?在微信商户后台没有看到相关内容
2024-05-21 - 微信支付分扣款失败微信垫付资金么?
微信支付分扣款失败微信垫付资金么?
2024-09-26 - “微信医保支付”功能介绍与接入指引
产品简介:基于微信进行医保移动支付结算的功能。 优势:无需线下排队,医保移动支付。 接入方式:需开发。 关键词:进阶功能,便捷就医。 01 功能介绍 用户通过微信绑定个人社保卡,便可将微信号与个人医保账户关联,在就医挂号&门诊缴费等环节,进行便捷的医保、自费或医保+自费混合支付。 微信医保支付流程交互: 1)绑卡 [图片] 用户通过微信城市服务或当地人社公众号/小程序,进行实人实名等信息校验,将个人微信关联个人社保卡。 2)支付 [图片] 绑卡用户到已接入微信医保支付的医院就医,通过服务号/小程序挂号,可选择微信医保支付,支付诊费/药费(具体使用规定以当地医保政策为准)。 02 业务架构说明 微信医保支付的业务的整体架构,可以类比微信支付,涉及的最基本的模块有三方:社保卡绑卡类比银行卡绑卡,人社局类比银行,医院类比商户。 业务操作流程说明如下: 微信医保支付架构: [图片] 1、用户使用微信绑定个人社保卡; 2、微信与人社系统同步记录用户微信与社保账户的关联关系; 3、医院通过服务商接入微信医保支付打通移动医保支付通路; 4、绑卡用户通过公众号/小程序就诊后通过微信进行移动医保支付; 5、医院上传用户处方单至人社系统,医保结算成功后人社将费用结算信息同步至微信及医院侧,由医院通过服务通路通知用户。 03 接入指引 1) 微信医保支付接入条件 所在城市已上线微信电子社保卡服务,目前已开通的城市:深圳、成都、铜川、郑州、厦门、嘉兴、宁波、开封、武汉,延安、白城、攀枝花,长沙、哈尔滨、沈阳、潍坊、苏州、葫芦岛、西安、宝鸡、咸阳、榆林、渭南、安康、商洛、永康、合肥,常熟、青岛、广州、邯郸、台州、南昌、通化、石家庄、长春、大庆、济南、桂阳县、安吉县、太仓县、东莞、无锡、枣庄、威海等。 医院/药店已开通微信公众号或小程序,且已开通微信支付。 已向当地人社局或医保监管机构申请,确认本医院/药店可申请接入。 2 )申请方式 联系当地人社局或医保监管机构申请确认后,通过以下方式申请: 国家公立医疗机构:可通过医院公众号后台线上申请医保权限,具体操作链接指引https://mp.weixin.qq.com/s/TnaBUREMR8ikZ4efetEfQg 私立医疗机构或药店:通过邮件方式申请开通医保权限,须联系腾讯工作人员对接指引。 以上案例素材,整理自试点小程序。
2020-01-10 - 购买押金是否属于虚拟支付?
https://mp.weixin.qq.com/s/CvawEWKvkIcIGOtwjsqZjg 针对虚拟支付会有一系列的相关的限制,如果有一个出租物品的微信小程序,对出租的物品需要购买押金及租金才可出租, 押金属于虚拟支付吗? 租金属于虚拟支付吗?
2024-08-14 - worklet.timing toValue 类型问题?
SharedValue worklet.shared(any initialValue)参数 any initialValue(任何类型包括数组)初始值,可通过 [代码].value[代码] 属性进行读取和修改。类型可以是 [代码]number | string | bool | null | undefined | Object | [代码][代码]Array [代码][代码]| Function[代码]。 但是AnimationObject worklet.timing(number toValue, Object options, function callback) 但timing 只支持number ? 如果用shared 创建了一个数组,如何用timing 去改变这个值? 官方例子都是用的number ,没有其他类型的说明。const { shared, sequence, timing, spring } = wx.worklet const offset = shared(0) offset.value = sequence(timing(100), spring(0)) 上面是官方例子,如要改成 数组呢怎么办? const offset = shared([0,100]) offset.value = sequence(timing(???????), spring(0)) 针对CSS不光有数值上的调整,字符串也需要,display:flex background-color: cornflowerblue ,这种怎么办?
2024-05-04 - 使用 Skyline Worklet 动画实现下拉页面放大头图
效果 [图片] 思路 监听滚动事件,映射滚动距离至头图的放大比例。 实现 <!-- index.wxml --> <scroll-view scroll-y class="scrollView" type="list" worklet:onscrollupdate="scrollViewOnScroll" > <image src="https://wx2.sinaimg.cn/large/007GYgpfly1hnfihmysbmj32yp281u0y.jpg" mode="aspectFill" class="headerImage" /> </scroll-view> /** index.ts */ const { shared } = wx.worklet /** 头图的放大高度 */ const headerImageHeight = shared(0) Component({ lifetimes: { attached() { this.applyAnimatedStyleToHeaderImage() } }, methods: { /** 绑定由 worklet 驱动的样式到头图 */ applyAnimatedStyleToHeaderImage() { this.applyAnimatedStyle( '.headerImage', () => { 'worklet' const scale = (240 + headerImageHeight.value) / 240 return {transform: `scale(${Math.max(1, scale)})`} }, {immediate: false} ) }, /** <scroll-view/> 的滚动回调 */ scrollViewOnScroll(event: any) { 'worklet' headerImageHeight.value -= event?.detail?.deltaY } } }) /* index.wxss */ .scrollView { width: 100vw; height: 100vh; } .headerImage { width: 100%; position: fixed; top: 0; left: 0; pointer-events: none; }
2024-07-07 - wxml2canvas-2d:简单易用的小程序海报、战绩等分享图片生成方案
Github 地址:https://github.com/ChrisChan13/wxml2canvas-2d 介绍 当前,众多小程序的多处场景都需要能够生成分享图便于用户进行二次传播,从而提升小程序的传播率以及加强品牌效应。 比较简单的分享图,如寥寥几行文字和一张小程序码,可以通过微信的 Canvas API 绘制。旧版 Canvas API 绘制过程繁琐,且每次绘制都需要调用 draw 方法,一不小心代码就写了上百行。 新版 Canvas API 基本与 Web Canvas 对齐,使得开发效率提高、性能得到优化。虽然免去了很多繁琐操作,但面对拥有元素众多、结构复杂的分享图片,依然解决不了代码冗长的问题。 目前开源的一些小程序图片生成方案,有的年久失修、有的依然使用旧版 Canvas API、有的使用方式不够简便,于是便有了开发 wxml2canvas-2d 的想法。 wxml2canvas-2d 的图片生成方式简单直观:首先在 wxml 页面上编写元素结构,其次在 wxss 中编写元素样式,最后调用 wxml2canvas-2d 的相关方法即可生成所需的分享图片。 wxml2canvas-2d 会通过 class 类名查询元素节点的 computedStyle 和节点属性,从而将元素节点绘制到画布上。这样做的好处是在编写 wxml 结构以及样式时,可以直观的看见样式的变化,方便调整。当然也有坏处,这个用来生成图片的“wxml 模板”,必须存在于页面上。若需要隐藏这个“模板”,只可用定位将其移至屏幕外,不可以使用 wx:if 或 hidden 隐藏。 wxml2canvas-2d 已经支持大部分常用的 CSS 属性,等你来测~ 示例 克隆此 Github 仓库,运行 [代码]npm i & npm run dev[代码],将 miniprogram_dev 文件夹导入微信开发者工具 效果预览 小程序内容: [图片] 生成的图片: [图片] 安装 npm 使用 npm 构建前,请先阅读微信官方的 npm 支持 [代码]# 通过 npm 安装 npm i wxml2canvas-2d -S --production [代码] 构建 npm 包 打开微信开发者工具,点击 工具 -> 构建 npm,并勾选 使用 npm 模块 选项,构建完成后,即可引入组件。 使用 在页面配置中引入 [代码]wxml2canvas-2d[代码] ; [代码]{ "usingComponents": { "wxml2canvas": "wxml2canvas-2d" } } [代码] 在页面中编写 wxml 结构,将要生成画布内容的根节点用名为 [代码]wxml2canvas-container[代码] 的样式类名称标记,将该根节点内部需要生成画布内容的节点用名为 [代码]wxml2canvas-item[代码] 的样式类名称标记(文字类节点需在对应节点声明 [代码]data-text[代码] 属性,并传入文字内容)。上述两个样式类名称可以自定义,只需将对应名称传入 [代码]wxml2canvas[代码] 组件的对应属性参数即可; [代码]<!-- pages/index/index.wxml --> <view class="wxml2canvas-container box"> <view class="wxml2canvas-item title" data-text="测试标题">测试标题</view> <image class="wxml2canvas-item image" src="/your-image-path.png" /> <view class="wxml2canvas-item content" data-text="测试内容,长文本。。">测试内容,长文本。。</view> </view> <button catchtap="generateSharingCard">生成画布内容</button> <wxml2canvas id="wxml2canvas" /> [代码] 补充各个节点样式; [代码]/* pages/index/index.wxss */ .box { /* 根节点(容器)的样式 */ } .title { /* 标题的样式 */ } .image { /* 图片的样式 */ } .content { /* 内容的样式 */ } [代码] 依据 wxml 结构以及 css 样式,生成画布内容,并将生成结果导出。 [代码]// pages/index/index.js Page({ async generateSharingCard() { const canvas = this.selectComponent('#wxml2canvas'); await canvas.draw(); const filePath = await canvas.toTempFilePath(); wx.previewImage({ urls: [filePath], }); }, }); [代码] 更多内容及文档 点击此处 前往查看!如果有好的建议或者想法,也欢迎提交 Issue 或 PR~
2024-11-21 - 微信支付报错:201商户订单号重复
发起微信支付但未支付,调用关闭订单接口后,查询订单也是关闭状态, [图片] 再次使用之前的商户订单号发起支付报错:201 商户订单号重复[图片]
2024-04-22 - Skyline 渲染引擎常见问题
Skyline 一定需要应用到整个小程序吗? 不需要,Skyline 支持按页面粒度开启,建议开发者逐个页面适配 在 Skyline 模式下,为什么使用真机调试会显示空白并且工具报错? 目前 Skyline 模式下暂不支持真机调试,建议使用真机预览完成调试,平台在尽快支持真机调试能力。 在 Skyline 模式下,为什么微信开发者工具热重载无响应? Skyline 模式暂不支持热重载,建议先关闭热重载,重新编译来预览渲染结果。后续平台将支持热重载能力。 开启 Skyline 后布局错乱 大多是由于没有全局滚动而导致挤压,以及 flex-direction 默认为 column 造成。前者只需要加上 scroll-view,后者可以在声明了display:flex 但又没指定 flex-direction的地方显示指定flex-direction:row。推荐开发者开启默认 Block 布局。 切换 Skyline后,为什么顶部原生导航栏消失? 不支持原生导航栏,需自行实现,或使用 weui 组件库 伪类及伪元素部分支持 对于伪类,目前只支持常用的 :first-child 和 :last-child 。其它伪类可通过按需添加 class 替代,如 :active 则手动给点击状态下的节点加个.active class 对于伪元素,目前只支持 ::before 和:after。其它伪元素建议用真实 WXML 节点实现。 全局固定元素失效 因不支持 fixed 导致,但由于没有全局滚动,在页面根节点下使用 absolute 即可达到 fixed 的效果,倘若封装原因无法移至页面根节点,可使用 root-portal 组件包裹 切换 Skyline 后,为什么 position: absolute 相对坐标不准确? 在 Skyline 模式下,所有节点默认是 relative,可能导致 absolute 相对坐标不准。建议开发者修改节点 position 或者修改相对坐标。 多段文本无法内联 因不支持 inline 布局导致,需改成 flex 布局实现,或者使用 text 组件包裹多段文本,而不是用 view 组件包裹,也可以使用 span 组件包裹 text 和 image 混合内联。如 、<span><image /></span>,<span><view style="width: 50px;"/></span> 多行文本的省略样式失效 在单行文本省略的基础上,通过 text 组件的 max-lines 属性设置最长行数,即 <text max-lines="{{2}}"></text> z-index 表现异常 这是由于 Skyline 不支持 web 标准的层叠上下文所致,只有在同层级的节点之前应用 z-index才有效,可根据实际情况调整取值 weui 扩展库无法使用 平台正在支持扩展库,预计近期上线。建议开发者使用 npm 安装 weui 组件库 后,将 node_ modules/weui-miniprogram 下的miniprogram_ dist 替换为 链接 中的 miniprogram_dist,然后在微信开发中工具中构建 npm 即可。 不支持组件 animate 动画接口 暂不支持组件 animate 动画接口。如需实现相关效果,可使用 worklet 动画机制 实现 svg 渲染不正确 Skyline 上的 SVG 不支持 <style> 选择器匹配,可自行转成内联的方式;不支持 rgba 格式,可使用 fill-opacity 替代;建议用 SVGO 在线工具优化 scroll-view 横向滚动不生效 横向滚动需打开 enable-flex 以兼容 WebView,同时 scroll-view 添加样式 display: flex; flex-direction: row;,scroll-view 子节点添加样式 flex-shrink: 0; icon-font 图标不显示 最新版本已支持伪元素,低版本可参考 代码片段 实现图标
2023-10-18 - 微信小程序深度合成-AI问答类目获取指引(AI小程序必备)
前言只要AI相关的小程序没有深度合成类目提交审核都会被拒绝,涉及到AI问答,AI绘画,AI换脸都需要补充类目才能提交,那么如何准备深度合成AI问答类目所需材料,才能通过【深度合成-AI问答】类目审核? [图片] 方案选择资质时选第二个方案: [图片] 2.1、使用第三方技术:同时提供: ① 技术主体的《互联网信息服务算法备案》(算法类型为“生成合成类(深度合成)”)或《互联网信息服务算法备案》(算法类型为“生成合成类”)在审批中的系统截图及 ②小程序主体与技术主体的合作协议(协议需含【算法名称】或【应用产品】或【备案编号】相关内容) 材料需要两个:1.大模型算法备案截图 2.合作协议截图 首先进入微信服务市场选择「接口和插件」 [图片] https://fuwu.weixin.qq.com/ 然后找到大模型服务任意选择一个大模型服务即可 [图片] 进去大模型服务选择一个套餐进行购买操作 [图片] 购买成后会生成一个订单截图,订单截图中包含了算法备案截图分别提交即可申请AI问答类目。 算法备案截图如下: [图片] 合作订单截图如下: [图片] 分别按顺序上传到这两处资质文件即可(1.算法备案2.合作订单) [图片]
2024-10-27 - 微信云开发支付签名错误,请情况?
说一下没有子商户,只有商户号,就是用的以前的支付接口,这是云开发没有后端代码那个notify_url不写还不行, 不知道哪里有问题, 签名在微信工具里验证通过,调用下单接口就报签名错误,希望会的人答疑解惑一下,谢谢啦 //2 openid 就是支付用户的识别号 const mch_id = 'xxx '; // 商户号 const key = 'xxxxx'; // 商户密钥 const cloud = require('wx-server-sdk') const rp = require('request-promise') const crypto = require('crypto') cloud.init() function getSign(args) { let sa = [] for (let k in args) sa.push(k + '=' + args[k]) sa.push('key=' + key) console.log(sa.join('&')) return crypto.createHash('md5').update(sa.join('&'), 'utf8').digest('hex').toUpperCase() } function getXml(args) { let sa = [] for (let k in args) sa.push('<' + k + '>' + args[k] + '</' + k + '>') sa.push('<sign>' + getSign(args) + '</sign>') let axml = '<xml>' + sa.join('') + '</xml>' console.log("最后签名:",axml) return axml } function getNonceStr(){ var chars = ['0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z']; var nums=""; for(var i=0;i<32;i++){ var id = parseInt(Math.random()*61); nums+=chars[id]; } nums= nums.toLowerCase() return nums ; } exports.main = async (event, context) => { const wxContext = cloud.getWXContext() const appId = appid = wxContext.APPID console.log("appid是:", appid, appId) console.log("key是:", key) const openid = wxContext.OPENID // const attach = 'attach' const body = event.msg; const total_fee = event.totalFee; const notify_url = "http://127.0.0.1" const spbill_create_ip = "127.0.0.1" const fee_type = "CNY" // const nonceStr = nonce_str = Math.random().toString(36).substr(2, 15) const nonceStr = nonce_str = getNonceStr() const timeStamp = parseInt(Date.now() / 1000) + '' const out_trade_no = event.outTradeNo; const trade_type = "JSAPI" const sign_type = "MD5" const xmlArgs = { appid, // attach, body, fee_type, mch_id, nonce_str, notify_url, openid, out_trade_no, sign_type, spbill_create_ip, total_fee, trade_type, } let xml = (await rp({ url: "https://api.mch.weixin.qq.com/pay/unifiedorder", method: 'POST', body: getXml(xmlArgs) })).toString("utf-8") console.log("签名是:",xml) if (xml.indexOf('prepay_id') < 0) return xml let prepay_id = xml.split("<prepay_id><![CDATA[")[1].split("]]></prepay_id>")[0] let payArgs = { appId, nonceStr, package: ('prepay_id=' + prepay_id), signType: 'MD5', timeStamp } return { ...payArgs, paySign: getSign(payArgs) } }
2024-10-23 - 记录一次云开发数据库查询的简单优化
云开发数据库联表查询,最开始表现优异,随着数据量的增加,某条语句的查询时间居然超过了2秒。后面着手优化查询到100毫秒以内。 优化主要事项: 1、将条件(match)、排序(sort)、分页(skip、limit)移到联表之前,先查出部分结果后再联表操作。 2、取消模糊查询连接后的子表字段。 优化后的语句如下: db.collection("form_answers") .aggregate() .match({ _openid: "xxxx" }) .sort({ createTime: -1 }) .skip(0) .limit(20) .lookup({ from: 'forms', let: { formId: '$formId' }, pipeline: $.pipeline() .match(_.expr($.and([ $.eq(['$_id', '$$formId']), ]))) .project({ _id: 0, name: 1, unionid: 1 }) .done(), as: 'formList', }) .replaceRoot({ newRoot: $.mergeObjects([{ formName: $.arrayElemAt(['$formList.name', 0]), formUnionid: $.arrayElemAt(['$formList.unionid', 0]) }, '$$ROOT']) }) .project({ formList: 0 }) .end()
2024-06-23 - 省钱有道之 减少云函数调用次数
由于云函数有一项计费规则是按调用次数计费,在小程序访问量比较小的情况下还比较无所谓,但当体量上来之后不得不考虑控制一下对公共接口的调用次数从而减少一些不必要的开销。比如获取用户信息接口、获取配置信息接口 这里分享一个我自己几个小程序用到的方法,公共接口的调用都放在app.js,然后提供函数供其他页面调用。同时由于异步问题,有可能页面加载完接口还未返回,因此还需能够注册回调函数,在接口返回数据后回调给调用页面 代码示例: app.js App({ onLaunch: async function (options) { //判断是否需要更新小程序 updateCheck.check(); await api.wxCloudInit(); //获取用户信息 this._getUserInfo().catch(res => { console.warn("获取用户信息失败,准备重试"); this._getUserInfo().then(); }); }, /** * 获取用户信息 * @param callback * @param refresh 等于true时表示重新查询用户信息,同时也会更新会员状态 */ getUserInfo: function (callback, refresh) { if (!refresh) { const userInfo = this.globalData.userInfo; if (!userInfo.ready) { if (typeof callback == 'function') { this.callbackFunctions.userInfoReadyCallback.push(callback); } if (!this.userInfoReadyCallback) { this.userInfoReadyCallback = res => { console.log("获取用户信息完毕,开始回调", res); const callbacks = this.callbackFunctions.userInfoReadyCallback; while (callbacks.length) { const callback = callbacks.pop(); typeof callback == 'function' && callback(res); } /*callbacks.forEach(callback => { typeof callback == 'function' && callback(res); })*/ } console.log("注册userInfoReadyCallback成功"); } else { console.log("已经注册了userInfoReadyCallback,不再重复注册"); } } else { typeof callback == 'function' && callback(userInfo); } } else { console.log("准备更新用户信息") this._getUserInfo().then(userInfo => { typeof callback == 'function' && callback(userInfo); }); } }, /** * 执行云函数,获取用户信息 * @returns {Promise<unknown>} * @private */ _getUserInfo: function () { return new Promise((resolve, reject) => { api.callCloudUserCenterFunction("UserInfoHandler/getUserInfo", {}, res => { console.log("获取用户数据完毕:", res.result); const result = res.result; if (result.success) { const data = result.data; this.globalData.userInfo = data; // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回 // 所以此处加入 callback 以防止这种情况 if (this.userInfoReadyCallback) { this.userInfoReadyCallback(data); } resolve(data); } else { console.error("没有获取到用户信息"); reject("没有获取到用户信息"); } }, e => { console.error("获取用户信息失败", e); reject("获取用户信息失败"); }); }); }, /** * 异步事件回调函数列表 * 增加这个列表是为了避免不同地方同时调用,互相覆盖回调函数 */ callbackFunctions: { //用户信息异步回调 userInfoReadyCallback: [], }, globalData: { userInfo: { confirm: false//用来标记用户信息查询动作是否已经结束,等于true时,userInfo才可信 }, } }) 某page.js app.getUserInfo(res => { const isVip = res.isVip; if (isVip) { console.log("已开通会员", res); } });
2024-02-07 - 微信小程序的Text组件,设置user-select之后,怎么样才能当一行放不下的时候不整体换行?
微信小程序的Text组件,设置user-select之后,当一行放不下这个text文本的时候,会整体换行。有没有什么办法让这是user-select的text组件按照原来的样式正常换行(填充完当前行之后剩下部分的再换行展示) <scroll-view class="scroll-area" type="list" scroll-y style="padding: 0px 20px; box-sizing: border-box;"> <view style="display: inline;"> <text >欢迎使用代码片段,</text> <text >:可在控制台查看代码片段的说明和文档</text> </view> <view style="margin-top: 50px;"> <text user-select>欢迎使用代码片段,</text> <text user-select>:可在控制台查看代码片段的说明和文档</text> </view> </scroll-view> [图片]
2024-10-21 - 强烈投诉,小程序服务类目被打回10次,一直被要求补充的资料卫健委说从不提供这种内容,为什么还不通过?
审核类型:服务类目审核“医疗服务>互联网医院” 提审时间:自9月20号上传第一版,到10月14号,过程中上传不少于10次,快要一个月过去了,依然没有审核通过。 面临问题: 申请服务类目为“医疗服务>互联网医院”,我们按照要求上传了相关资质。我们小程序主题是A,互联网医院牌照的主体是B。针对合作医院B的《医疗执业机构许可证》和A与B《合作协议》均没有什么异议。 但是,微信审核时,要求“《医疗主管部门许可文件》(含“互联网诊疗“相关内容)”和“《省级互联网医疗服务监管平台对接情况证明》”这两项资料, 需要上传任何一项资料。 在跟浙江省卫健委的领导沟通后,对方回复“我们已经发给你们了《医疗执业机构许可证》,还需要什么《医疗主管部门许可文件》,我们没有这样的内容可以提供。” 所以我们选择,上传省监管平台下载的《省级互联网医疗服务监管平台对接情况证明》,同时附了省监管平台及浙里办等官方平台查到的B互联网医院,以及公开可核实的官网链接,并加盖了我司公章。但是微信平台要求加盖政府相关部门单位的公章,可是浙江省卫健委的领导回复“我们没有这样的盖章流程,盖不了。” 想要问问小程序服务类目的审核人员: 1、你们要的《医疗主管部门许可文件》或者《省级互联网医疗服务监管平台对接情况证明》,具体是个什么样子,能否给个示例,我们也好发给浙江省卫健委的领导参考? 2、我们已经证明了B有有效期内的《医疗执业机构许可证》和A与B有有效期内的《合作协议》,上传另外两项资料的合理性是什么? 我们上传的资料你们真的看了么?你们的一次审核,关系到我们挣个团队一个季度的努力,但是你们并不会管我们死活。再审核不通过,我们整个产品部都要被开了。。。。客服联系不到,审核又不合理,真的。。。。
2024-10-14 - 小程序pdf、word、excel、ppt等文件页数与预览(第5篇)
一、实现思路: 通过调用第三方服务,将word、excel、ppt等文件通过转码,转成pdf文件方式,然后进行页码获取以及预览。 二、常用第三方转换平台: 阿里云文档处理:https://help.aliyun.com/zh/oss/user-guide/overview-65?spm=a2c4g.11186623.0.0.31927361ms2sPH 腾讯云文档处理:https://cloud.tencent.com/document/product/460/47495 wps开发平台:https://solution.wps.cn/docs/convert/principle.html 三、相关文章 在小程序里初步获取pdf页数在小程序里预览pdf文件小程序与h5接近实时的双向通信小程序页面通过webview获取pdf页数(改为:小程序页面实时计算pdf页数)小程序pdf、word、excel、ppt等文件页数与预览 备注:做过不少文档相关的服务,有需要可私信沟通。
2024-10-12 - 微信jsapi,一码多付
微信jsapi支付问题,想实现一码多付,就是客户只看到一个二维码120元,内部下两个订单,分别给商户a和商户b,可以实现吗?
2024-10-12 - 🎆我们开源啦 | 基于Skyline开发的组件库🚀
我们开源啦,希望可以给大家的开发之旅带来一些灵感。我后溪的小程序也都会基于这个组件库开发,并且会保持组件库的更新与维护。 我是第一次进行开源,肯定会有错漏,欢迎大家指正,我会以最快的时间响应修改。 Skyline UI 组件库 前言 Skyline 是微信小程序推出的一个类原生的渲染引擎,其使用更精简高效的渲染管线,性能比 WebView 更优异,并且带来诸多增强特性,如 Worklet 动画、手势系统、自定义路由、共享元素等。 使用这个组件库的前提是:通过微信小程序原生+skyline框架开发,所以目前我们不保证兼容webview框架(也就是电脑端与低版本的微信),但后续会进行系统性的兼容。 使用 Skyline UI前,请确保你已经学习过微信官方的 微信小程序开发文档 和 Skyline 渲染引擎文档 。 背景 随着Skyline 渲染引擎 1.1.0 版本发布,我们所运营的小程序也平稳的渡过了阵痛期,团队使用Skyline也越来得心应手,所以接下来,团队的开发重心全面偏向Skyline渲染框架,考虑有大量的UI交互重复,我们决定基于Skyline开发了这个UI组件库。 但团队力量有限,这个新生的组件可能有很多的不尽如人意,所以希望能以开源的方式吸引更多开发者使用Skyline框架,如果这个框架不适合你,也可以借鉴其思路。 Gitee Gitee仓库 在线预览 以下是目前两个使用该框架的小程序 SkylineUI组件库 [图片] NONZERO COFFEE [图片] 开始使用 UI库结构 Skyline UI组件库 依赖于以下四部分,具体使用参考以下的具体说明 utils工具库: 其中包含了UI库自定义的一个工具类SkyUtils,它包含了组件中所含的各种函数,非常重要。 各组件元素:sky-*(组件名) skywxss样式库:其中包含深浅色色彩、文字字体、布局等样式wxss 在小程序中引入 UI库 一、直接下载引入 点击下载组件包 将src下所有文件复制到您项目根目录下的components文件夹中,没有的话请自行新建。 二、npm引入 1.在小程序项目中,可以通过 npm 的方式引入 SkylineUI组件库 。如果你还没有在小程序中使用过 npm ,那先在小程序目录中执行命令: [代码]npm init -y [代码] 2.安装组件库 [代码]npm install jieyue-ui-com [代码] 3.npm 命令执行完后,需要在开发者工具的项目中点菜单栏中的 工具 - 构建 npm 两种引入方式的不同可能导致后续使用时,引用组件的路径不同,请注意区别 1.直接引入components文件夹内,引用地址通常是 ‘./components/‘ 2.npm引入,组件引用地址通常是’./miniprogram_npm/jieyue-ui-com/’ 如何使用 1.在app.js文件中初始化工具类,并且添加两个全局变量 [代码]// app.js App({ onLaunch() { ;(async ()=>{ // 全局注册工具类SkyUtils // 这里默认npm引用,地址为'./components/utils/skyUtils',如果是直接引用组件,地址可能是'./components/utils/skyUtils',后面不再说明 const SkyUtils = await import('./components/utils/skyUtils'); wx.SkyUtils = SkyUtils.default; // 初始化设备与系统数据 wx.SkyUtils.skyInit() // 小程序自动更新方法 wx.SkyUtils.versionUpdate() })() }, globalData: { sky_system:{}, sky_menu:{} }, }) [代码] 2.在app.wxss文件中引入样式文件 [代码]//wxss * _dark.wxss 是适配深色模式的色彩变量 @import '/miniprogram_npm/jieyue-ui-com/skywxss/skycolor.wxss'; @import '/miniprogram_npm/jieyue-ui-com/skywxss/skycolor_dark.wxss'; @import '/miniprogram_npm/jieyue-ui-com/skywxss/skyfontline.wxss'; @import '/miniprogram_npm/jieyue-ui-com/skywxss/skyfont.wxss'; @import '/miniprogram_npm/jieyue-ui-com/skywxss/skyother.wxss'; [代码] 3.page.json中引用组件 [代码]//page.json { "usingComponents": { "sky-text":"/miniprogram_npm/jieyue-ui-com/sky-text/sky-text" } } [代码] 4.页面中使用 [代码] // wxml <sky-text content="文本内容" max-lines="2" fade></sky-text> [代码] 5.其他组件具体使用请参考组件包中的redeme.md 适配深色模式 如果您在开发时,全部使用我们预设好的颜色变量,那么可以自动适配深色模式。 [代码].page{ background-color: var(--bg-l0); } [代码] [代码] <view style="background-color: var(--bg-l0)"></view> <view style="background-color: {{color}}"></view> [代码] [代码] Page({ data: { color: "var(--bg-l0)" } }) [代码]
2024-01-09 - 历时两年打造,完全基于skyline引擎的高性能图表工具【图表管家】小程序上线啦
基于微信最新的skyline引擎 微信最新的skyline渲染引擎提供了优异的性能支持,尤其是在页面部分渲染和长列表处理上,提供了底层支持。 基于echarts深度优化和适配 echarts是主流的图表框架,但是echarts团队的ec-canvas很久没有维护了,而skyline又是新发布的,究竟skyline和echarts能不能完美适配,似乎是一个非常大的疑问。经过我们长期的填坑和测试后,我们基于echarts官方组件完全重写,克服了在处理手势事件和其他需要高度自定义的场景的不足,实现了比较理想的适配 使用Taro+原生混合开发 原生小程序的开发方式和react hooks相比,开发效率低,样板代码多,组件的重渲染机制不够清晰,很多场景还必须使用wx.createSelectorQuery()。 更像是类似于angular的上一代组件框架。但是Taro 3的实现机制决定了是以牺牲性能换取对react的最大支持。因此,我们在非性能部分采用Taro+react hooks开发,在性能要求高的详情页面图表组件、以及表格组件,使用原生开发。基于glass-easel的最新特性,确保长表格的增删改查的高性能,当然,glass-easel仍有许多问题没有解决,我们也期待它的进一步优化跟进。 欢迎体验使用,技术交流 [图片][图片]
2024-02-26 - 如何隐藏微信小程序右上角的胶囊按钮呢?
我有一个需求,需要做到横屏时全屏展示,但是全屏展示时胶囊按钮隐藏不掉(微信开发者工具能够隐藏但是真机不会隐藏),我已经见到过有能横屏状态下不显示胶囊按钮的小程序,求问一下该怎么做
2024-04-07 - wx.miniapp.IAP实现 Apple 支付
使用微信Dount多端平台提供的wx.miniapp.IAP实现 Apple 支付的详细流程及注意事项 前言 微信Dount多端平台最近内测提供了可以将小程序转化为安卓和iOS的能力,想把之前做过的小程序转化为iOS,因为内容含有虚拟物品付费,所以就需要用到[代码]Apple 支付[代码],多端平台提供了wx.miniapp.IAP一整套接口,但是并没有详细地解释,让我一个没有做过iOS开发的前端人员很是苦恼。所以在我经历各种坑之后就有了这篇文章。 准备工作 首先你要有苹果后台账号创建自己的APP 创建完成之后在这个顶部的[代码]商务[代码]完成各种协议和收款账户的填写 [图片] 创建虚拟商品(链接讲的很详细) 创建自己的沙盒测试号必须是没有注册过苹果ID的邮箱 [图片] 退出测试设备的苹果商店账号 完成以上步骤就可以进行开发了 Apple 支付开发流程简述 [图片] 以下是微信Dount提供的接口和我自己的封装 一、添加交易队列观察者 API:[代码]wx.miniapp.IAP.addTransactionObserver[代码] 作用: 添加交易观察者以处理交易状态的更新,包括购买成功或失败等。 输入参数: [代码]ob[代码]:一个包含回调函数的对象,用于处理不同的交易事件。 [代码]updatedTransactions[代码]:处理交易状态更新。 [代码]restoreCompletedTransactionsFailedWithError[代码]:处理恢复购买时出现的错误。 [代码]paymentQueueRestoreCompletedTransactionsFinished[代码]:在恢复购买交易完成时调用。 [代码]shouldAddStorePayment[代码]:询问是否应该添加商店付款。 [代码]paymentQueueDidChangeStorefront[代码]:处理 App Store 店面变化。 [代码]didRevokeEntitlementsForProductIdentifiers[代码]:处理撤销某些产品的权限。 输出参数: 无直接输出参数,通过回调函数处理交易事件。 [代码]function initialize() { const ob = { updatedTransactions: (args) => { console.log('处理交易状态更新,例如购买成功或失败。:', args); args.transactions.forEach(item => handleTransaction(item)); }, restoreCompletedTransactionsFailedWithError: (args) => { console.log('处理恢复购买时出现的错误。:', args); }, paymentQueueRestoreCompletedTransactionsFinished: (args) => { console.log('在恢复购买交易完成时调用。', args); }, shouldAddStorePayment: (args) => { console.log('询问是否应该添加商店付款。', args); }, paymentQueueDidChangeStorefront: (args) => { console.log('处理 App Store 店面变化。', args); }, didRevokeEntitlementsForProductIdentifiers: (args) => { console.log('处理撤销某些产品的权限。', args); }, }; wx.miniapp.IAP.addTransactionObserver(ob); } [代码] 二、请求商品信息 API:[代码]wx.miniapp.IAP.requestSKProducts[代码] 作用: 请求指定商品的详细信息。 输入参数: [代码]productIdentifiers[代码]:商品标识符数组,指定需要请求信息的商品。 [代码]success[代码]:请求成功的回调函数,返回商品信息。 [代码]fail[代码]:请求失败的回调函数,返回错误信息。 输出参数: [代码]invalidProductIdentifiers[代码]:无效的商品标识符数组。 [代码]products[代码]:有效的商品信息数组。 [代码]function requestProduct(index, paySuccess, payFail) { const canMake = canMakePayments(); if (!canMake) { uni.showToast({ title: '没有支付环境', icon: 'none' }); payFail(); return; } gPaySuccess = paySuccess; gPayFail = payFail; wx.miniapp.IAP.requestSKProducts({ productIdentifiers: [],// 这里是用户要购买的商品ID success: (ret) => { console.log(ret.invalidProductIdentifiers, '无效商品标识'); console.log(ret.products, '商品标识'); if (ret.products.length > 0) { startPayment(null, ret.products); } else { startPayment('未查到商品', ret.products); uni.hideLoading(); } }, fail: (error) => { console.error(`获取商品信息失败: ${error}`); gPayFail(); uni.hideLoading(); } }); } [代码] 三、发起支付 API:[代码]wx.miniapp.IAP.addPaymentByProductIdentifiers[代码] 作用: 发起支付请求。 输入参数: [代码]productIdentifier[代码]:商品标识符,指定需要支付的商品。 [代码]success[代码]:支付请求成功的回调函数,返回支付请求的结果。 [代码]fail[代码]:支付请求失败的回调函数,返回错误信息。 输出参数: 无直接输出参数,通过回调函数处理支付请求的结果。 [代码]function startPayment(err, productIdentifier) { if (err) { uni.showToast({ title: err, icon: "none", }); gPayFail(); uni.hideLoading(); return; } wx.miniapp.IAP.addPaymentByProductIdentifiers({ productIdentifier: productIdentifier[0].productIdentifier, success: (args) => { console.log('拉起支付成功', args); uni.hideLoading(); }, fail: (args) => { console.error('拉起支付失败', args); gPayFail(); uni.hideLoading(); } }); } [代码] 四、处理交易 作用: 处理交易状态更新,包括成功、失败、恢复等。 输入参数: [代码]transaction[代码]:交易对象,包含交易状态、交易收据等信息。 输出参数: 无直接输出参数,通过交易状态处理不同的逻辑。 [代码]function handleTransaction(transaction) { if (transaction.transactionState === "SKPaymentTransactionStatePurchased" || transaction.transactionState === "SKPaymentTransactionStateRestored") { console.log(transaction.transactionReceipt, '订单收据'); finishTransaction(null, transaction); } else if (transaction.transactionState === "SKPaymentTransactionStateFailed") { finishTransaction('交易失败', transaction); gPayFail(); } else if (transaction.transactionState === "SKPaymentTransactionStateDeferred") { finishTransaction('等待外部操作', transaction); } } [代码] 五、结束交易 API:[代码]wx.miniapp.IAP.finishTransaction[代码] 作用: 完成交易,通知系统交易已经处理完毕。 输入参数: [代码]transactionIdentifier[代码]:交易标识符,指定需要结束的交易。 [代码]success[代码]:结束交易成功的回调函数,返回结果。 [代码]fail[代码]:结束交易失败的回调函数,返回错误信息。 输出参数: 无直接输出参数,通过回调函数处理结束交易的结果。 [代码]function finishTransaction(err, transaction) { if (err) { uni.showToast({ title: err, icon: "none", }); return; } wx.miniapp.IAP.finishTransaction({ transactionIdentifier: transaction.transactionIdentifier, success: (args) => { console.log('完成交易 success', args); gPaySuccess(); }, fail: (args) => { console.error('完成交易 fail', args); gPayFail(); } }); } [代码] 六、检查支付环境 API:[代码]wx.miniapp.IAP.canMakePayments[代码] 作用: 检查当前设备是否支持支付功能。 输入参数: 无 输出参数: 返回值:布尔值,表示设备是否支持支付功能。 [代码]function canMakePayments() { const canMake = wx.miniapp.IAP.canMakePayments(); console.log(canMake, "检查是否可以发起支付"); return canMake; } [代码] 七、获取交易列表 API:[代码]wx.miniapp.IAP.getTransactions[代码] 作用: 获取当前未完成的交易列表。 输入参数: [代码]success[代码]:获取交易列表成功的回调函数,返回交易列表。 [代码]fail[代码]:获取交易列表失败的回调函数,返回错误信息。 输出参数: [代码]transactions[代码]:未完成的交易列表。 [代码]function getTransactions(callback) { wx.miniapp.IAP.getTransactions({ success: (transactions) => { console.log('当前交易列表', transactions); callback(null, transactions); }, fail: (error) => { console.error('获取交易列表失败', error); callback('获取交易列表失败'); } }); } [代码] 八、恢复已完成的交易 API:[代码]wx.miniapp.IAP.restoreCompletedTransactions[代码] 作用: 恢复已完成的交易。 输入参数: [代码]success[代码]:恢复交易成功的回调函数,返回交易列表。 [代码]fail[代码]:恢复交易失败的回调函数,返回错误信息。 输出参数: [代码]transactions[代码]:恢复的交易列表。 [代码]function restoreTransactions(callback) { wx.miniapp.IAP.restoreCompletedTransactions({ success: (transactions) => { console.log('恢复的交易', transactions); callback(null, transactions); }, fail: (error) => { console.error('恢复交易失败', error); callback(error); } }); } [代码] 九、获取交易收据 URL API:[代码]wx.miniapp.IAP.getAppStoreReceiptURL[代码] 作用: 获取交易收据的 URL。 输入参数: [代码]success[代码]:获取收据 URL 成功的回调函数,返回收据 URL。 [代码]fail[代码]:获取收据 URL 失败的回调函数,返回错误信息。 输出参数: [代码]url[代码]:交易收据的 URL。 [代码]function getReceiptURL() { wx.miniapp.IAP.getAppStore ReceiptURL({ success: (url) => { console.log('交易收据 URL', url); getReceiptData(null, url); }, fail: (error) => { console.error('获取收据 URL 失败', error); getReceiptData('获取收据 URL 失败'); } }); } [代码] 十、获取交易收据数据 API:[代码]wx.miniapp.IAP.getAppStoreReceiptData[代码] 作用: 获取交易收据数据。 输入参数: [代码]success[代码]:获取收据数据成功的回调函数,返回收据数据。 [代码]fail[代码]:获取收据数据失败的回调函数,返回错误信息。 输出参数: [代码]data[代码]:交易收据数据。 [代码]function getReceiptData(err) { if (err) { uni.showToast({ title: err, icon: "none", }); return; } wx.miniapp.IAP.getAppStoreReceiptData({ success: (data) => { console.log('交易收据数据', data); // 调用后端接口传送交易数据 }, fail: (error) => { console.error('获取收据数据失败', error); } }); } [代码] 十一、刷新收据 API:[代码]wx.miniapp.IAP.requestSKReceiptRefreshRequest[代码] 作用: 发起请求刷新收据。 输入参数: [代码]success[代码]:刷新收据成功的回调函数,返回结果。 [代码]fail[代码]:刷新收据失败的回调函数,返回错误信息。 输出参数: [代码]args[代码]:刷新收据的结果。 [代码]function refreshReceipt(err, callback) { if (err) { uni.showToast({ title: err, icon: "none", }); return; } wx.miniapp.IAP.requestSKReceiptRefreshRequest({ success: (args) => { console.log('刷新收据成功', args); callback(null, args); }, fail: (error) => { console.error('刷新收据失败', error); callback(error); } }); } [代码] 十二、获取 App Store 信息 API:[代码]wx.miniapp.IAP.getStorefront[代码] 作用: 获取当前 App Store 店面的信息。 输入参数: [代码]success[代码]:获取店面信息成功的回调函数,返回店面信息。 [代码]fail[代码]:获取店面信息失败的回调函数,返回错误信息。 输出参数: [代码]info[代码]:App Store 店面信息。 [代码]function getStorefront(callback) { wx.miniapp.IAP.getStorefront({ success: (info) => { console.log('App Store 信息', info); callback(null, info); }, fail: (error) => { console.error('获取 App Store 信息失败', error); callback(error); } }); } [代码] 十三、移除交易观察者 API:[代码]wx.miniapp.IAP.removeTransactionObserver[代码] 作用: 移除交易观察者。 输入参数: [代码]success[代码]:移除交易观察者成功的回调函数,返回结果。 [代码]fail[代码]:移除交易观察者失败的回调函数,返回错误信息。 输出参数: [代码]args[代码]:移除交易观察者的结果。 [代码]function removeObserver() { wx.miniapp.IAP.removeTransactionObserver(ob); } [代码] 完整流程及注意事项 初始化:在应用启动时调用 [代码]initialize[代码] 函数添加交易队列观察者,以便处理交易状态的更新。 请求商品信息:用户选择商品后,调用 [代码]requestProduct[代码] 函数请求商品信息。首先检查设备是否支持支付([代码]canMakePayments[代码]),然后通过 [代码]wx.miniapp.IAP.requestSKProducts[代码] 请求商品信息。 发起支付:在成功获取商品信息后,调用 [代码]startPayment[代码] 函数发起支付请求,通过 [代码]wx.miniapp.IAP.addPaymentByProductIdentifiers[代码] 实现。 处理交易:交易状态更新会触发观察者的 [代码]updatedTransactions[代码] 回调函数,调用 [代码]handleTransaction[代码] 函数处理不同的交易状态。 结束交易:在处理完交易后,调用 [代码]finishTransaction[代码] 函数结束交易,通知系统交易已经处理完毕。 恢复交易:提供恢复已完成交易的功能,方便用户在重新安装应用或更换设备后恢复已购买的内容。 获取交易收据:在交易完成后,可以通过 [代码]getReceiptURL[代码] 和 [代码]getReceiptData[代码] 获取交易收据,并发送到后端进行验证。 注意事项 检查支付环境:确保设备可以进行支付([代码]canMakePayments[代码]),如果设备不支持支付,应提示用户并终止支付流程。 错误处理:每一步操作都需要进行错误处理,并且在错误发生时应向用户展示友好的提示信息。 安全性:获取订单收据后应及时发送到后端进行验证,确保支付的真实性。 交易状态处理:对于不同的交易状态,需要分别处理,确保在用户支付成功后能够正确提供商品或服务。 恢复交易:提供恢复已完成交易的功能,方便用户在重新安装应用或更换设备后恢复已购买的内容。 用户体验:在每一步操作(如请求商品信息、发起支付等)时应显示加载提示,确保用户了解当前正在进行的操作。 通过上述步骤和注意事项,可以完成苹果支付的流程,并确保在支付过程中为用户提供良好的体验。
2024-07-25 - 一个微信小程序是否支持多个公司主体的收款业务?
有两个不同的公司主体,我想做一个聚合小程序同时经营两个主体的业务,用两个商户号,这种可以吗?
2024-07-02 - AI智能体应用发布篇(公众号/小程序)
前言 上一篇《教你 3 分钟搭建 AI 助手(无需编码)》让大家快速搭建了微信云开发的AI智能体Web版和H5版。 如果想在微信生态中快速获取用户,那么公众号和小程序是必须要做的载体,所以这篇主要分享以下 3 点: AI智能体发布到公众号 小程序中集成AI智能体 AI深度合成类目如何申请 步骤 AI智能体发布到公众号 进入云模板 首先我们先进入云模板控制台,在这里再教大家一种快速进入云模版的方式,除了在《教你 3 分钟搭建 AI 助手(无需编码)》中提到的云开发控制台进入的方式之外。 还可以直接在微信开发者工具的代码目录区域右键呼出菜单然后选择「通过云模板或AI配置页面」菜单项。 注:非正式AppId,小游戏,游客态、代开发小程序,不是小程序开发者,看不到该菜单项。 [图片] 授权公众号 从「我的应用」列表进入「AI智能体应用」详情页点击「添加至多个平台」 [图片] 可以选择一个AI智能体进行「配置」支持 3 个平台 微信小程序客服 微信公众号(服务号) 微信公众号(订阅号) [图片] 配置方式非常方便只需要填写AppId即可 [图片] 前往微信公众平台“设置与开发” - “基本配置” - “公众号开发信息”,复制”开发者ID(AppID)”信息 [图片] 获取到AppID填写后点击「下一步」扫码授权即可,授权成功后 未授权 会变为 已授权 状态 [图片] 接下来来看下效果: [图片] 没有认证的公众号需要回复“继续”,已认证公众号无需回复“继续”可直接输出文案 小程序中集成AI智能体 集成应用 回到「添加至多个平台」面板选择「添加至小程序」 [图片] 根据操作指南下载好代码包解压 [图片] 复制到 miniprogram/ 目录下 [图片] 再将下载的 project.config.json 进行替换 [图片] 当以上两步都完成之后可以以下两种方式进行跳转: JS跳转代码 [代码]wx.navigateTo({url: "/$weda_root/packages/mIOXHS1t/pages/chat/index"}); [代码] WXML布局代码 [代码]<navigator url="/$weda_root/packages/mIOXHS1t/pages/chat/index">跳转至智能体</navigator> [代码] 在这里就相当于把整个AI智能体应用集成到小程序中了 [图片] 在这里需要注意,如果要发布小程序上线还需要在小程序管理后台更新域名配置 [图片] 集成API 如果想自定义界面,可以直接集成API即可 回到「AI智能体应用」详情页面切换到「接口展示」 [图片] 可直接复制代码在小程序端进行调用,以查看AI智能体列表接口为例 [代码] wx.cloud.callFunction({ name: 'cloudbase_module', data: { name: 'ai_bot_get_bot_list', data: { filter: { where: { }, }, select: { $master: true, // 常见的配置,返回主表字段 }, }, }, success: (res) => { console.log(res) }, }); [代码] [图片] 每个接口除了有示例代码,还有详细的参数说明: [图片] [图片] AI深度合成类目如何申请 想要上线AI相关的小程序,必须申请深度合成类目,所以这一步至关重要,回到「AI智能体应用」详情页切换到「AI算法备案资料」- 「获取AI算法合作协议」输入小程序主题即可 [图片] 截图证明,在这里需要注意截图一定要露出云模板字样,这样便于类目审核人员区分截图证明来源 [图片] 然后到小程序管理后台添加类目选择【深度合成 - AI问答】选择 2.2 使用第三方技术,上传截图证明即可 [图片] 通过以上方式类目已审核通过 [图片] 总结 本篇主要讲解了AI智能体应用的多平台发布,整体而言从创建到发布非常方便,不管你是公众号运营者还是小程序开发都可以拥有自己AI智能体应用,赶紧去试试吧~
2024-06-28 - 小程序吸顶、网格、瀑布流布局都拿下
来看看新版 scroll-view 带来的新能力吧~ —————— 在之前的文章中,我们知道了新 scroll-view 可以让小程序的长列表做到丝滑滚动~ 也提到了新 scroll-view 提供了很多新能力 sticky、网格布局、瀑布流布局等,这一篇,我们就来看看这些新能力是怎么使用的~ 新 scroll-view 在原来列表模式(type="list")的基础上,新增了自定义模式(type="custom") 在自定义模式下,新增了以下新组件供开发者调用: list-view:列表布局容器sticky-section / sticky-header:吸顶布局容器grid-view:网格布局容器,可实现网格布局、瀑布流布局等 sticky布局sticky 布局即在应用中常见的吸顶布局,与 CSS 中的 position: sticky 实现的效果一致,当组件在屏幕范围内时,会按照正常的布局排列,当组件滚出屏幕范围时,始终会固定在屏幕顶部。 常见的使用场景有:通讯录、账单列表、菜单列表等等。 与 position: sticky 不同的是,position: sticky 很难实现列表滚动需要的交错吸顶效果,而 sticky 组件则可以帮忙开发者轻松实现交错吸顶的效果。 sticky 的使用非常简单: 将 scroll-view 切换到 custom 模式采用 sticky-section 作为 scroll-view 的子元素sticky-header 放置吸顶内容list-view 放置列表内容<scroll-view type="custom"> <sticky-section wx:for="{{list}}"> <sticky-header> <view>{{item.name}}</view> </sticky-header> <list-view> <view>...</view> </list-view> </sticky-section> </scroll-view> 我们来看下采用 sticky 布局做出来的通讯录效果~ [视频] sticky 布局也可以通过给 sticky-section 配置 push-pinned-header 来声明吸顶元素重叠时是否继续上推 像下图输入框和标签列表这种类型,标签列表吸顶时还是希望保留输入框吸顶。 [视频] 网格布局网格布局即将列表切割成格子,每一行的高度固定,常见的视频列表、照片列表等通常都采用网格布局。 在此之前,实现网格布局需要开发者自行实现网格切割,再嵌入到 scroll-view 中。 新 scroll-view 直接提供了 grid-view 组件供开发者使用~ 将 scroll-view 切换到 custom 模式采用 grid-view 类型为 aligned 做为直接子节点grid-view 中直接编写列表<scroll-view type="custom"> <grid-view type="aligned" cross-axis-count="3"> <view wx:for="{{list}}"> <image src="{{item.image_url}}" mode="aspectFit"></image> <view>...</view> </view> </grid-view> </scroll-view> 下面是使用网格布局实现的图片列表效果~ [视频] 瀑布流布局瀑布流布局与网格布局类似,不同的是瀑布流布局中每个格子的高度都可以是不一致的,所以在小程序中实现瀑布流布局就比较复杂了。 开发者需要通过计算格子高度,然后再进行瀑布流拼接,当滚动内容过多时还需要处理节点过多导致内存不足等问题。 grid-view 组件直接支持了瀑布流模式供开发者直接使用,grid-view 组件会根据子元素高度自动布局: 将 scroll-view 切换到 custom 模式采用 grid-view 类型为 masonry 做为直接子节点grid-view 中直接编写列表<scroll-view type="custom"> <grid-view type="masonry" cross-axis-count="2"> <view wx:for="{{list}}"> <image src="{{item.image_url}}" mode="widthFix"></image> <view>...</view> </view> </grid-view> </scroll-view> 下面是使用瀑布流布局实现的图片列表效果~ [视频] 想要立即体验?现在通过微信开发者工具导入 代码片段,即可体验新版 scroll-view 组件能力~
2023-10-20 - 小程序压测怎么做?快来试试 Donut X 小程序云测的解决方案吧
需求场景 很多小程序都会进行一些运营活动,这时候会有很多用户同时进来访问。当用户量比较大时,业务方可能会担心压力过大导致小程序功能异常。 这时开发同学/测试同学希望能够对小程序进行一次压力测试,比如可以模拟1000个用户同时打开小程序场景。 自然而然的,一个常见想法是直接用1000个手机,同时打开小程序,模拟用户并发情况。但是用纯UI自动化方式去压测,会遇到以下几个问题: 由于每台手机的性能不一,控制多台手机都是在同一个时刻去打开小程序基本无法做到 用1000台手机去做端到端的压测成本高。而且由于手机数量是有限的,压力瓶颈有明显上限 解决方案 微信推出的 Donut 微信安全网关 支持小程序压测能力,可以生成微信code,实现真实用户请求业务接口以及微信开放接口的全链路压测 微信安全网关的压测工具主要关注服务端请求响应情况,包括请求的正常响应、请求耗时等内容;在小程序 UI 相关的表现感知不强 这里可以将 小程序云测 的自动化/性能测试能力和微信安全网关结合起来。首先利用微信安全网关对后台服务器进行发压,例如并发用户量调整为1000。压力上来后,在云测正常执行对应的测试任务。这样相当于用几台手机模拟测试了“1000台手机同时打开小程序”的场景。 通过结合云测的自动化测试和性能测试能力(比如收集到的截图,录屏,体验评分,启动性能 ,网络请求,是否存在黑白屏JSError等异常情况),用户可以全面观察小程序在压力情况下的实际UI表现 实践案例 需求:小程序A希望对首页内容进行压测,观察100个用户同时打开首页的表现是否有异常。可以按照以下步骤进行: 1. Donut发压 用户在 Donut 平台创建安全网关后,前往「压测工具」页面(压测工具在内测期间时,需要联系技术支持开通),创建压测任务并调试后台压测请求链路,例如小程序A希望对首页进行压测,请求的链路为: 首先获取用户code 用微信code访问服务器后台,识别用户 识别用户身份后,生成首页内容数据 [图片] 调试成功后,可以按照实际需求去预约压测任务,如配置并发用户数为100,压测时长为30分钟 2. 云测跑测质检任务 压测任务启动后,建议立即启动云测任务(由于云测需要对真机进行初始化操作,一般需要2-5分钟后才会真正拉起小程序)。 执行云测的任务类型推荐使用小程序质检能力,因为质检会同时发起启动性能测试和自动化测试任务: 启动性能测试:观察压力情况下,小程序启动耗时是否会变慢 自动化测试:自动化任务可以根据业务实际需求,使用Monkey、录制回放、Minium的一种。主要目的是观察压力情况下,小程序是否会出现功能异常或性能问题(如Monkey测试可以检测JSError,黑白屏等异常) 这里小程序A使用Monkey作为自动化测试方案。一般情况下,首页内容是正常加载的,但是当服务器压力较大,网络返回较慢时,小程序A出现了JSError问题 [图片] 后续规划 目前用户需要手动在Donut和云测端分别操作去启动任务。 后续云测和Donut结合起来,可以让用户在Donut中,执行压测任务时可以选择同时启动云测任务,带来更好地压测体验。 关于压测有任何问题,欢迎在帮助页面,加入官方企微群,和云测小助手一起探讨
2024-05-30 - 微信小程序如何将doc, xls, ppt, pdf, docx, xlsx, pptx文件保存到本地
大家好,我是兔兔,兔兔答题开发者。 最近在做兔兔答题时,涉及到将文件保存到微信本地,这里的本地是指微信文件助手或者微信好友,是直接分享文件而不是做微信分享好友的形式。 在微信开放社区中,也有不少关于该话题的帖子。大家感兴趣的也可以去搜索一下。 [图片] 对于第一次做微信小程序,或者是没去了解过这块的,刚开始不知道如何着手,也不知道如何实现。当你发现其实是非常简单的,就几行代码就敲定了。在这里就不像其他的文章,还单独分享一下各种API,我就直接贴正确代码。需要注意的是,我这里使用的是uniapp开发,如果你是微信原生小程序开发,你直接使用微信原生的语法调用这两个函数即可。 [代码]let _that = this uni.downloadFile({ url: _that.url, success: function(res) { uni.openDocument({ filePath: res.tempFilePath, showMenu: _that.is_download == 1 ? true : false, success: function(res) { uni.previewImage({ urls: ['https://imgcdn.tutudati.com/20231001004615552606228.png'], }) } }) }, fail(res) { _that.$func.showToast(res.errMsg) } }) [代码] 需要注意的是$func.showToast()函数是我自己封装的组件。 通过上述代码,其实也不难看出来,就只调用了两个uniapp的函数就实现了功能。 第一个方法是[代码]uni.downloadFile()[代码],这个函数是将远程文件下载到本地,你会获取到一个临时文件地址[代码]tempFilePath[代码]。 第二个方法是[代码]uni.openDocument()[代码],这个函数是打开本地临时文件地址,这里的临时文件地址就是第一步中获取到的[代码]tempFilePath[代码],例如PDF文件,会直接进行预览显示。 关于第二个方法中,我添加了一个[代码]showMenu[代码]的配置项,这是一个非常重要的地方。如果你设置为false,当文件进行预览时,右上角是不会显示功能菜单,也就是说你没法把文件进行保存到本地。当你开启时,将是如下效果。 [图片] 右上角有三个点,当你点击三个点就会弹窗转发好友的选项,你直接点击转发好友就可以保存到文件助手或者你的微信好友了。 注意事项 这个功能看起来,体验性就不是很强。但也是目前为止,能够解决的方案。在使用该方式保存文件,你需要注意如下几个地方: 1、在微信小程管理后台,文件的域名要和文件下载域名保持一致,否则在调用[代码]uni.downloadFile()[代码]函数时就会提示,下载域名不是合法的域名。 2、在调用[代码]uni.openDocument()[代码]函数时,filePath一定是小程序内本地文件地址,你也可以通过其他的函数下载文件来获取本地文件地址,也可以使用文章中的这个函数。 3、打开的文件也是有限制的,目前根据uniapp官方文档来看,只支持doc, xls, ppt, pdf, docx, xlsx, pptx这几种文档类型。查看了一下微信小程序的官方文档,也是支持这几种格式。对于不在这几种格式的范围内,可能就需要通过其他的方式实现。例如通过文件链接,让用户打开浏览器预览;还有是直接通过webview来实现。 关于微信小程序如何将文件保存到本地的解决方案就算完成啦,希望这篇文章的分享对你有所帮助。
2024-05-19 - 长列表:按需渲染vs回收创建
在 Skyline 支持了长列表按需渲染之后,还是有很多开发者对于按需渲染表示疑惑: 开发者A:scroll-view 下拉不太流畅 开发者B:list-view 有什么作用呢? 开发者C:关于长列表的按需渲染功能,我们如何能检测到这个功能正确触发了呢? 关于以上几个问题,我们一一来解答: Q:scroll-view 下拉不太流畅? 当发现 scrll-view 下拉不够流畅时,可能是用法不对导致的不流畅。 根据 type 不同,按需渲染的用法也不同,建议按以下方式检查一下 type="list" : 根据直接子节点是否在屏来按需渲染type="custom" : 只渲染在屏节点,对于列表、网格、瀑布流等,子节点必须包裹在 list-view、grid-view 内部才会按需渲染。 Q:list-view 有什么作用呢? 对于 list-view、grid-view 等 *-view 组件,符合规定的写法则会按需渲染。 默认情况下,视口外节点不渲染。也可以根据业务需要,设置 scroll-view 的 cache-extent 指定视口外渲染区域的距离来优化滚动体验和加载速度。 [图片] 当然 cache-extent 越大也会提高内存占用且影响首屏速度,建议大家按需启用。 Q:关于长列表的按需渲染功能,我们如何能检测到这个功能正确触发了呢? 当使用按需渲染时,例如下面用的 type="list",其实直接子节点都是一开始就创建的,所以没有办法从开发者工具检查到这个功能正常触发。 [图片] 不过可以在真机上开启 “开发调试 - Debug Skyline - checkerboardRasterCacheImages” 调试 [图片] 当滚动 view 离开屏幕回来之后颜色变了,说明节点重新渲染了,以此来确认按需渲染功能正确触发 👇例如下图中第一个节点,一开始是紫色,当离开屏幕重新滚动回屏幕时,变成了黄色,证明按需渲染成功~ 注意:不是所有的组件都会形成 RasterCache,需要结构复杂一些才会; [图片] *-builder 组件 除了 *-view 组件,很多开发者应该也注意到了 *-builder 组件 list-view 对应 list-buildergrid-view 对应 grid-builder看文档描述的能力是一样的,但是为什么会分成两个组件呢? 因为目前 *-view 组件是按需渲染,节点还是会不断的创建,当长列表越来越长时,内存占用会越来越多。 于是我们新增了 *-builder 组件来支持 scroll-view 的可回收,可以更大程度降低创建节点的开销。 我们来看下效果,可以从开发者工具的 wxml 看到,当列表滚动时,list-builder 中渲染的 view 节点只有在屏的几个 [图片] 除了使用 wxml 板块查看之外,*-builder 组件还提供了监听事件,开发者可以监听列表创建和回收 binditembuild:列表项创建时触发,event.detail = {index},index 即被创建的列表项序号binditemdispose:列表项回收时触发,event.detail = {index},index 即被回收的列表项序号 使用场景既然 *-builder 组件拥有回收+创建能力,是不是可以不用 *-view 组件啦? 当然不是啦~~~ 回收+创建能力本身就是有开销的,所以也要根据业务场景按需使用哦 *-builder:对于长列表、无限滚动列表等,或者节点内存占用高的,每个时刻都确保不会有太多节点创建出来,使用 *-builder 可以节省内存*-view:对于短列表,或者内存占用不高的列表则比较适合使用 *-view 代码片段:https://developers.weixin.qq.com/s/rp07iKmW7UQS
2024-05-16 - 利用 CSS 解决 slot 显示默认值
起因 众所周知,小程序至今还未支持 slot 显示默认值(五年啦),但是业务中这个需求还是挺普遍的,故此分享下我是怎么实现该需求的。 构建一个场景 有一个列表单项组件,当名为 icon 的 slot 有内容传入时,显示该 slot,否则显示默认的 icon。 示例代码 需要将组件的 js 中的 multipleSlots 设置为 true。 [代码]<!-- ListItem.wxml --> <view class="list-item"> <view class="list-item__content"> <view class="list-item__left"> <view class="list-item__left-icon--slot"> <slot name="icon"></slot> </view> <view class="list-item__left-icon"></view> <view>{{title}}</view> </view> <view class="list-item__right"> <slot></slot> </view> </view> </view> [代码] [代码]// ListItem.scss .list-item { .list-item__content { .list-item__left { &-icon--slot { margin-right: 12rpx; &:empty { display: none; } &:not(:empty) + .list-item__left-icon { display: none; } } &-icon { width: 40rpx; height: 40rpx; margin-right: 12rpx; color: var(--color-text-disabled); background-color: currentColor; -webkit-mask-repeat: no-repeat; -webkit-mask-position: center; -webkit-mask-size: 100%; -webkit-mask-image: url('xxx.png') } } } } [代码] 总结 实现的原理解释: 当 slot 没有内容时,利用了 [代码]:empty[代码] 伪类选择器,隐藏 [代码].list-item__left-icon--slot[代码] 的元素。 当 slot 有内容时,利用了 [代码]:not[代码] 以及 [代码]+[代码] 选择器,使与 [代码].list-item__left-icon--slot[代码] 紧邻且在其之后的 [代码].list-item__left-icon[代码] 的元素隐藏 不过需要注意的一点是,官方文档中 wxss 支持的选择器很有限,但是实测是大部分支持的,目前个人尝试已知不可用的选择器有 [代码]*[代码]、[代码]~[代码] 以及属性选择器(还有一些复杂情况可能也不支持,需要大家自己尝试)。 希望这次分享对大家有一定的帮助吧。 参考 微信小程序开发文档(小程序框架 /视图层 /WXSS)
2024-04-02 - 个人开发者把小程序发布到 App Store 5个步骤(保姆级教程)
用完即走,小程序已经成为连接用户与服务的重要桥梁,无论是购物、出行还是娱乐,小程序都以其便捷性和高效性赢得了用户的青睐。 那小程序是否有边界,能否拓展到 App? 微信开发者工具的最新升级,为这一问题提供了创新的解答。现在,开发者们可以轻松将已有的小程序代码构建为全功能的 App,而无需从零开始开发,这不仅节省了大量的人力和时间成本,更为小程序开发者们打开了通往 App Store 巨大流量的大门。 在这篇文章中,我们将深入探讨微信开发者工具支持小程序 App 化的五大步骤,分析其潜在价值,并通过实际案例来展示这一过程的效果。 背景:个人开发者,将小程序代码构建为 iOS App,以下为整体流程,适合 iOS 开发 / 上架零基础的同学。 [图片] 缘起 一个周末,突然奇想,我还没有搞过 App,要不搞搞玩玩😄 从 0 开始学还是很慢的,毕竟时间有限,好在了解过提示工程 [代码]《ChatGPT 进阶:提示工程入门 陈颢鹏 李子菡》[代码],问了一下助手 ChatGPT 几个常见的问题。 开发适用于 iPhone 的 App 的流程是什么 注册开发者账号 -> 下载 Xcode -> 学习 Swift -> 设计 + 编码 + 测试 -> TestFlight 内测 -> 准备上架 (准备) -> 提交审核 -> 应用上架 -> 应用更新和维护 很好,请给出可运行的应用实例,完成查询本机 IP 地址 我是一个新手,请问在 XCode 中如何运行你提供的代码 几轮对话后,大约用了 1 个小时,一个 iOS Demo 在模拟器上跑成功了,有点意思😄 不过功能有点简单,几年前用 Vue 写过一个还在跑的网站,同时我知道 ChatGPT 的编程能力,于是我丢了一个问题给他。 [代码]你是一个开发,会 Vue 、iOS 开发(使用 SwiftUI 框架 )两种语言,现在需要你根据 Vue 的代码重写为 iOS 代码,以下是 Vue 代码 [代码] [图片] 笔者是一个运维平台的产品,为了不忘记运维场景和技能,自己维护一个业务场景,开发语言:golang + Vue,部署架构:腾讯云 CLB+TKE+ 服务网格,运营系统:CLS+ 云函数 +Kafka+Doris+Flink+Hadoop+Streamsets。 结果惊奇的发现,真的可以执行,不服不行 👍 [图片] 1. 转折:把小程序代码构建为 iOS App 测试包(1 小时) 如果仅仅只是这样,那么这篇文章标题就该叫“GPT 如何将 Vue 改写为 App”。 万万没想到,过了 2 周从朋友那里获悉 微信开发者工具可以直接将小程序代码构建为 App,就像 Golang 一样,可以通过参数 [代码]GOARCH[代码] 控制构建的程序是在跑在 [代码]amd64[代码],还是 [代码]arm64[代码] CPU 架构上。 [图片] 现实就是这么巧,几年前使用 Vue 开发站点时,同时也开发了同款小程序。 有点意思,参照文档 构建你的第一个应用 花了 1 个小时,在我的 iPhone 上跑了 测试版 的 App. [图片] 此处应该给多端应用的产品和开发点个赞👍🏻 搞到这里,我其实进入了这款的第一个哎哈时刻,确实很爽,因为我不需要花心思用 GPT 来迁移 Vue 程序,直接用微信开发者工具构建为 App 即可,交互完全一致。 另外记录构建过程中遇到的两个问题 问题 1:小程序的图片在 App 中无法渲染 启用 Media SDK 即可 [图片] 问题 2:App 带有 Vconsole 入口 一开始以为在模拟器中才有,最后发现是一个配置,需要自己主动关闭。 [图片] 2. 构建正式包 谁不想在 App Store 能搜到自己的 App 呢,第二步,构建正式包。 2.1 准备苹果开发者账号 在 MacBook Air 或 iPhone 中安装 Apple Developer,然后注册苹果开发者账号 [图片] 一年 688 元会费 [图片] 正常情况下,交完会费后,第二天会收到一封欢迎加入 Apple Developer Program 的邮件,代表苹果开发者账号注册成功。 很遗憾,我注册时提示“未知错误,请再试一次” 找 Apple Developer 客服反馈,最后答复 [代码]由于一个或多个原因,您无法完成 Apple Developer Program 的注册。我们目前无法继续处理您的注册。[代码]。 好吧,估计是被风控命中了,于是找了家人的账号来注册,直接成功😄 2.2 生成 Bundle ID/ 证书 /Profile 生成 App 备案和构建正式包都需要的 Bundle ID/ 证书 /Profile。 生成 Bundle ID Bundle ID 是一个唯一的标识符,用来识别你的应用程序。它通常采用反向域名格式,例如 com.example.myapp。在开发和发布应用程序时,你需要在苹果的开发者账户中注册一个 Bundle ID,这样苹果的服务才能识别出你的应用程序。 参照 文档 生成 Bundle ID。 生成 证书 /Profile 证书(Certificates)用于建立开发者的身份,并确保应用是由已注册的开发者发布的。开发者需要从苹果开发者中心申请证书,用来对应用进行签名,这样 iOS 设备才会信任并运行这个应用程序。 配置文件(Provisioning Profiles)是一个包含证书、应用程序 ID、设备 ID 和其他信息的文件,它告诉 iOS 设备一个应用程序可以被安装和运行。配置文件将应用、开发者和设备联系起来,控制哪些设备可以安装和运行你的应用程序。 参照 文档 生成 iOS 证书和 Provisioning Profile。 [图片] 拓展资料:创建证书签名请求 问题:申请的 iPhone Distribution 证书不受信任 导入 Apple WWDRCA 证书 即可,可能原因:大致是分发的根证书没有导入你的 Mac 上。 更多资料详见 Apple PKI。 [图片] 2.3 备案(10 天 +) App 如果没有备案,在中国大陆将无法上架,这是苹果官方的说明。 中国工业和信息化部(MIIT)要求 App 必须具备有效的互联网信息服务提供者(ICP)备案号,了解更多 [图片] 其实备案比较简单,参照 App 备案 ,使用上一部分申请的 [代码]Bundle ID[代码]、证书(可查看 [代码]公钥[代码]、[代码]签名 MD5 值[代码])即可,不需要把 App 开发完,再来备案。 备案最长需要 20 个工作日,笔者用了 10 个工作日,在一个周五的下午收到了工信部发来的备案通过短信。 2.4 创建移动应用 移动应用是为了让 App 能用上微信的能力(比如分享到朋友圈或发送给朋友、微信登录 / 支付等),在移动应用中同时登记了 Bundle ID 和 Universal Links,这将会传递给下一步的多端框架,这是构建可正式包(采用苹果的分发证书)的必备条件。 先介绍一下 Universal Links。当用户使用 iPhone 手机访问你的网站,同时安装了 App 时,能在网站顶部快速跳转到 App。具体可以看下苹果官方的文档 Supporting associated domains 你需要有一个网站,未来要放 Universal Links 要用到的 [代码]apple-app-site-association[代码] 文件,不过对于我来说,这个功能好像用处不大,我更需要的是当用户用 iPhone 访问网站,引导他去 Apple Store 安装 App. 这里有一个关键信息,如果你不需要微信支付 / 微信登录 / 微信卡券的能力,不需要做开发者认证(开发者认证不能是个人主体) 访问 微信开放平台,创建移动应用,提交审核,几个小时就审核通过了。 [图片] 2.5 绑定多端框架 在 Donut 开发平台 中将 多端应用绑定上一步创建的移动应用,这样可以用到移动应用中登记的 Bundle ID 和 Universal Links,官方这么做比较合理,关键信息必须通过移动应用这关人工审核来起到一定的约束。 [图片] 绑定后,在多端应用中可以看到 Bundle ID 和 Universal Links 了。 [图片] 2.6 准备 App icon 等资料 App Icon 先用工具为你的 App 设计一个 1024px X 1024px 的图标,然后在 App Icon Generator 上生成 iPhone 所有规格的图标,之后在 [代码]project.miniapp.json[代码] 配置。 [图片] 启动图片 App 启动一般需要 2~4 秒,如果没有启动图片是白屏,用户会有点慌,不知道当前 App 是否正在启动,启动图片就是解决这个问题,同时在启动图片中传达 App 的价值主张。 我是直接用 Sketch 设计的,分辨率为 1290px x 2796px,这是兼容性最强的 6.7 寸(iPhone 15 Pro Max/15 Plus/14 Pro Max)手机的分辨率。 考虑到启动图片在不同机型上的兼容性,如果你用 Xcode 开发,苹果官方会推荐使用 Launch Screen Storyboard 隐私信息访问许可描述 小程序虽然没有用到摄像头、麦克风等权限,但多端的 SDK 中有(具体详见 Donut 官方文档 上架应用市场常见问题),所以得提前申明,不然把包通过 [代码]Transporter[代码] 上传后,会收到苹果发出的不合规邮件。 [图片] 以下是根据苹果官方打回的邮件中定义的隐私信息访问许可描述,应该是最基础的了,可以贴到你的 [代码]project.miniapp.json[代码] 文件中(用编辑器打开)。 [代码]{ "privateDescriptions": { "NSBluetoothPeripheralUsageDescription": "为了提供完整的功能,我们的应用程序需要访问蓝牙外设。这将用于与其他设备进行通信和数据交换。我们承诺保护用户隐私和数据安全。", "NSMicrophoneUsageDescription": "为了提供完整的功能,我们的应用程序需要访问麦克风。这将用于录制音频和进行语音交互。我们承诺保护用户隐私和数据安全。", "NSCalendarsUsageDescription": "为了提供完整的功能,我们的应用程序需要访问日历。这将用于提醒和日程管理。我们承诺保护用户隐私和数据安全。", "NSLocationAlwaysAndWhenInUseUsageDescription": "","NSBluetoothAlwaysUsageDescription":" 为了提供完整的功能,我们的应用程序需要始终访问蓝牙外设。这将用于与其他设备进行通信和数据交换。我们承诺保护用户隐私和数据安全。","NSPhotoLibraryUsageDescription":" 为了提供完整的功能,我们的应用程序需要始终访问相册。这将用于 IP 查询时显示 ISP 的图标。我们承诺保护用户隐私和数据安全。","NSCameraUsageDescription":" 为了提供完整的功能,我们的应用程序需要访问摄像头。这将用于录制视频。我们承诺保护用户隐私和数据安全。","NSLocationWhenInUseUsageDescription":" 为了提供完整的功能,我们的应用程序需要在使用时访问位置信息。这将用于提供定位服务和相关功能。我们承诺保护用户隐私和数据安全。" } } [代码] 2.7 构建正式版版本包 参照 打包生成 IPA 生成正式版的版本,注意使用分发证书。 [图片] 报错:file must be in miniprogram project 解决:把 mobileprovision 放在 miniprogram 目录下,因为 profile 不像 App icon 一样会自动上传到 miniprogram/ 目录下。 2.8 使用 Transporter 上传版本 参照 官方文档 上传正式版的 APK 包。 [图片] 遇到问题: Transporter,无法为 App “comxxxx.ipa” 创建临时 .itmsp 软件包。No suitable application records were found. Verify your bundle identifier ‘com.xxxx’ is correct and that you are signed into Xcode with an Apple ID that has access to the app in App Store Connect. [图片] 解决办法:去 App Store Connect 添加 App,绑定 [代码]Bundle id[代码],这样 Transporter 可以验证包在 App Store Connect 中已注册。 3. 使用 TestFlight 测试 在 App Store Connect 的 TestFlight 页面,可以选择内部、外部测试,外部测试版本需要 Apple 官方审核,把 公开链接发给朋友即可。 [图片] 在测试的同时,可以同步准备上架 App Store 的资料了。 4. 准备上架 Apple Store 审核资料 截屏 截屏是用来在 App Store 中显示你的 App 产品介绍页的,具体参照 截屏规范 [图片] 有 [代码]iPhone 15 Plus[代码] 和 [代码]iPhone 8 Plus[代码] 这两款机型就足够了,其他型号的手机能复用,分辨率应该是等比率缩放。 如果你像我一样,没有这两款手机,那用 iOS 模拟器。 Xcode -> 工具栏 Windows -> Devices and Simulators -> Create a new simulator -> Download more simulator runtimes [图片] 在微信开发者工具中运行这两款模拟器,利用模拟器自带截屏工具即可。 隐私政策 找一下常见 App 的隐私政策,在其产品介绍页中可以跳转过去。 如果你有网站就放在网站上,如果没有可以放在腾讯文档上。 [图片] 选择 App 供应的地区范围 哪些地区的用户可以下载你的 App。 [图片] 提交审核 一切准备好了后(包含备案),开启提交审核。 下午 5:35 提交审核,第二天早上 3:40 上架成功。✌🏻 [图片] 5. App Store 的数据 上架后刚好一周,看看最近一周的数据,还不错。 [图片] 这是评分数据 [图片] 6. 引流 二维码引流:草料二维码 通过草料二维码生成 App 的下载链接,放在网站上,引导用户跳转至 App。 Universal Links 参照 Apple 官网文章 Supporting associated domains 准备 Universal Links。 前面已经介绍了这个东东是干嘛的。 准备 [代码]apple-app-site-association[代码] 文件,放在网站的 [代码].well-known[代码] 目录下,完整路径为 [代码]/.well-known/apple-app-site-association[代码] 以下为示例,特别注意的是 [代码]appID[代码] 是由 [代码]团队 ID[代码] + [代码]Bundle ID[代码] 组成。 [代码]{ "applinks":{"apps":[], "details":[ { "appID":"<team_id>.<bundle_id>", "paths":["*"] } ] } } [代码] team_id 从 开发者账户 中获取 [图片] 顶部导航 当用户访问网站时,顶部引导用户跳转到 App 下载页。 等有空了搞搞。 7. 后记 小程序转 App,让个人或企业可以快速拥有 App,获取应用市场的流量,让开发者把精力放在业务逻辑上。 同时在开发小程序的过程中,发现开发者生态会散落在多个地方,比如 github,提供一些小程序模版、组件等能力,无法集中在一个地方比较方便的找到整个开发者生态的能力,和 VSCode 插件生态有点区别。 [图片] 先说 IDE 插件,比如我用 GPT4-Turbo 来写先代码或排查问题会在微信开发者工具和 Web 间跳转,操作流不太顺,如果能在微信开发者工具的插件入口中找到对应的 AI 代码助手,用起来应该很爽。 一旦平台的开放能力放出来,这些能力将源源不断的涌入到这个市场中,而不是作为平台方来集成这些能力,毕竟精力有限,同时还不一定做的最好,用插件可以让用户有更多的选择。 再说说 小程序组件,以大模型为例,目前市场有备案的大模型基座模型有好几家,在小程序开发过程中其实比较缺整体组件(UI + 背后的 API),有点像商场一样,平台方构建开放的能力,引导各个供应商提供开箱即用的能力,让用户可以快速上手,赶上这波大模型的技术趋势。 比如我自己在设计开放能力时的思考,平台专注骨架功能的开发,让开发者能参与到平台的建设中来,把生态盘活起来,最终提升大家研发运营的效率。 最后就是管理后端比较分散,比如 开放平台、donut、we 分析、云测、云托管,云开发,产品矩阵看不清,不容易知道整体的能力,缺少一个集中的控制台。 最后希望小程序越来越好 😄
2024-01-30 - 云调用能力—图像处理和OCR
云调用有些接口属于 AI 服务的范畴,比如借助于人工智能来进行智能裁剪、扫描条码/二维码、图片的高清化等图像处理和识别银行卡、营业执照、驾驶证、身份证、印刷体、驾驶证等 OCR,有了这些接口我们也能在小程序里使用人工智能了。接下来我们以小程序的条码/二维码识别和识别印刷体为例来介绍一下云调用。 13.3.1 图像处理使用开发者工具新建一个云函数,如 scancode,然后在 config.json 里添加 img.scanQRCode 云调用的权限,使用 npm install 安装依赖之后,上传并部署所有文件(此时也会更新权限)。 { "permissions": { "openapi": [ "img.scanQRCode" ] } } 然后再在 index.js 里输入以下代码,注意[代码]cloud.openapi.img.scanQRCode[代码]方法和[代码]img.scanQRCode[代码]权限的对应写法,不然会报 604100 的错误。 const cloud = require("wx-server-sdk"); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }); exports.main = async (event, context) => { try { const result = await cloud.openapi.img.scanQRCode({ imgUrl: "https://tcb-1251009918.cos.ap-guangzhou.myqcloud.com/demo/qrcodetest.png", //注意二维码必须是条码/二维码,不能是小程序码 }); return result; } catch (err) { console.log(err); return err; } }; 调用该云函数之后,返回的 result 对象里包含 result 对象,在 codeResults 的 data 里可以得到二维码里包含的内容。 codeResults: [{ data: "使用云开发来开发微信小程序可以免费。。。", pos: {leftTop: {…}, rightTop: {…}, rightBottom: {…}, leftBottom: {…}},typeName: "QR_CODE"}] errCode: 0 errMsg: "openapi.img.scanQRCode:ok" imgSize: {w: 260, h: 260} 13.3.2 OCR 人工智能识别使用开发者工具新建一个云函数,如 ocrprint,然后在 config.json 里添加 ocr.printedText 云调用的权限,使用 npm install 安装依赖之后,上传并部署所有文件(此时也会更新权限)。 { "permissions": { "openapi": [ "ocr.printedText" ] } } 调用该云函数之后,返回的 result 对象里包含 result 对象,在 codeResults 的 data 里可以得到二维码里包含的内容。 const cloud = require("wx-server-sdk"); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }); exports.main = async (event, context) => { try { const result = await cloud.openapi.ocr.printedText({ imgUrl: "https://tcb-1251009918.cos.ap-guangzhou.myqcloud.com/demo/ocrprint.png", }); console.log(result); return result; } catch (err) { console.log(err); return err; } }; 调用该云函数之后,返回的 result 对象里包含 result 对象,在的 items 里可以返回图片包含的文字内容。 items: Array(4) 0: {text: "JavaScript入门", pos: {…}} 1: {text: "JavaScript是目前世界上最流行的编程语言之一,它也是小程序开发最重要的基础语言。要做出一个功能复杂的小程序,除了需要掌握JavaScript的基本语", pos: {…}} 2: {text: "法,还要了解如何使用JavaScript来操作小程序(通过API接口)", pos: {…}} 3: {text: "过API接口)。", pos: {…}} 13.3.3 图像处理拓展能力图片是小程序非常重要的元素,尤其是旅游照片、社交图片、电商产品图片、相册类小程序、媒体图文等,图片的加载速度、清晰度、图片的交互、图片效果的处理以及图片加载的 CDN 消耗都是一个不得不需要去关注的问题。而云开发图像处理拓展能力结合云存储则可以非常有效的解决很多问题。 强烈建议所有有图片处理需求的用户都应该安装图像处理拓展能力,这个能力大大弥补和增强了云存储在图片处理能力,尤其是图片按照需求的规格进行缩放可以大大减少 CDN 的消耗以及图片的加载速度以及我们可以按照不同的业务场景使用快速缩略模板,而这一切的操作和云存储的结合都是非常实用且易用的。 1、图像处理能力介绍云开发图像处理能力结合的是腾讯云数据万象的图片解决方案,图像处理提供多种图像处理功能,包含智能裁剪、无损压缩、水印、格式转换等,图像处理拓展能力所包含的功能非常丰富,使用如下图片处理的费用是按量计费的,计费周期为月,10TB 以内免费,超出 10TB,按 0.025 元/GB 来计费,省事而便宜: 缩放:等比缩放、设定目标宽高缩放等多种方式;裁剪:普通裁剪、缩放裁剪、内切圆、人脸智能裁剪;旋转:普通旋转、自适应旋转;格式转换:jpg、bmp、gif、png、webp、yjpeg 格式转换,gif 格式优化,渐进显示功能;质量变换:针对 JPG 和 WEBP 图片进行质量变换;高斯模糊:对图片进行模糊处理;锐化:对图片进行锐化处理;图片水印:提供图片水印处理功能;文字水印:提供实时文字水印处理功能;获取图片基本信息:查询图片基本信息,包括格式、长、宽等;获取图片 EXIF:查询图片 EXIF 信息,如照片的拍摄参数、缩略图等;获取图片主色调:获取图片主色调信息;去除元信息:去除图片元信息,减小图像体积;快速缩略模板:快速实现图片格式转换、缩略、剪裁等功能,生成缩略图;管道操作符:对图片按顺序进行多种处理当我们在腾讯云云开发网页控制台(注意要使用微信公众号的方式登录)添加完图像处理的拓展能力之后,我们可以在腾讯云的数据万象存储桶里看到云开发的云存储,而关于图像处理能力的深入使用,也可以参考腾讯云数据万象的技术文档。在小程序云开发里使用图像处理能力的方法有三种: 图像地址的拼接,只需要在图片的下载地址 url 里拼接一些简单的参数(API 管道操作符),就能够使用到图像处理的能力,非常方便易用,这个不会把图片处理的结果存储到云存储,不会占用云存储的空间;在获取图片基本信息、获取图片 EXIF、获取图片主色调等方面非常方便;在前端(小程序端)做持久化图像处理,支持有结果图输出的处理操作,也就是我们可以把缩放、裁剪、格式转换、质量变换等处理之后的图片存储到云存储方便以后使用;在云函数端做持久化图像处理,支持有结果图输出的处理操作 01图像地址的拼接在了解图像处理能力之前,我们需要先了解一下云存储文件的 fileID、下载地址以及下载地址携带的权限参数 sign(图像处理能力的参数拼接就是基于下载地址的),如下图所示: [图片] 在安装了图像处理拓展能力的情况下,我们可以直接拿云存储的下载地址进行拼接,拼接之后的链接我们既可以在小程序里使用,也可以用于图床,比如原始图片下载地址为: https://786c-xly-xrlur-1300446086.tcb.qcloud.la/hehe.jpg?sign=b8ac757538940ead8eed4786449b4cd7&t=1591752049 而相关的图像处理能力的拼接案例如下,具体的操作可以看技术文档,实际的效果,可以复制粘贴链接到浏览器或小程序里体验(换成自己的地址),注意拼接方式就是在下载地址后面加了一个[代码]&imageMogr2/thumbnail/!20p[代码](注意这里由于已经有了一个 sign 参数,所以拼接时用的是[代码]$[代码],不能写成[代码]?[代码],否则不会生效),直接就可以啦,非常易用: //将图片等比例缩小到原来的20% https://786c-xly-xrlur-1300446086.tcb.qcloud.la/hehe.jpg?sign=b8ac757538940ead8eed4786449b4cd7&t=1591752049&imageMogr2/thumbnail/!20p 后面为了方便,我们将[代码]https://786c-xly-xrlur-1300446086.tcb.qcloud.la/hehe.jpg?sign=b8ac757538940ead8eed4786449b4cd7&t=1591752049[代码]简写为 download_url: //缩放宽度,高度不变,下面案例为宽度为原图50%,高度不变 download_url&imageMogr2/thumbnail/!50px //缩放高度,宽度不变,下面案例为高度为原图50%,宽度不变 download_url&imageMogr2/thumbnail/!x50p //指定目标图片的宽度(单位为px),高度等比压缩,注意下面的是x,不是px,p与x在拼接里代表着不同的意思 download_url&imageMogr2/thumbnail/640x //指定目标图片的高度(单位为px),宽度等比压缩: download_url&imageMogr2/thumbnail/x355 //限定缩略图的宽度和高度的最大值分别为 Width 和 Height,进行等比缩放 download_url&imageMogr2/thumbnail/640x355 //限定缩略图的宽度和高度的最小值分别为 Width 和 Height,进行等比缩放 download_url&imageMogr2/thumbnail/640x355r //忽略原图宽高比例,指定图片宽度为 Width,高度为 Height ,强行缩放图片,可能导致目标图片变形 download_url&imageMogr2/thumbnail/640x355! //等比缩放图片,缩放后的图像,总像素数量不超过 Area download_url&imageMogr2/thumbnail/150000@ //取半径为300,进行内切圆裁剪 download_url&imageMogr2/iradius/300 //取半径为100px,进行圆角裁剪 download_url&imageMogr2/rradius/100 //顺时针旋转90度 download_url&imageMogr2/rotate/90 //将jpg格式的原图片转换为 png 格式 download_url&imageMogr2/format/png //模糊半径取8,sigma 值取5,进行高斯模糊处理 download_url&imageMogr2/blur/8x5 //获取图片的基础信息,返回的是json格式,我们可以使用https请求来查看图片的format格式,width宽度、height高度,size大小,photo_rgb主色调 download_url&imageInfo 2、小程序端持久化图像处理当我们希望把缩放、裁剪、旋转、格式变换等图像处理的结果(也就是处理之后的图片)存储到云存储,这个就叫做持久化图像处理,在安装了图像处理能力之后,我们也可以在小程序端做图像处理。 当用户把原始图片上传到小程序端时,我们需要对该图片进行一定的处理,比如图片过大就对图片进行裁剪缩小;比如图片需要进行一定的高斯模糊、旋转等处理,这些虽然在图像处理之前,也是可以使用 js 来做的,但是小程序端图像处理的效果并没有那么好或者过于复杂,使用图像处理的拓展能力就非常实用了。在小程序端构建图像拓展依赖 首先在开发者工具小程序根目录(一般为 miniprogram),右键“在终端中打开”,然后在终端里输入以下代码,也就是在小程序端安装图像拓展依赖,安装完时,我们就可以在 miniprogram 文件夹下看到 node_modules: npm install --save @cloudbase/extension-ci-wxmp@latest 然后点击开发者工具工具栏里的工具-构建 npm,构建成功之后,就可以在 miniprogram 文件夹下看到 minprogram_npm 里有@cloubase 文件夹,里面有 extension-ci-wxmp,说明图像拓展依赖就构建完成。 在小程序端进行图像处理 使用开发者工具新建一个 imgprocess 的页面,然后在 imgprocess.wmxl 里输入如下代码,我们新建一个 button 按钮: 处理图片button> 然后再在 imgprocess.js 的 Page()函数的上面(外面)引入图像处理依赖,代码如下: const extCi = require("./../../miniprogram_npm/@cloudbase/extension-ci-wxmp"); 然后再在 imgprocess.js 的 Page()函数的里面写一个 imgprocess 的事件处理函数,点击 button 之后会先执行 readFile()函数,也就是获取图片上传到小程序临时文件的结果(是一个对象),然后再调用 imageProcess()函数,这个函数会对图片进行处理,图片会保存为[代码]tcbdemo.jpg[代码],而处理之后的图片会保存为 image_process 文件夹下的 tcbdemo.png,相当于保存了两张图片: async imgprocess(){ const readFile = async function() { let res = await new Promise(resolve=>{ wx.chooseImage({ success: function(res) { let filePath = res.tempFilePaths[0] let fm = wx.getFileSystemManager() fm.readFile({ filePath, success(res){ resolve(res) } }) } }) }) return res } let fileResult = await readFile(); //获取图像的临时文件上传结果 const fileContent = fileResult.data //获取上传到临时文件的图像,为Uint8Array或Buffer格式 async function imageProcess() { extCi.invoke({ action: "ImageProcess", cloudPath: "tcbdemo.jpg", // 图像在云存储中的路径,有点类似于wx.cloud.uploadFile接口里的cloudPath,上传的文件会保存为云存储根目录下的hehe.jpg operations: { rules: [ { fileid: "/image_process/tcbdemo.png", //将图片存储到云存储目录下的image_process文件夹里,也就是我们用image_process存储处理之后的图片 rule: "imageMogr2/format/png", // 处理样式参数,我们可以在这里写图片处理的参数拼接 } ] }, fileContent }).then(res => { console.log(res); }).catch(err => { console.log(err); }) } await imageProcess() } 可能你的开发者工具会报以下错误:[代码]https://786c-xly-xrlur-1300446086.pic.ap-shanghai.myqcloud.com 不在以下 request 合法域名列表中,请参考文档:https://developers.weixin.qq.com/miniprogram/dev/framework/ability/network.html[代码],这个要按照参考文档将链接加入到合法域名当中,不然不会生成图片;[代码]action[代码]是操作类型,它的值可以为:ImageProcess 图像处理,DetectType 图片安全审核(后面会介绍),WaterMark 图片忙水印、DetectLabel 图像标签等。[代码]operations[代码]是图像处理参数,尤其是 rule 和我们之前 url 的拼接是一致的,比如[代码]imageMogr2/blur/8x5[代码]、[代码]imageMogr2/rradius/100[代码]等参数仍然有效。上面函数里的 fileContent 不是必要的,也就是说我们可以不在小程序端上传图片,而是直接修改云存储里面已有的图片,并将图片处理后的照片保存,这种情况代码可以写成如下: async imgprocess(){ extCi.invoke({ action: "ImageProcess", cloudPath: "tcbdemo.jpg", // 会直接处理这张图片 operations: { rules: [ { fileid: "/image_process/tcbdemo.png", rule: "imageMogr2/format/png", // 处理样式参数,与下载时处理图像在url拼接的参数一致 } ] }, }).then(res => { console.log(res); }).catch(err => { console.log(err); }) } 3、云函数端持久化图像处理在云函数端的处理和小程序端的处理,使用的方法大体上是一致的,不过云函数的处理图片的场景和小程序端处理图片的场景会有所不同,小程序端主要用于当用于上传图片时就对图片进行处理,云函数则主要用于从第三方下载图片之后进行处理或者对云存储里面的图片进行处理(比如使用定时触发器对云存储里指定文件夹的图片进行处理)。不建议把图片传输到云函数端再来对图片进行处理。 使用开发者工具新建一个 imgprocess 的云函数,然后在 package.json 里添加 latest 最新版的[代码]@cloudbase/extension-ci[代码],并右键云函数目录选择在终端中打开输入命令 npm install 安装依赖: "dependencies": { "wx-server-sdk": "latest", "@cloudbase/extension-ci": "latest" } 然后再在 index.js 里输入以下代码,代码的具体含义可以参考小程序端的内容讲解: const cloud = require("wx-server-sdk"); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }); const extCi = require("@cloudbase/extension-ci"); cloud.registerExtension(extCi); async function process() { try { const opts = { rules: [ { fileid: "/image_process/tcbdemo.jpeg", rule: "imageMogr2/format/png", }, ], }; const res = await app.invokeExtension("CloudInfinite", { action: "ImageProcess", cloudPath: "tcbdemo.jpg", fileContent, operations: opts, }); console.log(res); return res; } catch (err) { console.log(err); } }
2021-09-10 - 小程序开发新能力解读
这个月小程序释放了什么新能力?又有哪些新规则?收藏课程,及时了解小程序开发动态,听官方为你解读新能力。
2023-01-17 - IOS scroll-view中的自定义组件fixed问题
这个是正常现象,因为 iOS 下加了 -webkit-overflow-scrolling: touch,这个会产生滚动惯性,体验更好,但会改变 fixed 的行为,建议不在 scroll-view 里有 fixed 元素
2020-04-23 - 代开发小程序的第三方,该怎么调用物流助手的接口?
目前,只能先由小程序在后台绑定物流账号,第三方平台获知其biz_id并得到其授权后调用业务api。后续,将支持通过api绑定物流账号。
2019-12-30 - 第三方服务平台,怎么申请通用的订阅消息模板?
每个帐号的行业类目不尽相同,仅可根据小程序类目,调用行业匹配的业务订阅需求模板消息。
2020-04-23 - 网络请求优化之使用本地缓存
[视频] 你好,我是李艺。 上节课我们主要学习了有关setData调用相关的优化技巧,这节课我们学习网络请求相关的优化技巧。 首先我们看一下问题,针对网络请求的优化主要有以下三个方面: 一、减少不必要的网络请求,使用本地缓存的数据代替从后端接口拉取的数据 二、优化网络请求参数,提高网络请求的通讯效率 三、优化网络请求的并发数,让优先级高的请求先执行 其中第二项在第6.8讲我们已经讲过了,这节课我们重点看一三两项的优化,下面看项目实践 。 首先看实践一:在本地缓存数据。 在首页的JS文件里边有加载小程序导航数据的代码,我们可以在这里尝试使用本地缓存技术,如我们屏幕上看到的截图,首先从本地缓存中尝试取出缓存数据,如果取到了就先用上,然后向后端发起网络请求,拿到最新的导航数据以后再调用setData重新设置一下数据,并把本地数据也刷新一遍,避免本地缓存过时,运行以后如我们屏幕上看到的,在调试区的Storage面板里边可以看到本地缓存的导航数据,但是这个实现方案是有瑕疵的,有什么瑕疵,先从本地缓存,再从后端接口请求。这是一个顺序的并发过程,实际上这个过程还可以再优化一下,改用并发复合命令,让两个异步操作同时并行。或者我们更简单一些,改成两个异步函数,同时开始执行也可以,优化就不做演示了。留给你自己实践一下。 下面我们进行实践一的代码演示。 首先我们看一下我们最终实现的一个源码,找到主页的JS文件,在这个地方,我们有一个从主页中,从后端加载导航列表这样的一段代码。这段代码它的主要的一个作用,在这个地方。从这个地方开始,它的主要的一个作用就是从接口去拉取导航数据。拉取完成以后,然后我们再去设置我们的data数据对象里面的navs,这样的一个列表,同时设置完以后,我们还需要将我们本地缓存里边的这个navs这个列表然后进行一个更新。由于缓存里面它存储的是字符串,所以这个地方我们要拿json方法,进行序列化,更新一下。 在前面我们是先向本地,通过getStorage这个方法,然后去取了一下本地缓存的数据。当然这个地方有可能会取不到,所以我们会首先做一个判断。如果能取到的话,我们就将它进行设置,同时在设置之前我们还需要拿JSON.parse,然后进行一个解析。因为我们取到的数据它是一个字符串的数据,这就是我们主要的代码。首先我们将这部分代码给它拷贝一下,来到我们的小程序项目里面,找到我们首页的js文件,然后在这个里面我们先搜索一下。搜索navs 在这个位置,这是我们现有的代码,它一上就是从后端进行下载,我们添加一个从本地获取数据这样的一个代码,同时在这个地方这个代码已经有了,所以我们不需要再添加了。本质上在之前这个代码其实它是不需要的,因为我们如果前边没有消费代码的话,我们在这个地方去设置本地数据,它本质上它也是无用的。现在这个代码我们已经设置完了,单击编译按钮,然后进行测试,注意看我们的导航区。当然我们编译模式现在可以改一下了,改成我们的普通编译模式,注意看一下我们的调试区,这里面有一个已取到缓存的导航数据,这个代码是在这一行打印的。然后在下面还有一个也取到了后端的导航数据,这行代码是在这个地方打印的。也就是说我们这个代码它首先会从本地,然后取到缓存的数据,然后并且马上启用,同时它接着又向后端发起接口的请求,然后再获取数据,同时将本地的缓存的数据然后进行一个刷新。下面再看在我们这个调试区它有一个面板,一我们本地的这个Storage面板,这个里面navs这就是我们本地缓存的数据,这个代码演示我们就到这里。 下面我们看实践二。 打破网络请求的10个并发限制,并按优先级排序。由wx.request接口发出的网络请求,有最大10个的并发限制。为了破除这个限制,同时让高优先级的网络请求操作先执行,我们可以进一步改造我们的request工具函数,改造以后这个函数的代码如我们屏幕上显示的。首先我们引入了一个自带优先级的异步队列,叫做priority-async-queue。这个模块需要使用yarn或者npm安装,安装指令如屏幕上所示。安装以后在工具菜单栏,别忘记选择构建npm进行模块代码的构建。在使用自定义的request方法的时候,针对重要的网络请求只需要添加一个值等于urgent的一个priority参数即可,如我们屏幕上显示的这样。感觉是不是很简单。调用方法及其他参数都不需要修改,这是接口迭代进化中的向后兼容性,可以最大程度的保证旧代码在项目迭代中的一个持续使用,运行效果与之前一样。在网络环境顺畅的情况下基本上是无感知的。 下面我们看代码演示。 查看package.json这个文件,在这个文件里面我们可以看到多了一个模块的引用,这个模块叫做priority-async-queue,我们将这个给它复制一下,模块名给它复制一下,后面的版本是2.1.1,如果为了保证这个版本一致,稍后我们在安装的时候还可以将这个版本号给它加上。拷贝以后我们需要打开一个本地的终端窗口,终端窗口我们可以在VSCode里面操作,当然也可以在我们的微信小程序里面也是可以的。在我们miniprogram上面选择内建终端打开,选择以后这个地方我们使用yarn add,然后将我们刚才那个名字给它拷贝一下,还可以附带我们的版本号。添加,很快它就已经装上了。装上以后我们还可以顺带看一下这个目录下的package.json文件,确认一下 这个地方已经有这个模块了,这是第一步。第二步就是改造我们的request方法,目前我们request方法它是不支持优先级的,我们需要对它进行一个改造,打开我们已经修改好的代码,首先在最上面有一个对我们这个模块新安装模块的一个引入,同时下面有一个queue对象的创建,这个数字代表是我们最大允许的一个并发数字默认等于10。当然我们也可以传一个其他的数字都是可以的,然后将这个代码放在文件的最上面,再往下这个地方有一个关于优先级的定义,包括这个地方,它有一个导出,这个代码我们都需要拷贝过来,然后放在这里这些参数,还有这些参数其实都不需要修改,然后这个地方有一个关于默认的优先级的设置,如果它没有优先级的话我们就给它一个normal的这样的优先级。再往下在这个里边重点的代码在这个地方有一个queue.addTask,同时将priority优先级也给它传进去,后面是一个箭头函数,这个箭头函数它代表的是一个匿名的一个闭包。我们可以将这个代码给它拷贝一下,然后放在这个里面可以对比一下我们上下,它这个代码的一个区别。其实等于是我们将原来的代码,也就是这个代码放在了它的里边,同时将用addTask方法对它进行了一个封装。封装以后我们原来这个代码它是作为,然后由闭包函数然后封装一下,然后作为第二个参数,然后传进来的,是这样的一个改造方式,这样就可以了。 现在我们需要对我们原有的代码做一个改造。我们目前有一个是拉取首页数据的这样的一个代码我们修改一下它的优先级,在这个文件里面retrieve_home_data.js,然后这个里边,有对request的方法的一个调用,在这个地方,有一个调用,然后我们要在原来的这个位置,原来它是有一个url参数,我们再加另外的一个参数,就是priority,然后它的值我们让它等于urgent,等于这个,确认一下priority。这是一个priority,让它等一个urgent代表是最高的一个优先级,这样修改就可以了。修改完成以后我们单击编译按钮,然后进行测试,这个地方出现了一个错误。在调试区,大意是说module,然后这个module is not defined没有定义。这个模块没有定义,为啥没有定义,为什么没有定义?因为我们在本地刚才安装了第三方的模块以后我们没有选择,我们没有在这个菜单里面选择工具构建npm。这一步很重要,只有构建以后,我们新添加的模块它才会从我们的目录下面小程序这个目录下面有一个是node modules,从这个目录下面然后再转到我们的这个npm,就转到这个miniprogram_npm 转到这个目录下,转到这个目录下以后,然后我们才可以去加载和使用priority-async-queue这样的一个模块。现在这个目录下它已经有了,说明我们现在可以访问了,我们再次单击编译进行测试。现在代码错误已经不存在了,然后我们再看数据的表现,数据仍然可以加载,也没有问题,这个代码演示就到这里。 下面我们看一下小结,关于本地缓存接口,前面我们已经介绍过了,它们都是同步接口,即使像wx.setStorage、wx.getStorage这样不以Sync结尾的接口,由于某种历史原因,它们也是同步接口。那么至少目前是这样的,以后可能会修改,所以在使用这些接口的时候,我们一定要特别注意使用的时机,最好在Page.onReady周期函数中,或者是在之后的时机使用本地缓存接口,使用本地缓存。另外一个特别需要注意的点是一定要时刻铭记,本地缓存的数据是不可靠的,本地数据有可能因为各种原因缺失或者损坏。使用本地缓存的数据,但不能依赖本地数据。当获取本地缓存数据失败的时候一定要有后端接口可以顶上,或者是其他的方式可以顶上。对于小程序里边的wx.request接口可以管控起来,不仅因为网络请求在低版本的基础库版本中有最大10个并发限制,还处于优先级排序的需求,以及有可能存在的页面访问的权限控制要求。在实际的项目开发里面,某些后端接口是一定要用户实现鉴权以后才允许访问的。这类统一的鉴权访问控制就适合在request工具函数中统一实现,这部分内容不属于优化内容,但是对项目来说也十分必要,如果需要拓展的话都可以在request.js文件的基础之上然后进行修改。 点击查看相关文档: 数据缓存 /wx.setStoragepriority-async-queue这节课就讲到这里,上面的网址是本课涉及的文档地址。 这节课我们主要学习了如何使用本地缓存数据,即如何使用优先级队列优化网络请求。下节课我们学习图片优化技巧。 最后我们看一下思考题。这里有个问题请你思考一下,webp是Google在2010年推出的一种新的图片格式,它使用更优的图片压缩算法在相同的图片质量下能让图片保持更小的图片体积,Youtube的视频缩略图采用webp格式以后网络加载速度提升了10%左右,Google的Chrome网上应用商店采用webp的格式以后每天大概节省了几TB的一个带宽,现在小程序中的image组件也开始支持使用webp格式的图片了,但一般我们在团队开发中使用的图片多半是png,或者是jpg这样的格式,那么有什么办法可以快速将这些图片转化为webp格式,并且在小程序项目里边使用。下节课我们就一起来深入探讨一下这个问题。
2022-07-15 - 如何创建高效数据库索引
在创建索引上,建议每个生产环境查询都应有索引支持,并且尽可能使用组合索引,同时注意组合索引升降序,并利用覆盖索引高效查询。此外,还有大数据量下应该避免使用低区分度操作符等8个实践建议,一起通过视频了解一下吧。 [视频]
2021-09-22 - 使用串发命令模式延迟同步请求(上)
[视频] 你好,我是李艺。 上节课我们主要学习了如何将前端计算工作后移,这节课我们学习如何使用数据缓存。 下面我们看一下问题,一般情况下wx API以Sync结尾的接口是同步接口,例如像wx.getSystemInfoSync还有wx.setStorageSync,这些接口都属于同步接口,反之不以Sync结尾的接口都是异步接口,由于历史原因,有些接口虽然名称上它由Sync结尾,但实际上却仍然是同步接口,例如像那个wx.getSystemInfo还有这个wx.getStorage以及wx.setStorage这三个接口在开发里面还经常用到,而且经常是在App.onLaunch还有Page.onLoad,这些周期函数里面用到的,这对启动性能其实它是十分有影响的,小程序的启动流程是不能有任何人为的同步代码阻塞主线程的,如果需要拉取这些系统信息的话,在优化的时候我们可以由分接口代替,我们看一下都有哪些分接口。 例如第一个getSystemSetting接口是获取设备信息,还有像getAppAuthorizeSetting接口是获取微信APP授权信息 授权设置信息,再往下是getDeviceInfo是获取设备基础信息,第四个是getWindowInfo是获取窗口信息,最后一个是getAppBaseInfo是获取微信APP的一个基础信息,这里的每一个分接口它返回的信息都不一样,需要什么信息我们就调用什么样的信息的一个分接口就可以了。 还有在小程序启动过程中,我们尽量先使用默认参数在启动完成以后,也就是在Page.onReady这个事件派发以后再进行相关接口的一个调用,对于缓存后端接口数据的本地缓存存取代码,如果没有必要的话也要尽量放在启动流程完成以后,也就是Page.onReady这个事件派发以后再去调用,这里有一点我们需要注意,就是小程序现在它有一个接口叫做wx.getSystemInfoAsync,注意这个里面多了一个A ,这是一个不多见的以Async结尾的这样一个接口,它是一个异步接口,可以异步拉取这个系统信息,不阻塞主线程,但是这个接口需要一定的微信客户端版本支持,如果不在受支持的客户端上面使用、调用的时候,它会自动地又用原来的同步接口进行代替,这是我们要注意的,下面我们看项目实践。 首先看实践一,创建SystemInfoManager模块使用串发命令模式延迟同步请求。 目前在我们的app.js文件里面,在它的App.onLaunch这个周期函数里面有对wx.getSystemInfo接口的调用,这个代码是同步的,它会阻塞我们小程序启动流程的主线程的一个执行,我们必须将它进行改写,可以在globalData里面先定义默认参数,在启动的时候拿这些默认参数先给程序使用,然后在Page.onReady这个事件派发以后再拉取实际需要的这些数据。 下面在改造过程中我们将创建一个SystemInfoManager模块,用这个模块专门用于处理系统信息的一个拉取,目前我们需要的系统信息是比较有限的,可以先实现一些基础的代码,后续如果还需要其他的一些系统信息可以再逐步进行扩展,接下来就是使用串发复合指令对象延迟执行系统信息的一个拉取,这部分代码我们要放在app.js文件里面,这个代码量可能会稍微有一点点多,但是这个代码的逻辑还是比较清晰的。我们先在globalData上面设置默认的全局信息,然后再创建串发的复合命令对象 创建完成以后将它进行执行,那么我们什么时候让串发命令对象开始执行,可以在首页的JS文件里面,它的onReady周期函数里面设置命令的一个启动,这样关于系统信息的拉取操作,它其实就是在首屏渲染完成以后才开始执行的,这样一种设置其实不影响我们小程序整体的启动,串发复合命令的对象它有一个特点,就是前面的子命令完成以后后面的子命令它才会开始执行,我们利用这个特点就可以延迟这系统信息的一个拉取了。 在这里有一个问题请你思考一下,为什么我们要把拉取系统信息的主要代码要写在app.js文件里面的onLaunch周期函数里面?因为globalData它是在这里定义的这段代码的一个主要作用,为了拉取 往globalData对象里面存储的这些信息,那么将这些代码放在这里是最合适的了,面向对象模拟真实世界的事物关系,它使得我们软件设计有规可依,但是面向对象它有一个缺陷就是容易将相互联系在一起的代码人为地给它隔离开,这种情况下 我们利用这种串发的复合命令对象就可以巧妙地弥补缺陷,下面我们看代码演示。 首先打开我们微信小游戏这个项目,在开始改造之前我们先看一下我们目前的项目在执行的时候,它这个信息是如何获取的,重启一下我们这个项目,注意看一下我们调试区,这个地方有一个打印 已取到系统消息,这个信息我们是在哪里打印的呢,是在app.js里面对不对,我们打开代码看一眼,现在开发者工具因为我同时开了录屏的原因,它启动以及运行变得非常的一个卡顿,其实如果把录屏关了以后,它运行效果还是可以的,我们看一下这个代码里面,这个地方有个打印已取到系统消息了,这是我们在调用getSystemInfo接口以后,就是我们拿到这个系统消息以后打印的一个信息,然后注意一下我们这个信息打印,它其实是在哪里,在index onready之前,它其实在它的前面打印的,也就是说我们的首页渲染还没有完成,这个系统消息已经拉取了对不对,当然这个代码它是同步的,它会阻塞我们整体的一个流程的进行,所以我们需要将它进行优化。 优化的第一步,首先我们要创建一个SystemInfoManager,这样的一个管理器模块,管理器模块我们要放在我们的library manager放在这个下面,这个下面还没有,但是我们可以去我们的最终源码里面看一下,因为那个地方肯定有已经写好的代码对不对,6.5.1找到miniprogram,然后library manager,这个文件就是我们需要的文件 将它拷贝一下放到我们目前的目录下面,library然后manager放在下面。 现在我们看一下我们这个代码主要是做了什么事情,首先是引入它,因为我们接下来有相关接口的调用需要用到这个工具方法,这是一个类 SystemInfoManager,然后在下面我们有个导出,其实直接导出的是它的一个实例,它实例化的一个实例,本身这个模块在我们程序里面可以说是单例的,在主线程里面它是唯一的,在这个里面有一个很重要的方法就是retrieveSystemInfo,一上来我们会判断一下是不是可以调用这样的一个接口,这个接口,先前我们提到了它其实是一个什么样的接口,一个特殊的异步接口对不对,getSystemInfoAsync,很少有接口这个里面是加A的,一般都是加Sync后面的后缀,看它能不能用,如果能用我们就用它,如果不可以我们就用后面getSystemInfo进行调用,进行调用完以后拿到这个结果,这个地方拿到结果以后,在这个地方我们不能直接用这样的一种方式,就是wx.getSystemInfoSync等于它 这样是不可以的,这个代码我们是想重写接口的一个实现,让这个接口直接返回,我们已经取到的信息不需要重复地去拉取了,当然这样不可以的话可以变通一种方式,用Object的里面的一个defineProperty,用这样的一个接口,这个是JS的方法,JS对象的一个方法用于我们在一个对象上,定义它的属性,我们用这个,然后同时将后面的参数给它置为true,把它value写成一个箭头函数让它返回,这样就可以了,这个方法执行完成以后,我们这个信息就拉取到了,拉取到了以后我们再调用这个方法,就是getSystemInfoSync 再调用它的话其实它已经不是一个同步接口了,它其实就相当于调箭头函数,然后直接把已经拿到的本地的信息给它取出来,再取出它的statusBarHeight就是状态栏的高度,这个高度我们在代码里面会用到,还有屏幕的分辨率也会用到,还有这个信息稍后也会用到,这就是它的简单的一个实现,管理器代码然后完成以后,接下来我们要去调用它。 首先我们要看一下app.js,app.js里面我们有哪些改造,在这个地方有一个按新方式拉取系统信息,这是我们的主要的一个代码,这个代码稍微有一点点长对吧,这个代码给它拷贝一下到这个地方拉取新的信息,这是我们原来代码 原来这个代码里面干了什么事情,拿到这个信息以后,我们设置了这个信息,还有是又调用它 拿到custom又设置这些信息对吧,还有这个信息,它设置了一系列的一个信息,这些信息可能是在接下来这个程序运行的时候需要用到的一些信息,将这个先给它注掉,然后将我们新代码给它放在这个地方来,看一下新代码。 首先一上来我们先设置这些,这个是我们默认配置,我们当前以什么样的一个屏幕大小进行测试的时候,我们就设置什么样的一个默认的信息,把这个信息给它设置上 设置完以后,我们这个程序就可以用这些默认的信息了,再往下是我们引入了三个模块,其中就包括我们新创建的 system_info_manager,引用它的实例,再往下这个地方我们有个ClosureCommand,ClosureCommand是我们的一个闭包指令对象,在这个里面,我们首先会这个地方注意,这有一个await,因为我们这个地方是async 这个地方是await,然后去调用它的retrieveSystemInfo,因为这个调用它会占用一些时间,所以它是同步的,会占用一些时间,取到以后会打印这些信息,已取到系统信息,在取到完成以后,我们这个地方看一下,这有一个statusBarHeight的一个获取,我们可以调这个接口拿到,还有custom 调用它里面的方法拿到,然后这个信息 就是这些信息包括这个信息其实就是我们原来的这些信息,它就是稍微修改一下用新的方式去设置了,这就是它的一个修改,我们现在不需要了把它删掉,这是在app.js里面所要做的一些修改。 接下来我们还要看在我们主页里面还有一个修改,主页里面还有修改,在index目录下面要找到我们的onReady周期函数,在这个地方允许异步拉取系统信息了,将这个给它拷贝一下到我们项目里面来 这里,放在这个地方 onReady完毕了,我们调用了asyncRetrieveSystemInfo,这个对象它的getCommand取到它的第一个子命令,然后markComplete标记它的完成,然后后续的子命令才可以执行,因为这个对象它本身是一个串发复合命令。 我们再确认一下我们这个里边,在app.js里面,我们来看一下它这个命令,其实在这个地方复制的 看到没有,等于它对不对,然后这个里面它的这个地方有一个new ClosureCommand,它虽然没有传任何代码,但是它也占据了一席之地,然后它的第二个子命令才是cmd,才是我们前面的这些,第一个子命令它不完成的话,第二个子命令它就不会执行,也就是我们上面这些代码就不会执行,然后它里面的同步调用它就不会占用时间,这样一来对我们启动 整体的小程序的启动它就没有影响了,而在我们主页里面,在这个页面首页 首屏渲染完成以后,把第一个指令然后设置完成了,设置完成以后,它这个复合对象就开始执行,第二个指令就开始执行,然后拉取系统信息代码也开始执行,这样的话就不影响了,代码改完了。 接下来我们打开我们微信开发者工具单击编译,看一下它的实际运行效果,注意看一下已取到了系统信息,看到没有,这有一条打印信息,现在信息的打印它已经在index onready,index onload,这两个信息打印在它的后面了 也就是说它现在要做的工作其实已经不再影响,我们小程序的一个正常启动了,代码演示就到这里。
2022-07-14 - 富文本editor怎么实现首行缩进?
可以通过 this.editCtx.format('textIndent', '2em') 的方式实现
2019-09-16 - 云开发短信跳小程序(自定义开发版)教程
写在前面如果你想要自主开发,但没有云开发相关经验,可以采用演示视频来学习本教程: [视频] 一、能力介绍境内非个人主体的认证的小程序,开通静态网站后,可以免鉴权下发支持跳转到相应小程序的短信。短信中会包含支持在微信内或微信外打开的静态网站链接,用户打开页面后可一键跳转至你的小程序。 这个链接的网页在外部浏览器是通过 URL Scheme 的方式来拉起微信打开主体小程序的。 总之,短信跳转能力的实现分为两个步骤,「配置拉起网页」和「发送短信」。本教程将介绍如何执行操作完成短信跳转小程序的能力。 如果你想要无需写代码就能完成短信跳转小程序的能力,可以参照无代码版教程进行逐步实现。 二、操作指引1、网页创建首先我们需要构建一个基础的网页应用,在任何代码编辑器创建一个 html 文件,在教程这里命名为 index.html 在这个 html 文件中输入如下代码,并根据注释提示更换自己的信息: window.onload = function(){ window.web2weapp.init({ appId: 'wx999999', //替换为自己小程序的AppID gh_ID: 'gh_999999',//替换为自己小程序的原始ID env_ID: 'tcb-env',//替换小程序底下云开发环境ID function: { name:'openMini',//提供UrlScheme服务的云函数名称 data:{} //向这个云函数中传入的自定义参数 }, path: 'pages/index/index.html' //打开小程序时的路径 }) } 以上引入的 web2weapp.js 文件是教程封装的有关拉起微信小程序的极简应用,我们直接引用即可轻松使用。 如果你想进一步学习和修改其中的一些WEB展示信息,可以前往 github 获取源码并做修改。 有关于网页拉起小程序的更多信息可以访问官方文档 如果你只想体验短信跳转功能,在执行完上述文件创建操作后,继续以下步骤。 2、创建服务云函数在上面创建网页的过程中,需要填写一个UrlScheme服务云函数。这个云函数主要用来调用微信服务端能力,获取对应的Scheme信息返回给调用前端。 我们在示例中填写的是 openMini 这个命名的云函数。 我们前往微信开发者工具,定位对应的云开发环境,创建一个云函数,名称叫做 openMini 。 在云函数目录中 index.js 文件替换输入以下代码: const cloud = require('wx-server-sdk') cloud.init() exports.main = async (event, context) => { return cloud.openapi.urlscheme.generate({ jumpWxa: { path: '', // 打开小程序时访问路径,为空则会进入主页 query: '',// 可以使用 event 传入的数据制作特定参数,无需求则为空 }, isExpire: true, //是否到期失效,如果为true需要填写到期时间,默认false expire_time: Math.round(new Date().getTime()/1000) + 3600 //我们设置为当前时间3600秒后,也就是1小时后失效 //无需求可以去掉这两个参数(isExpire,expire_time) }) } 保存代码后,在 index.js 右键,选择增量更新文件即可更新成功。 接下来,我们需要开启云函数的未登录访问权限。进入小程序云开发控制台,转到设置-权限设置,找到下方未登录,选择上几步我们统一操作的那个云开发环境(注意:第一步配置的云开发环境和云函数所在的环境,还有此步操作的环境要一致),勾选打开未登录 [图片] 接下来,前往云函数控制台,点击云函数权限,安全规则最后的修改,在弹出框中按如下配置: [图片] 3、本地测试我们在本地浏览器打开第一步创建的 index.html ;唤出控制台,如果效果如下图则证明成功! 需要注意,此处本地打开需要时HTTP协议,建议使用live server等扩展打开。不要直接在资源管理器打开到浏览器,会有跨域的问题! [图片] 4、上传本地创建好的 index.html 至静态网站托管将本地创建好的 index.html 上传至静态网站托管,在这里静态托管需要是小程序本身的云开发环境里的静态托管。 如果你上传至其他静态托管或者是服务器,你仍然可以使用外部浏览器拉起小程序的能力,但会丧失在微信浏览器用开放标签拉起小程序的功能,也不会享受到云开发短信发送跳转链接的能力。 如果你的目标小程序底下有多个云开发环境,则不需要保证云函数和静态托管在一个环境中,无所谓。 比如你有A、B两个环境,A部署了上述的云函数,但是把 index.html 部署到B的环境静态托管中了,这个是没问题的,符合各项能力要求。只需要保证第一步 index.html 网页中的云开发环境配置是云函数所在环境即可。 部署成功后,你便可以访问静态托管的所在地址了,可以通过手机外部浏览器以及微信内部浏览器测试打开小程序的能力了。 5、短信发送云函数的配置在上面创建 openMini 云函数的环境中再来一个云函数,名字叫 sendsms 。 在此云函数 index.js 中配置如下代码: const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }) exports.main = async (event, context) => { try { const config = { env: event.env, content: event.content ? event.content : '发布了短信跳转小程序的新能力', path: event.path, phoneNumberList: event.number } const result = await cloud.openapi.cloudbase.sendSms(config) return result } catch (err) { return err } } 保存代码后,在 index.js 右键,选择增量更新文件即可更新成功。 6、测试短信发送能力在小程序代码中,在 app.js 初始化云开发后,调用云函数,示例代码如下: App({ onLaunch: function () { wx.cloud.init({ env:"tcb-env", //短信云函数所在环境ID traceUser: true }) wx.cloud.callFunction({ name:'sendsms', data:{ "env": "tcb-env",//网页上传的静态托管的环境ID "path":"/index.html",//上传的网页相对根目录的地址,如果是根目录则为/index.html "number":[ "+8616599997777" //你要发送短信的目标手机,前面需要添加「+86」 ] },success(res){ console.log(res) } }) } }) 重新编译运行后,在控制台中看到如下输出,即为测试成功: [图片] 你会在发送的目标手机中收到短信,因为短信中包含「退订回复T」字段,可能会触发手机的自动拦截机制,需要手动在拦截短信中查看。 需要注意:你可以把短信云函数和URLScheme云函数分别放置在不同云开发环境中,但必须保证所放置的云开发环境属于你操作的小程序 另外,出于防止滥用考虑,短信发送的云调用能力需要真实小程序用户访问才可以生效,你不能使用云端测试、云开发JS-SDK以及其他非wx.cloud调用方式(微信侧WEB-SDK除外),会提示如下错误: [图片] 如果你想在其他处使用此能力,可以使用服务端API来做正常HTTP调用,具体访问官方文档 7、查看短信监控图表进入 云开发控制台 > 运营分析 > 监控图表 > 短信监控,即可查看短信监控曲线图、短信发送记录。 [图片] 三、总结短信跳转小程序核心是静态网站中配置的可跳转网页,外部浏览器通过URL Scheme 来实现的,这个方式不适用于微信浏览器,需要使用开放标签才可以URL Scheme的生成是云调用能力,需要是目标小程序的云开发环境的云函数中使用才可以。并且生成的URL Scheme只能是自己小程序的打开链接,不能是任意小程序(和开放标签的任意不一致)短信发送能力的体验是每个有免费配额的环境首月100条,如有超过额度的需求可前往开发者工具-云开发控制台-对应按量付费环境-资源包-短信资源包,进行购买。如当前资源包无法满足需求也可通过云开发 工单 提交申请[图片]短信发送也是云调用能力,需要真实小程序用户调用才可以正常触发,其他方式均报错返回参数错误,出于防止滥用考虑云函数和网页的放置可以不在同一个环境中,只需要保证所属小程序一致即可。(需要保证对应环境ID都能接通)如果你不需要短信能力,可以忽略最后两个步骤CMS配置渠道投放、数据统计可参考官方文档
2021-04-07 - 云调用能力—客服消息
在前面的章节,我们已经在小程序端将 button 组件 open-type 的值设置为 contact ,点击 button 就可以进入客服消息。不过这个客服消息使用的是官方的后台,没法进行深度的定制,我们可以使用云开发作为后台来自定义客服消息来实现快捷回复、添加常用回答等功能。 如果是使用传统的开发方式,需要填写服务器地址(URL)、令牌(Token) 和 消息加密密钥(EncodingAESKey)等信息,然后结合将 token、timestamp、nonce 三个参数进行字典序排序、拼接、并进行 sha1 加密,然后将加密后的字符串与 signature 对比来验证消息的确来自微信服务器,之后再来进行接收消息和事件的处理,可谓十分繁琐,而使用云开发相对简单很多。 13.8.1 客服消息的配置与说明使用开发者工具新建一个云函数,比如 customer,在 config.json 里,设置以下权限后部署上传到服务端。 { "permissions": { "openapi": [ "customerServiceMessage.send", "customerServiceMessage.getTempMedia", "customerServiceMessage.setTyping", "customerServiceMessage.uploadTempMedia" ] } } 然后再打开云开发控制台,点击右上角的设置,选择全局设置,开启云函数接收消息推送,添加消息推送配置。为了学习方便我们将所有的消息类型都指定推送到 customer 云函数里。 text,文本消息image,图片消息miniprogram,小程序卡片event,事件类型 user_enter_tempsession,进入客服消息时就会触发以上有四种消息类型,但是发送客服消息的 customerServiceMessage.send 的 msgtype 属性的合法值有 text、image、link(图文链接消息)、miniprogrampage 四种,也就是我们还可以发图文链接消息。 13.8.2 自动回复文本消息和链接1、自动回复文本消息使用开发者工具新建一个页面,比如 customer,然后在 customer.wxml 里输入以下按钮, 进入客服button> 当用户通过 button 进入到客服消息之后,在聊天界面回复信息,就能触发设置好的 customer 云函数,比如下面的例子就是当用户发一条消息(包括表情)到客服消息会话界面,云函数就会给调用 customerServiceMessage.send 接口给用户回复两条文本消息(一次性可以回复多条),内容分别为[代码]等候您多时啦[代码]和[代码]欢迎关注云开发技术训练营[代码],一个云函数里也是可以多次调用接口的: const cloud = require("wx-server-sdk"); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }); exports.main = async (event, context) => { const wxContext = cloud.getWXContext(); try { const result = await cloud.openapi.customerServiceMessage.send({ touser: wxContext.OPENID, msgtype: "text", text: { content: "等候您多时啦", }, }); const result2 = await cloud.openapi.customerServiceMessage.send({ touser: wxContext.OPENID, msgtype: "text", text: { content: "欢迎关注云开发技术训练营", }, }); return event; } catch (err) { console.log(err); return err; } }; 发送文本消息时,支持插入跳小程序的文字链接的,比如我们把上面的文本消息改为以下代码: content: '欢迎浏览点击跳小程序a>'; data-miniprogram-appid 项,填写小程序 appid,则表示该链接跳小程序;data-miniprogram-path 项,填写小程序路径,路径与 app.json 中保持一致,可带参数;对于不支持 data-miniprogram-appid 项的客户端版本,如果有 herf 项,则仍然保持跳 href 中的网页链接;data-miniprogram-appid 对应的小程序必须与公众号有绑定关系。 2、自动回复链接我们还可以给用户回复链接,我们可以把 customer 云函数修改为以下代码,当用户向微信聊天对话界面发送一条消息时,就会回复给用户一个链接,这个链接可以是外部链接哦。 const cloud = require("wx-server-sdk"); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }); exports.main = async (event, context) => { const wxContext = cloud.getWXContext(); try { const result = await cloud.openapi.customerServiceMessage.send({ touser: wxContext.OPENID, msgtype: "link", link: { title: "快来加入云开发技术训练营", description: "零基础也能在10天内学会开发一个小程序", url: "https://cloud.tencent.com/", thumbUrl: "https://tcb-1251009918.cos.ap-guangzhou.myqcloud.com/love.png", }, }); return event; } catch (err) { console.log(err); return err; } }; 3、根据关键词来回复用户将上面的云函数部署之后,当用户向客服消息的聊天会话里输入内容时,不管用户发送的是什么内容,云函数都会回给用户相同的内容,这未免有点过于死板,客服消息能否根据用户发送的关键词回复用户不同的内容呢?要做到这一点我们需要能够获取到用户发送的内容。 我们可以留意云开发控制台云函数日志里看到,customer 云函数返回的 event 对象里的 Content 属性就会记录用户发到聊天会话里的内容: {"Content":"请问怎么加入云开发训练营", "CreateTime":1582877109, "FromUserName":"oUL-mu...XbuEDsn8", "MsgId":22661351901594052, "MsgType":"text", "ToUserName":"gh_b2bbe22535e4", "userInfo":{"appId":"wxda99ae4531b57046","openId":"oUL-m5FuRmuVmxvbYOGuXbuEDsn8"}} 由于 Content 是字符串,那这个关键词既可以是非常精准的,比如“训练营”,或“云开发训练营”,还可以是非常模糊的“请问怎么加入云开发训练营”,我们只需要对字符串进行正则匹配处理即可,比如当用户只要发的内容包含“训练营”,就会收到链接: const cloud = require("wx-server-sdk"); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }); exports.main = async (event, context) => { const wxContext = cloud.getWXContext(); const keyword = event.Content; try { if (keyword.search(/训练营/i) != -1) { const result = await cloud.openapi.customerServiceMessage.send({ touser: wxContext.OPENID, msgtype: "link", link: { title: "快来加入云开发技术训练营", description: "零基础也能在10天内学会开发一个小程序", url: "https://cloud.tencent.com/", thumbUrl: "https://tcb-1251009918.cos.ap-guangzhou.myqcloud.com/love.png", }, }); } return event; } catch (err) { console.log(err); return err; } }; 在前面的案例里,我们都是使用[代码]touser: wxContext.OPENID,[代码], 13.8.2 自动触发 event 事件要触发 event 事件,我们可以将 customer.wxml 的按钮改为如下代码,这里的 session-from 是用户从该按钮进入客服消息会话界面时,开发者将收到带上本参数的事件推送,可用于区分用户进入客服会话的来源。 进入客服button> 由于我们开启了 event 类型的客服消息,事件类型的值为 user_enter_tempsession,当用户点击 button 进入客服时,就会触发云函数,不用用户发消息就能触发,同时我们返回 event 对象. const cloud = require("wx-server-sdk"); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }); exports.main = async (event, context) => { const wxContext = cloud.getWXContext(); try { const result = await cloud.openapi.customerServiceMessage.send({ touser: wxContext.OPENID, msgtype: "text", text: { content: "欢迎来到等候您多时啦", }, }); return event; } catch (err) { console.log(err); return err; } }; 我们可以去云开发控制台查看返回的 event 对象 {"CreateTime":1582876587, "Event":"user_enter_tempsession", "FromUserName":"oUL-m5F...8", "MsgType":"event", "SessionFrom":"文章详情的客服按钮", "ToUserName":"gh_b2bbe22535e4", "userInfo":{"appId":"wxda9...57046", "openId":"oUL-m5FuRmuVmx...sn8"}} 在云函数端,我们是可以通过 event.SessionFrom 来获取到用户到底是点击了哪个按钮从而进入客服对话的,也可以根据用户进入客服会话的来源不同,给用户推送不同类型,比如我们可以给 session-from 的值设置为“训练营”,当用户进入客服消息会话就能推送相关的信息给到用户。 还有一点就是,bindcontact 是给客服按钮绑定了了一个事件处理函数,这里为 onCustomerServiceButtonClick,通过事件处理函数我们可以在小程序端做很多事情,比如记录用户点击了多少次带有标记(比如 session-from 的值设置为“训练营”)的客服消息的按钮等功能。 13.8.3 自动回复图片要在客服消息里给用户回复图片,这个图片的来源只能是来源于微信服务器,我们需要先使用 customerServiceMessage.uploadTempMedia,把图片文件上传到微信服务器,获取到 mediaId(有点类似于微信服务器的 fileID),然后才能在客服消息里使用。 在 customer 云函数的 index.js 里输入以下代码并部署上线,我们将获取到的 mediaId 使用 cloud.openapi.customerServiceMessage.send 发给用户: const cloud = require("wx-server-sdk"); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }); exports.main = async (event, context) => { const wxContext = cloud.getWXContext(); try { //我们通常会将云存储的图片作为客服消息媒体文件的素材 const fileID = "cloud://xly-xrlur.786c-xly-xrlur-1300446086/1572315793628-366.png"; //uploadTempMedia的图片类型为Buffer,而从存储下载的图片格式也是Buffer const res = await cloud.downloadFile({ fileID: fileID, }); const Buffer = res.fileContent; const result = await cloud.openapi.customerServiceMessage.uploadTempMedia({ type: "image", media: { contentType: "image/png", value: Buffer, }, }); console.log(result.mediaId); const mediaId = result.mediaId; const wxContext = cloud.getWXContext(); const result2 = await cloud.openapi.customerServiceMessage.send({ touser: wxContext.OPENID, msgtype: "image", image: { mediaId: mediaId, }, }); return event; } catch (err) { console.log(err); return err; } }; 客服消息还能给用户回复小程序消息卡片,以及客服当前的输入状态给用户(使用 customerServiceMessage.setTyping 接口)。
2021-09-10 - 小程序联盟公测
各位微信开发者: 你们好。 为了更好的帮助小程序商家提高商品销量,微信官方提供的推广工具“小程序联盟”,于2021年3月1日开始公测。 小程序联盟具有“先成交后付费”的特点,商家在管理后台发布商品推广需求和佣金,佣金在推客(推广者)成功完成推广后才会结算。 功能简介与接入标准请参考下方内容。 一、功能简介 小程序联盟分别为商家和推客(推广者)提供了管理后台: 商家可在管理后台设置商品推广佣金,查看推广效果,具体说明请查看《商家端功能说明》;[图片] 2.推客可在管理后台挑选商品,获取推广素材,查看推广效果,提现佣金,具体说明请查看《推客端功能说明》; [图片] 二、接入要求 拥有商品,希望被推广的小程序商家可申请成为小程序联盟商家。没有货源,希望通过分享商品创造价值的推广者,可以申请成为小程序联盟推客。 具体接入条件如下: 1. 商家 满足以下条件之一,即可开通小程序联盟: 已开张的企业/个体工商户的小商店; 已有小程序并完成标准版交易组件接入。 接入指引,请参考《商家接入指引》。 企业/个体工商户为主体的小商店可直接前往PC端后台开通联盟功能。 [图片] 2. 推客: 支持企业/个体工商户主体接入,通过主体认证即可。 暂未向个人开放。 接入指引,请参考《推客接入指引》。
2021-03-03 - (10)群聊能力
我们在后台收到很多朋友的反馈,希望更好的运用小程序群聊的能力。于是我们想写写群聊的故事。 微信群是小程序在微信这个社交工具下传播的重要途径,我们经常能通过群聊看见小程序的身影。我们希望开发者在实现小程序逻辑的时候,能理解每一个群聊,可实现小程序与各个群聊紧密相关的功能。 基于此,我们开放了群聊 ID(openGID)的功能,供开发者区分标识每个群聊。对于每个群聊而言,小程序所获取到的 openGID 是不变的。但对于同一个群,不同的小程序内获得的 openGID 是不一样的。这一特性类似于标识用户身份的 openID。 拥有了群聊 ID,开发者可以把用户的操作按照群聊 ID 来聚合、沉淀信息,实现群协作功能。此外,通过 openID+openGID 的方式,还可以实现群排行的功能。 例如“群影”小程序以群聊ID聚合用户上传的图片,实现群相册的功能。 [图片] (“群影”小程序) 01 如何获取群聊ID 开发者获取 openGID 要依托于用户转发到群聊的小程序卡片,具体步骤如下: 1 设置带 shareTicket 的分享 在小程序内,开发者调用接口wx.updateShareMenu 带参数withShareTicket:true ,设置当前页面分享到群聊时能获取 openGID。而shareTicket本身就是获取 openGID 的凭证。 [图片] 而 iOS/Android App 分享场景当中,微信SDK也支持把所分享的消息设置成带 shareTicket。 值得注意的是,带 shareTicket 的分享卡片会被固定在某个群聊的,也就是说分享卡片会变成不能被长按转发。 2 由启动参数获取 shareTicket 当用户从某个带 shareTicket 的卡片进入小程序时,开发者可以在App.onLaunch 或者App.onShow 获取 shareTicket,而在小游戏上开发者可以通过监听 wx.onShow 或者同步调用wx.getLaunchOptionsSync 获取shareTicket。 shareTicket 实际上是小程序启动时临时生成的变量,在小程序生命周期内仅作为调用接口的凭证。生命周期结束后 shareTicket 就没有意义了。 3 通过 shareTicket 获取 openGID 开发者调 wx.getShareInfo 接口以 shareTicket 换取 openGID 的加密数据包,这是为了保证开发者服务器收到的 openGID 是可信的,开发者需要把加密数据交由后台解密,拿到真实的 openGID。数据加密机制更多请参看[数据加密相关文档]。 注意事项 ▷▷ 由于2018年7月5日起,新提交发布的小程序版本将无法通过用户分享获得群ID,即开发者通过wx.onShareAppMessage获取群 ID 的方式将不再支持,后续仅支持通过启动参数获取群 ID。请开发者及时调整。 02 群聊名称组件 除了群聊 ID 以外,开发者还能使用群聊对应的名称。出于保护用户隐私的考虑,我们不会把真实的群聊名称暴露给开发者,而是通过 open-data 组件让开发者在小程序前端展示某个 openGID 对应的群名称。 [图片] 其中 openGID 就是小程序获取到的群聊 ID。 open-data 组件只展示那些用户所在群聊ID对应的名称。如果设置了非微信提供的群聊 ID,将无法展现群聊名称。 03 群聊功能 1 分享设置 wx.updateShareMenu : [查看文档] 2 小程序启动参数 App.onLaunch / App.onShow : [查看文档] 3 小游戏启动参数 wx.onShow : [查看文档] getLaunchOptionsSync : [查看文档] 4 获取 openGID wx.getShareInfo : [查看文档] 5 群名称组件 open-data>/open-data>: [查看文档]
2018-08-17 - 开放报名:微信开放平台公交地铁行业小程序乘车码激励活动
开放报名:微信开放平台公交地铁行业小程序乘车码激励活动 为更好的鼓励服务商开拓公交地铁行业小程序乘车码场景业务,为广大用户提供高效、便捷、贴心的出行体验,微信开放平台推出本激励活动,服务商代所授权的小程序商户报名成功并满足规定的条件后,服务商可获得相应的奖励。 一.活动规则1.有效期:2020年8月1日到2021年1月31日 2.行业范围:公交地铁行业小程序乘车码业务(需开通微信小程序广告流量主功能并接入小程序广告) 3.奖励对象:取得公交、地铁、城市通卡公司官方授权的小程序服务商(非腾讯主体) 4.奖励规则: 4.1.奖励计算方式 以小程序APPID为计算单位,符合准入条件的服务商报名并经审核通过后方可参与本活动,参与本活动并满足达标条件的小程序,给予服务商按小程序广告流量主的收入流水的18%进行奖励。 5.准入条件: 5.1.服务商与小程序具备绑定授权关系; 5.2.小程序类目为公交地铁且具备乘车码功能; 5.3.小程序的公交地铁行业的上个自然月代扣笔数>100; 5.4.提供公交地铁行业公司关联性证明材料。 6.达标要求:详情请登录服务平台查看。 二.参与流程1. 服务商注册open账号并创建第三方平台 1.1.第三方平台的申请和上线流程参照点击查看【注册第三方平台操作指引】; 1.2.如果在创建第三方平台时选择的是“定制化开发服务商“,则需要做如下操作:可将自己已经开发出的定制化小程序关联到服务商平台中,生成凭证(票据)填充到小程序代码包中进行关联,平台获取开发关系,点击查看【生成凭证操作指引】; 2.服务商入驻服务平台 完成第三方平台创建后需入驻服务平台,点击查看【入驻服务平台操作指引】; 3.服务商报名激励活动 登录服务平台-服务商激励,按照页面提示完成服务商及小程序报名流程,报名审核通过方可参与本激励活动,点击查看【报名激励活动操作指引】。 具体的活动方案细则请登录服务平台查看。 微信团队 2020年08月24日
2020-08-24