- #小程序云开发挑战赛#-sentry 小程序客户端-我做的都队
tldr: 使用方式可以看视频: https://v.qq.com/x/page/d3141vp8o8e.html 应用场景 不想开电脑但是却被报警电话打爆想简单看看 bug 然后 assign 给其他小弟闲着没事看一下线上的 bug 情况如何,需不需要处理。适合在带薪拉屎或者给小孩换尿布的时候使用目标用户 一线程序员程序员的老板(部门老大不在列表中)实现思路 通过调用 sentry 提供的 API 完成 sentry 小程序的客户端。以切换 host 和 access token 的形式支持第三方部署的 sentry 服务。通过 serverless 做后端请求转发和基本的配置信息保存更新 基本请求时序图 [图片] user: 你 mp: 小程序客户端 cloud: 小程序 serverless 服务 db: 小程序 serverless 数据库 sentry: sentry 服务器(可以是 sentry.io 也可以是自己部署的服务) 效果截图 [图片] [图片] 功能代码展示 代码在 https://github.com/AnnatarHe/sentry-mp-client 欢迎 star, fork, 发 issue, 提 pull request 作品体验二维码 [图片]
2020-08-25 - #小程序云开发挑战赛#-趣婚礼-趣婚礼
<h3 align=“center”> <a href=“https://github.com/wforguo/wedding-app” title=“趣婚礼”>趣婚礼</a> </h3> <p align=“center”> <img alt=“趣婚礼 Logo” src=“https://forguo-1302175274.cos.ap-shanghai.myqcloud.com/wedding/assets/img/qrcode.jpeg” width=“180”> </p> <p align=“center”> <a href=“https://github.com/wforguo/wedding-app” title=“趣婚礼”>基于Taro2 + 云开发 打造婚礼邀请函</a> </p> 项目名称 趣婚礼 基于[代码]Taro2[代码] + 云开发 打造婚礼邀请函 Taro2 云开发 项目介绍 结婚的时候婚礼邀请函是一道必不可少的程序,但是没法去很好的留存我们的数据和回忆(除非有后端支持)。 最近刚好在学习[代码]Taro[代码],所有就尝试基于[代码]Taro[代码] + 云开发快速的搭建一个婚礼邀请函小程序。 也有人会问,使用了云开发,怎么去管理数据呢,不用担心,云开发[代码]CMS[代码]帮你搞定,支持文本、富文本、图片、文件、关联类型等多种类型的可视化编辑。 CMS 概览 项目效果截图 模块划分 邀请 =》邀请函信息 相册 =》相册展示 导航 =》婚礼举办地 祝福 =》婚礼视频及弹幕留言,留言保存至留言列表 留言 =》好友留言 目录结构 [代码]├── config # 配置文件 ├── cloud # 云函数存放 ├── dist # 打包文件 ├── node_modules # 依赖的模块包 ├── package.json # 项目基本信息 ├── src # 项目的核心组件 │ ├── service # 资源文件(css、image、config) │ ├── common # 资源文件(css、image、config) │ ├── components # 公共组件 │ ├── store # 状态管理(redux) | ├── pages # 页面文件目录 | | ├── Index # index页面目录 | | | ├── index.jsx # index页面逻辑 | | | └── index.scss # index页面样式 | | | └── index.config.js # index页面配置(小程序page.json内容) │ ├── util # 公共方法(util.js、globalData.js) │ ├── app.jsx # 入口文件 │ ├── app.scss # 公共样式 │ ├── index.html # 主页模板 ├── static # 静态资源(CDN) ├── README.md # 项目描述信息 [代码] 效果预览 [图片] 视频演示 视频演示 部署 clone [代码]clone[代码]该项目,并在[代码]project.config.json[代码]下修改自己的小程序[代码]appid[代码] 开通云开发 [图片] 首先需要你在小程序的控制台去开通云开发,并拿到环境名称 在[代码]src/service/config[代码]下修改[代码]DBID[代码]为你自己申请的环境ID`; 新建数据库并导入 表的设计 wedding_invite:婚礼信息 wedding_msgs:留言祝福 wedding_photos:相册 wedding_video:视频 数据库文件存放在[代码]static/db[代码]下,按照文件名新建数据库集合,并导入数据文件即可完成数据库创建。 项目启动 使用[代码]yarn[代码] 安装依赖 [代码]yarn [代码] 编译和打包 [代码] yarn dev:weapp yarn build:weapp [代码] 使用[代码]npm[代码] 安装依赖 [代码] npm install [代码] 编译和打包 [代码] npm run dev:weapp npm run build:weapp [代码] 具体可查看[代码]Taro[代码]教程 到此你就可以看到效果了… 云开发CMS的使用 用于管理云开发数据的CMS CMS文档 CMS截图 CMS Web端 CMS端需要建立与数据库对应的内容模型,才能在列表正常展示对应数据库数据。 可直接导入内容模型,位于 static/schema 模型管理 [图片] 内容管理 [图片] 开发者工具端 [图片] 圈出来的部分是和小程序云开发控制台数据库所对应的. 技术及框架 1.小程序 具体入门和使用,请访问官方文档。 小程序文档 小程序云开发 开发者可以使用云开发开发微信小程序、小游戏,无需搭建服务器,即可使用云端能力。包含云函数 、数据库、存储和云调用。 2.Taro2 + Redux Taro2 Taro 是一个开放式跨端跨框架解决方案,支持使用 React/Vue/Nerv 等框架来开发微信/京东/百度/支付宝/字节跳动/ QQ 小程序/H5 等应用。 项目中使用了最新版本[代码]Taro2[代码],由于[代码]Taro3[代码]使用期间不是很丝滑,所以选择了[代码]Taro2[代码] 所以本项目可以作为Taro的学习入门,也可以作为小程序云开发的一个入门Demo 3.TaroUI 基于Taro2的UI框架 TaroUI 云开发的使用 官方文档 需要在控制台去开启云开发,并获取DBID(数据库初始化用到) 云开发入口 [图片] 数据库配置 [图片] 数据库 可以在项目中的service中去查看数据库的CURD代码。 有数据库基础的很容易就上手了,小程序的数据库其实就是一个[代码]JSON[代码],类似于[代码]MongoDb[代码]。 顾名思义,数据库中的每条记录都是一个 JSON 格式的对象。一个数据库可以有多个集合(相当于关系型数据中的表),集合可看做一个 JSON 数组,数组中的每个对象就是一条记录,记录的格式是 JSON 对象。 操作 初始化 在开始使用数据库 API 进行增删改查操作之前,需要先获取数据库的引用。 [代码]const db = Taro.cloud.database({ env: 'DBID' }); [代码] 数据库操作 要操作一个集合,需先获取它的引用。在获取了数据库的引用后,就可以通过数据库引用上的 collection 方法获取一个集合的引用了 [代码]db.collection('wedding_msgs') .orderBy('createTime', 'desc') // 时间升序 .skip(current * 10) .limit(pageSize) .get() [代码] 这样每次都比较麻烦,所以我做了统一的处理,都写在[代码]service/cloud/index.js[代码]当中,并export,只需要按需引入,并传入要操作的数据库名称即可。 [图片] Tips:如果发现数据库有数据,但是拿不到所有数据,那应该就是数据库的权限问题了,改成仅创建者可写,所有人可读就可以了 云函数 官方文档 使用云函数去获取用户信息就变得简单多了,具体可翻阅文档! 项目中云函数所在目录,[代码]cloud/functions.js[代码],项目中使用到了一个云函数,留言的内容过滤功能[代码]msgSecCheck[代码], 具体使用方法:security.msgSecCheck 声明 [代码]/** * @Description: 文本内容过滤; */ // 云函数入口文件 const cloud = require('wx-server-sdk'); cloud.init(); // 云函数入口函数 exports.main = async function (event, context) { console.log(event); let opts = { content: event.content || '' }; let fun = cloud.openapi.security.msgSecCheck(opts); return fun.then(res => { return res; }).catch(err => { return err; }); }; [代码] 调用 [代码] // 云调用内容安全过滤 Taro.cloud.callFunction({ name: 'msgCheck', data: { content: data.userMsg }, }).then(res => { if (res && res.result && res.result.errCode === 0) { Taro.showLoading({ title: '发送中...', mask: true }); // 数据库插入留言数据 cloud.add('wedding_msgs', data).then(msgRes => { resolve(msgRes); }, (err) => { console.log(err); reject(err); }); } else { reject(res.result); } }).catch(err => { console.log(err); reject(err); }); [代码] ToDos 朋友圈海报生成… … 这次的版本使用了Taro + 云开发,后面打算出一版[代码]Taro + Antd + koa2 + MongoDb[代码]的版本,初步内容已经差不错了,详见下面项目地址 Taro + Antd + koa2 + MongoDb Taro3的坑 redux使得下拉刷新和上拉加载冲突 无法阻止事件冒泡 无法使用 小程序的[代码]selectComponent[代码],获取组件实例 [代码]const barrageComp = this.selectComponent('.barrage')[代码] 使用 [代码]Taro.createRef();[代码] 代替 关于 <h3 align=“center”> <a href=“https://forguo-1302175274.cos.ap-shanghai.myqcloud.com/wedding/assets/img/wechart.jpg” title=“微信:iforguo”>微信:iforguo</a> </h3> <p align=“center”> <img alt=“微信:iforguo” src=“https://forguo-1302175274.cos.ap-shanghai.myqcloud.com/wedding/assets/img/wechart.jpg” width=“480”> </p> 个人主页 CSDN 掘金 本项目仅供学习和交流,部分素材来源于网络,如有侵权联系删除。 项目有需改进也请留言和Issues,如有合作请在博客留言或wx:([代码]iforguo[代码])。 编码不易,感谢各位大佬的吐槽和GitHub的Star
2021-12-10 - canvas画图片圆角
[图片] 此方法绘制圆角在ios系统上是正常的,在安卓下点的位置会偏移。
2018-04-09 - weapp-qrcode-canvas-2d在微信小程序中生成二维码,新版canvas-2d接口
weapp-qrcode-canvas-2d weapp-qrcode-canvas-2d 是使用新版canvas-2d接口在微信小程序中生成二维码(外部二维码)的js包。canvas 2d 接口支持同层渲染且性能更佳,建议切换使用,可大幅提升生成图片的速度。 仓库地址 weapp-qrcode-canvas-2d【码云gitee】 weapp-qrcode-canvas-2d【github】 [图片] 测试环境 微信小程序基础库版本:2.10.4 开发者工具版本:Stable 1.03.2101150 Usage 先在 wxml 文件中,创建绘制的 [代码]canvas[代码],并定义好 [代码]width[代码], [代码]height[代码], [代码]id[代码] , [代码]type[代码] ,其中type的值必须为[代码]2d[代码] [代码]<canvas type="2d" style="width: 260px; height: 260px;" id="myQrcode"></canvas> [代码] 安装方法1:直接引入 js 文件 直接引入 js 文件,使用 [代码]drawQrcode()[代码] 绘制二维码 [代码]// 将 dist 目录下,weapp.qrcode.esm.js 复制到项目中。路径根据实际引用的页面路径自行改变 import drawQrcode from '../../utils/weapp.qrcode.esm.js' [代码] 安装方法2:npm安装 [代码]npm install weapp-qrcode-canvas-2d --save [代码] // 然后需要在小程序开发者工具中:构建npm [代码]import drawQrcode from 'weapp-qrcode-canvas-2d' [代码] 安装完成后调用 例子1:没有使用叠加图片 [代码]const query = wx.createSelectorQuery() query.select('#myQrcode') .fields({ node: true, size: true }) .exec((res) => { var canvas = res[0].node // 调用方法drawQrcode生成二维码 drawQrcode({ canvas: canvas, canvasId: 'myQrcode', width: 260, padding: 30, background: '#ffffff', foreground: '#000000', text: 'abc', }) // 获取临时路径(得到之后,想干嘛就干嘛了) wx.canvasToTempFilePath({ canvasId: 'myQrcode', canvas: canvas, x: 0, y: 0, width: 260, height: 260, destWidth: 260, destHeight: 260, success(res) { console.log('二维码临时路径:', res.tempFilePath) }, fail(res) { console.error(res) } }) }) [代码] 例子2:使用叠加图片(在二维码中加logo) [代码]const query = wx.createSelectorQuery() query.select('#myQrcode') .fields({ node: true, size: true }) .exec((res) => { var canvas = res[0].node var img = canvas.createImage(); img.src = "/image/logo.png" img.onload = function () { // img.onload完成后才能调用 drawQrcode方法 var options = { canvas: canvas, canvasId: 'myQrcode', width: 260, padding: 30, paddingColor: '#fff', background: '#fff', foreground: '#000000', text: '123456789', image: { imageResource: img, width: 80, // 建议不要设置过大,以免影响扫码 height: 80, // 建议不要设置过大,以免影响扫码 round: true // Logo图片是否为圆形 } } drawQrcode(options) // 获取临时路径(得到之后,想干嘛就干嘛了) wx.canvasToTempFilePath({ x: 0, y: 0, width: 260, height: 260, destWidth: 600, destHeight: 600, canvasId: 'myQrcode', canvas: canvas, success(res) { console.log('二维码临时路径为:', res.tempFilePath) }, fail(res) { console.error(res) } }) }; }) [代码] API drawQrcode([options]) options Type: Object 参数 必须 说明 示例 canvas 必须 画布标识,传入 canvas 组件实例 canvasId 非 绘制的[代码]canvasId[代码] [代码]'myQrcode'[代码] text 必须 二维码内容 ‘123456789’ width 非 二维码宽度,与[代码]canvas[代码]的[代码]width[代码]保持一致 260 padding 非 空白内边距 20 paddingColor 非 内边距颜色 默认与background一致 background 非 二维码背景颜色,默认值白色 [代码]'#ffffff'[代码] foreground 非 二维码前景色,默认值黑色 [代码]'#000000'[代码] typeNumber 非 二维码的计算模式,默认值-1 8 correctLevel 非 二维码纠错级别,默认值为高级,取值:[代码]{ L: 1, M: 0, Q: 3, H: 2 }[代码] 1 image 非 在 canvas 上绘制图片,层级高于二维码,v1.1.1+版本支持。具体使用见:例子2 [代码]{imageResource: '', width:80, height: 80, round: true}[代码]
2023-04-02 - 目前为止项目中用到的一些校验
// 判断是否是json字符串 isJSON(str) { if (typeof str == 'string') { try { var obj=JSON.parse(str); if(typeof obj == 'object' && obj ){ return true; }else{ return false; } } catch(e) { return false; } } }, //验证手机号 checkPhone(phone){ var res = /^1[3456789]\d{9}$/; return res.test(phone);//返回true:手机号正确 false:手机号错误 }, // 国内座机 checkTel(phone){ var res = /\d{3}-\d{8}|\d{4}-\d{7}/; return res.test(phone);//返回true:正确 false:错误 }, // 验证邮箱(支持中文邮箱) checkEmail(email) { var res =/^[A-Za-z0-9\u4e00-\u9fa5]+([-_.][A-Za-z\d]+)*@([A-Za-z\d]+[-.])+[A-Za-z\d]{2,5}$/; return res.test(email);//返回true:邮箱正确 false:邮箱错误 }, // 验证中国大陆身份证号 checkIdcard(idcard) { var res = /(^\d{8}(0\d|10|11|12)([0-2]\d|30|31)\d{3}$)|(^\d{6}(18|19|20)\d{2}(0\d|10|11|12)([0-2]\d|30|31)\d{3}(\d|X|x)$)/; return res.test(idcard);//返回true:正确 false:错误 }, // 验证中国大陆一代身份证号 checkIdcard1(idcard){ var res =/^\d{8}(0\d|10|11|12)([0-2]\d|30|31)\d{3}$/; return res.test(idcard);//返回true:正确 false:错误 }, // 验证中国大陆二代身份证号 checkIdcard2(idcard){ var res = /^\d{6}(18|19|20)\d{2}(0\d|10|11|12)([0-2]\d|30|31)\d{3}(\d|X|x)$/; return res.test(idcard);//返回true:身份证正确 false:身份证错误 }, // 验证统一社会信用代码和组织机构代码 CheckSocialCreditCode(code){ var res = /^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$/; return res.test(code);//返回true:正确 false:错误 }, // 验证是否是图片链接 isImageUrl(str){ var res=/^https?:\/\/.*?(?:gif|png|jpg|jpeg|webp|svg|psd|bmp|tif)$/i; return res.test(str);//返回true:正确 false:错误 }, // 验证是否是视频链接 isVideoUrl(str){ var res=/^https?:\/\/.*?(?:swf|avi|flv|mpg|rm|mov|wav|asf|3gp|mkv|rmvb|mp4)$/i; return res.test(str);//返回true:正确 false:错误 }, // 是否是base64 isBase64(str){ var res=/^\s*data:(?:[a-z]+\/[a-z0-9-+.]+(?:;[a-z-]+=[a-z0-9-]+)?)?(?:;base64)?,([a-z0-9!$&',()*+;=\-._~:@/?%\s]*?)\s*$/i; return res.test(str);//返回true:正确 false:错误 }, // 验证银行卡号 isBankCard(str){ var res=/^[1-9]\d{9,29}$/; return res.test(str);//返回true:正确 false:错误 }, // 中文姓名 isChineseName(str){ var res=/^(?:[\u4e00-\u9fa5·]{2,16})$/; return res.test(str);//返回true:正确 false:错误 }, // 英文姓名 isEnglishName(str){ var res=/(^[a-zA-Z]{1}[a-zA-Z\s]{0,20}[a-zA-Z]{1}$)/; return res.test(str);//返回true:正确 false:错误 }, // 是否是新能源车牌号 isNewCarCard(str){ var res=/[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领 A-Z]{1}[A-HJ-NP-Z]{1}(([0-9]{5}[DF])|([DF][A-HJ-NP-Z0-9][0-9]{4}))$/; return res.test(str);//返回true:正确 false:错误 }, // 非新能源车牌号 isOldCarCard(str){ var res=/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领 A-Z]{1}[A-HJ-NP-Z]{1}[A-Z0-9]{4}[A-Z0-9挂学警港澳]{1}$/; return res.test(str);//返回true:正确 false:错误 }, // 车牌号(新能源+非新能源) isCarCard(str){ var res=/^(?:[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领 A-Z]{1}[A-HJ-NP-Z]{1}(?:(?:[0-9]{5}[DF])|(?:[DF](?:[A-HJ-NP-Z0-9])[0-9]{4})))|(?:[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领 A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9 挂学警港澳]{1})$/; return res.test(str);//返回true:正确 false:错误 }, // 护照 (包含香港、澳门) checkPassport(str){ var res=/(^[EeKkGgDdSsPpHh]\d{8}$)|(^(([Ee][a-fA-F])|([DdSsPp][Ee])|([Kk][Jj])|([Mm][Aa])|(1[45]))\d{7}$)/; return res.test(str);//返回true:正确 false:错误 }, // 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线组合) checkAccount(str){ var res=/^[a-zA-Z][a-zA-Z0-9_]{4,15}$/; return res.test(str);//返回true:正确 false:错误 }, // 纯中文汉字 checkChineseWords(str){ var res=/^(?:[\u3400-\u4DB5\u4E00-\u9FEA\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0])+$/; return res.test(str);//返回true:正确 false:错误 }, // 纯英文字母 checkEnglistWords(str){ var res=/^[a-zA-Z]+$/; return res.test(str);//返回true:正确 false:错误 }, // 是否是小数 isDecimal(str){ var res=/^\d+\.\d+$/; return res.test(str);//返回true:正确 false:错误 }, // 纯数字 isNumber(str){ var res=/^\d{1,}$/; return res.test(str);//返回true:正确 false:错误 }, // qq号 isQQNumber(str){ var res=/^[1-9][0-9]{4,10}$/; return res.test(str);//返回true:正确 false:错误 }, // 微信号 6至20位,以字母开头,字母,数字,减号,下划线 checkWxCode(str){ var res=/^[a-zA-Z][-_a-zA-Z0-9]{5,19}$/; return res.test(str);//返回true:正确 false:错误 }, // 中国邮政编码 checkChinaPostalCode(str){ var res=/^(0[1-7]|1[0-356]|2[0-7]|3[0-6]|4[0-7]|5[1-7]|6[1-7]|7[0-5]|8[013-6])\d{4}$/; return res.test(str);//返回true:正确 false:错误 }, // 判断字符串中是否含有表情 isEmojiCharacter(substring) { // if(isEmojiCharacter(str)){console.log('不能含有表情')} for (var i = 0; i < substring.length; i++) { var hs = substring.charCodeAt(i); if (0xd800 <= hs && hs <= 0xdbff) { if (substring.length > 1) { var ls = substring.charCodeAt(i + 1); var uc = ((hs - 0xd800) * 0x400) + (ls - 0xdc00) + 0x10000; if (0x1d000 <= uc && uc <= 0x1f77f) { return true; } } } else if (substring.length > 1) { var ls = substring.charCodeAt(i + 1); if (ls == 0x20e3) { return true; } } else { if (0x2100 <= hs && hs <= 0x27ff) { return true; } else if (0x2B05 <= hs && hs <= 0x2b07) { return true; } else if (0x2934 <= hs && hs <= 0x2935) { return true; } else if (0x3297 <= hs && hs <= 0x3299) { return true; } else if (hs == 0xa9 || hs == 0xae || hs == 0x303d || hs == 0x3030 || hs == 0x2b55 || hs == 0x2b1c || hs == 0x2b1b || hs == 0x2b50) { return true; } } } }, // 判断字符串中是否含有特殊字符 isTeShuString(substring){ var reg = /[~#^$@%&!?%*]/gi; return reg.test(substring); }, 不足之处忘大家指正,非常感谢
2022-02-22 - 小程序实现看一看视频滑动切换
最终效果 由于很多人不知道看一看还有个视频功能,所以这里先让大家看下我们最终要完成的效果。 它的入口在 发现 -> 看一看 -> 精选 -> 随便找个视频点进去即可。 [图片] 初步想法 由效果可以看出,其实就是需要监听视频的滚动,当超出可视区范围多少px,就切换到下一个视频。 要实现这个功能,大多数人的想法都是:监听scroll 事件后,在调用目标元素的getBoundingClientRect()方法,得到它对应于视图的坐标,再判断是否在可视区域之内,然后切换视频。 缺点 但是这样做的缺点是:调用目标元素的getBoundingClientRect 是会触发重排的,尤其是元素一多起来,调用所有元素的getBoundingClientRect得到信息在进行判断所以很容易造成性能问题。而且这种切换计算的逻辑会写的非常复杂,可以自行脑补一下。 所以我们要换个思维,不要通过监听scoll事件去计算目标元素距离顶部或者底部距离。而应该是直接监听当前目标元素是否还在可视区域内。当离开可视区域的时候,切换到下一个。 整理完大致思路之后,终于要开搞了。 [图片] 实现 前面已经分析了要通过监听当前目标元素是否还在可视区域内来做切换的动作,那么有什么API是可以用来做这件事的呢? 答案是: IntersectionObserver API 这个API是用来观察目标元素与指定元素交集的变化。当交集 < 0 的时候,说明不在指定元素区域内。当目标元素进入或者退出指定元素的时候,会执行相应的回调函数。所以我们可以通过这个API注册一个回调函数用于切换视频。 在小程序里,同样提供了这个API,是IntersectionObserver。 有了这个API,就可以开始干活了。由于我们这个区域是一个滚动区域,所以我用了scoll-view。 index.wxml 文件 [代码]<scroll-view> <view wx:for="{{ videos }}" wx:for-index="idx" wx:for-item="videoItem"> <!-- <view class="{{ currentPlayVideoIndex === idx ? 'active test' : 'test'}}" data-index="{{ idx }}" id="{{ videoItem.video_id }}"> {{ idx }}dddd</view> --> <span class="{{ currentPlayVideoIndex === idx ? 'active' : ''}}">{{ idx }}ddddddd </span> <video id="{{ videoItem.video_id }}" data-index="{{ idx }}" preload src="http://wxsnsdy.tc.qq.com/105/20210/snsdyvideodownload?filekey=30280201010421301f0201690402534804102ca905ce620b1241b726bc41dcff44e00204012882540400&bizid=1023&hy=SH&fileparam=302c020101042530230204136ffd93020457e3c4ff02024ef202031e8d7f02030f42400204045a320a0201000400" class='video-item' muted controls> </video> </view> </scroll-view> [代码] index.wxss [代码].video-item { height: 450px; } .test { width: 100%; height: 450px; border: 1px solid red; padding: 30px; } .active { color: pink; } [代码] [代码]Page({ /** * 页面的初始数据 */ data: { videos: [{ video_id: 'mpVideo0', url: 'http://wxsnsdy.tc.qq.com/105/20210/snsdyvideodownload?filekey=30280201010421301f0201690402534804102ca905ce620b1241b726bc41dcff44e00204012882540400&bizid=1023&hy=SH&fileparam=302c020101042530230204136ffd93020457e3c4ff02024ef202031e8d7f02030f42400204045a320a0201000400', }, { video_id: 'mpVideo1', url: 'http://mpvideo.qpic.cn/tjg_2394158861_50000_01730a9db3924ffa98201662d51615ed.f10002.mp4?dis_k=23639703f249e3c59cf674369cfcac86&dis_t=1562297977', }, { video_id: 'mpVideo2', url: 'http://mpvideo.qpic.cn/tjg_2394158861_50000_01730a9db3924ffa98201662d51615ed.f10002.mp4?dis_k=23639703f249e3c59cf674369cfcac86&dis_t=1562297977', }, { video_id: 'mpVideo3', url: 'http://mpvideo.qpic.cn/tjg_2394158861_50000_01730a9db3924ffa98201662d51615ed.f10002.mp4?dis_k=23639703f249e3c59cf674369cfcac86&dis_t=1562297977', }, { video_id: 'mpVideo4', url: 'http://mpvideo.qpic.cn/tjg_2394158861_50000_01730a9db3924ffa98201662d51615ed.f10002.mp4?dis_k=23639703f249e3c59cf674369cfcac86&dis_t=1562297977', }, { video_id: 'mpVideo5', url: 'http://mpvideo.qpic.cn/tjg_2394158861_50000_01730a9db3924ffa98201662d51615ed.f10002.mp4?dis_k=23639703f249e3c59cf674369cfcac86&dis_t=1562297977', }, { video_id: 'mpVideo6', url: 'http://mpvideo.qpic.cn/tjg_2394158861_50000_01730a9db3924ffa98201662d51615ed.f10002.mp4?dis_k=23639703f249e3c59cf674369cfcac86&dis_t=1562297977', }, { video_id: 'mpVideo7', url: 'http://mpvideo.qpic.cn/tjg_2394158861_50000_01730a9db3924ffa98201662d51615ed.f10002.mp4?dis_k=23639703f249e3c59cf674369cfcac86&dis_t=1562297977', }], currentPlayVideoIndex: 0, isActive: true }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { // onLoad 的时候立刻调用handleVideoScroll // 对视频进行监听 this.handleVideoScroll(); // cgi请求,用于获取videos 的数据,由于是demo演示,我直接写死了videos... }, /** * 页面上拉触底事件的处理函数 */ onReachBottom: function () { }, controlVideos: function (res) { console.log('调用controvideos', res); }, handleVideoScroll: function () { const currentId = this.data.videos[this.data.currentPlayVideoIndex].video_id; // 关键代码 // relativeToViewport 这里指定对比的就是viewport,viewport的意思就是document中的可视区域 this.observerObj = wx.createIntersectionObserver().relativeToViewport(); console.log('listen ' + currentId); // 监听目标视频跟viewport相交区域的变化 this.observerObj.observe(`#${currentId}`, this.controlVideos); } }) [代码] copy上面的代码进入小程序,你就会看到这样一个界面。 [图片] 其中外面的一圈表示的是viewPort,里面一层就是我们现在正在监听的视频,我用右上角的粉色字体来标记了,它的回调函数是controlVideos。当目标视频进入或者退出viewport的时候,controlVideos就会执行。 onLoad的时候执行了handleVideoScroll,这时候开始对目标视频进行监听,此时目标元素在viewport内,所以会调用controVideos,打印出相关信息。 [图片] 其他的字段先不说,其中的intersectionRatio表示了他们相交的比例。其中1表示完全在viewport内,0表示不在viewport内。 如果我持续去滚动第一个视频,直到它看不到了,就会看到控制台打印出 [图片] 这时候的intersectionRatio = 0,代表已经不在viewport内了,所以我们就可以将currentPlayIndex 切换到下一个了。 切换代码如下: [代码] controlVideos: function (res) { const { currentPlayVideoIndex } = this.data; console.log('当前currentIndex', currentPlayVideoIndex) const currentId = this.data.videos[currentPlayVideoIndex].video_id; if (res && res.intersectionRatio > 0) { // 视频在可视区域内,播放视频 wx.createVideoContext(currentId).play(); console.log("play" + currentPlayVideoIndex) } else { // 需要切换视频的时候,将当前视频暂停播放 // 并且通过handleVideoScroll 来播放下一个视频 wx.createVideoContext(currentId).pause(); // 切换到下一个视频 this.setData({ 'currentPlayVideoIndex': currentPlayVideoIndex + 1 }, () => { // 注意切换完成之后,还需要在调用handleVideoScroll 来对下一个视频进行绑定 this.handleVideoScroll(); }); } }, [代码] 到这一步,应该就可以看到这样的向下切换的效果了。 [图片] 但是,我们现在只是做下向下滚动的切换。那么向上的呢?要做向上滑动的切换,首先要知道视频是在向下还是向上滑动。这里有个字段可以帮助我们识别:boundingClientRect 。 它表示的是目标元素相对与viewport的节点信息。当视频向上滚动的时候,它距离viewport的top值为负,向下滚动的时候,为正值。 [图片] 有了这个字段,我们就可以通过判断向上还是向下的滚动,来切换视频了。 [代码] controlVideos: function (res) { const { currentPlayVideoIndex } = this.data; console.log('当前currentIndex', currentPlayVideoIndex) const currentId = this.data.videos[currentPlayVideoIndex].video_id; if (res && res.intersectionRatio > 0) { // 视频在可视区域内,播放视频 wx.createVideoContext(currentId).play(); console.log("play" + currentPlayVideoIndex) } else { // 需要切换视频的时候,将当前视频暂停播放,并且通过handleVideoScroll 来播放下一个视频 wx.createVideoContext(currentId).pause(); // 当top < 0的时候,说明是在向上滑动,这时候currentPlayVideoIndex 需要加1 if (res.boundingClientRect.top < 0) { if (currentPlayVideoIndex < this.data.videos.length - 1) { this.setData({ 'currentPlayVideoIndex': currentPlayVideoIndex + 1 }, () => { // 同时解绑第一个视频,保证同一个时间只监听一个视频 this.observerObj.disconnect(); this.handleVideoScroll(); }); } } else { // 当top > 0的时候,说明是在向下滑动,这时候currentPlayVideoIndex 需要减1 if (currentPlayVideoIndex - 1 < 0) { return; } this.setData({ 'currentPlayVideoIndex': this.data.currentPlayVideoIndex - 1 }, () => { this.observerObj.disconnect(); this.handleVideoScroll(); }) } }, [代码] 但是我们的产品在体验的过程中,会提出并不是完全看不见了才去切换,可能想要还剩个150px就切换了,所以我这里要对viewport调整一下 [代码] this.observerObj = wx.createIntersectionObserver().relativeToViewport({ top: -300, bottom: -300 }); [代码] 完成之后,你就可以缓缓的滑动你的视频,实现视频切换的效果了。可以看到当视频差不多被遮住不到一半,就开始切换了。 [图片] 总结 整个过程其实就是好好利用了IntersectionObserver这个API而已。当然现在只是一个非常简单的实现,性能问题,以及快读滑动的情况都无法应对,我们下一篇在接着~。
2019-08-15 - 如何监听小程序中的手势事件(缩放、双击、长按、滑动、拖拽)
mina-touch [图片] [代码]mina-touch[代码],一个方便、轻量的 小程序 手势事件监听库 事件库部分逻辑参考[代码]alloyFinger[代码],在此做出声明和感谢 change log: 2019.03.10 优化监听和绘制逻辑,动画不卡顿 2019.03.12 修复第二次之后缩放闪烁的 bug,pinch 添加 singleZoom 参数 2020.12.13 更名 mina-touch 2020.12.27 上传 npm 库;优化使用方式;优化 README 支持的事件 支持 pinch 缩放 支持 rotate 旋转 支持 pressMove 拖拽 支持 doubleTap 双击 支持 swipe 滑动 支持 longTap 长按 支持 tap 按 支持 singleTap 单击 扫码体验 [图片] demo 展示 demo1:监听 pressMove 拖拽 手势 查看 demo 代码 [图片] [图片] demo2: 监听 pinch 缩放 和 rotate 旋转 手势 (已优化动画卡顿 bug) 查看 demo 代码 [图片] [图片] demo3: 测试监听双击事件 查看 demo 代码 [图片] [图片] demo4: 测试监听长按事件 查看 demo 代码 [图片] [图片] demo 代码 demo 代码地址 mina-tools-client/mina-touch 使用方法 大致可以分为 4 步: npm 安装 mina-touch,开发工具构建 npm 引入 mina-touch onload 实例化 mina-touch wxml 绑定实例 命令行 [代码]npm install mina-touch[代码] 安装完成后,开发工具构建 npm *.js [代码]import MinaTouch from 'mina-touch'; // 1. 引入mina-touch Page({ onLoad: function (options) { // 2. onload实例化mina-touch //会创建this.touch1指向实例对象 new MinaTouch(this, 'touch1', { // 监听事件的回调:multipointStart,doubleTap,longTap,pinch,pressMove,swipe等等 // 具体使用和参数请查看github-README(底部有github地址 }); }, }); [代码] NOTE: 多类型事件监听触发 setData 时,建议把数据合并,在 touchMove 中一起进行 setData ,以减少短时内多次 setData 引起的动画延迟和卡顿(参考 demo2) *.wxml 在 view 上绑定事件并对应: [代码]<view catchtouchstart="touch1.start" catchtouchmove="touch1.move" catchtouchend="touch1.end" catchtouchcancel="touch1.cancel" > </view> <!-- touchstart -> 实例对象名.start touchmove -> 实例对象名.move touchend -> 实例对象名.end touchcancel -> 实例对象名.cancel --> [代码] NOTE: 如果不影响业务,建议使用 catch 捕获事件,否则易造成监听动画卡顿(参考 demo2) 以上简单几步即可使用 mina-touch 手势库 😊😊😊 具体使用和参数请查看Github https://github.com/Yrobot/mina-touch 如果喜欢mina-touch的话,记得在github点个start哦!🌟🌟🌟
2021-06-24 - 请问大家border-radius内圆角怎么弄?凹凸角?
[图片]就像这种角怎么弄出来?有大神指点吗?
2020-07-22 - border-radius在ios失效问题?
[图片] 描述:battery-copy类设置border-radius,在开发工具中查看正常显示,但是在ios预览和体验版显示却不生效,下图是ios显示的效果。将橙色块高度调至0时,底部的圆角也变成直角了。安卓未测试。 尝试:在battery-copy类中添加过以下代码,不生效。 backface-visibility: hidden; transform: translate3d(0, 0, 0); [图片] 在此请求大佬们帮忙看一下,代码片段:https://developers.weixin.qq.com/s/lgvHxDmn7Xlq
2020-11-06 - css 毛玻璃效果
[图片] css width: 100rpx; height: 130rpx; background: linear-gradient(to right bottom, rgba(44,44,69,0.6), rgba(44,44,69,0.3), rgba(44,44,69,0.2)); backdrop-filter: blur(9px); border-top: 2rpx solid rgba(44,44,69,0.8); border-left: 2rpx solid rgba(44,44,69,0.8); border-radius: 30rpx; padding: 20rpx; box-shadow: 0 9px 15px 0 rgba(25,21,40,0.39),0 -9px 15px 0 rgba(25,21,40,0.39);
2021-04-26 - 微信小程序小结 (自定义tabBar,换肤等)
第一次发帖 不知道写些啥 就说说我和我的微信小程序吧 之前一直对微信小程序有兴趣 空余时间做了模仿 永辉生活做了个便利店 然后悲催的发现个体号不能用支付接口什么的 一下索然无味 然后 删了~ 对!删了 然后 过了好久 又想玩玩 然后 又做了个记账的小程序 因为文档太枯燥 就 边做边查 想到点什么 就加上去 现在基本成型,我就说说 我在这过程中遇到的一些坑死我这个小白的问题吧 要是有大佬们有什么骚操作和 建议 务必留个言 让我吸收一下子哈 一.tabBar1.自定义导航栏首先小程序底部导航栏; 官方的是在app.json里面统一配置和管理的;但是它 并不支持自定义点击事件 解决方法: 先把app.json 中tabBar的配置项 => "custom": true, 这样就使原生的导航栏失效 默认读取custom-tab-bar/index 这个就需要自己去创建写啦具体看这个https://developers.weixin.qq.com/community/develop/article/doc/0004ae44048af028877ac58ce51413 然后这个方式需要1先做个自定义的tabBar组件;2.每个需要的导航页引入该组件 3.每个导航页的onshow 执行跳转函数。至于那个自定义点击事件就 在tabBar里面实现。 2.导航页边组件。 有一说一哈 我觉得自定义导航栏好麻烦 然后我最近又把他改掉了 先新建个新的页面四件套 wxml里面实现tabBar的展示 之上则是根据条件加载不同的组件 ,这些组件就是原先的导航页 改的(这个改的很省事) 是这样的 <!--index.wxml--> <home id="component" wx:if="{{PageCur=='home'}}"></home> <canvasInfo id="component" wx:if="{{PageCur=='canvasInfo'}}"></canvasInfo> <new id="component" wx:if="{{PageCur=='new'}}"></new> <count id="component" wx:if="{{PageCur=='count'}}"></count> <wd id="component" wx:if="{{PageCur=='wd'}}"></wd> <view class="cu-bar tabbar {{skin}} shadow foot"> <view class="action" bindtap="NavChange" data-cur="home"> <view class='cuIcon-cu-image'> <image src="/images/明细{{PageCur=='home'?'':'1'}}.png"></image> </view> <view class="{{PageCur=='home'?'text-green':'text-gray'}}">明细</view> </view> </view> <!--index.json--> { "usingComponents": { "home": "/pages/home/index", "canvasInfo": "/pages/canvasInfo/canvasInfo", "new": "/pages/new/new", "count": "/pages/count/count", "wd": "/pages/wd/wd" } } <!--index.js--> data: { PageCur: 'home', skin: app.globalData.skin, }, NavChange(e) { var a = e.currentTarget.dataset.cur if (a == "new" ) { if (!getApp().globalData.isAuthorize) { console.log("用户没有授权"); wx.navigateTo({ url: '/pages/getWxUserInfo/getWxUserInfo', }) return } wx.navigateTo({ url: "/pages/" + a + "/" + a }) return; } this.NavChange1(a) }, NavChange1(e) { this.setData({ PageCur: e }) let myComponent = this.selectComponent('#component'); // 页面获取自定义组件实例 console.log(myComponent) myComponent.onLoad(); // 通过实例调用组件事件 myComponent.onShow(); }, 二.皮肤嫌着蛋疼 没想出新功能的时候就想到些花里胡哨的玩意儿,换肤功能 刚刚开始都想哭了因为最早页面样式我想写的到处都是 整理死我了 大体这样: 1.将全部需要受到换肤影响的元素都找出来 2.在每个页面js的data中增加一个skin(名字你随意)作为 1中的className (一个皮肤不同位置有不同的的样式 那就多配几个) 3增加一个wxss文件 里面写好所有的皮肤样式 样式为类选择器 类名用用于skin赋值 4.app.js中globalData也新增skin(用于每个页面skin默认值) ,并新增一个设置相应界面skin值的函数setSkin(that){} 5.提供一个选择皮肤的功能,选择对应的皮肤后 将所选的skinName 存入缓存(为了持续生效) 后调用app.js的setSkin函数 setSkin函数通过获取缓存中的数据 为相应页面skin赋值 5.在每个页面的onshow中 调用app.js的setSkin(that){}函数为skin赋值 globalData: { skin: "normal-skin", tab1:'normal-skin-tab1', tab2:'normal-skin-tab2', }, setSkin:function(that){ wx.getStorage({ key: 'skin', success: function(res) { if(res){ console.log(res) that.setData({ skin: res.data, tab1:res.data+'-tab1', tab2:res.data+'-tab2', }) } } }) } data: { CustomBar: app.globalData.CustomBar, StatusBar: app.globalData.StatusBar, skin: app.globalData.skin, skinList: [ { 'name': '默认', 'val': 'normal-skin' }, { 'name': '黑色', 'val': 'dark-skin' }, { 'name': '粉色', 'val': 'red-skin' }, { 'name': '黄色', 'val': 'yellow-skin' }, { 'name': '绿色', 'val': 'green-skin' }, { 'name': '灰色', 'val': 'cyan-skin' }, { 'name': '蓝色', 'val': 'blue-skin' }, ]} , //换肤 setSkin: function (e) { console.log('=================setSkin=======================') var skin = e.target.dataset.flag; console.log(skin) app.globalData.skin = skin this.setData({ skin: skin, openSet: false }) wx.setStorage({ key: "skin", data: skin }) app.setSkin(this); this.hideModal() app.setSkin(getCurrentPages()[0]) }, [图片] 三.一些小通用小函数function formatTime(date) { var year = date.getFullYear() var month = date.getMonth() + 1 var day = date.getDate() var hour = date.getHours() var minute = date.getMinutes() var second = date.getSeconds() return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':') } function formatNumber(n) { n = n.toString() return n[1] ? n : '0' + n } //2020-11-20 转为Unix function dateStrToUnix(date) { date = date.substring(0, 19); date = date.replace(/-/g, '/'); //必须把日期'-'转为'/' return new Date(date).getTime(); } function unixToDateStr(timestamp) { var d = new Date(timestamp); //根据时间戳生成的时间对象 return (d.getFullYear()) + "/" + (d.getMonth() + 1) + "/" + (d.getDate()) + " " } function weekAndday(dates) { let date = new Date(dates); return weekAnddayByDate(date); } function weekAnddayByDate(date) { let dateObj = {}; let show_day = ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"]; date.setDate(date.getDate()); let day = date.getDate(); let yearDate = date.getFullYear(); let month = ((date.getMonth() + 1) < 10 ? (date.getMonth() + 1) : date.getMonth() + 1); let dayFormate = (date.getDate() < 10 ? + date.getDate() : date.getDate()); dateObj.time = yearDate + "-" + month + "-" + dayFormate; dateObj.week = show_day[date.getDay()]; dateObj.day = day; dateObj.year = yearDate; dateObj.month = month; return dateObj; } // 除法函数 function accDiv(arg1, arg2) { var t1 = 0, t2 = 0, r1, r2; try { t1 = arg1.toString().split(".")[1].length } catch (e) { } try { t2 = arg2.toString().split(".")[1].length } catch (e) { } r1 = Number(arg1.toString().replace(".", "")); r2 = Number(arg2.toString().replace(".", "")); return (r1 / r2) * Math.pow(10, t2 - t1); } // 乘法函数 function accMul(arg1, arg2) { var m = 0, s1 = arg1.toString(), s2 = arg2.toString(); try { m += s1.split(".")[1].length } catch (e) { } try { m += s2.split(".")[1].length } catch (e) { } return Number(s1.replace(".", "")) * Number(s2.replace(".", "")) / Math.pow(10, m) } //减法函数 function accSub(arg1, arg2) { var r1, r2, m, n; try { r1 = arg1.toString().split(".")[1].length } catch (e) { r1 = 0 } try { r2 = arg2.toString().split(".")[1].length } catch (e) { r2 = 0 } m = Math.pow(10, Math.max(r1, r2)); //last modify by deeka //动态控制精度长度 n = (r1 >= r2) ? r1 : r2; return ((arg1 * m - arg2 * m) / m).toFixed(n); } /*用来得到精确的加法结果 *说明:javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显。这个函数返回较为精确的加法结果。 *调用:accAdd(arg1,arg2) *返回值:arg1加上arg2的精确结果 */ function accAdd(arg1, arg2) { var r1, r2, m; try { r1 = arg1.toString().split(".")[1].length } catch (e) { r1 = 0 } try { r2 = arg2.toString().split(".")[1].length } catch (e) { r2 = 0 } m = Math.pow(10, Math.max(r1, r2)) return (arg1 * m + arg2 * m) / m } //给Number类型增加一个add方法,调用起来更加方便。 Number.prototype.add = function (arg) { return accAdd(arg, this); } //计算今天是今年第几周 function getWeek(y, m, d) { let day1 = new Date(y, parseInt(m) - 1, d); let day2 = new Date(y, 0, 1); let day = Math.round((day1.valueOf() - day2.valueOf()) / 86400000); return Math.ceil((day + ((day2.getDay() + 1) - 1)) / 7) // console.log(y+"+"+m+"+"+d) // var arr=whichWeek(y); // console.log(arr) // for(var i=0;i<arr.length;i++){ // if(arr[i].month==m&&arr[i].date<=d&&((arr[i].last.month==m&&arr[i].last.date>=d)||(arr[i].last.month>m))){ // return i+1 // } // } } //时间戳转年月日 参数是秒的时间戳 函数返回一个对象 对象里有年 月 日 function yearDay(long) { var time = new Date(long * 1000) var year = time.getFullYear(); var month = (time.getMonth() + 1) < 10 ? '0' + (time.getMonth() + 1) : (time.getMonth() + 1); var date = time.getDate() < 10 ? '0' + time.getDate() : time.getDate(); var yearday = { year, month, date } return yearday } //计算一年中的每一周都是从几号到几号 //第一周为1月1日到 本年的 第一个周日 //第二周为 本年的 第一个周一 往后推到周日 //以此类推 再往后推52周。。。 //如果最后一周在12月31日之前,则本年有垮了54周,反之53周 //12月31 日不论是周几,都算为本周的最后一天 //参数年份 ,函数返回一个数组,数组里的对象包含 这一周的开始日期和结束日期 function whichWeek(year) { var d = new Date(year, 0, 1); while (d.getDay() != 1) { d.setDate(d.getDate() + 1); } let arr = [] let longnum = d.setDate(d.getDate()) if (longnum > +new Date(year, 0, 1)) { let obj = yearDay(+new Date(year, 0, 1) / 1000) obj.last = yearDay(longnum / 1000 - 86400) arr.push(obj) } let oneitem = yearDay(longnum / 1000) oneitem.last = yearDay(longnum / 1000 + 86400 * 6) arr.push(oneitem) var lastStr for (var i = 0; i < 51; i++) { let long = d.setDate(d.getDate() + 7) let obj = yearDay(long / 1000) obj.last = yearDay(long / 1000 + 86400 * 6) lastStr = long + 86400000 * 6 arr.push(obj) } if (lastStr < +new Date(year + 1, 0, 1)) { let obj = yearDay(lastStr / 1000 + 86400) obj.last = yearDay(+new Date(year + 1, 0, 1) / 1000 - 86400) arr.push(obj) } else { arr[arr.length - 1].last = yearDay(+new Date(year + 1, 0, 1) / 1000 - 86400) } return arr } // 是否是数组 function isArray(obj){ return (typeof obj=='object')&&obj.constructor==Array; } const getDaysInMonth = (year, month) => { let date = new Date(year, month, 1); return new Date(date.getTime() - 864e5).getDate(); } // 俩个时间间隔 function subTime(startTime,endTime) {// 时间new Date('2018-1-1') console.log(endTime - startTime); // 毫秒数 console.log(Math.floor((endTime - startTime) / 1000)); // 秒数 console.log(Math.floor((endTime - startTime) / 1000 / 60)); // 分钟 console.log(Math.floor((endTime - startTime) / 1000 / 60 / 60)); // 小时 console.log(Math.floor((endTime - startTime) / 1000 / 60 / 60 / 24)); // 天数 } // 是否同一天 function isSameDay(startTime, endTime) { // debugger const startTimeMs = new Date(startTime).setHours(0,0,0,0); const endTimeMs = new Date(endTime).setHours(0,0,0,0); return startTimeMs === endTimeMs ? true : false } // 获取元素在数组中位置 module.exports = { formatTime: formatTime, dateStrToUnix: dateStrToUnix, unixToDateStr: unixToDateStr, weekAndday: weekAndday, accSub: accSub, accAdd: accAdd, accDiv: accDiv, accMul: accMul, getWeek: getWeek, weekAnddayByDate: weekAnddayByDate, whichWeek: whichWeek, getDaysInMonth: getDaysInMonth, isArray:isArray, isSameDay:isSameDay } 四.新版本发布 自动更新 小程序发布之后没做处理的话 得需要用户冷启动才可以获取新的版本 所以需要我们加个检测机制我是 直接加在了app.js的 onshow页面调用的时候检测是否有新版本有的话弹窗让用户更新重启 onShow:function(options){ console.log('app.js==================onShow') //处理小程序更新 this.autoUpdate(); wx.onMemoryWarning(function () { console.log('onMemoryWarningReceive') console.log('内存炸了') }) // wx.BaaS.reportTemplateMsgAnalytics(options) }, /*** * 小程序更新机制处理 */ autoUpdate(){ let that = this //获取小程序更新机制兼容 if(wx.canIUse('getUpdateManager')){ //获取全局唯一的版本更新管理器,用于管理小程序更新 let updateManager = wx.getUpdateManager() //1.检查小程序是否有新版本发布 updateManager.onCheckForUpdate(function(res){ //请求完新版本信息的回调 if(res.hasUpdate){ //2.小程序有新版本,则静默下载新版本,做好更新准备 updateManager.onUpdateReady(function(){ wx.showModal({ title: '更新提示', content: '新版本已经准备好,是否重启应用?', success: function(res) { if(res.confirm){ //3.新的版本已经下载好,调用 applyUpdate 应用新版本并重启 updateManager.applyUpdate() }else if(res.cancel){ //如果需要强制更新,则给出二次弹窗;如果不需要,则这里的代码都可以删除 wx.showModal({ title: '温馨提示~', content: '本次版本更新涉及到新的功能添加,旧版本无法正常访问哦', success: function(resc) { that.autoUpdate() return //第二次提示后,强制更新 if(resc.confirm){ //新的版本已经下载好,调用 applyUpdate 应用新版本并重启 updateManager.applyUpdate() }else if(resc.cancek){ //重新回到版本更新提示 that.autoUpdate() } } }) } } }) }) updateManager.onUpdateFailed(function(){ //新的版本下载失败 wx.showModal({ title: '已经有新版本了哟~', content: '新版本已经上线啦~,请您删除当前小程序,重新搜索打开哟~', }) }) } }) }else{ //如果希望用户在最新版本的客户端上体验您的小程序,可以这样提示 wx.showModal({ title: '提示', content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试', }) } }, 五.问题求助 ok现在我有个坑一直没解决想要看看哪个大佬搞得来 我的小程序中引入echarts 用来做图表 我只是用初始化 和setOption()的函数 日常使用正常 但是用小程序开发工具的Audits测评 说是存在定时器未跟随页面回收 都是echarts.js里面的 我实在搞不懂这东西 看看哪位大佬帮我解决下 [图片] 六.分享几个echarts的坑 1 echarts的图表在开发工具的 真机调试中会报错 这是工具的问题 2.最近发现echarts 的图表 绘制在canvas 2d里面的话 工具上正常 当手机访问的时候 会直接导致微信闪退(目前不晓得咋搞 坐等大佬解决) 解决方法 改用 canvas 好啦,还有些东西我一时也想不起来先这些啦,下面是我的小程序二维码 感兴趣可以看看 (处女作=>基操勿喷哈)然后希望大家有什么 好的建议和骚操作可以共享下哟 拜拜~ [图片]
2021-01-06 - 持续更新:收藏整理官方隐藏的小程序功能/参数/方法/API
简介 一门热门的编程语言通常会留下一些带惊喜的「彩蛋」让你去自己深挖,小程序也是如此。此文是收集与整理官方未公布的一些彩蛋。希望大家多多提供线索。本文仅整理官方未公布的小程序相关包括但不限于:组件属性、功能、方法、API。 组件类 scroll-view里面的隐藏属性 throttle="{{false}}" 功能:关闭函数节流功能,可以更精准的bindScroll。但也更耗手机性能 [代码]app.json里面配置关闭同层渲染功能: "window": { "renderingMode": "seperated" } [代码] 收集中… 功能类 wx:for支持Object列表渲染 [代码]{ '2018-1-9':{ address: '....', name: '....' }, '2018-1-10':{ address: '....', name: '....' }, '2018-1-11':{ address: '....', name: '....' } } //wxml <view wx:for="{{obj}}" wx:for-index="key" wx:for-item="value"> {{key}} : {{value.address}} </view> [代码] 2.wx.chooseContact 作用:从手机通讯录中选择联系人并获取相关信息(iPhone下有兼容问题) [代码]参考代码: wx.chooseContact({ success:res=>{ that.customer.name = res.displayName; that.customer.contract = res.phoneNumber; }, complete: function(res) { } }); [代码] 收集中… 开发者工具类 完美支持各种插件安装,详见: 动手打造更强更好用的微信开发者工具-编辑器扩展篇 | 微信开放社区 https://developers.weixin.qq.com/community/develop/article/doc/000a8816e18748dd52f96f8975b413 历史版本的隐藏下载链接 升级开发者工具到最新,种种原因需要恢复旧版,旧版安装包找不到应急处理方法 | 微信开放社区 https://developers.weixin.qq.com/community/develop/article/doc/0008c4833f4450f8e32ab0dbc51013 官方社区类 有几个隐藏的官方的美女甩锅采用非官方账号在社区带节奏。具体我就不点名了。🐶🐶🐶🐶 收集中… 其他未公布的隐藏功能: 给此文点赞后的码农写代码永无BUG ,CP设计的产品人见人爱,BOSS每年收入翻番! ↓↓↓↓
2020-06-26 - 微信、支付宝小程序CI尝鲜
背景微信上传代码流程 PR -> 审核通过 -> 切到master分支 -> git pull -> 切到ide -> 点击上传代码 -> 填入备注和版本号 CI PR -> 审核通过 -> CI上传 利用CI后除了一开始的人工提PR和审核后,不需要进行任何操作,并且会给出开始上传和上传完成的通知,解决了之前上传代码流程较长的人工链路,释放自己,不用一直做无意义且无趣的事。 执行过程的效果图如下: [图片] 上传成功后 我们需要去小程序后台提交审核信息。 [图片] 钉钉自定义机器人文档:钉钉webHooks,码云webHook对钉钉的支持,钉钉自定义机器人SDK 需要设置群机器人群设置 -> 智能群助手 -> 添加机器人 -> 自定义机器人 -> 勾选安全设置加签使用access_token和secret完成自定义机器人初始化[图片] 码云WebHooks文档:码云webHooks,码云webHook推送数据格式说明 在URL处填入刚刚的接口,比如https://xxx.com/upload勾选Pull Request,然后点击更新即可,这里需要注意他会发送好几次,比如创建PR发送一次,审核通过发送一次,合并发送一次,所以在接口里需要判断一下只有合并完成的时候才执行后续逻辑(state === 'merged'[图片] 微信小程序准备工作微信官方CI API 密钥:微信公众号 -> 开发 -> 开发设置,下载代码上传密钥,然后在调用CI时设置好privateKeyPath白名单配置:微信公众号 -> 开发 -> 开发设置,配置白名单(必须IP)开发先来个流程图,让大家了解一下接口的逻辑 [图片]判断是否是合并到master(十一后可能要改为main了)值得一提的是目前码云的webHook的推送数据格式是Request Payload,所以我们需要利用koa2-formidable插件对数据进行处理后就能直接使用ctx.request.body获取数据了 router.post('/api/upload', async ctx => { const body = ctx.request.body // 合并状态并且合到master才会自动上传到后台 if (body.state === 'merged' && body.target_branch === 'master') { ... } } 钉钉初始化const Robot = require("dingtalk-robot-sdk") const robot = new Robot({ accessToken: config.robotAccessToken, secret: config.robotSecret }); const text = new Robot.Text('开始上传') robot.send(text) 是否存在项目const xcxPath = path.join(process.cwd(), config[type].publicProject) // 判断是否存在xcx项目文件夹,没有就去clone,有就pull最新代码 if (fs.existsSync(xcxPath)) { child_process.execSync('git pull', { cwd: xcxPath }) } else { child_process.execSync(`git clone ${config[type].git}`, { cwd: path.join(process.cwd(), config.publicRoot) }) } 项目初始化和上传// 创建项目对象 const project = new ci.Project({ appid: config.wx.appid, type: 'miniProgram', projectPath: publicProject, privateKeyPath: path.join(process.cwd(), config.publicRoot, '../wx.key'), // wx.key为小程序appSecret ignores: ['node_modules/**/*'], }) const year = new Date().getFullYear() - 2000 let month = new Date().getMonth() + 1 const day = new Date().getDate() // 根据年月日当版本号 const version = '2.5.' + year + (month < 10 ? '0' + month : month) + (day < 10 ? '0' + day : day); // 上传 const previewResult = await ci.upload({ project, version, desc: body.title, setting: { es6: true, minify: true, autoPrefixWXSS: true, minifyWXML: true, minifyWXSS: true, minifyJS: true } }) 钉钉通知完成const markdown = new Robot.Markdown() .setTitle('上传完成!!!!') .add(`### [上传完成](https://mp.weixin.qq.com)\n`) .add(`1. version:${version}`) .add(`2. size:${fullSize}`) .add(`3. ${JSON.stringify(previewResult)}`) robot.send(markdown) 支付宝小程序准备工作文档:支付宝官方CLI文档,支付宝官方SDK文档 开发其余逻辑均与上面的微信小程序开发一致,只有项目初始化和上传是不一致的,所以这里我们只说一下项目初始化和上传 项目初始化使用alipaydev key create -w 生成的私钥和工具id完成项目初始化 // 初始化项目 alipaydev.setConfig({ toolId: config.ali.toolId, privateKey: config.ali.privateKey, }) 上传version不传的话默认线上包版本自增0.0.1,所以我们不需要传version const uploadResult = await alipaydev.miniUpload({ appId: config.ali.appid, clientType: 'alipay', project: publicProject, experience: true // 设置体验版(不是主账户,所以其实目前没用) })
2020-09-29 - 前端开发,必备的学习网站!
入门阶段 MDN 地址:https://developer.mozilla.org/zh-CN/docs/Web [图片] w3school 地址:https://www.w3school.com.cn/index.html [图片] JavaScript教程 地址:https://www.liaoxuefeng.com/wiki/1022910821149312 [图片] 进阶阶段 ES6 入门教程 地址:https://es6.ruanyifeng.com/ [图片] Cnode 地址:https://cnodejs.org/ [图片] 通用 GitHub 找轮子,开源项目有它就够了 地址:https://github.com/ [图片] Stack Overflow 提问题有它就够了 地址:https://stackoverflow.com/ [图片]
2020-08-19 - 推荐一个自定义导航栏开源库
前言大家都知道官方提供的小程序导航栏相对有限,那么我们如何应对产品大大无限的需求呢? 那么肯定就需要自定义导航栏,而今天我要给大家推荐一款很棒的自定义导航栏开源项目。 少啰嗦,看效果。⬇️ 看效果[图片] [图片][图片] [图片] [图片] [图片] [图片] 看属性[图片] 如何使用?配置 app.json 中的 navigationStyle 和 usingComponents { "window": { "backgroundTextStyle": "light", "navigationBarBackgroundColor": "#fff", "navigationBarTitleText": "自定义导航栏", "navigationBarTextStyle": "black", "navigationStyle": "custom" }, "usingComponents": { "navBar": "/components/navBar/navBar" }, "sitemapLocation": "sitemap.json" } 页面代码: 上地址地址:https://github.com/lingxiaoyi/navigation-bar
2020-08-21 - 【集合】花了 3 个月,写了 40 篇小程序文章
前言 花了3个月,一共输出 40 篇文章,这也算是一个阶段性的总结。在此做个文章分类集合,希望对大家有所帮助。 小程序前端 《专治按钮效果不明显(扩散动画效果)》 《小程序开发必备,这 5 款超实用开源插件!》 《仿抽奖助手奖品详情页面向上翻页效果》 《推荐 5 款高仿知名应用的开源项目!》 《生成海报很复杂?有它轻松搞定!》 《推荐一个自定义导航栏开源库》 《前端开发,必备的学习网站!》 《情侣券-领取动画分析》 《通过玩游戏来学习CSS》 《CSS不规范导致的布局显示问题》 《微信小程序如何引入npm包?》 《情侣券-选中卡片翻转动画》 《CSS:实现卡片洗牌效果》 《情侣券 v2.0 使用的 4 款开源组件》 小程序云开发 《使用聚合函数实现打卡排行榜》 《使用云开发做内容安全检查》 《云开发-实现分页功能》 《云开发-实现维护用户表》 《云开发-实现模糊搜索》 《云开发实战:实现订阅消息推送》 《如何优雅的调用云函数?》 《云开发实战-如何维护用户表?(优化版)》 《推荐 10 款使用云开发的开源项目》 《云开发:CloudBase CMS 实战使用指南》 小程序产品 《如何利用小程序提高10倍活动效果?》 《实战:让数据说话之自定义埋点分析》 《#小程序云开发挑战赛#-情侣券》 《小程序运营必备的 3 款官方小程序》 《小程序云开发挑战赛:情侣券 v1.1 版本迭代》 《云开发挑战赛复赛:情侣券介绍PPT》 《参加#小程序云开发挑战赛#复赛收获》 《云开发挑战赛决赛:情侣券介绍PPT》 通用知识 《如何重构?》 《如何高效学习?》 《如何看懂时序图?》 《为什么优秀的程序员都写博客?》 《我从 Android 转到 微信小程序 的思考》 最后 后续计划会写更多云开发相关的文章以及小程序基础系列学习文章。
2020-11-24 - 小程序图片裁剪插件 image-cropper
之前的插件类目没有了导致搜不到了,重新发个文章。 image-cropper 一款高性能的小程序图片裁剪插件,支持旋转。 [图片] 优势 [代码]1.功能强大。[代码] [代码]2.性能超高超流畅,大图毫无卡顿感。[代码] [代码]3.组件化,使用简单。[代码] [代码]4.点击中间窗口实时查看裁剪结果。[代码] ㅤ 初始准备 1.json文件中添加image-cropper [代码] "usingComponents": { "image-cropper": "../image-cropper/image-cropper" }, "navigationBarTitleText": "裁剪图片", "disableScroll": true [代码] 2.wxml文件 [代码]<image-cropper id="image-cropper" limit_move="{{true}}" disable_rotate="{{true}}" width="{{width}}" height="{{height}}" imgSrc="{{src}}" bindload="cropperload" bindimageload="loadimage" bindtapcut="clickcut"></image-cropper> [代码] 3.简单示例 [代码] Page({ data: { src:'', width:250,//宽度 height: 250,//高度 }, onLoad: function (options) { //获取到image-cropper实例 this.cropper = this.selectComponent("#image-cropper"); //开始裁剪 this.setData({ src:"https://raw.githubusercontent.com/1977474741/image-cropper/dev/image/code.jpg", }); wx.showLoading({ title: '加载中' }) }, cropperload(e){ console.log("cropper初始化完成"); }, loadimage(e){ console.log("图片加载完成",e.detail); wx.hideLoading(); //重置图片角度、缩放、位置 this.cropper.imgReset(); }, clickcut(e) { console.log(e.detail); //点击裁剪框阅览图片 wx.previewImage({ current: e.detail.url, // 当前显示图片的http链接 urls: [e.detail.url] // 需要预览的图片http链接列表 }) }, }) [代码] 参数说明 属性 类型 缺省值 取值 描述 必填 imgSrc String 无 无限制 图片地址(如果是网络图片需配置安全域名) 否 disable_rotate Boolean false true/false 禁止用户旋转(为false时建议同时设置limit_move为false) 否 limit_move Boolean false true/false 限制图片移动范围(裁剪框始终在图片内)(为true时建议同时设置disable_rotate为true) 否 width Number 200 超过屏幕宽度自动转为屏幕宽度 裁剪框宽度 否 height Number 200 超过屏幕高度自动转为屏幕高度 裁剪框高度 否 max_width Number 300 裁剪框最大宽度 裁剪框最大宽度 否 max_height Number 300 裁剪框最大高度 裁剪框最大高度 否 min_width Number 100 裁剪框最小宽度 裁剪框最小宽度 否 min_height Number 100 裁剪框最小高度 裁剪框最小高度 否 disable_width Boolean false true/false 锁定裁剪框宽度 否 disable_height Boolean false true/false 锁定裁剪框高度 否 disable_ratio Boolean false true/false 锁定裁剪框比例 否 export_scale Number 3 无限制 输出图片的比例(相对于裁剪框尺寸) 否 quality Number 1 0-1 生成的图片质量 否 cut_top Number 居中 始终在屏幕内 裁剪框上边距 否 cut_left Number 居中 始终在屏幕内 裁剪框左边距 否 [代码]img_width[代码] Number 宽高都不设置,最小边填满裁剪框 支持%(不加单位为px)(只设置宽度,高度自适应) 图片宽度 否 [代码]img_height[代码] Number 宽高都不设置,最小边填满裁剪框 支持%(不加单位为px)(只设置高度,宽度自适应) 图片高度 否 scale Number 1 无限制 图片的缩放比 否 angle Number 0 (limit_move=true时angle=n*90) 图片的旋转角度 否 min_scale Number 0.5 无限制 图片的最小缩放比 否 max_scale Number 2 无限制 图片的最大缩放比 否 bindload Function null 函数名称 cropper初始化完成 否 bindimageload Function null 函数名称 图片加载完成,返回值Object{width,height,path,type等} 否 bindtapcut Function null 函数名称 点击中间裁剪框,返回值Object{src,width,height} 否 函数说明 函数名 参数 返回值 描述 参数必填 upload 无 无 调起wx上传图片接口并开始剪裁 否 pushImg src 无 放入图片开始裁剪 是 getImg Function(回调函数) [代码]Object{url,width,height}[代码] 裁剪并获取图片(图片尺寸 = 图片宽高 * export_scale) 是 setCutXY X、Y 无 设置裁剪框位置 是 setCutSize width、height 无 设置裁剪框大小 是 setCutCenter 无 无 设置裁剪框居中 否 setScale scale 无 设置图片缩放比例(不受min_scale、max_scale影响) 是 setAngle deg 无 设置图片旋转角度(带过渡效果) 是 setTransform {x,y,angle,scale,cutX,cutY} 无 图片在原有基础上的变化(scale受min_scale、max_scale影响) 根据需要传参 imgReset 无 无 重置图片的角度、缩放、位置(可以在onloadImage回调里使用) 否 GitHub https://github.com/wx-plugin/image-cropper/tree/master 如果有什么好的建议欢迎提issues或者提pr
2021-12-15 - banner效果美化
先上图 [图片] wxss: .bannerSwiper { height:300rpx; width: 100vw; position: absolute; left: 0rpx; top: 10rpx; } .imageBanner { width: 100%; height: 100%; border-radius: 20rpx; position: relative; padding: 0; line-height: 100%; background: transparent; text-align: left; } .imageBanner_small { transform: scale(0.9); transition: 0.2s, ease-in 0s; border-radius: 10rpx; width: 100%; height: 100%; position: absolute; bottom: 0; z-index: 100; padding: 0; line-height: 100%; background: transparent; text-align: left; opacity: 0.5; } wxml: <view class="container"> <swiper class='bannerSwiper' previous-margin="80rpx" next-margin='80rpx' indicator-dots="true" indicator-color='#999' indicator-active-color='#fff' indicator-dots="{{indicatorDots}}" autoplay="{{autoplay}}" interval="{{interval}}" duration="{{duration}}" bindchange='onChange' circular='true'> <block wx:for="{{banner}}"> <swiper-item> <image class="{{index==selindex?'imageBanner':'imageBanner_small'}}" style="background:{{item}}"></image> </swiper-item> </block> </swiper> </view> github地址:https://github.com/jieqwer/-Banner
2020-11-27 - 利用第三方授权 更新并发布多套小程序
开发一个小程序管理平台,其他小程序管理员只需一次授权给第三方,第三方平台即可帮助他发布小程序,不同管理员的配置参数不同,其他功能都基本相同 开发步骤: 一、注册开放平台: 到微信开放平台注册账号 :https://open.weixin.qq.com/cgi-bin/readtemplate?t=regist/regist_tmpl&lang=zh_CN 二、申请第三方平台开发 申请第三方平台必须拥有一定的开发者资质,必须先通过开发者资质认证,才可以开始第三方平台开发,在开发平台账号管理中可进行资质认证 三、创建第三方平台 申请完成后,在开发平台的管理中心,点击第三方平台,在下方可看到创建第三方按钮 [图片] 点击创建第三方平台,进入下方页面,选择平台型服务商, [图片] 1.填写基本信息,与定制化服务商一致 2.选择权限,只能选择业务必须的权限集,否则无法通过审核,公众号或小程序也可能会拒绝授权给你。(权限集是公众号或小程序的权限集合,用于实现业务) 3.填写开发资料 4.开发资料 ①授权发起也域名(即用户打开我们自己的授权页域名) ②授权事件接收URL(我们接收所有授权小程序或公众号取消授权通知、授权成功通知、授权更新通知事件的url地址 , 包括接收微信平台推送的ticket) ③消息与事件接收URL (我们接收所有授权小程序或公众号的消息和事件推送,例如客服消息 微信就会推送到这个地址上) 这里要注意一点:该参数按规则填写(需包含/$APPID$,如www.abc.com/$APPID$/callback) 填写的地址需要包含/$APPID$ 我们后续可以用nginx 重写地址 把访问指向同一个地址就可以了 例如:填写的地址是 www.abc.com/msg/$APPID$/msgEventPath.php nginx重写地址: rewrite ^/msg/(.)/(.).php /msgEventPath.php last; ④其它按照提示填写就可以了,添加上白名单ip 然后提交审核就可以了,如果信息没有问题是马上就能审核成功的,然后再管理中心的第三方平台即可看到改第三方服务商,详情里面即有改第三方平台相关的配置信息 四、小程序管理员授权给第三方平台 只有小程序管理员授权给第三方,第三方才能为该小程序发布,更新部署代码。 授权开发步骤: 1.保存component_verify_ticket, 微信端会定时推送消息到配置好的授权事件接收URL(创建三方平台时填写的,可在该三方详情中查看) 上,我们需要保存这个component_verify_ticket和 不断更新,component_verify_ticket必须保持是微信端推送的最新一个 2.用component_verify_ticket去换取第三方平台的token(第三方平台指的就是我们自己在开发的平台)token是有有效期的,所以我们要保存它的过期时间,并将token做缓存,当token没过期时就不用再去换取,反之我们要利用最新的component_verify_ticket去重新获取token 3.换取预授权码pre_auth_code,pre_auth_code是用来换取微信端的授权二维码的 4.跳转到授权页面(两种方式),建议第二种,方便 用户授权的时候会先打开我们自己的一个页面 (例如 http://www.abc.com/authorization.php ),这个页面里需要做一个按钮或者用js去跳转到微信的授权页面 ①扫码授权 :跳转后得到授权码,注意这个页面只能用网页访问,小程序访问不了,因为不能将微信域名配置为业务域名用户扫码后 就可以授权给第三方平台了 https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=xxxx&pre_auth_code=xxxxx&redirect_uri=xxxx&auth_type=xxx。 ②点击移动端链接快速授权https://mp.weixin.qq.com/safe/bindcomponent?action=bindcomponent&auth_type=3&no_scan=1&component_appid=xxxx&pre_auth_code=xxxxx&redirect_uri=xxxx&auth_type=xxx&biz_appid=xxxx#wechat_redirect 请求参数(两种方式一样) component_appid 第三方平台方appid pre_auth_code 预授权码 redirect_uri 回调URI 必须和授权地址同一个域名 auth_type 要授权的帐号类型:1则商户点击链接后,手机端仅展示公众号、2表示仅展示小程序,3表示公众号和小程序都展示。如果为未指定,则默认小程序和公众号都展示。第三方平台开发者可以使用本字段来控制授权的帐号类型。 前四步总结(移动端快速授权流程): 用户自己获取授权连接: 需要后台配合,给出一个接口,请求该接口则直接返回最新的预授权码(pre_auth_code),拿到授权码之后,再通过拼接返回一个授权地址,跳转到改地址,即为授权页面下方图二 ,用户点击授权即可授权给第三方。用户点击授权后,授权页会自动跳转进入回调URI,并在URL参数中返回授权码和过期时间(redirect_url?auth_code=xxx&expires_in=600),我们可以通过 $GET[‘authcode’] 去获取授权用户的小程序或二维码 调用接口的accesstoken(有效期两小时) 并将其保存/更新,然后我们就可以获取授权用户小程序或公众号的信息 [图片] 5.使用授权码换取公众号或小程序的接口调用凭据和授权信息 接口调用请求说明 http请求方式: POST(请使用https协议) https://api.weixin.qq.com/cgi-bin/component/api_query_auth?component_access_token=xxxx(component_access_token在第二步可获取) POST请求参数示例: { “component_appid”:“appid_value” ,//第三方平台appid “authorization_code”: “auth_code_value”//授权code,会在授权成功时返回给第三方平台 } 请求成功后拿到 authorizer_access_token:授权方接口调用凭据(在授权的公众号或小程序具备API权限时,才有此返回值),也简称为令牌,后面调用小程序待开发的api中使用, authorizer_refresh_token:接口调用凭据刷新令牌(在授权的公众号具备API权限时,才有此返回值),刷新令牌主要用于第三方平台获取和刷新已授权用户的access_token,只会在授权时刻提供,请妥善保存。 一旦丢失,只能让用户重新授权,才能再次拿到新的刷新令牌 五、小程序模板开发 第三方平台帮助旗下已授权的小程序进行代码管理时,需先开发完成小程序模版,再将小程序模版部署到旗下小程序帐号中,具体流程如下: 第一步:绑定开发小程序 (1)第三方平台的开发人员需先到微信公众平台(mp.weixin.qq.com)申请一个普通的小程序并完善小程序的头像、昵称、简介、服务类目等信息。 (2)进入微信开放平台,在第三方平台详情中,将该小程序添加为开发小程序。 注意:绑定为开发小程序后,该小程序的在开发工具中上传,代码会直接上传到开放平台,不会上传到公众平台。 第二步:小程序模版的开发和上传 使用开发小程序的开发者微信号登录微信web开发者工具(IDE),开发者工具中按照正常的小程序开发流程进行代码开发和调试。开发完成后,在开发工具中点击上传。更新模板后需要更部署到旗下小程序之前必须上传到模板库。注意:上传时版本号要求不一样,一样的版本号会被默认为同一版本,判断为管理员没有更新 第三步:添加到小程序模版库,获得模版ID 从开发者工具中上传的代码,会先存在草稿箱中,每个开发小程序只保留最新一份上传记录。开发者可将草稿箱中的代码添加到小程序模版库中,小程序模版库中的模版不会被覆盖。最多可以有五十个代码模版,添加后可以获得模版ID(TemplateID) 拿到模板ID后,再加上之前获取到的authorizer_access_token(令牌),就能为授权过给该第三方平台的小程序部署代码了。 六、为旗下小程序进行代码管理 举个例子:为授权的小程序帐号上传小程序代码 1、为授权的小程序帐号上传小程序代码 请求方式: POST(请使用https协议) https://api.weixin.qq.com/wxa/commit?access_token=TOKEN POST数据示例 { “template_id”:0, “ext_json”:“JSON_STRING”, //ext_json需为string类型,请参考下面的格式 “user_version”:“V1.0”, “user_desc”:“test”, } 参数说明: access_token 请使用第三方平台获取到的该小程序授权的authorizer_access_token template_id 代码库中的代码模版ID ext_json 第三方自定义的配置 user_version 代码版本号,开发者可自定义(长度不要超过64个字符) user_desc 代码描述,开发者可自定义 通过此请求,第三方平台会自动将模板中的代码自动部署到授权给该第三方的小程序上 更多代码管理查看文档 https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1453779503&token=1a70ae891ca6e0339cf56bd1b3c322b0ec86eec9&lang= 持续更新中… 相关文章:https://developers.weixin.qq.com/community/develop/doc/0000ee097e0f00dcd55b8e40856800?jumpto=reply&parent_commentid=00004e9efb84388cd95ba8023514&commentid=000c0623e98068f0e85be2b97564
2020-12-09 - popButtons
本月份的组件分享,事件效果由 wxs事件完成,注释在代码片段里有。 按钮可拖动,子菜单上没有点击事件,是根据touchend位置来判断点击的谁。有bug欢迎反馈 代码片段:https://developers.weixin.qq.com/s/9BNYnhmG7tcl 社区这会儿效果图gif上传不了。。不是我的错 图标iconfont下的,没版权问题吧。。
2019-10-21 - 专治按钮效果不明显(扩散动画效果)
效果 [图片] 需求 背景 由于最近自家小程序用户活跃用户下滑,老板看看自家小程序,发现分享按钮不够明显,于是乎有了下面这段对话。 老板:小明,你过来下,看看这个分享按钮不明显 小明:好的,给它点颜色瞧瞧 小明给按钮来了个红色,发给了BOSS BOSS:还是不明显 小明:好的,给它点放大瞧瞧 小明把按钮从原来的60rpx放大到了230rpx,发给了BOSS BOSS:还是不明显 小明:好的,让它动起来! 需求:提高分享率,做个扩散动画效果让这个按钮成为整个页面最靓的仔。 思路分析: 从小到大的变化 从颜色从深到浅 反复进行该动作 动画代码 实用 CSS3 的 animation 属性 代码 [代码].share-btn { width: 200rpx; height: 200rpx; } .share-btn::before { // 省略无关代码 animation: wave 1.5s ease-out infinite; } @keyframes wave { 50%, 75% { width: 230rpx; height: 230rpx; } 80%, 100% { opacity: 0; } } [代码] 分析 我们先来看看 animation 参数: animation: wave 1.5s ease-out infinite; animation: 关键帧名称 动画时长 动画形式 播放次数; ease-out:动画以低速结束 infinite:无限次播放 从参数可以得出来使用了wave这个关键帧参数,1.5完成一次扩散的动画,从快到满的速度,无限重复这个动画。 然后我们再来看看 keyframes 参数: 百分比:动画持续时间的百分比。 属性:CSS样式属性 从参数可以得出来 时间 50%->75% 的时候就会改变大小从200rpx-230rpx。 时间 80%->100% 的时候会改变透明度从0-1。 第一步:原始大小(高度:200,宽度:200,透明度:1) [图片] 第二步:改变大小(高度:230,宽度:230,透明度:1) [图片] 第三步:改变透明度(高度:230,宽度:230,透明度:0) [图片] 第四步:回到第一步 总结 做动画先分析步骤,然后设置 animation 参数。如果你觉得CSS比较麻烦的话,还可以使用小程序提供 Animation 对象实现。 css3 的 animation 的所有参数不局限以上这些,了解更多点击传送门 Animation 对象,了解更多点击传送门
2020-08-17 - 【开箱即用】分享几个好看的波浪动画css效果!
以下代码不一定都是本人原创,很多都是借鉴参考的(模仿是第一生产力嘛),有些已忘记出处了。以下分享给大家,供学习参考!欢迎收藏补充,说不定哪天你就用上了! 一、第一种效果 [图片] [代码]//index.wxml <view class="zr"> <view class='user_box'> <view class='userInfo'> <open-data type="userAvatarUrl"></open-data> </view> <view class='userInfo_name'> <open-data type="userNickName"></open-data> , 欢迎您 </view> </view> <view class="water"> <view class="water-c"> <view class="water-1"> </view> <view class="water-2"> </view> </view> </view> </view> //index.wxss .zr { color: white; background: #4cb4e7; /*#0396FF*/ width: 100%; height: 100px; position: relative; } .water { position: absolute; left: 0; bottom: -10px; height: 30px; width: 100%; z-index: 1; } .water-c { position: relative; } .water-1 { background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjYwMHB4IiBoZWlnaHQ9IjYwcHgiIHZpZXdCb3g9IjAgMCA2MDAgNjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCAzLjQgKDE1NTc1KSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT53YXRlci0xPC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGRlZnM+PC9kZWZzPgogICAgPGcgaWQ9IuaIkSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc2tldGNoOnR5cGU9Ik1TUGFnZSI+CiAgICAgICAgPGcgaWQ9Ii0iIHNrZXRjaDp0eXBlPSJNU0FydGJvYXJkR3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xMjEuMDAwMDAwLCAtMTMzLjAwMDAwMCkiIGZpbGwtb3BhY2l0eT0iMC4zIiBmaWxsPSIjRkZGRkZGIj4KICAgICAgICAgICAgPGcgaWQ9IndhdGVyLTEiIHNrZXRjaDp0eXBlPSJNU0xheWVyR3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyMS4wMDAwMDAsIDEzMy4wMDAwMDApIj4KICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0wLDcuNjk4NTczOTUgTDQuNjcwNzE5NjJlLTE1LDYwIEw2MDAsNjAgTDYwMCw3LjM1MjMwNDYxIEM2MDAsNy4zNTIzMDQ2MSA0MzIuNzIxMDUyLDI0LjEwNjUxMzggMjkwLjQ4NDA0LDcuMzU2NzQxODcgQzE0OC4yNDcwMjcsLTkuMzkzMDMwMDggMCw3LjY5ODU3Mzk1IDAsNy42OTg1NzM5NSBaIiBpZD0iUGF0aC0xIiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj48L3BhdGg+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==") repeat-x; background-size: 600px; -webkit-animation: wave-animation-1 3.5s infinite linear; animation: wave-animation-1 3.5s infinite linear; } .water-2 { top: 5px; background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjYwMHB4IiBoZWlnaHQ9IjYwcHgiIHZpZXdCb3g9IjAgMCA2MDAgNjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCAzLjQgKDE1NTc1KSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT53YXRlci0yPC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGRlZnM+PC9kZWZzPgogICAgPGcgaWQ9IuaIkSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc2tldGNoOnR5cGU9Ik1TUGFnZSI+CiAgICAgICAgPGcgaWQ9Ii0iIHNrZXRjaDp0eXBlPSJNU0FydGJvYXJkR3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xMjEuMDAwMDAwLCAtMjQ2LjAwMDAwMCkiIGZpbGw9IiNGRkZGRkYiPgogICAgICAgICAgICA8ZyBpZD0id2F0ZXItMiIgc2tldGNoOnR5cGU9Ik1TTGF5ZXJHcm91cCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTIxLjAwMDAwMCwgMjQ2LjAwMDAwMCkiPgogICAgICAgICAgICAgICAgPHBhdGggZD0iTTAsNy42OTg1NzM5NSBMNC42NzA3MTk2MmUtMTUsNjAgTDYwMCw2MCBMNjAwLDcuMzUyMzA0NjEgQzYwMCw3LjM1MjMwNDYxIDQzMi43MjEwNTIsMjQuMTA2NTEzOCAyOTAuNDg0MDQsNy4zNTY3NDE4NyBDMTQ4LjI0NzAyNywtOS4zOTMwMzAwOCAwLDcuNjk4NTczOTUgMCw3LjY5ODU3Mzk1IFoiIGlkPSJQYXRoLTIiIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDMwMC4wMDAwMDAsIDMwLjAwMDAwMCkgc2NhbGUoLTEsIDEpIHRyYW5zbGF0ZSgtMzAwLjAwMDAwMCwgLTMwLjAwMDAwMCkgIj48L3BhdGg+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==") repeat-x; background-size: 600px; -webkit-animation: wave-animation-2 6s infinite linear; animation: wave-animation-2 6s infinite linear; } .water-1, .water-2 { position: absolute; width: 100%; height: 60px; } .back-white { background: #fff; } @keyframes wave-animation-1 { 0% { background-position: 0 top; } 100% { background-position: 600px top; } } @keyframes wave-animation-2 { 0% { background-position: 0 top; } 100% { background-position: 600px top; } } .user_box { display: flex; z-index: 10000 !important; opacity: 0; /* 透明度*/ animation: love 1.5s ease-in-out; animation-fill-mode: forwards; } .userInfo_name { flex: 1; vertical-align: middle; width: 100%; margin-left: 5%; margin-top: 5%; font-size: 42rpx; } .userInfo { flex: 1; width: 100%; border-radius: 50%; overflow: hidden; max-height: 50px; max-width: 50px; margin-left: 5%; margin-top: 5%; border: 2px solid #fff; } [代码] 二、第二种效果 [图片] [代码]//index.wxml <view class="waveWrapper waveAnimation"> <view class="waveWrapperInner bgTop"> <view class="wave waveTop" style="background-image: url('https://s2.ax1x.com/2019/09/26/um8g7n.png')"></view> </view> <view class="waveWrapperInner bgMiddle"> <view class="wave waveMiddle" style="background-image: url('https://s2.ax1x.com/2019/09/26/umGZ38.png')"></view> </view> <view class="waveWrapperInner bgBottom"> <view class="wave waveBottom" style="background-image: url('https://s2.ax1x.com/2019/09/26/umGuuQ.png')"></view> </view> </view> //index.wxss .waveWrapper { overflow: hidden; position: absolute; left: 0; right: 0; height: 300px; top: 0; margin: auto; } .waveWrapperInner { position: absolute; width: 100%; overflow: hidden; height: 100%; bottom: -1px; background-image: linear-gradient(to top, #86377b 20%, #27273c 80%); } .bgTop { z-index: 15; opacity: 0.5; } .bgMiddle { z-index: 10; opacity: 0.75; } .bgBottom { z-index: 5; } .wave { position: absolute; left: 0; width: 500%; height: 100%; background-repeat: repeat no-repeat; background-position: 0 bottom; transform-origin: center bottom; } .waveTop { background-size: 50% 100px; } .waveAnimation .waveTop { animation: move-wave 3s; -webkit-animation: move-wave 3s; -webkit-animation-delay: 1s; animation-delay: 1s; } .waveMiddle { background-size: 50% 120px; } .waveAnimation .waveMiddle { animation: move_wave 10s linear infinite; } .waveBottom { background-size: 50% 100px; } .waveAnimation .waveBottom { animation: move_wave 15s linear infinite; } @keyframes move_wave { 0% { transform: translateX(0) translateZ(0) scaleY(1) } 50% { transform: translateX(-25%) translateZ(0) scaleY(0.55) } 100% { transform: translateX(-50%) translateZ(0) scaleY(1) } } [代码] 三、第三种效果 [图片] [代码]//index.wxml <view class="container"> <image class="title" src="https://ftp.bmp.ovh/imgs/2019/09/74bada9c4143786a.png"></image> <view class="content"> <view class="hd" style="transform:rotateZ({{angle}}deg);"> <image class="logo" src="https://ftp.bmp.ovh/imgs/2019/09/d31b8fcf19ee48dc.png"></image> <image class="wave" src="wave.png" mode="aspectFill"></image> <image class="wave wave-bg" src="wave.png" mode="aspectFill"></image> </view> <view class="bd" style="height: 100rpx;"> </view> </view> </view> //index.wxss image{ max-width:none; } .container { background: #7acfa6; align-items: stretch; padding: 0; height: 100%; overflow: hidden; } .content{ flex: 1; display: flex; position: relative; z-index: 10; flex-direction: column; align-items: stretch; justify-content: center; width: 100%; height: 100%; padding-bottom: 450rpx; background: -webkit-gradient(linear, left top, left bottom, from(rgba(244,244,244,0)), color-stop(0.1, #f4f4f4), to(#f4f4f4)); opacity: 0; transform: translate3d(0,100%,0); animation: rise 3s cubic-bezier(0.19, 1, 0.22, 1) .25s forwards; } @keyframes rise{ 0% {opacity: 0;transform: translate3d(0,100%,0);} 50% {opacity: 1;} 100% {opacity: 1;transform: translate3d(0,450rpx,0);} } .title{ position: absolute; top: 30rpx; left: 50%; width: 600rpx; height: 200rpx; margin-left: -300rpx; opacity: 0; animation: show 2.5s cubic-bezier(0.19, 1, 0.22, 1) .5s forwards; } @keyframes show{ 0% {opacity: 0;} 100% {opacity: .95;} } .hd { position: absolute; top: 0; left: 50%; width: 1000rpx; margin-left: -500rpx; height: 200rpx; transition: all .35s ease; } .logo { position: absolute; z-index: 2; left: 50%; bottom: 200rpx; width: 160rpx; height: 160rpx; margin-left: -80rpx; border-radius: 160rpx; animation: sway 10s ease-in-out infinite; opacity: .95; } @keyframes sway{ 0% {transform: translate3d(0,20rpx,0) rotate(-15deg); } 17% {transform: translate3d(0,0rpx,0) rotate(25deg); } 34% {transform: translate3d(0,-20rpx,0) rotate(-20deg); } 50% {transform: translate3d(0,-10rpx,0) rotate(15deg); } 67% {transform: translate3d(0,10rpx,0) rotate(-25deg); } 84% {transform: translate3d(0,15rpx,0) rotate(15deg); } 100% {transform: translate3d(0,20rpx,0) rotate(-15deg); } } .wave { position: absolute; z-index: 3; right: 0; bottom: 0; opacity: 0.725; height: 260rpx; width: 2250rpx; animation: wave 10s linear infinite; } .wave-bg { z-index: 1; animation: wave-bg 10.25s linear infinite; } @keyframes wave{ from {transform: translate3d(125rpx,0,0);} to {transform: translate3d(1125rpx,0,0);} } @keyframes wave-bg{ from {transform: translate3d(375rpx,0,0);} to {transform: translate3d(1375rpx,0,0);} } .bd { position: relative; flex: 1; display: flex; flex-direction: column; align-items: stretch; animation: bd-rise 2s cubic-bezier(0.23,1,0.32,1) .75s forwards; opacity: 0; } @keyframes bd-rise{ from {opacity: 0; transform: translate3d(0,60rpx,0); } to {opacity: 1; transform: translate3d(0,0,0); } } [代码] wave.png(可下载到本地) [图片] 在这个基础上,再加上js的代码,即可实现根据手机倾向,水波晃动的效果 wx.onAccelerometerChange(function callback) 监听加速度数据事件。 [图片] [代码]//index.js Page({ onReady: function () { var _this = this; wx.onAccelerometerChange(function (res) { var angle = -(res.x * 30).toFixed(1); if (angle > 14) { angle = 14; } else if (angle < -14) { angle = -14; } if (_this.data.angle !== angle) { _this.setData({ angle: angle }); } }); }, }); [代码] 四、第四种效果 [图片] [代码]//index.wxml <view class='page__bd'> <view class="bg-img padding-tb-xl" style="background-image:url('http://wx4.sinaimg.cn/mw690/006UdlVNgy1g2v2t1ih8jj31hc0p0qej.jpg');background-size:cover;"> <view class="cu-bar"> <view class="content text-bold text-white"> 悦拍屋 </view> </view> </view> <view class="shadow-blur"> <image src="https://raw.githubusercontent.com/weilanwl/ColorUI/master/demo/images/wave.gif" mode="scaleToFill" class="gif-black response" style="height:100rpx;margin-top:-100rpx;"></image> </view> </view> //index.wxss @import "colorui.wxss"; .gif-black { display: block; border: none; mix-blend-mode: screen; } [代码] 本效果需要引入ColorUI组件库
2019-09-26 - 一个奇葩思路实现的瀑布流双列布局
传统的瀑布流布局实现一般关键是去计算每一列的高度,从而判断下一个元素应该插入到哪一列(当然是最短的那列)。 这个奇葩思路没有任何计算,主要思路如下: 在瀑布流容器底部加入一根细线 利用微信小程序的IntersectionObserver,为每一列和细线添加监听 逐个加入要插入的item元素 根据监听相交变化结果判断下一个item应该插入哪一列(简单来说就是插入到当前不与细线相交的那一列,因为比较短) 这个思路实际上就是把计算高度换成了监听判断哪列更高,因此也不必知道每个元素的高度。 目前只能支持两列布局的情况,如果列数更多我没办法不通过计算来知道哪列最短,如果有思路或想法的童鞋欢迎交流~ 实现过程也比较简单,就分享个思路,不贴代码了(问就是懒!) 感兴趣的童鞋可以看代码片段,里面有完整的实现代码: https://developers.weixin.qq.com/s/nH5pg4mE78dG
2019-11-23 - 同一页面存在多个video时,video无法正常播放一直在加载转圈
不建议同个页面使用多个video组件,建议不超过3个video,如果要实现video列表功能,请进行优化(image列表,选中时将image替换成video)
2019-08-29 - 小程序开发进阶之前端开发
4 节课,教你快速入门小程序前端。本系列视频,由腾讯课堂NEXT学院、微信学堂联合出品。
2021-12-14