个人案例
- 沣西一小码行天下云平台
西咸新区沣西第一小学官方小程序
沣西一小在线学习平台扫码体验
- 微信小程序5个不常见,但很实用的配置。
一、微信小程序资料页客服按钮配置。 1、配置前: [图片] 2、配置后: [图片] 3、配置方法: [图片] [图片] 注意要点: ·有客户通过这个按钮咨询,客服微信会收到服务通知。但要切换成在线状态才能收到。 ·因咨询有时效性,尽量让客户加客服微信再继续深度沟通。 [图片] 二、微信小程序允许被搜索开关。 这个功能跟另一个“暂停服务设置”有点像,但搜索开关和暂停服务还是有很大区别。比如一些内部的管理小程序,内部会经常用到。但不希望被非相关人员搜索到。又或者要注销的小程序,也可以关闭搜索再注销。不然注销完成了,还是有搜索信息展示。 配置方法: 小程序后台 > 设置 > 功能设置 > 私隐设置 [图片] 三、微信小程序资料页关联小程序。 1、配置前: [图片] 2、配置后: [图片] 3、配置路径: 小程序后台 > 设置 > 功能设置 > 相关小程序 [图片] 四、微信小程序资料页关联公众号。 1、配置前: [图片] 2、配置后: [图片] 3、配置路径: 小程序后台 > 设置 > 功能设置 > 相关公众号 [图片] 五、微信小程序支付后关注公众号。 本来这一条应该放在第一位,但可惜去年因影响用户体验,2021年8月19日功能下线。 [图片] 为什么还把这条不能配置放进来呢?不是为了凑数,而是想提醒社区的每一位开发或运营不要滥用微信生态功能!自己就亲身经历了很多士多连锁,支付后就收到公众号推送的一大堆广告。 除了这5个微信小程序配置,还想知道哪个配置(非代码)?可以评论区回复。
2022-01-06 - 小程序app.onLaunch与page.onLoad异步问题的最佳实践
场景: 在小程序中大家应该都有这样的场景,在onLaunch里用wx.login静默登录拿到code,再用code去发送请求获取token、用户信息等,整个过程都是异步的,然后我们在业务页面里onLoad去用的时候异步请求还没回来,导致没拿到想要的数据,以往要么监听是否拿到,要么自己封装一套回调,总之都挺麻烦,每个页面都要写一堆无关当前页面的逻辑。 直接上终极解决方案,公司内部已接入两年很稳定: 1.可完美解决异步问题 2.不污染原生生命周期,与onLoad等钩子共存 3.使用方便 4.可灵活定制异步钩子 5.采用监听模式实现,接入无需修改以前相关逻辑 6.支持各种小程序和vue架构 。。。 //为了简洁明了的展示使用场景,以下有部分是伪代码,请勿直接粘贴使用,具体使用代码看Github文档 //app.js //globalData提出来声明 let globalData = { // 是否已拿到token token: '', // 用户信息 userInfo: { userId: '', head: '' } } //注册自定义钩子 import CustomHook from 'spa-custom-hooks'; CustomHook.install({ 'Login':{ name:'Login', watchKey: 'token', onUpdate(token){ //有token则触发此钩子 return !!token; } }, 'User':{ name:'User', watchKey: 'userInfo', onUpdate(user){ //获取到userinfo里的userId则触发此钩子 return !!user.userId; } } }, globalData) // 正常走初始化逻辑 App({ globalData, onLaunch() { //发起异步登录拿token login((token)=>{ this.globalData.token = token //使用token拿用户信息 getUser((user)=>{ this.globalData.user = user }) }) } }) //关键点来了 //Page.js,业务页面使用 Page({ onLoadLogin() { //拿到token啦,可以使用token发起请求了 const token = getApp().globalData.token }, onLoadUser() { //拿到用户信息啦 const userInfo = getApp().globalData.userInfo }, onReadyUser() { //页面初次渲染完毕 && 拿到用户信息,可以把头像渲染在canvas上面啦 const userInfo = getApp().globalData.userInfo // 获取canvas上下文 const ctx = getCanvasContext2d() ctx.drawImage(userInfo.head,0,0,100,100) }, onShowUser() { //页面每次显示 && 拿到用户信息,我要在页面每次显示的时候根据userInfo走不同的逻辑 const userInfo = getApp().globalData.userInfo switch(userInfo.sex){ case 0: // 走女生逻辑 break case 1: // 走男生逻辑 break } } }) 具体文档和Demo见↓ Github:https://github.com/1977474741/spa-custom-hooks 祝大家用的愉快,记得star哦
2023-04-23 - 在小程序中执行命令安装 npm 包:npm install ,为什么失败了?
[图片] [图片]
2019-12-02 - H5如何跳转微信小程序?
之前遇到一个需求,就是要从H5跳转到小程序里,但是微信之前一直没有提供接口做跳转,我们只能做降级方案,在要跳转小程序的地方做了一个弹窗,弹窗里面放小程序码,引导用户长按识别小程序码,然后跳转到小程序内,整个流程非常之长,转化率可想而知也是很低的。 今天刚好看到有人技术群里面问了这个问题,于是我就去看了下微信的文档,发现微信偷偷的更新的这个接口,可以让微信浏览器下的H5跳转到小程序内。 相关文档在这边: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_Open_Tag.html 用的是JS-SDK的接口,需要使用到js-sdk-1.6.0的版本才有支持,https://res.wx.qq.com/open/js/jweixin-1.6.0.js wx.config({ debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印 appId: '', // 必填,公众号的唯一标识 timestamp: , // 必填,生成签名的时间戳 nonceStr: '', // 必填,生成签名的随机串 signature: '',// 必填,签名 jsApiList: [], // 必填,需要使用的JS接口列表 openTagList: [] // 可选,需要使用的开放标签列表,例如['wx-open-launch-app'] }); 在wx.config下面多了一项openTagList,开放标签列表,目前支持配置wx-open-launch-weapp,wx-open-launch-app wx-open-launch-weapp 指H5跳转小程序 wx-open-launch-app 指H5跳转app 我们主要介绍的是wx-open-launch-weapp H5跳转小程序 先上才艺: [图片][图片][图片] html代码如下: var btn = document.getElementById('launch-btn'); btn.addEventListener('launch', function (e) { console.log('success'); }); btn.addEventListener('error', function (e) { console.log('fail', e.detail); }); username为小程序的原始id,path对应的是小程序的链接地址。之前有写过微信H5的应该会知道怎么把这段代码嵌入到之前的代码里面。 目前此功能仅开放给已认证的服务号,网页的域名要在服务号的“JS接口安全域名”下。 亲测<wx-open-launch-weapp>可以跳转到任意合法合规的小程序,是任意的小程序都能跳转!!!!这个接口真开放(不怕人干坏事?) PS: 有个坑,官方文件说的path是/a/b/c?d=1&e=2#fg,类似的这样的链接格式,但是我自己亲测如果直接使用/a/b/c?d=1&e=2#fg这样格式的链接会报页面不存在,然后我想到了小程序那边复制链接的时候会在链接后面加上.html,于是挖槽的事情发生了,把path链接格式换成/a/b/c.html?d=1&e=2#fg这样就能正常访问,不知道是微信故意这样设计的还是bug,有待考证。 然后这个接口真的可以干好多坏事,希望大家能用正确的价值观来正确使用此接口。 微信开放标签有最低的微信版本要求,以及最低的系统版本要求。 如果开发过程中出现以下情况的,要确认一下,微信版本要求为:7.0.12及以上。 系统版本要求为:iOS 10.3及以上、Android 5.0及以上。 [图片]
2020-07-09 - 抽奖助手小程序二
本文背景在前面我分享过一个抽奖助手小程序,为避免引起误会,我先解释下,首先这里的抽奖助手小程序并非抽奖助手,而是指能提供抽奖服务的小程序,之前那次分享纯粹是从如何搭建运行这个维度展开 本文内容本文主要是在开源小程序做了几个改造,把这个记录下来 (1)开奖模式,之前小程序是通过人数,如果人数达到设定的开奖人数才会开奖,我把这个模式支持了日期,不管是否达到开奖人数都会在某个设定的时间到来自动开奖,具体位于云函数run (2)抽奖逻辑,其实说时候我没有很仔细的看开源小程序里面的抽奖逻辑,我把这块重写了,逻辑位于云函数draw (3)首页增加了插屏广告 (4)抽奖按钮接入了订阅消息以及激励式广告 总体评价这个抽奖助手小程序UI是非常不错的,通过这种简单的改造就可以完成期望的目的。 界面截图 f[图片] f [图片] [图片] √ [图片] f 本文总结本文主要讲述我在开源的抽奖助手小程序上进行了几个改造,使其符合我的用途,通过这种改造使我对抽奖类小程序有了更深的理解,比如具体的抽奖逻辑,开奖逻辑等等
2020-07-20 - 微信小程序-自定义导航栏-免费开源
先看看效果吧。 [图片] [图片] [图片] [图片] 两种状态(可在组件灵活配置) 1,在tabBar页面,统一显示在左边。(看上面图片) 2,在非tabBar页面,统一显示在中间。(看上面图片) 配置返回弹窗:(对物理按键无效 1,将需要返回弹窗的页面路径配置在returnWindow数组里面即可。 [图片] 2,wxml页面,设置弹窗内容 [图片] [图片] 引用:(需要开发版Nightily工具才能正常体验 app.json全局配置"navigationStyle": "custom"。或者单个页面也行。app.json全局引入自定义组件和icon图标。需要引入weui。路径的话,自己看着办。app.json相对你组件的位置。 [图片] 3,到页面wxml引用了,就一行代码。<navigation title="搜索"></navigation> [图片] 解释: [图片] index即首页。 tabBarList中的页面即展示左边标题。不在的页面则展示左边菜单,中间标题。可到组件中灵活配置 returnWindow返回需要弹窗的页面数组。 样式方面: 使用的是cover-view+fixed定位 真实案例小程序:(问卷调查时间) [图片] 代码片段: https://developers.weixin.qq.com/s/UA7X0emv7wes
2020-04-28 - 微信小程序自定义导航栏组件(完美适配所有手机),可自定义实现任何你想要的功能
背景 在做小程序时,关于默认导航栏,我们遇到了以下的问题: Android、IOS手机对于页面title的展示不一致,安卓title的显示不居中 页面的title只支持纯文本级别的样式控制,不能够做更丰富的title效果 左上角的事件无法监听、定制 路由导航单一,只能够返回上一页,深层级页面的返回不够友好 探索 小程序自定义导航栏已开放许久>>了解一下,相信不少小伙伴已使用过这个功能,同时不少小伙伴也会发现一些坑: 机型多如牛毛:自定义导航栏高度在不同机型始终无法达到视觉上的统一 调皮的胶囊按钮:导航栏元素(文字,图标等)怎么也对不齐那该死的胶囊按钮 各种尺寸的全面屏,奇怪的刘海屏,简直要抓狂 一探究竟 为了搞明白原理,我先去翻了官方文档,>>飞机,点过去是不是很惊喜,很意外,通篇大文尽然只有最下方的一张图片与这个问题有关,并且啥也看不清,汗汗汗… 我特意找了一张图片来 [图片] 分析上图,我得到如下信息: Android跟iOS有差异,表现在顶部到胶囊按钮之间的距离差了6pt 胶囊按钮高度为32pt, iOS和Android一致 动手分析 我们写一个状态栏,通过wx.getSystemInfoSync().statusBarHeight设置高度 Android: [图片] iOS:[图片] 可以看出,iOS胶囊按钮与状态栏之间距离为:4px, Android为8px,是不是所有手机都是这种情况呢? 答案是:苹果手机确实都是4px,安卓大部分都是7和8 也会有其他的情况(可以自己打印getSystemInfo验证)如何快速便捷算出这个高度,请接着往下看 如何计算 导航栏分为状态栏和标题栏,只要能算出每台手机的导航栏高度问题就迎刃而解 导航栏高度 = 胶囊按钮高度 + 状态栏到胶囊按钮间距 * 2 + 状态栏高度 注:由于胶囊按钮是原生组件,为表现一致,其单位在各种手机中都为px,所以我们自定义导航栏的单位都必需是px(切记不能用rpx),才能完美适配。 解决问题 现在我们明白了原理,可以利用胶囊按钮的位置信息和statusBarHeight高度动态计算导航栏的高度,贴一个实现此功能最重要的方法 [代码]let systemInfo = wx.getSystemInfoSync(); let rect = wx.getMenuButtonBoundingClientRect ? wx.getMenuButtonBoundingClientRect() : null; //胶囊按钮位置信息 wx.getMenuButtonBoundingClientRect(); let navBarHeight = (function() { //导航栏高度 let gap = rect.top - systemInfo.statusBarHeight; //动态计算每台手机状态栏到胶囊按钮间距 return 2 * gap + rect.height; })(); [代码] gap信息就是不同的手机其状态栏到胶囊按钮间距,具体更多代码实现和使用demo请移步下方代码仓库,代码中还会有输入框文字跳动解决办法,安卓手机输入框文字飞出解决办法,左侧按钮边框太粗解决办法等等 胶囊信息报错和获取不到 问题就在于 getMenuButtonBoundingClientRect 这个方法,在某些机子和环境下会报错或者获取不到,对于此种情况完美可以模拟一个胶囊位置出来 [代码]try { rect = Taro.getMenuButtonBoundingClientRect ? Taro.getMenuButtonBoundingClientRect() : null; if (rect === null) { throw 'getMenuButtonBoundingClientRect error'; } //取值为0的情况 if (!rect.width) { throw 'getMenuButtonBoundingClientRect error'; } } catch (error) { let gap = ''; //胶囊按钮上下间距 使导航内容居中 let width = 96; //胶囊的宽度,android大部分96,ios为88 if (systemInfo.platform === 'android') { gap = 8; width = 96; } else if (systemInfo.platform === 'devtools') { if (ios) { gap = 5.5; //开发工具中ios手机 } else { gap = 7.5; //开发工具中android和其他手机 } } else { gap = 4; width = 88; } if (!systemInfo.statusBarHeight) { //开启wifi的情况下修复statusBarHeight值获取不到 systemInfo.statusBarHeight = systemInfo.screenHeight - systemInfo.windowHeight - 20; } rect = { //获取不到胶囊信息就自定义重置一个 bottom: systemInfo.statusBarHeight + gap + 32, height: 32, left: systemInfo.windowWidth - width - 10, right: systemInfo.windowWidth - 10, top: systemInfo.statusBarHeight + gap, width: width }; console.log('error', error); console.log('rect', rect); } [代码] 以上代码主要是借鉴了拼多多的默认值写法,android 机子中 gap 值大部分为 8,ios 都为 4,开发工具中 ios 为 5.5,android 为 7.5,这样处理之后自己模拟一个胶囊按钮的位置,这样在获取不到胶囊信息的情况下,可保证绝大多数机子完美显示导航头 吐槽 这么重要的问题,官方尽然没有提供解决方案…竟然提供了一张看不清的图片??? 网上有很多ios设置44,android设置48,还有根据不同的手机型号设置不同高度,通过长时间的开发和尝试,本人发现以上方案并不完美,并且bug很多 代码库 Taro组件gitHub地址详细用法请参考README 原生组件npm构建版本gitHub地址详细用法请参考README 原生组件简易版gitHub地址详细用法请参考README 由于本人精力有限,目前只计划发布维护好这2种组件,其他组件请自行修改代码,有问题请联系 备注 上方2种组件在最下方30多款手机测试情况表现良好 iPhone手机打电话和开热点导致导航栏样式错乱,问题已经解决啦,请去demo里测试,这里特别感谢moments网友提出的问题 本文章并无任何商业性质,如有侵权请联系本人修改或删除 文章少量部分内容是本人查询搜集而来 如有问题可以下方留言讨论,微信zhijunxh 比较 斗鱼: [图片] 虎牙: [图片] 微博: [图片] 酷狗: [图片] 知乎: [图片] [图片] 知乎是这里边做的最好的,但是我个人认为有几个可以优化的小问题 打电话或者开启热点导致样式错落,这也是大部门小程序的问题 导航栏下边距太小,看起来不舒服 搜索框距离2侧按钮组距离不对等 自定义返回和home按钮中的竖线颜色重了,并且感觉太粗 如果您看到了此篇文章,请赶快修改自己的代码,并运用在实践中吧 扫码体验我的小程序: [图片] 创作不易,如果对你有帮助,请移步Taro组件gitHub原生组件gitHub给个星星 star✨✨ 谢谢 测试信息 手机型号 胶囊位置信息 statusBarHeight 测试情况 iPhoneX 80 32 281 369 48 88 44 通过 iPhone8 plus 56 32 320 408 24 88 20 通过 iphone7 56 32 281 368 24 87 20 通过 iPhone6 plus 56 32 320 408 24 88 20 通过 iPhone6 56 32 281 368 24 87 20 通过 HUAWEI SLA-AL00 64 32 254 350 32 96 24 通过 HUAWEI VTR-AL00 64 32 254 350 32 96 24 通过 HUAWEI EVA-AL00 64 32 254 350 32 96 24 通过 HUAWEI EML-AL00 68 32 254 350 36 96 29 通过 HUAWEI VOG-AL00 65 32 254 350 33 96 25 通过 HUAWEI ATU-TL10 64 32 254 350 32 96 24 通过 HUAWEI SMARTISAN OS105 64 32 326 422 32 96 24 通过 XIAOMI MI6 59 28 265 352 31 87 23 通过 XIAOMI MI4LTE 60 32 254 350 28 96 20 通过 XIAOMI MIX3 74 32 287 383 42 96 35 通过 REDMI NOTE3 64 32 254 350 32 96 24 通过 REDMI NOTE4 64 32 254 350 32 96 24 通过 REDMI NOTE3 55 28 255 351 27 96 20 通过 REDMI 5plus 67 32 287 383 35 96 28 通过 MEIZU M571C 65 32 254 350 33 96 25 通过 MEIZU M6 NOTE 62 32 254 350 30 96 22 通过 MEIZU MX4 PRO 62 32 278 374 30 96 22 通过 OPPO A33 65 32 254 350 33 96 26 通过 OPPO R11 58 32 254 350 26 96 18 通过 VIVO Y55 64 32 254 350 32 96 24 通过 HONOR BLN-AL20 64 32 254 350 32 96 24 通过 HONOR NEM-AL10 59 28 265 352 31 87 24 通过 HONOR BND-AL10 64 32 254 350 32 96 24 通过 HONOR duk-al20 64 32 254 350 32 96 24 通过 SAMSUNG SM-G9550 64 32 305 401 32 96 24 通过 360 1801-A01 64 32 254 350 32 96 24 通过
2019-11-17 - 多形态小程序日历组件,轻松搞定项目需求
小程序日历组件 小程序日历组件,支持多种模式,简单易用好上手。 4种日历模式 3种日期选择方式 支持自定义节假日 支持自定义日期内容 懒加载保证渲染性能 支持农历 支持根据指定日期自动生成 支持跨无数据月份 [图片] [图片] [图片] [图片] [图片] 日历组件基础配置 wxml模板 [代码]<ui-calendar dataSource="{{config}}" /> [代码] 配置日历组件 [代码]Pager({ data: { source: { $$id: 'calendar', mode: 1, // 纵向日历 type: 'range', // 区域选择 tap: 'onTap', // page响应事件 total: 365, // 指定日历总天数 data: [], // 按给定日期计算total值,自动构建日历 rangeCount: 28, // 区选区间28天 rangeMode: 2, // 区选模式 rangeTip: ['入住', '离店'], // 区选提示 festival: true, // 开启节假日显示 alignMonth: false, // 月份对齐,swiper切换时 lunar: false, // 是否显示农历 date: [], // 指定日期显示的内容 value: ['2019-12-24', '2020-01-05'], // 默认值 toolbox: { monthHeader: true, // 是否显示月头 discontinue: false, // 自动构建时,是否省略无数据的月份 }, methods: { // 响应 tap事件 onTap(e, param, inst) { // param.date 选中的当前日期 // 当区选模式时 // param.range === 'start' 区选第一天 // param.range === 'end' 区选最后一天 } } } } }) [代码] github地址:https://github.com/webkixi/aotoo-xquery 小程序demo演示 [图片]
2020-06-30 - 啥?四分钱就能学会开发电商和游戏小程序?!
微信小程序自2017年发布至今,以其速度快、体验棒、无适配等优点,不断实现着使应用“触手可及”的梦想,为用户生活的各个方面带来了便利。 [图片] 小程序如此火爆的一大原因是其拥有较低的开发成本和门槛,而云开发正是让小程序这一优点充分发挥的一大利器!【小贴士:小程序·云开发是微信团队联合腾讯云提供的Serverless无服务开发服务,帮助开发者快速构建适用于小程序的云端数据库、云端存储、云端计算。】 而且,云开发还具有商业和技术两方面的多项优点,能让小程序开发更加高效。以电商开发为例,小程序·云开发就在后端代码、服务器、费用等方面有着独特的优势,从而解决了传统电商后台开发的一些局限性。 [图片] 目前,小程序·云开发让开发者看到了更多的可能性,也让越来越多的开发爱好者有机会将天马行空的想象付诸实践,从而诞生出一个又一个有趣又实用的小程序。 [图片] 例如,借助云开发实现一个开发撑起过亿用户小程序的“壮举”(腾讯相册)。 [图片] 腾讯相册 还有,无需服务器,即可开发出精美的小游戏~ [图片] 此外,利用云开发,无需后台与备案域名,即可高效实现经典的小程序支付功能,瞬间还原微信支付有木有! [图片] 甚至,还有一群年轻的00后用云开发做了一系列好玩儿的校园小程序,管理工具、寻宝探秘、技术社区等类型的小程序百花齐放~ [图片] 怎么样,你是不是也想轻松开发一个专属的小程序? 亲手将自己的创意idea落个地? 那么问题来了 功能强大的云开发怎么使用?上手难吗?做一个自己的小程序需要多久? 不要方,小编来告诉你 你与成为一个优秀的小程序·云开发er可能只隔了三节公开课! 云开发联合腾讯课堂TEXT公开课为你奉上干货满满的课程礼包!三位男神老师将带你认识小程序·云开发,让你轻松get电商与游戏这两个超热门领域小程序的开发技巧! 快点击下方链接获取吧~ 小程序·云开发基础 电商小程序·云开发初探 轻松入门微信小游戏·云开发 更给力的是,收获这三门循序渐进、干货满满的课程一共只需4分钱! [图片] So,想了解一大票网红电商小程序如何诞生,想设计开发自己的游戏吗? 快点击上方链接进行报名吧!
2019-07-22 - 小程序宝箱抽奖组件
在上一篇中《小程序canvas开发水果老虎机》我们做了一个水果机的抽奖组件,为水果抽奖机新增一种新的抽奖模式,复用水果机抽奖组件,实现上基本没什么难度,跳开水果机算法就基本实现了,效果如下 [图片] 支持有序抽奖 支持无序抽奖 支持自定义每个奖品 支持自定义奖品个数(>=2) 支持中奖后的回调方法 安装方法 GITHUB上有详细说明。 配置 wxml [代码]<view wx:if="{{fruitConfig}}" class="fruit-container {{fruitConfig.containerClass||''}}" style="{{fruitConfig.containerStyle||''}}"> <ui-list list="{{fruitConfig}}" /> <canvas type='2d' disable-scroll="true" id="fruit-canvas" class='fruit-canvas'></canvas> </view> [代码] js [代码]const Pager = require('../../components/aotoo/core/index') const mkFruits = require('../../components/modules/fruit') Pager({ data: { fruitConfig: mkFruits({ id: 'fruitTable', ... ... }, // callback,动画结束后响应 function (param) { console.log(param); // 中奖数据 console.log(param.value); // 中奖值 }) } }) [代码] id 配置实例的Id containerClass 容器样式 containerStyle 容器内联样式 max 设置max值后,根据平方算法,自动计算果盘数量,max定义一行几个水果,默认九宫格,max为3 count 与max的区别在于果盘数量由用户指定,且果盘不再为老虎机的游戏模式,max数量仍为定义一行几个水果 设置[代码]count[代码]为有效数据后,将以常规抽奖盘代替老虎机抽奖盘 confuse 使果盘数据无序化,只有当count为有效数据时,confuse才能生效 fruitsData Object,指定对应位置的格子内容,支持文字/图片 回调方法 mkFruits(config, callback)水果盘动画结束后响应方法 如何设置 设置宝箱抽奖盘 常规抽奖盘不再以水果老虎机的游戏形式展现 [代码]mkFruits({ id: 'fruit', count: 3, // count默认为0,当count设定>0时,组件不再输出水果盘 max: 3, // count>0时,max表示每行显示个数 fruitsData: { 1: {title: '石头', value: '001'}, // value默认为下标叙述,也可以手动指定 2: {title: '剪刀', value: '002'}, 3: {title: '布', value: '003'} }, }) [代码] 设置果盘内容(图片) 通过[代码]fruitsData[代码]设置指定位置显示内容,支持文本/图片 [代码]mkFruits({ id: 'fruit', max: 4, fruitsData: { 1: {title: '一等奖', value: '001'}, 5: {img: {src: '/images/xxx.jpg'}, value: '003'}, }, }) [代码] 如何获取水果盘的实例 运行水果盘需要调用实例方法,首先需要通过getElementsById获取水果盘实例 [代码]const Pager = require('../../components/aotoo/core/index') const mkFruits = require('../../components/modules/fruit') Pager({ data: { fruitConfig: mkNavball({ id: 'fruit' }), }, onReady(){ // 获取水果盘实例 const instance = this.fruit // 或者 // const instance = this.getElementsById('fruit') } }) [代码] run 运行水果盘 [代码]onReady() { // 获取水果盘实例 const instance = this.getElementsById('fruit') instance.run() } [代码] 源码戳这里 下列小程序DEMO包含[代码]多形态日历,下拉菜单、筛选列表、索引列表、markdown(包含表格)、评分组件、水果老虎机、折叠面板、双栏分类导航(左右)、刮刮卡、日历[代码]等组件 [图片]
2020-06-24 - 「分享」在微信小程序中快速的创建一个滚动卡片
最近做项目遇到的一个功能要求,没在网上找到类似代码,所以自己写了一个,整理分享出来,有需要的拿走。 手指上下滑动,可以滚动这个卡片;点击卡片顶部可以快速的展开或收起卡片。 [图片] 目前许多手机应用都使用了这种设计,使用 ScrollCard 的代码可以在微信小程序中快速的创建一个类似交互的卡片。 ScrollCard 主要使用了微信小程序的 scroll-view 组件,然后结合 CSS 的位置控制,从而实现视觉上的效果,几乎没有用到 Javascript(如果不需要点击卡片顶部快速展开或收起卡片,那么就完全不需要用到 Javascript)。 源码:https://github.com/impony/miniprogram-scrollcard
2020-06-10 - 小程序识别身份证,银行卡,营业执照,驾照
最近老是有同学问我小程序ocr识别的问题,就趁机研究了下,实现了小程序识别身份证,银行卡,驾照,营业执照,图片文字的功能。今天来给大家讲讲详细的实现流程。 先画一张流程图出来 [图片] 第一次看到这个流程图,可能有点萌,什么云开发,云函数。。。。 不要着急,我们接下来会一步步带大家实现。 先看下我们的页面和效果图。 [图片] 功能其实很简单,就是我们点对应的按钮后,去拍照或者去相册选择对应的图片。然后把图片上传到云存储,会有一个对应的图片url,然后把这个图片url传递到云函数,然后云函数里使用小程序的开发ocr能力,来识别图片,返回对应的信息回来。如下图所示,我们识别银行卡(身份证什么的就不演示了,涉及到石头哥个人隐私) [图片] 接下来就是代码的实现了。 一,首先要创建一个云开发的小程序项目 这里我前面文章有讲解过,就不再细说了,不会的同学去翻看下我之前的文章。或者看下我录制的 讲解视频 这里有一点需要注意的给大家说下 [图片] 二,创建一个简单的小程序页面 1,index.wxml如下 [图片] 2,index.js完整代码如下 [代码]Page({ //身份证 shenfenzheng() { this.photo("shenfenzheng") }, //银行卡 yinhangka() { this.photo("yinhangka") }, //行驶证 xingshizheng() { this.photo("xingshizheng") }, //拍照或者从相册选择要识别的照片 photo(type) { let that = this wx.chooseImage({ count: 1, sizeType: ['original', 'compressed'], sourceType: ['album', 'camera'], success(res) { // tempFilePath可以作为img标签的src属性显示图片 let imgUrl = res.tempFilePaths[0]; that.uploadImg(type, imgUrl) } }) }, // 上传图片到云存储 uploadImg(type, imgUrl) { let that = this wx.cloud.uploadFile({ cloudPath: 'ocr/' + type + '.png', filePath: imgUrl, // 文件路径 success: res => { console.log("上传成功", res.fileID) that.getImgUrl(type, res.fileID) }, fail: err => { console.log("上传失败", err) } }) }, //获取云存储里的图片url getImgUrl(type, imgUrl) { let that = this wx.cloud.getTempFileURL({ fileList: [imgUrl], success: res => { let imgUrl = res.fileList[0].tempFileURL console.log("获取图片url成功", imgUrl) that.shibie(type, imgUrl) }, fail: err => { console.log("获取图片url失败", err) } }) }, //调用云函数,实现OCR识别 shibie(type, imgUrl) { wx.cloud.callFunction({ name: "ocr", data: { type: type, imgUrl: imgUrl }, success(res) { console.log("识别成功", res) }, fail(res) { console.log("识别失败", res) } }) } }) [代码] 上面代码注释讲解的很清楚了,再结合我们的流程图,相信你可以看明白。 [图片] 三,重头戏来了,识别的核心代码是下面这个云函数 [图片] 云函数的完整代码也给大家贴出来 [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') cloud.init() // 云函数入口函数 exports.main = async(event, context) => { let { type, imgUrl } = event switch (type) { case 'shenfenzheng': { // 识别身份证 return shenfenzheng(imgUrl) } case 'yinhangka': { // 识别银行卡 return yinhangka(imgUrl) } case 'xingshizheng': { // 识别行驶证 return xingshizheng(imgUrl) } default: { return } } } //识别身份证 async function shenfenzheng(imgUrl) { try { const result = await cloud.openapi.ocr.idcard({ type: 'photo', imgUrl: imgUrl }) return result } catch (err) { console.log(err) return err } } //识别银行卡 async function yinhangka(imgUrl) { try { const result = await cloud.openapi.ocr.bankcard({ type: 'photo', imgUrl: imgUrl }) return result } catch (err) { console.log(err) return err } } //识别行驶证 async function xingshizheng(imgUrl) { try { const result = await cloud.openapi.ocr.vehicleLicense({ type: 'photo', imgUrl: imgUrl }) return result } catch (err) { console.log(err) return err } } [代码] 其实没什么特别的,就是用一个switch方法,根据用户传入的不同的type值,来实现不同的识别效果。 如用传入的type是‘ yinhangka’,我们就调用银行卡识别 [代码]try { const result = await cloud.openapi.ocr.bankcard({ type: 'photo', imgUrl: imgUrl }) return result } catch (err) { console.log(err) return err } [代码] 进而把识别的结果返回给小程序端,如下图 [图片] 到这里我们就完整的实现了,小程序识别身份证,银行卡,行驶证的功能。至于别的更多的ocr识别,可以去看小程序官方文档,结合着我的这篇文章,相信你也可以轻松实现更多的图片识别。 [图片] 源码其实在上面都已经贴给大家了,如果你觉得不完整,想要完整的源码可以在文章底部留言或者私信我。
2019-10-30 - 小程序上Typescript啦
期待已久的 Typescript 为什么要用 Typescript 关于 Typescript,可以看看以前写过的这篇《关于Typescript》。文末的故事,便是大多数情况下 Typescript 能帮我们解决的痛点。 过了很久之后,想法还是一样:Typescript 这事情,当你管理大点的应用的时候,就会感受到它的好处了。尤其涉及团队配合的时候! 当然,如果你的项目比较小,或是写个小公(工)举(具)、小 demo 的时候, store 状态管理、typescript 编译这些,除非已经很熟悉、没有额外成本的时候,才勉强适合接入。离了具体场景谈架构,都是耍(xia)流(che)氓(dan)。 为什么要用 Typescript? 变量类型不明确 之前带外包写小程序,除了代码风格不一致之外,还遇到一个会变的变量问题。 [代码]let formGroups = this.currentStep.formGroups; // 猜猜我的 formGroups 是数组数组 [[], [], []],还是对象数组 [{}, {}, {}]? let flattenFields = _.flatten(formGroups); // 不用猜了,我用个 flatten 抹平,它就一定是对象 [{}, {}, {}] 了! flattenFields.forEach(item => { if (item.fields) { // 猜猜我的 item.fields 是数组还是对象? flattenFields.push(..._.values(item.fields)); // 不用猜了,我用个 values 抹平,它就一定是对象了! } }); [代码] 当我帮忙 debug 个问题的时候,打断点看到: [图片] [图片] 喵喵喵??? [代码]# 我和外包童鞋的对话: 我:话说你这些到底是什么类型,从命名和上下文都看不出来。。 我:得去翻更细的代码。。 外包童鞋:values好像可以改一下试试 我:是数组还是对象? 外包童鞋:有的是数组,有的是对象 外包童鞋:一般带复数的是数组 我:(刀.jpg) 我:卧槽 我:你这item.fields,有时候是数组,有时候是对象,这样真的好吗 我:大哥 我:(刀.jpg)* 2 [代码] 接口协议不符合 [代码]前端:帮忙看看这个接口为什么返回失败了? 后台:你这个接口字段少了啊,这个xxx (哼哧哼哧修改) 前端:帮忙看看这个接口为啥又报错了啊? 后台:你这个字段类型不对...我协议里有写的 前端:喔不好意思我改 (哼哧哼哧修改) 前端:(泪光)帮忙看看这个接口为啥还报错? 后台:...你这字段名拼错了啊!!!! [代码] 当然,这个案例里稍微夸张了一点,一般我们都会自己一个个对着协议检查哪里不对,但是很多时候被 bug 光环环绕的时候,你就是发现不了问题。 这个时候,我们就可以用 Typescript 来管理接口啦。 [代码]interface IDemoResponse { date: string; someNumber: number; otherThing: any; } [代码] 1. 使用约定的变量的时候,会有相关提示(请忽略我的强行any)。 [图片] 2. 使用约定以外的属性时候,会报错提示。 [图片] 除此以外,还有很多很棒的用法呢~ 一键调整协议 前端和后台协议约定后,就开始各自开发了。但是,我们总会遇到各种各样的问题,可能导致我们的协议变更。 字段的变更什么的最讨厌了,例如后台要把某个接口下[代码]date[代码]改成[代码]day[代码]。一般来说前端是拒绝的,你不能说让我改我就得改,我得看看我写了多少代码,评估下工作量。 什么,全局替换?你知道使用[代码]date[代码]多普遍吗?万一我替换错了咋办?? 这时候,如果你使用了 Typescript 并定义了协议接口的话,就很好办了~ 依然是这段代码: [代码]interface IDemoResponse { date: string; someNumber: number; otherThing: any; } const demoResponse: IDemoResponse = {} as any; const date = demoResponse.date; [代码] 1. 选中需要重命名的属性。 [图片] 2. 按下F2,重新输入属性名。 [图片] 3. 按下回车,使用到的地方都会更新。 [图片] 是不是很酷~~~ 跨过 Babel 直接使用 ES6/ES7,跨过 eslint 直接使用 prettier 其实小程序工具本身也支持了不少的 ES6 新语法,不过像[代码]async/await[代码]这种,则还是需要自己搞个 Babel 来编译。 现在直接上 Typescript,连 Babel 都可以直接跳过啦。 Prettier 这里重点推荐 prettier 神器,也是团队配合的好工具啊: 项目代码没有配 eslint?导致每次拉下来的代码一大堆冲突? 团队成员使用不同的编辑器?有的没有自动格式化?导致拉下来代码还是一堆冲突? 用 standard?有些规范和实际项目不符合,但是偏偏没得改?? 偷偷地往项目里装个 Prettier,然后所有的矛盾都不见啦。不管你的代码格式多独特,最终在 Git commit 的时候,就被同化啦,而且 Prettier 的格式化也不会影响到 Git 记录。 小程序与 Typescript Typescript 编译下就可以用? 其实小程序它最终运行的还是 Javascript,那不是我们直接自己编译下就好了吗? 少年你太天真了。咱们写 Typescript 最重要的是什么呀?是 Typing 库呀! [图片] 网上开源的关于小程序和 Typescript 的工具或者脚手架也一大堆,为啥不用呢?因为小程序的 API 在不断地变化呀~ 有了官方的支持,即使小程序的 API 变了,我们也可以及时地更新呀(奸笑)~ 开箱即用的尝鲜 既然官方提供支持了,义不容辞地使用呀! 1. 首先,我们更新到最近的工具版本,然后创建项目就能看到了: [图片] 2. 创建模版,我们来看看代码长什么样子。 [图片] 我们可以看到,在 package.json 里面多了俩脚本,其实也就是将 ts 文件原地编译,然后上传代码的时候忽略掉了。 3. 仔细瞧瞧代码。 [图片] 额,好像混入了一些奇怪的东西进去,感叹号是什么鬼??? 后面问了下开发GG,是因为这里比较特殊,目前定义的文件暂时没法兼顾,等后面的版本会兼容。 [图片] 终于用上 Typescript 啦,爽歪歪~ 调整下代码结构 小项目的话,其实也不用带什么编译啦。不过如果你还想用 less,也想用 typescript,还不想看到项目下面乱糟糟的文件: [代码]index.js index.ts index.json index.less index.wxss index.wxml [代码] 我们就简单弄个 gulp,把编译加上吧~ [图片] 然后我们再把 prettier 愉快地加上。这里就不多讲解啦,大家也可以参考我的 demo 项目: wxapp-typescript-demo 对了,目前官方的 typing 库也不是非常完善,如果需要写组件、插件、小游戏的你,可能会面临一大堆的 any 冲击波噢~ [图片] 参考 小程序工具更新 结束语 Typescript 的普及度其实不算高,小程序的确是又一次给到惊喜。反观下我们自己呢?有没有被业务代码冲得找不到方向呢? 很多时候,我们总爱说写业务没啥技术提升,但真的是这样吗?我看过很棒的业务代码,从框架设计到具体的实现,开发者都对自己做了很高的要求。而写技术需求代码的,就一定会写得很好吗? “我们是业务部门,技术肯定不能比” “随便找一些能用的就好了,不要浪费时间在这些上面” “能用就行了,不要在意这些细节” … 写代码是个思考的过程,要对自己有点追求。当然项目急的时候可以理解,事后一定要把欠下的债务给还了。(较真脸)
2019-02-20 - 通过代码实现简单的卡片式个人中心
[图片] <view class="img"> <image src="/image/img/bg.png" /> </view> <view class="header"> <view class="content"> <view class="logo"> <image src="/image/img/logo.jpg" mode="aspectFill"/> </view> <view class="desc"> <text class="nickName">Clearlove</text> <text class="addr">河南 许昌</text> <text class="sign">Hello world~~~</text> </view> </view> </view> .header { height: 1000rpx; background-color: #eee; display: flex; align-items: center; justify-content: center; } .img { height: 260rpx; background-color: #eee; } .img image { width: 100%; height: 100%; } .content { height: 300rpx; width: 90%; background-color: white; position: absolute; top: 100rpx; display: flex; } .content .logo{ height: 200rpx; width: 200rpx; margin: 50rpx 0; margin-left: 40rpx; } .content .logo image{ height: 100%; width: 100% } .content .desc{ flex: 1; padding: 50rpx; } .content .desc text{ display: block; } .content .desc .nickName{ font-size: 38rpx; font-weight: bold; color: orange; } .content .desc .addr{ font-size: 30rpx; margin: 35rpx 0; } .content .desc .sign{ font-size: 30rpx; color: gray; }
2020-05-05 - [有点炫]自定义navigate+分包+自定义tabbar
自定义navigate+分包+自定义tabbar,有需要的可以拿去用用,可能会存在一些问题,根据自己的业务改改吧 大家也可以多多交流 代码片段:在这里 {"version":"1.1.5","update":[{"title":"修复 [复制代码片段提示] 无法使用的问题","date":"2020-06-15 09:20","imgs":[]}]} 更新日志: 2019-11-25 自定义navigate 也可以调用wx.showNavigationBarLoading 和 wx.hideNavigationBarLoading 2019-11-25 页面滚动条显示在自定义navigate 和 自定义tabbar上面的问题(点击“体验custom Tabbar” [图片] [图片] 其他demo: 云开发之微信支付:代码片段
2020-06-15 - 【好文】如何管理页面的多弹窗实现
如何管理页面的多弹窗实现 最近在做一个小程序老项目的关于页面活动弹窗的修改,很多人一听到“老”字,汗毛竖起;当然我也不例外,我大概总结了一下,存在以下问题: [代码]js[代码] 文件判断弹窗组件的逻辑代码嵌套多层 if else 判断,难以抽离; [代码]wxss[代码] 文件关于处理弹窗组件的样式也是嵌套了多层 wx:if 判断,代码十分难看,字段判断嵌套过深,组件元素处理过于分散; 编写弹窗初期没有做好前期规划,多种弹窗样式耦合其中,文件代码冗余过多,实在难以快速定位到合适的样式进行修改; 历史剖析 重构老代码最怕就是缺少个别的逻辑处理,导致在某一个条件下无法触发窗口弹出,出现bug; 重构毕竟是一个吃力不讨好的活儿,毕竟功能在线上运行了这么久没出问题,万一自己踩到雷区,接盘侠妥妥的了,背锅也是妥妥的了,还会惹来一身麻烦,更别提绩效了,这也是很多开发不愿意触碰老代码的最主要原因; 由于业务关系,本文不会用公司项目的代码作为例子,我会重新写一个例子以供大家参考,这是抽丝拔茧后的解决方案,希望可以帮助大家。 调研 我大概分析了一下,弹窗大致分为以下几种样式: 通知弹窗 (纯文本) 广告弹窗(大图) 活动弹窗(即:自定义弹窗,样式不定,有可能是领优惠券,有可能是特定节日活动) 小记:通知类型弹窗和广告大图弹窗是固定样式,可以作为一个组件单独实现;活动弹窗具有时效性,根据自己需要自行扩展实现。 文字通知弹窗 + 广告大图弹窗实现 场景描述: 很多时候运营需要在同个页面顺序弹出多个窗口(可以参考拼夕夕,一打开首页疯狂弹窗,让人抓狂),于是,我这里用到了上一篇文章提到的 发布订阅模式 实现,有需要的同学可以先去简单阅读一下。 编写通用组件实现 编写静态文件 [代码]// modal.wxml <view class="modal" wx:if="{{ show }}"> <view class="modalContent"> <!-- 文本弹窗 --> <view class="textModal" wx:if="{{ type == 'text' }}">{{ value }}</view> <!-- 广告大图弹窗 --> <view class="picModal" wx:elif="{{ type == 'pic' }}"> <image class="adImg" mode="widthFix" src="{{ value }}"></image> </view> <view class="customModal"> <slot name="customModal"></slot> </view> <view class="flex flex-center" bindtap="hide"> <image class="close-icon" src="/asserts/icon_bt_close_white.png" /> </view> </view> </view> [代码] 处理组件逻辑代码 [代码]// modal.js const Event = require('../../designPatterns/observer'); Component({ options: { multipleSlots: true, }, properties: { }, data: { value: '', type: 'pic', show: false, }, attached() { // 建立监听 Event.listen('modal', (props) => { this.setData({ ...props, show: true, }) }) }, methods: { hide() { this.setData({ show: false }) // 关闭弹窗通知回调页面,用于处理下一个弹窗继续弹出 this.triggerEvent('hide') }, show() { this.setData({ show: true }) } } }) [代码] 页面调用弹窗组件实现 页面引用组件 [代码]// modalPage.json { "usingComponents": { "modal": "/components/modal/modal" } } [代码] [代码]// modalPage.wxml <view> <modal bind:hide="triggerModal"/> </view> [代码] 页面触发弹窗调用 [代码]// modalPage.js const Event = require('../../designPatterns/observer'); Page({ // 弹窗mock数据 modalData: [ { id: 1, level: 10, // 弹出的顺序,数字越大代表越先弹出 type: 'text', value: '我是文本弹窗1', }, { id: 2, level: 11, type: 'pic', value: '/asserts/OIP.jpeg' }, { id: 3, level: 9, type: 'text', value: '我是文本弹窗2', } ], onLoad() { this.modalData = this.modalData.sort((prev, next) => -(prev.level - next.level)) this.triggerModal() }, triggerModal() { const targetModalList = this.modalData.splice(0, 1) if (targetModalList && targetModalList.length > 0) { // 继续触发弹窗 Event.trigger('modal', targetModalList[0]) } } }) [代码] 小记: 本着一次编码,幸福后代的原则,要是简单地把弹窗的管理交由各自页面单独处理,其实也是不美观的; 在我看来,弹窗们应该是有一个管理员的角色,用来管理它们,由管理员去负责它们(排序,触发等动作),接下来我们在自定义弹窗组件编码中来看看应该如何实现 自定义活动弹窗 多余代码不再复制累赘了,代码可以参考 [代码]/components/modal/activityModal[代码] 的相关文件 弹窗管理类:ModalManage [代码]// ModalManage.js const Event = require('../designPatterns/observer'); class ModalManage { // 弹窗类型,用于触发特定弹窗 modalType = { text: 'modal', pic: 'modal', activity: 'activityModal' } constructor(modalList) { // 初始化的时候需要排序,决定弹出优先级 this.modalList = modalList.sort((prev, next) => -(prev.level - next.level)) } triggerModal() { const targetModalList = this.modalList.splice(0, 1) if (targetModalList.length > 0) { const currentModal = targetModalList[0] // 发送弹窗通知 Event.trigger(this.modalType[currentModal.type], currentModal) } } } module.exports = ModalManage [代码] 页面触发弹窗调用 [代码]const Event = require('../../designPatterns/observer'); const ModalManage = require('../../model/ModalManage'); Page({ modalData: [ { id: 1, level: 10, type: 'text', value: '我是文本弹窗1', }, { id: 2, level: 11, type: 'pic', value: '/asserts/OIP.jpeg' }, { id: 3, level: 9, type: 'text', value: '我是文本弹窗2', }, { id: 4, level: 10, type: 'activity', value: '我是活动弹窗啦啦啦啦' } ], onLoad() { this.modalManage = new ModalManage(this.modalData) this.modalManage.triggerModal() }, triggerModal() { this.modalManage.triggerModal() } }) [代码] 小记:相比于第一次的处理方式,这次更加的浅显易懂,简单明了。 示例展示 [图片] 项目地址 项目地址:https://github.com/csonchen/mina-app 我想记录一些关于小程序日常开发所遇到的问题,进而引起的一些思考,能否给大家提供多一些角度去思考问题,解决问题,能帮助大家就好。希望大家多多支持,多多star哈
2020-05-29 - IM聊天教程:发送图片/视频/语音/表情
经常有朋友问起,如何在IM即时通讯中实现发送图片、视频、语音和表情? 为此,小编特意写了一个vue版本的Demo,实现了图片视频文件和表情的的发送,参考这个Demo源代码,相信你就可以轻松的用Uniapp和小程序完成类似的功能。 [图片] 本文的Demo全套的源码已经开源在码云上,供大家clone或者下载:https://gitee.com/goeasy-io/GoEasyDemo-vue-AudioPictureVideo 一、图片/视频/语音发送 对于语音、视频和图片的发送,您如果有注意的话,在使用QQ或者微信的时候,当有朋友发送图片和视频给您时,收到后,需要等一会儿才能显示出来。就是因为在发送的时候,只发送了文件的路径,您收到后,需要加载才能显示出来。因为当前主流的IM包括微信,QQ等对于图片和视频的发送,通常的做法都是: 上传文件到文件服务器 推送文件路径 收到文件路径 加载文件 并不会通过网络直接传送源文件,因为对于大文件的传输,会影响消息的即时性。 对于文件的上传,您可以选择直接上传到您自己的服务器,也可以选择上传到各种云服务的对象存储服务,也就是OSS上。 参考源码: [代码]DemoService.prototype.sendFileMessage = function (type,content) { let uploadResult = restapi.uploadFile(content); let message = new Message(type, uploadResult.url); uploadResult.promise.then(() => { this.publish(message); },() => { var error = new Message(MessageType.TEXT, "文件上传失败."); this.messages.unshift(error) }); return uploadResult.promise; }; [代码] 云服务的OSS具有更好的稳定性和高可用性,上传的速度也有保证,另外也可以和CDN配合,所以我们建议用GoEasy配合OSS服务来实现图片和视频的发送。 在本文的源码里,选择了使用阿里云的OSS作为文件上传服务器,您也可以切换为您自己实现的文件上传服务器,或者选择其他云服务的OSS,原理都是一样的。 二、发送表情 表情的发送也是非常简单的,只是对于一些第一次实现表情发送的同学来说,需要一个思路而已。 细心点的朋友,肯定有发现,当我们在QQ上聊天的时候,我们输入一个反斜杠+“cy”, 就像这样:/cy ,QQ就会立即显示为一个呲牙的表情,就像下图一样: [图片] 哈哈哈,相信你已经心里已经明白了十之八九了,对吧? 没错,表情在发送的过程中其实就是发一个像“/cy”这样定义好的的字符串,在对方收到后“翻译”成表情而已。 那为什么不直接发图片,而要进行这么复杂的“翻译”呢? 因为字符串比图片更小,发送的速度更快,用户体验更好。一个系统中的用户成千上万,用字符串可以节约大量的带宽,节约系统资源。 原理讲明白了,我们就开始干活儿吧: 第一步、定义表情 定义一个key value的对象,key作为表情标签,value则为每个表情标签对应的图片: [代码]let expressions = { "[risus]": './images/risus.png', "[kiss]": './images/kiss.png', "[cry]": './images/cry.png', "[die]": './images/die.png', "[anger]": './images/anger.png', } [代码] 然后画一个表情选择的界面: [图片] 第二步、选择表情 为每个图片的onclick事件中传入这个表情的字符串标签,当用户点击的时候,将表情的标签写入输入框,就成为了一个普通的字符串。在发送的时候,发送的其实就是这个表情的标签,也就是一个字符串。 [代码]<div class="goeasy-expression"> <div :class="[appearanceClass, 'goeasy-appearance']" @click="show = true">{{text}}</div> <div class="expression-container" v-show="show"> <div class="expression-icon-content"> <div class="expression-icon__item" v-for="expression in list" :key="expression.id" @click="selectExpression(expression)"> <img :src="expressions[expression.tag]"> </div> </div> <div class="close-expression" @click="show = false"></div> </div> </div> [代码] 第三步、收到表情和展示表情 当对方收到一个字符串后,跟第一步定义的key-value列表去匹配,如果能找到对应的表情,就在页面上展示对应的表情图片,如果找不到,就是一个普通的文本信息。 原理讲清楚了后,具体实现是不是很简单了? 参考我们提供的Demo源代码,相信你很快就能掌握实现方法。 Demo源码:https://gitee.com/goeasy-io/GoEasyDemo-vue-AudioPictureVideo GoEasy系列教程: 搭建websocket消息推送服务,必须要考虑的几个问题 websocket IM聊天教程-教你用GoEasy快速实现IM聊天 Websocket直播间聊天室教程-GoEasy快速实现聊天室 微信小程序使用GoEasy实现websocket实时通讯 Uniapp使用GoEasy实现websocket实时通讯 IM聊天教程:发送图片/视频/语音/表情
2020-05-21 - 订阅信息恶心至极?没有服务器的我们也能直接发消息到微信(公众号)! [即抄即用,拎包入住]
更新时间(2020/12/2) 大家好,众所周知,今年左右新出的订阅消息对商家和用户的友好度都极低,少了一个直接发信息到微信的最重要的渠道。 [图片] 那么我们动动歪心思,直接发消息到公众号(其实这是不是wx的本意???)。 所有服务号都可以在功能->添加功能插件处看到申请模板消息功能的入口,但只有认证后的服务号才可以申请模板消息的使用权限并获得该权限。 为方便公众号用户方便、快捷地接入小程序服务,公众号用户可复用公众号资质创建小程序。当前每个账号的模板消息的日调用上限为10万次,单个模板没有特殊限制。当账号粉丝数超过10W/100W/1000W时,模板消息的日调用上限会相应提升。这还不美滋滋吗,对于小商家来说等于无限次了。 所以我们一开始最好就在公众号进行微信认证,复用公众号的资质来注册小程序(注意,可以复用5个(好像))。 (我付出了¥300的惨痛代价)。 -------------------------------------------------------------------------------- 以上搞定后,流程走起 重点中的重点:为您的函数申请公网固定ip 有了这个ip之后,没有服务器的我们(穷)就能绕过ip白名单,这不正是真正的云开发精神吗? [图片] 0.用你的小程序账号登陆腾讯云,并在里面新建一个云函数 [图片]>>[图片] 选择你要发消息到公众号的小程序 [图片] [图片] -----------------我是2020/10/23的拎包哥----------------------- 创建并选择角色 记得在 https://console.cloud.tencent.com/cam/role 创建角色 [图片] 勾选tcb,scf [图片] 在搜索框里搜scf,tcb后,有什么策略就勾选什么策略 [图片] [图片] 创建角色名 [图片] 在云函数启用刚刚新建的角色 [图片] ps. 记得做好这一步,不然各种报错 missing authorationo key // 报错:缺失授权键 you are not authorized to xxx // 你没有权限去xxx 1.打开 https://cloud.tencent.com/document/product/583/38198 ,申请白名单 [图片] 2.审核通过后,再跟着步骤走。 [图片] 最后你的云函数会得到一个公网固定ip [图片] 3.开始码云函数的代码 如下,记得把wx-server-sdk和request-promise的包都npm下来。 依赖包可以通过这里上传上来 [图片] 我的做法 把获取的accessToken储存在云开发的数据库里。这样就不用担心access token的生成次数超过限制了 'use strict'; const cloud = require('wx-server-sdk') cloud.init() const db = cloud.database() const resInfo = db.collection('resInfo') const appid = '公众号的APPID'; // APPID const secret = '公众号的密钥'; // Secret const rp = require('request-promise') exports.main_handler = async (event, context, callback) => { var that = this var options = { url: 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=' + appid + '&secret=' + secret, json: true } return await rp(options) .then(async res1 => { return await resInfo.where({name:'outInfo'}).update({ data: { access_token: res1.access_token } }).then(res2 => { return res2 }) }).catch(err => { return err }) } 公众平台以access_token为接口调用凭据,来调用接口,所有接口的调用需要先获取access_token,access_token在2小时内有效,过期需要重新获取,但1天内获取次数有限,开发者需自行存储,详见获取接口调用凭据(access_token)文档。4.设置触发器(触发器偶尔会失灵,所以最好是59min触发一次)。 [图片] 权限设置 由于云函数的访问不存在openid,所以安全规则必须为任何人可读可写。 [图片] 5.有了可以稳定刷新的access_token后,根据需求挑选你的公众号模板消息,开始你的表演。 例如:做餐饮小程序的朋友都想用户下单后发送订单信息到商户。那么就需要 获取商户的openid 步骤 注:公众号的openid在小程序开发工具就可以查出来 5.1 获取公众号openid列表 https://api.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN 注意我没有加上next_openid = NEXT_OPENID,是为了取出公众号的所有的openidopenid列表在 res.data.openid5.2 for循环openid列表,根据商户的微信信息(nickname,city等等)找出只属于商户的openid https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN --------------------------------------- var openidList = openid列表 for(var i in openidList){ wx.request({ url:'https://api.weixin.qq.com/cgi-bin/user/info?access_token='+ openList[i] + '&lang=zh_CN', success(){ console.log(....) } }) } 这里就不用unionid了,不用浪费时间在上面), 就可以使用只发给商户的模板消息了。 公众号对应文档链接 https://developers.weixin.qq.com/doc/offiaccount/User_Management/Getting_a_User_List.htmlhttps://developers.weixin.qq.com/doc/offiaccount/User_Management/Get_users_basic_information_UnionID.html#UinonId, -----------------------------------------效果图--------------------------------------- [图片] -------------------------------------------------------------------------------- 哎,差不多了,感觉是有点折腾,如果感觉还是不够直白的,可以指出来我继续补充 。 求点赞,你的评论就是对拎包哥最大的支持。 [图片][图片] ===================更新于2020/10/23======================
2020-12-02 - 小程序图片裁剪插件 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 - 腾讯云 Web 直播互动组件
腾讯云 Web 直播互动组件 简介 腾讯云 Web 直播互动组件,以腾讯云 Web 超级播放器 - TcPlayer 和腾讯云即时通信 IM - TIM 为基础,封装了简单易用的 API,提供了免费开源的 Demo,方便开发者快速接入和使用。适用于 Web 直播互动场景,如大型会议、活动、课程、讲座等的在线直播,带货直播的微信 H5 分享等,效果如下: [图片] [图片] 在线体验 微信扫一扫二维码 [图片] 或者 点我体验 开发背景 前端开发同学经常遇到这样的需求: 项目周期赶,甲方爸爸急着要,或者公司要做推广活动,只给了不到一周的时间(╮(╯▽╰)╭,业界常态) 微信扫一扫、或者用手机浏览器扫一扫就能看直播,并且能跟其他看直播的人聊天互动,也能点赞、送礼(给我做一个虎牙或者斗鱼的那种直播效果出来!) 在 Windows 或 Mac 浏览器上也需要上述的功能(小孩子才做选择,产品经理:我全都要!) 开发同学接到这样的需求,一般会怎么实现呢?对 Web 直播有一定了解的会选择 flv.js 或者 hls.js 来播放直播源。聊天互动用 websocket 快速写一个简单的消息收发 demo。然而写原型 demo 不难,但接下来会遇到到很多挑战: 服务器该怎么布点才能让用户就近接入?遇到蜂拥请求,服务器扛不住并发压力怎么办? 直播活动人数往往较多,全国各地的用户访问,消息通道建立不起来怎么办? 短时间内自研一个 IM Server,如何保证服务高可用? 消息量大,IM Server 推送消息遇到性能问题,导致消息堆积,或者丢消息怎么办? 用户在直播间骂人,发表涉黄、涉政言论怎么办? 如果用第三方 IM 服务,选择谁好呢?万一有坑,反馈问题没人理,导致项目延期怎么办? 第三方 IM 服务往往有一大堆概念和 API,需要花时间熟悉和使用,留给开发业务逻辑的时间太仓促 由此可见,在短时间内如果自己从头开始组装开发,往往是加班加点,赶鸭子上架,开发同学身心俱疲,直播效果也不一定好。现在直播这么火热,难道就没有一个开源的,组合了直播和聊天互动功能的项目,让我稍微改一改就能用起来么? 腾讯云终端研发中心 Web 团队,开发了腾讯云 Web 超级播放器和即时通信 IM SDK(还有其它 SDK 暂且按下不表←_←),面对这样常见的需求和痛点,于是以这两个可靠优秀的产品为基础,开发了开源的腾讯云 Web 直播互动组件,供开发者使用和参考。 对开发者和项目有什么好处? 1、为开发者节省大量重复造轮子的时间,可专注于开发业务逻辑 使用腾讯云 Web 直播互动组件,开发者仅需在下载好 SDK 后简单填入几个参数,即可快速把一个包含直播视频播放、聊天互动、点赞送礼等常见功能的项目跑起来。如下所示: [代码]// npm i tweblive import TWebLive from 'TWebLive'; let options = { SDKAppID: 0, // 接入时需要将0替换为您的云通信应用的 SDKAppID domID: "id_test_video", // 页面上播放器容器 ID,如 <div id="id_test_video" style="width:100%; height:auto;"></div> // 必须同时同时填入 hls 和 flv 流地址 // 在支持 MSE 的浏览器上,直播组件会优先选择使用 flv 直播源,延时更低,直播效果更好 // 在不支持 MSE 的浏览器上,直播组件会使用 hls 直播源,延时稍大,但在移动端适应性好 m3u8: "http://200002949.vod.myqcloud.com/200002949_b6ffc.f0.m3u8", // 请替换成实际可用的播放地址 flv: "http://200002949.vod.myqcloud.com/200002949_b6ffc.f0.flv" // 请替换成实际可用的播放地址 }; // 创建实例 let tweblive = new TWebLive(options); // SDK 进入 ready 状态时触发,接入侧监听此事件,然后可调用 SDK 发送消息等api,使用 SDK 的各项功能 let onIMReady = function() { tweblive.sendTextMessage({ roomID: 'TWebLiveDeveloperHub', // 替换为已加入的直播间 ID text: 'hello from TWebLive' }).then(function(res) { console.log('demo sendTextMessage OK', res); }).catch(function(err) { console.log('demo sendTextMessage failed', err); }); } tweblive.on(TWebLive.EVENT.IM_READY, onIMReady); // 收到直播间其他人发的文本消息 let onTextMessageReceived = function(event) { event.data.forEach(function(message) { // 有昵称则用昵称,无昵称用 userID console.log('demo ' + (message.nick || message.from) + ' 说: ', message.payload.text); }); } tweblive.on(TWebLive.EVENT.TEXT_MESSAGE_RECEIVED, onTextMessageReceived); // 收到直播间其他人发的送礼、点赞等自定义消息 let onCustomMessageReceived = function(event) { event.data.forEach(function(message) { console.log('demo ' + 'data:' + message.payload.data + ' description:' + message.payload.description + ' extension:' + message.payload.extension); }); } tweblive.on(TWebLive.EVENT.CUSTOM_MESSAGE_RECEIVED, onCustomMessageReceived); // 收到其他人加入直播间的通知 let onRemoteUserJoin = function(event) { event.data.forEach(function(message) { // 有昵称则用昵称,无昵称用 userID console.log('demo ' + (message.nick || message.payload.userIDList[0]) + ' 来了'); }); } tweblive.on(TWebLive.EVENT.REMOTE_USER_JOIN, onRemoteUserJoin); // 更多事件请参考:https://imsdk-1252463788.file.myqcloud.com/IM_DOC/Web/module-EVENT.html // 加入直播间,未登录时匿名加入直播间,只能收消息,不能发消息 tweblive.enterRoom(""); // 接入时填要加入的直播间 ID,对应于 IM 系统的直播大群(AVChatRooM)的 groupID [代码] 2、为开发者节省大量定位和解决问题的时间 直播场景观众数量多,消息量大时,自研服务容易出现性能瓶颈,如服务器扛不住并发压力导致请求成功率低、消息堆积、丢消息、消息收发延时严重等难以排查和难以解决的问题。腾讯云 Web 直播互动组件的消息服务集成的是腾讯云即时通信 IM,以 QQ 多年的 IM 能力为基础,保证高并发、高可靠的即时通信能力,且有完善的统计和日志排障系统,遇到问题可快速定位解决。 腾讯云 Web 直播互动组件支持设置消息优先级,如主播发言、观众送礼物等可设置为 高优先级,点赞等不重要的消息可设置为 低优先级,IM 系统会保证高优先级消息的下发(直播间消息量超过40条/秒时 IM 后台会限频)。 3、为项目节省大量的开发和运维成本 腾讯云 Web 直播互动组件是完全免费开源的,其集成的腾讯云 Web 超级播放器是免费的,仅腾讯云即时通信 IM 是增值服务。如果您的项目处于起步阶段,可以使用免费版的 IM 服务。项目发展好,用户量大时,可以购买 IM 旗舰版,价格非常美丽,全行业最低。 腾讯云即时通信 IM 的直播大群(AVChatRoom),群成员人数无上限(这个厉害了,让我叉会腰)。 腾讯云 IM 服务器的节点覆盖面广,可保证用户就近接入,且不用担心服务器扩容问题。 支持针对涉黄、涉政以及不雅词的安全打击,满足安全监管需求。 腾讯云 IM 服务团队响应即时,为您的项目保驾护航。 开发者需要做什么? 1、提供直播源,推荐用 腾讯实时音视频 TRTC 的 旁路推流 为了同时兼容 PC 和移动端,开发者必须同时提供 flv 和 hls 两种格式的直播源,在支持 MSE 的浏览器上,直播组件会优先选择使用 flv 直播源,延时更低,直播效果更好;在不支持 MSE 的浏览器上,直播组件会使用 hls 直播源,延时稍大,但在移动端适应性好。 如果在 Windows 或 Mac 平台推直播流,强烈建议使用 TRTC Electron,旁路推流可同时生成 flv 和 hls 流,跟腾讯云即时通信 IM 完美结合,稳定可靠,服务周到,价格美丽。 跑通 Electron Demo 这篇文档会帮您快速实现直播和旁路推流,效果如下: [图片] 2、注册腾讯云即时通信 IM 应用 在 即时通信 IM 控制台 注册应用,获得 SDKAppID。 生成 UserSig。 用 REST API 向 IM 系统 导入账号。 在 即时通信 IM 控制台 或者用 REST API 创建直播大群(AVChatRoom) 3、在腾讯云 Web 直播互动组件的基础上,开发相关业务逻辑 常见问题 1、进入直播间,其他人看到的提示信息和聊天消息都用的是 [代码]userID[代码],能否支持用昵称(nick)展示? 如果进直播间想要展示昵称,需要先设置昵称(已设置过可忽略此步骤),设置成功后再加入 [代码]// 只有已登录的用户才能修改自己的昵称 tweblive.setMyProfile({ nick: "胡八一" }).then(() => { tweblive.enterRoom(""); // 填要加入的直播间 ID,对应于 IM 系统的直播大群(AVChatRooM)的 groupID }); [代码] 2、组件什么时候会选择播放 flv 流?flv 和 hls 直播源的播放时延分别是多少? 在支持 MSE 的浏览器上,如 PC Chromium 内核浏览器(360极速浏览器,Chrome浏览器等),或者 TBS 模式下(Android 的微信、QQ 浏览器),组件会优先选择播放 flv 流。播放时延对比: 浏览器 播放时延 Windows Chrome 浏览器 3s~5s Mac Chrome 浏览器 3s~5s Mac Safari 浏览器 10s~20s iOS Safari 浏览器 10s~20s iOS 微信 10s~20s Android 微信(TBS) 3s~5s Android QQ 浏览器 3s~5s Android Chrome 浏览器 3s~5s Android 其它浏览器 10s~20s 参考文档 腾讯云 Web 直播互动组件 API 腾讯实时音视频 TRTC TRTC Electron API 腾讯云 Web 超级播放器 TcPlayer 腾讯云即时通信 IM WebIM API
2020-06-24 - 如何只使用一个云函数搞定一个庞大而复杂的系统
吐槽 翻遍社区的文章,关于云开发的干货,少之又少,大部分都还是官方文档的搬来搬去,没啥营养,是时候放出一点技术"干货"了(有经验的开发者都能想到的方案)! 正题 小程序云开发的云函数的最大限制是 [代码]50[代码] 个,假设每个接口都使用 [代码]1[代码] 个云函数的话,有 [代码]10[代码] 张表,每张表都有 [代码]增删改查[代码] 四个接口,那么就会有 [代码]40[代码] 个接口,再加上一些其他接口,差不多刚刚好够用,那如果有 [代码]20[代码] 张表,甚至更多的表、更多的接口呢?对于中小型的小程序来说足够使用,那如果一个非常庞大而复杂的系统该怎么办呢? 而且每一个云函数的运行环境是独立的,想要共享一些数据也不是特别方便,那么有没有什么办法突破这样的限制呢? 其实解决方案很简单,只需要一点点的 [代码]OOP[代码] 思想和利用 [代码]JavaScript[代码] 的特性,一个云函数就可以搞定所有的接口。 具体的实现请往下看。 思路 云函数的运行环境是 [代码]Nodejs[代码] , 那么使用的语言就是 [代码]JavaScript[代码] ,可以充分的利用 [代码]JavaScript[代码] 的特性。 [代码]JavaScript[代码] 中的 [代码]属性访问表达式[代码] 有两种语法 [代码]expression . identifier expression [ expression ] [代码] 第一种写法是一个表达式后跟随一个句点 [代码].[代码] 和一个标识符。表达式指定对象,标识符则指定需要访问的属性的名称。 第二种写法是使用方括号 [代码][][代码],方括号内是另一个表达式(这种方法适用于对象和数组)。第二个表达式指定要访问的属性的名称或者代表要访问数组元素的索引。 不管使用哪种形式的属性访问表达式,在 [代码].[代码] 和 [代码][][代码] 之前的表达式总是会首先计算。 虽然 [代码].[代码] 的写法更加简单,但这种方式只适用于要访问的属性名称的合法标识符,并需要准确知道访问的属性的名字,如果属性的名称是一个保留字或者包含空格和标点符号,或者是一个数字(对于数组来说),则必须使用方括号 [代码][][代码] 的写法。当属性名是通过运算得出的值而不是固定值的时候,这时也必须使用方括号 [代码][][代码] 写法。 感谢社区大神 @卢霄霄 提供参考资料,详见 [代码]JavaScript权威指南[代码] (犀牛书)4.4章节。 可以使用 [代码][][代码] 的形式来完成动态的属性访问。具体实现请往下看。 实现 上面说了太多废话了,下面直接开干吧。 新建云函数 在云开发目录中新建一个云函数,我这里命名为 [代码]cloud[代码]。 打开 [代码]index.js[代码] 文件你会看到下面这段代码 [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') // 初始化 cloud cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) // 云函数入口函数 exports.main = async (event, context) => { const wxContext = cloud.getWXContext() return { event, openid: wxContext.OPENID, appid: wxContext.APPID, unionid: wxContext.UNIONID, } } [代码] 这个云函数仅作为入口使用,上面提到了云函数的运行环境是 [代码]Nodejs[代码] 那么 [代码]Nodejs[代码] 的特性也是可以使用的,这里主要用到的是全局对象 [代码]global[代码],详见文档 在文件中,写入一些必要的全局变量,主要还是云数据库方面的,方便后面使用。 在初始化后面插入代码 [代码]global.cloud = cloud global.db = cloud.database() global._ = db.command global.$ = _.aggregate [代码] 这样就可以在同一个云函数环境中直接访问这些全局变量。 创建公共类 然后新建一个文件夹,我这里命名为 [代码]controllers[代码] ,这个文件夹用于存放所有的接口。 在 [代码]controllers[代码] 中新建一个 [代码]base-controller.js[代码] 文件,创建一个叫做 [代码]BaseController[代码] 的类,用于提供一些公用的方法。 内容如下: [代码]class BaseController { /** * 调用成功 */ success (data) { return { code: 0, data } } /** * 调用失败 */ fail (erroCode = 0, msg = '') { return { erroCode, msg, code: -1 } } } module.exports = BaseController [代码] 看到这里大家可能有点没看懂在做什么,那么请继续往下看。 创建接口 假设创建一些要操作用户相关的的接口,可以在 [代码]controllers[代码] 文件夹中新建一个 [代码]user-controller.js[代码] 的文件,创建一个名为 [代码]UserController[代码] 的类,并继承上面的 [代码]BaseController[代码] 类,内容如下: [代码]const BaseController = require('./base-controller.js') class UserController extends BaseController { // ... } module.exports = UserController [代码] 可以在这个类中编写所有关于 [代码]user[代码] 的接口方法。 编写接口 假设要分页查询用户信息,可以在 [代码]UserController[代码] 类中创建一个 [代码]list[代码] 方法。 代码如下: [代码]async list (data) { const { pageIndex, pageSize } = data let result = await db.collection('users') .skip((pageIndex - 1) * pageSize) .limit(pageSize) .get() .then(result => this.success(result.data)) .catch(() => this.fail([])) return result } [代码] 由于上面已经定义了全局变量 [代码]db[代码] 所以在 [代码]UserController[代码] 中无需引入 [代码]wx-server-sdk[代码] 引入接口类 写到这里接口已经完成了,还需要再引入这些接口类才可以进行访问。在 [代码]index.js[代码] 中引入 [代码]user-controller.js[代码] [代码]const User = require('./controllers/user-controller.js') [代码] 然后创建一个 [代码]api[代码] 变量,[代码]new[代码] 一个 [代码]User[代码] 实例 [代码]const api = { user: new User() } [代码] 在 [代码]main[代码] 方法中调用 [代码]UserController[代码] 中的方法。 [代码]exports.main = async (event, context) => { const { data } = event let result = await api['user']['list'](data) return result } [代码] 写到这里基本已经完成了接口的调用,但想要一个云函数动态调用所有接口还需要做一些改动。 动态调用接口 刚开始的时候介绍了 [代码]属性访问表达式[代码],限制稍微改动一下 [代码]main[代码] 方法 [代码]exports.main = async (event, context) => { const { controller, action, data } = event const result = await api[controller][action](data) return result } [代码] 在小程序调用云函数时,需要传入 [代码]controller[代码]、[代码]action[代码] 和 [代码]data[代码] 参数即可 [代码]const result = await wx.cloud.callFunction({ name: 'cloud', data: { controller, action, data } }) [代码] 完整 [代码]index.js[代码] 文件的代码 [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') const User = require('./controllers/user-controller.js') const api = { user: new User() } // 初始化 cloud cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) global.cloud = cloud global.db = cloud.database() global._ = db.command global.$ = _.aggregate // 云函数入口 exports.main = async (event, context) => { exports.main = async (event, context) => { const { controller, action, data } = event const result = await api[controller][action](data) return result } } [代码] 其他实现 云开发官方团队打造的轮子 tcb-router
2020-05-26 - 小程序富文本解析
wxParse 微信小程序富文本解析 原因 由于原作者仓库 wxParse 不再维护,我们项目中商品信息展示又是以wxParse这个用做富文本解析的; 于是乎,决定采用 递归Component 的方式对其进行重构一番; 原项目使用的 [代码]template[代码] 模板的方式渲染节点,存在以下问题: 节点渲染支持到12层,超出会原样输出 [代码]html[代码] 代码; 每一层级的循环模板都重复了一遍所有的可解析标签,代码十分臃肿。 [代码]li[代码]标签不支持 [代码]ol[代码] 有序列表渲染(统一采用的是 [代码]ul[代码] 无序列表),[代码]a[代码]标签无法实现跳转,也无法获取点击事件回调等等; 节点渲染没有绑定 [代码]key[代码] 值,一是在开发工具看到一堆的 [代码]warning[代码]信息(看着十分难受),二是节点频繁删除添加,无法比较[代码]key值[代码],造成 [代码]dom[代码] 节点频繁操作。 功能标签 目前该项目已经可以支持以下标签的渲染: audio标签(可自行更换组件样式,暂时采用微信公众号文章的[代码]audio[代码]音乐播放器的样式处理) ul标签 ol标签 li标签 a标签 img标签 video标签 br标签 button标签 h1, h2, h3, h4标签 文本节点 其余块级标签 其余行级标签 支持 npm包 引入 [代码]npm install --save wx-minicomponent [代码] 使用 原生组件使用方法 克隆 项目 代码,把 components目录 拷贝到你的小程序根目录下面; 在你的 page页面 对应的 [代码]json[代码] 文件引入 [代码]wxParse[代码] 组件 [代码]{ "usingComponents": { "wxParse": "/components/wxParse/wxParse" } } [代码] 组件调用 [代码]<wxParse nodes="{{ htmlText }}" /> [代码] npm组件使用方法 安装组件 [代码]npm install --save wx-minicomponent [代码] 小程序开发工具的 [代码]工具[代码] 栏找到 [代码]构建npm[代码],点击构建; 在页面的 json 配置文件中添加 [代码]wxParse[代码] 自定义组件的配置 [代码]{ "usingComponents": { "wxParse": "/miniprogram_npm/wx-minicomponent/wxParse" } } [代码] [代码]wxml[代码] 文件中引用 wxParse [代码]<wxParse nodes="{{ htmlText }}" /> [代码] 提示:详细步骤可以参考小程序的npm使用文档 补充组件:代码高亮展示组件使用 在 [代码]page[代码]的 [代码]json[代码] 文件里面引入 [代码]highLight[代码] 组件 原生引入: [代码]{ "usingComponents": { "highLight": "/components/highLight/highLight" } } [代码] npm组件引入: [代码]{ "usingComponents": { "highLight": "/miniprogram_npm/wx-minicomponent/highLight" } } [代码] 组件调用 [代码]<highLight codeText="{{codeText}}" /> [代码] 参数文档 wxParse:富文本解析组件 参数 说明 类型 例子 nodes 富文本字符 String “<div>test</div>” language 语言 String 可选:“html” | “markdown” (“md”) 受信任的节点 节点 例子 audio <audio title=“我是标题” desc=“我是小标题” src=“https://media.lycheer.net/lecture/25840237/5026279_1509614610000.mp3?0.1” /> a <a href=“www.baidu.com”>跳转到百度</a> p div span li ul ol img button h1 h2 h3 h4 … highLight:代码高亮解析组件 参数 说明 类型 例子 codeText 原始高亮代码字符 String “var num = 10;” language 代码语言类型 String 可选值:“javascript/typescript/css/xml/sql/markdown” 提示:如果是html语言,language的值为xml wxAudio:仿微信公众号文章音频播放组件 参数 说明 类型 例子 title 标题 String “test” desc 副标题 String “sub test” src 音频地址 String 示例展示 富文本解析 html文本解析实例 [图片] markdown文本解析实例 [图片] 代码高亮 [图片] 更新历史 2020-5-31 迁移utils目录到wxParse目录下; 富文本增加markdown文本解析支持; 2020-5-21: 富文本组件image标签添加loading过渡态,优化图片加载体验 2020-5-17: 完善组件参数文档,增加wxParse对audio标签标题,副标题的解析功能 2020-5-13: 增加css,html,ts,sql,markdown代码高亮提示支持 2020-5-6: 增加图片预览功能 项目地址 项目地址:https://github.com/csonchen/wxParse
2020-06-01 - wxParser 插件更新,让你在小程序中更快速更完美部署富文本
作者:知晓云 - 小程序开发快人一步 [图片] wxParser 小程序插件时隔一年终于更新了。这次更新主要针对在 gitthub 上同学们提出的 issue 进行一些特性的更新,更新后的 wxParser 将支持更多可配置项,让富文本更完美地在小程序端显示。 新特性:image-lazy-load 属性,支持给所有图片设置懒加载;image-preview 属性设置图片是否支持点击放大,默认 true,点击放大;bindImgLoad 事件,监听小程序 image 标签的 bindload ,即图片加载完成的事件;组件生命周期的 ready, attached, detached 的监听事件,通 bind:ready,bind:attached, bind:detached 进行监听;支持外挂 class,可以在 wxss 文件给富文本内容添加自定义样式。插件能做什么?「wxParser」小程序插件由知晓云团队发布,经过对微信小程序富文本渲染引擎 wxParser 进行一层封装,解决了使用起来太过麻烦的问题。 使用「wxParser」插件并配合富文本编辑器,可以很方便地开发内容展示类小程序,使用知晓云富文本编辑器效果更佳。例如「知晓课堂」小程序中的微信小程序开发课程便是使用「wxParser」插件配合知晓云内容库完成的。 [图片] 以对在知晓云编写的富文本内容进行解析为例,进入到知晓云控制台后,点击左侧「内容」菜单项,进入内容库管理面板,添加内容,即可看见富文本编辑器。编辑的内容(左)即经过「wxParser」解析后的样式(右)如下: [图片][图片] [图片] 当然,并非一定要使用知晓云的内容库才能使用「wxParser」,例如你可以使用百度的 UEditor 富文本编辑器编写你的内容,然后将生成的 HTML 配置到「wxParser」即可。 如何接入「wxParser」插件?在小程序中使用「wxParser」,你需要在项目中引入「wxParser」的 JS 库,同时,需要在相应的 WXML、WXSS 和 JS 文件中引入「wxParser」的模板、样式文件和编写初始化代码,少了任何一步,程序都不能正常工作。 而在使用「wxParser」小程序插件后,不再需要引入「wxParser」JS 库了,你可以像使用普通组件一样使用「wxParser」,只需要对组件的属性进行配置即可,省去了引入多个库文件的操作。 1. 申请使用插件在「小程序管理后台 - 设置 - 第三方设置 - 插件管理」中查找插件名称「wxParser」(appid: wx9d4d4ffa781ff3ac),并申请使用。 [图片] 2. 引入插件代码version 表示目前插件版本为 0.3.0,provider 为该插件的 AppID,而 wxparserPlugin 为自定义的插件名称。 "plugins": { "wxparserPlugin": { "version": "0.3.0", "provider": "wx9d4d4ffa781ff3ac" } } 3. 在需要使用到该插件的小程序页面的 JSON 配置文件中,做如下配置: "usingComponents": { "wxparser": "plugin://wxparserPlugin/wxparser" } } 4. 设置你的富文本内容,定义为 richText:Page({ data: { richText: '<h1>Hello world!</h1>' } }) 最后在需要展示富文本内容的地方,使用「wxParser」组件,为 rich-text 属性赋值上你的富文本内容即可。 <wxparser rich-text="{{richText}}" /> 你是否正好有内容展示类的小程序,亦或准备开发一个?查看开发文档,立即体验「wxParser」小程序插件。 扫描下方二维码,体验文章所提及的「知晓课堂」小程序。并带你遨游在小程序开发的知识海洋中。 [图片]
2020-03-06 - 如何在小程序中快速实现环形进度条
在小程序开发过程中经常涉及到一些图表类需求,其中环形进度条比较属于比较常见的需求 [图片] [中间的文字部分需要自己实现,因为每个项目不同,本工具只实现进度条] 上图中,一方面我们我们需要实现动态计算弧度的进度条,还需要在进度条上加上渐变效果,如果每次都需要自己手写,那需要很多重复劳动,所以决定为为小程序生态圈贡献一份小小的力量,下面来介绍一下整个工具的实现思路,喜欢的给个star咯 https://github.com/lucaszhu2zgf/mp-progress 环形进度条由灰色底圈+渐变不确定圆弧+双色纽扣组成,首先先把页面结构写好: .canvas{ position: absolute; top: 0; left: 0; width: 400rpx; height: 400rpx; } 因为进度条需要盖在文字上面,所以采用了绝对定位。接下来先把灰色底圈给画上: const context = wx.createContext(); // 打底灰色曲线 context.beginPath(); context.arc(this.convert_length(200), this.convert_length(200), r, 0, 2*Math.PI); context.setLineWidth(12); context.setStrokeStyle('#f0f0f0'); context.stroke(); wx.drawCanvas({ canvasId: 'progress', actions: context.getActions() }); 效果如下: [图片] 接下来就要画绿色的进度条,渐变暂时先不考虑 // 圆弧角度 const deg = ((remain/total).toFixed(2))*2*Math.PI; // 画渐变曲线 context.beginPath(); // 由于外层大小是400,所以圆弧圆心坐标是200,200 context.arc(this.convert_length(200), this.convert_length(200), r, 0, deg); context.setLineWidth(12); context.setStrokeStyle('#56B37F'); context.stroke(); // 辅助函数,用于转换小程序中的rpx convert_length(length) { return Math.round(wx.getSystemInfoSync().windowWidth * length / 750); } [图片] 似乎完成了一大部分,先自测看看不是满圆的情况是啥样子,比如现在剩余车位是120个 [图片] 因为圆弧函数arc默认的起点在3点钟方向,而设计想要的圆弧的起点从12点钟方向开始,现在这样是没法达到预期效果。是不是可以使用css让canvas自己旋转-90deg就好了呢?于是我在上面的canvas样式中新增以下规则: .canvas{ transform: rotate(-90deg); } 但是在真机上并不起作用,于是我把新增的样式放到包裹canvas的外层元素上,发现外层元素已经旋转,可是圆弧还是从3点钟方向开始的,唯一能解释这个现象的是官方说:小程序中的canvas使用的是原生组件,所以这样设置css并不能达到我们想要的效果 [图片] 所以必须要在canvas画图的时候把坐标原点移动到弧形圆心,并且在画布内旋转-90deg [图片] // 更换原点 context.translate(this.convert_length(200), this.convert_length(200)); // arc原点默认为3点钟方向,需要调整到12点 context.rotate(-90 * Math.PI / 180); // 需要注意的是,原点变换之后圆弧arc原点也变成了0,0 真机预览效果达成预期 [图片] 接下来添加环形渐变效果,但是canvas原本提供的渐变类型只有两种: 1、LinearGradient线性渐变 [图片] 2、CircularGradient圆形渐变 [图片] 两种渐变中离设计效果最近的是线性渐变,至于为什么能够形成似乎是随圆形弧度增加而颜色变深的效果也只是控制坐标开始和结束的坐标位置罢了 const grd = context.createLinearGradient(0, 0, 100, 90); grd.addColorStop(0, '#56B37F'); grd.addColorStop(1, '#c0e674'); // 画渐变曲线 context.beginPath(); context.arc(0, 0, r, 0, deg); context.setLineWidth(12); context.setStrokeStyle(grd); context.stroke(); 来看一下真机预览效果: [图片] 非常棒,最后就剩下跟随进度条的纽扣效果了 [图片] 根据三角函数,已知三角形夹角根据公式radian = 2*Math.PI/360*deg,再利用cos和sin函数可以x、y,从而计算出纽扣在各部分半圆的坐标 const mathDeg = ((remain/total).toFixed(2))*360; // 计算弧度 let radian = ''; // 圆圈半径 const r = +this.convert_length(170); // 三角函数cos=y/r,sin=x/r,分别得到小点的x、y坐标 let x = 0; let y = 0; if (mathDeg <= 90) { // 求弧度 radian = 2*Math.PI/360*mathDeg; x = Math.round(Math.cos(radian)*r); y = Math.round(Math.sin(radian)*r); } else if (mathDeg > 90 && mathDeg <= 180) { // 求弧度 radian = 2*Math.PI/360*(180 - mathDeg); x = -Math.round(Math.cos(radian)*r); y = Math.round(Math.sin(radian)*r); } else if (mathDeg > 180 && mathDeg <= 270) { // 求弧度 radian = 2*Math.PI/360*(mathDeg - 180); x = -Math.round(Math.cos(radian)*r); y = -Math.round(Math.sin(radian)*r); } else{ // 求弧度 radian = 2*Math.PI/360*(360 - mathDeg); x = Math.round(Math.cos(radian)*r); y = -Math.round(Math.sin(radian)*r); } [图片] 有了纽扣的圆形坐标,最后一步就是按照设计绘制样式了 // 画纽扣 context.beginPath(); context.arc(x, y, this.convert_length(24), 0, 2 * Math.PI); context.setFillStyle('#ffffff'); context.setShadow(0, 0, this.convert_length(10), 'rgba(86,179,127,0.5)'); context.fill(); // 画绿点 context.beginPath(); context.arc(x, y, this.convert_length(12), 0, 2 * Math.PI); context.setFillStyle('#56B37F'); context.fill(); 来看一下最终效果 [图片] 最后我重新review了整个代码逻辑,并且已经将代码开源到https://github.com/lucaszhu2zgf/mp-progress,欢迎大家使用
2020-05-27 - 自己开发的小程序可不可以跳转到“饿了么”或者“美团外卖”里自己的店铺?需要appid嘛?
如果不能怎么才能引流到外卖平台上自己的店铺呢?
2020-05-07 - 小程序·云开发实战 - 校园约拍小程序
创意来源于生活,之所以开发这个校园约拍小程序,是因为在摄影选修课上常听老师抱怨外出写生老找不到模特,许多大学生都想拥有一套专属自己记忆的摄影作品,记录下不会磨灭的美好回忆,可如何找到让自己满意的摄影师是他们的难题。悦拍屋是一个校园摄影o2o的约拍平台,提供全方位的约拍服务,同时提供一个自我展示,学习交流,互动娱乐的平台。接下来我将结合项目的讲解给大家分享一些实用技术和对于云开发的一些经验,希望对正在学习小程序的你有帮助。 前言 在开发一个项目之前首先要进行技术选型从而降低产品开发的技术风险和提高开发效率,技术选型必须得紧紧围绕着业务场景来选择。 产品原型设计:墨刀 UI组件库 1.微信原生样式库[代码]WeUI[代码],让用户使用感知更加统一 2.注重视觉交互体验的[代码]ColorUI[代码]组件库,在感知统一的基础上视觉元素多样化 前端 1.小程序原生语法以及[代码]API[代码] 2.[代码]Promise[代码]实现异步调用 3.[代码]ES6[代码]编写页面交互逻辑 后端 1.云函数:无需自建服务器,在云端运行的代码,微信私有协议天然鉴权,开发者只需编写自身业务逻辑代码 2.云数据库:无需自建数据库,一个既可在小程序前端操作,也能在云函数中读写的 [代码]JSON[代码] 数据库 3.云存储:实现小程序前端直接上传/下载云端文件,在云开发控制台可视化管理 4.云调用:由原生微信服务集成,基于云函数免鉴权使用小程序开放接口的能力,包括服务端调用、获取开放数据等能力 其他 1.使用微信提供的云测试对未上线的小程序进行缺陷测试、性能数据分析、机型覆盖测试,确保小程序上线后正常运营 2.使用基于云开发的[代码]AI视觉能力[代码]-身份证识别实现实名认证,智能鉴黄结合人工完成发布信息的审核 3.开发工具:微信开发者工具、VScode 4.部分图标使用自阿里巴巴矢量图标库 总体设计 功能结构图 大家可以通过此图了解整个项目的主要功能点 [图片] 产品原型图 此处给出一张主页原型图示例,墨刀还是挺好用的 [图片] 色彩设计图 悦拍屋的整体色调为浅蓝色,各位小伙伴在开发自己项目的时候可以根据色彩标准搭配来设计项目所采用的色彩,合适的色彩搭配可以给用户良好的视觉体验 [图片] 功能模块详解 接下来我会对部分功能模块以图文结合的形式详细描述,将其中涉及的技术、知识分享给大家 约拍邀请 用户可在首页查看约拍需求,并点击查看需求详情,用户在了解需求后,若自己符合条件即可提交约拍信息,等待发布者的回复,可将此需求收藏方便查看 [图片] 技术分享:自定义顶部导航栏 官方默认的导航栏只能对背景颜色进行更改,对于想要在导航栏添加一些比较酷炫的效果则需要通过自定义导航栏实现 实现原理:通过设置[代码]app.json[代码]中页面配置的[代码]navigationStyle[代码](导航栏样式)配置项的值为[代码]custom[代码],即可实现自定义导航 [代码]"window":{ "navigationStyle":"custom" } [代码] 本项目的部分页面自定义导航栏实现使用了[代码]ColorUI[代码]的导航栏组件,在完成上一步属性设置后再引入导航栏组件即可 [代码]"usingComponents":{ "cu-custom":"/colorui/components/cu-custom" //该路径替换为自己项目内ColorUI组件所在位置 } [代码] 主页自定义导航栏通过设置背景图片加上GIF波浪效果 [代码] <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://image.weilanwl.com/gif/wave.gif" mode="scaleToFill" class="gif-black response" style="height:100rpx;margin-top:-100rpx;"></image> </view> </view> [代码] 效果图 [图片] 使用组件定义的导航栏 [代码]<cu-custom bgImage="https://s2.ax1x.com/2019/05/02/Etiyng.jpg" isBack="{{true}}"> <view slot="backText">返回</view> <view slot="content">认证信息说明 </view> </cu-custom> [代码] 效果图 [图片] [代码]特别提醒1:使用自定义导航后,页面的返回需要在自定义导航栏中自行设置 [代码] [代码]特别提醒2:导航栏组件需要自行引入ColorUI组件库后才能使用,具体引入教程地址在附录中给出 [代码] 发布约拍 选择发布约拍功能填写约拍需求,提交审核通过后可在首页实时查看发布结果 [图片] 技术分享:入场动画 额。。录制可能略微有点卡顿,实际效果挺流畅的,各位大佬有什么好的录制工具推荐可以在评论中回复 实现原理:通过[代码]toggleDelay[代码]的布尔值为真动态添加动画类名,在生命周期函数[代码]onReady[代码]中控制[代码]toggleDelay[代码]的值从而控制整个动画过程(原理与[代码]Vue[代码]的动态类名相似) [代码]data:{ toggleDelay;false }, onReady:function(){ let that = this //toggleDelay的值为真,动画开始 that.setData({ toggleDelay: true }) //控制整个动画的时长 setTimeout(function() { that.setData({ toggleDelay: false }) }, 2000) } [代码] [代码]<view class="padding-xs {{toggleDelay?'animation-slide-bottom':''}}" style="animation-delay: {{item.time}}s;" wx:for="{{list}}" wx:key="{{index}}"> <image class="img" id='img{{index}}' src="{{item.src}}" mode="widthFix" /> </view> [代码] [代码]//所有动画的定义 [class*=animation-] { animation-duration: .5s; animation-timing-function: ease-out; animation-fill-mode: both } //animatioon-slide-bottom所定义的动画 .animation-slide-bottom { animation-name: slide-bottom } //动画效果 @keyframes slide-bottom { 0% { opacity: 0; transform: translateY(100%) } 100% { opacity: 1; transform: translateY(0) } } [代码] [代码]animation-slide-bottom[代码]是动画类名,[代码]animation-delay[代码]是每一个卡片动画执行的延迟时间,每一个动画的执行时长为0.5s,所以延迟时间是以0.5s递增的,三个卡片的动画总时长就为2s,即2s后就执行[代码]onReady[代码]中的[代码]settimeout[代码]事件结束动画 [代码]特别提醒:动画的延迟时间,执行时间可以自行设计,动画效果过渡自然即可 [代码] [代码]特别提醒:由于触发动画的钩子函数定义在页面初次渲染的生命周期函数中,故只有在页面初次渲染时才执行,避免每次显示页面时加载动画造成用户的视觉疲劳 [代码] 智能推荐约拍对象 系统会根据约拍需求自动推荐约拍对象(个人开发精力有限,推荐算法后续推出。。。) [图片] 技术分享:CSS3实现酷炫搜索动画 在模态框内放置两个[代码]view[代码]标签,以下是标签定义 [代码] <view id='preloader'> //外围的圆形框定义 <view id='loader'></view> //内部的线条定义 </view> [代码] [代码]#preloader { width: 150px; height: 150px; border-radius: 50%; border: 1px solid #97b2ff; } #loader { //中间线条定义 display: block; position: relative; left: 50%; top: 50%; width: 150px; height: 150px; margin: -75px 0 0 -75px; border-radius: 50%; border: 3px solid transparent; border-top-color: #97b2ff; -webkit-animation: spin 2s linear infinite; animation: spin 2s linear infinite; } #loader:before { //通过伪类元素定义外围线条 content: ""; position: absolute; top: 5px; left: 5px; right: 5px; bottom: 5px; border-radius: 50%; border: 3px solid transparent; border-top-color: #97b2ff; -webkit-animation: spin 3s linear infinite; animation: spin 3s linear infinite; } #loader:after { //通过伪类元素定义最内部线条 content: ""; position: absolute; top: 15px; left: 15px; right: 15px; bottom: 15px; border-radius: 50%; border: 3px solid transparent; border-top-color: #97b2ff; -webkit-animation: spin 1.5s linear infinite; animation: spin 1.5s linear infinite; } [代码] 实名认证 [图片] 嘿嘿,由于懒得给个人信息打码,就暂时不给大家演示认证过程了。。 技术分享:Ai视觉能力 很多小伙伴都有过在自己项目中使用AI技术的想法,但又因为入门AI的难度比较大,并且需要的时间较长就放弃了,现在给大家安利一个可以直接使用的AI服务,让AI不再具有神秘感(AI大佬可以忽略此部分。。) 方案一 在腾讯云中搜索身份证识别,上面会有详细的API文档以及测试工具帮助你快速使用 [图片] 点击查看腾讯云-身份证识别 方案二 方案一是以提供API接口的形式提供身份证识别服务,而接下来要介绍的方案真的就比较简单了,在腾讯云中搜索智能图像,其中的增值服务AI智能图像能力,你可以通过云函数和云存储实现相应功能,基于小程序云开发的 AI DEMO中开发好了部分功能,你只需通过教程将云函数和组件引入你的项目即可使用 [图片] 点击查看腾讯云-智能图像 点击查看基于小程序云开发的 AI DEMO [代码]特别提醒:当然使用这些服务也并非是完整的解决方案,对于身份证信息的加密、存储方案、安全协议等还是需要各位小伙伴自行设计解决方案哦。 [代码] 云开发 云开发为开发者提供完整的原生云端支持和微信服务支持,弱化后端和运维概念,无需搭建服务器,使用平台提供的 API 进行核心业务开发,即可实现快速上线和迭代,同时这一能力,同开发者已经使用的云服务相互兼容,并不互斥。 官方文档中API被分为了小程序端和服务端,一开始看过两端的API之后,感觉好像没有什么不同啊,在查阅相关资料以及实际开发中某些业务的处理总结出一些经验后才明白了两者的不同,下面给各位具体说说两者的不同之处,应该能帮助大家在使用云开发实战时少踩一点坑 初始化的不同 小程序端 全局声明一次 [代码]if (!wx.cloud) { console.error('请使用 2.2.3 或以上的基础库以使用云能力') } else { wx.cloud.init({ env:'xxx', traceUser: true, }) } [代码] 服务端 每个云函数中声明一次 [代码]const cloud = require('wx-server-sdk') cloud.init() [代码] 权限不同 小程序端 在小程序端可以选择直接操作数据库,但由于是前端操作数据库存在一些安全问题,有较多的权限限制,在云控制中可对每个集合进行权限设置,这也就是为什么有小伙伴在小程序端对某些数据进行更新,显示更新成功但并未更新数据,就是因为小程序端默认只能更新当前用户写入的数据 [图片] [代码]特别提醒:在小程序端使用创建者的权限对数据进行修改时一定要确保该集合中有_openid字段,否则系统在权限判断时是没有办法识别当前操作为创建者的,数据修改无法执行 [代码] 服务端 服务端拥有管理员的权限,对所有数据拥有读写权限 语法支持不同 小程序端 在微信开发者工具里,以及Android端手机(浏览器内核是QQ浏览器的X5),[代码]async[代码]/[代码]await[代码]是天然支持的,但 iOS 端手机在较低版本则不支持,因此需要引入额外的[代码]polyfill[代码]。可以在有使用[代码]async[代码]/[代码]await[代码] 的文件当中引入[代码]polyfill[代码]文件。 [代码]const runtime = require('相对路径/lib/runtime') [代码] 服务端 在云函数里,由于 Node 版本最低是 8.9,因此是天然支持 async/await 语法的 示例:获取约拍需求列表 [代码]//云函数入口文件 const cloud = require('wx-server-sdk') //初始化 cloud.init() //连接数据库 const db = cloud.database() async function getAll(){ const result = await db.collection('ypList') .orderBy('cameraInfo.launchTime','desc').where({}).get() return result } // 云函数入口函数 exports.main = async (event, context) => { //此处的action是用来判断该调用哪一个方法 if(event.action === 'getAll'){ return getAll() } } [代码] 结语 一个人手撸个全栈项目确实很辛苦,但收获也很多。至少对于小程序的实战开发更为熟练了,对MVVM的思想的理解也更加深刻了。技术发展得很快,学习一项技术如果不深入其本质,那么技术是学不完的。深入学习就是个解决问题的过程,或是帮助别人解决问题,或是借助他人的力量解决问题。目前在正在学习Vue、React、TypeScript等技术,后续会推出相关技术的项目解析文章,希望对于同样在学习的你有帮助。 [代码]特别说明:本项目已参加2019届中国高校计算机-微信应用开发赛完,开源至github,感兴趣的小伙伴可以看看 [代码] 附录 在此提供一些本项目涉及到的技术、工具等链接供大家学习使用 产品原型设计工具:墨刀 色彩搭配设计:配色网 在线作图:ProcessOn UI样式库:WeUI UI样式库:ColorUI 图标库:Iconfont阿里巴巴矢量图标库 开发工具:微信开发者工具 开发者工具:Vscode 腾讯云服务:身份证识别 腾讯云服务:智能图像 API文档:微信官方文档.小程序 技术文档:ES6 源码链接 https://github.com/TencentCloudBase/Good-practice-tutorial-recommended 如果你有关于使用云开发CloudBase相关的技术故事/技术实战经验想要跟大家分享,欢迎留言联系我们哦~比心! [图片]
2019-08-05 - 【开箱即用】分享几个好看的波浪动画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 - 开发者工具那些你未必知道的console命令
build 编译 相当于点击“编译”按钮 [图片] preview 预览 [图片] upload 上传代码 [图片] cleanAppCache 清除应用缓存 清除完需要重新编译 [图片] showRequestInfo 查看请求过链接的信息 检查证书一大利器 [图片] showSystemInfo 显示当前开发者工具占用内存及其他信息 [图片] checkProxy 检测目标网址是否启用代理 [图片] openToolsLog 打开开发者工具日志目录 openPlugin 打开插件目录 openVendor 打开供应商目录 更多详细的命令使用 help 进行获取 [图片]
2019-11-08 - 那些年你没权限调用的API
api 顾名思义 wx.openMiniProgramHistoryList wx.openMiniProgramProfile wx.openMiniProgramSearch wx.openMiniProgramStarList 不再多BB直接上图 1.隐藏/显示右上角胶囊按钮 [图片] 2.截屏 [图片] 最近玩console有点上头 现在的小程序api已经达到了373个了 我是怎么知道的?看图 [图片] 往下拉一看 373 [图片] 这里边有很多的api 是文档中没写的(也可能永远不会写) 先说下 普通的小程序里边是没有权限的调用会提示 {errMsg: “openUserProfile:fail:access denied”} 或者 {errMsg: “getABTestConfig:fail permission denied”, errCode: 1} 代码片段:https://developers.weixin.qq.com/s/FgIssdmP7jdv 可用的api wx.navigateBackH5 --webview 通过这个api可以返回上一个web页面 更多的BUG等你们去处理 我吃饭去咯 end
2019-11-28 - 复制任意微信小程序页面路径
以下以微信小程序“虎牙直播”为例,演示如何复制微信小程序页面的路径。 1.进入小程序的“关于虎牙直播”页面 [图片] 2.点击右上角的“…”进入“更多资料”页面 [图片] [图片] [图片] 3.复制AppID:wx74767bf0b684f7d3 4.进入小程序后台输入appid并搜索,然后点下一步 [图片] 5.鼠标移动到“获取更多页面路径”,在弹出窗口输入当前登陆的小程序的任意开发者微信号,然后点击开启,出现顶部的“开启入口成功”就可以使用手机访问“虎牙直播”任意页面进行复制了 [图片] 6.某个直播间的页面路径:pages/main/liveRoom/index.html?anchorUid=1678113423&source=search[图片] PS:复制出来的页面路径在小程序里使用的时候记得删除 .html 才能正常访问。
2020-01-16 - 做了一个颜色选择器
edit at 11/12 代码传到了:https://github.com/eclipseglory/zasi-components , DEMO演示在文章结尾 小程序没有提供color-picker类似的组件,只能自己做。 可传统的RGB颜色选择器,真的腻了,而且在手机上也不是很操作,就跑网上搜了一圈,发现有一种圆环形的(基于HSV)我很喜欢: [图片] 我自诩对canvas2d和webgl很熟悉,做个这玩意儿很轻松,开始做!没想到痛苦开始了。 从上周5开始,一共做了三个版本: 1.纯canvas版本 2.canvas+组件版本 3.纯组件版本 纯canvas版本这个版本做了整整一天! [图片] 由于canvas绘制性能问题,特别是因为没有requestAnimationFrame可以调用,别说在真机上测试特别不流畅,就是在模拟器上也小卡小卡的。而且,在纯的canvas进行触摸定位等事件响应处理,计算起来太麻烦,bug不断,只能放弃了。 混合版本因为wxs模块是提供requestAnimationFrame接口的,所以我就想,使用canvas作为底部颜色环,上面就直接用view作为指针,这样,事件触发和处理比起纯canvas要简单得多,而且还能利用rAF回调页面接口去绘制其他canvas。 的确,我的想法得到了证实,这个混合版本比起第一个要流畅得多! 可就要完工的时候,我却发现,在真机上,cover-view的鼠标事件有很大问题,坐标值飘忽不定,也就是说拖动指针会发生鬼畜般的抖动!加上我不知道怎么debug到wxs模块中,于是跟个sb一样fix,找了半天也没找到问题在哪儿,直到我搜索时,返现有人也遇到和我一样的问题,我才安心了:这是小程序的问题。 动手改!既然cover-view有不行,那就不用它。 实际上canvas在该组件中的作用无非就是绘制一个圆环而已,如果我利用离屏canvas事先画好,然后保存成图片,再用image加载它,这样就可以避免使用canvas来显示圆环了,也就可以不用cover-view放到其顶部! 想法是好的,可是到了真机上,绘制保存出来的图片时好时坏: [图片] 只能放弃,又耽误我一天。 无canvas版本刚才说了,canvas在该组件中的作用,仅仅是绘制一个颜色环而已,除此之外真没什么用。 那我就用css模拟一个类似圆环就好了,精确到每一度一个颜色一点意义没有。 所以就利用css的background-image属性,做了4个四分之一圆弧,然后拼在一起,得到了一个彩色原版,再用一个小的view遮挡,让它们只露出一部分,圆环就做好了。 之前的代码都不用改,直接用新作的圆环views替换canvas的标签即可。主体框架和功能,不到一天就完成了,不得不说,比起纯的canvas绘制,要方便太多太多。 这是截图: [图片] 代码片段这里是 演示DEMO,要使用的话,复制里面的组件出来用就好。 有些代码我混淆过,但不耽误使用。 有问题找我
2019-11-12 - 单张、多张图片上传(图片转base64格式)实践经验
定义初始数据: data: { imgList: [], // 图片集合 baseImg: [], // base64图片集合 maxImg: 8, // 图片上传最高数量(根据需求设置) } 第一步:从本地相册选择图片或使用相机拍照(wx.chooseImage) // 选择图片 selectPictures: function() { const that = this; // 最多上传图片数量 if (that.data.imgList.length < that.data.maxImg) { wx.chooseImage({ count: that.data.maxImg - that.data.imgList.length, // 最多可以选择的图片张数(最大数量-当前已上传数量=当前可上传最大数量) sizeType: "compressed", success: function(res) { for (let i = 0; i < res.tempFilePaths.length; i++) { that.data.imgList.push(res.tempFilePaths[i]); } // 显示图片(同步渲染到页面) that.setData({ imgList: that.data.imgList }) } }) } else { wx.showToast({ title: "最多上传" + that.data.maxImg + "张照片!" }) } } count:最多可以选择的图片张数(默认9) sizeType:所选的图片的尺寸(original-原图,compressed-压缩图) sourceType:选择图片的来源(album-从相册选图,camera-使用相机) 第二步:将图片本地路径转为base64图片格式(wx.getFileSystemManager().readFile) // 图片转base64 conversionAddress: function() { const that = this; // 判断是否有图片 if (that.data.imgList.length !== 0) { for (let i = 0; i < that.data.imgList.length; i++) { // 转base64 wx.getFileSystemManager().readFile({ filePath: that.data.imgList[i], encoding: "base64", success: function(res) { that.data.baseImg.push('data:image/png;base64,' + res.data); //转换完毕,执行上传 if (that.data.imgList.length == that.data.baseImg.length) { that.upCont(that.data.textCont, that.data.baseImg); } } }) } } else { wx.showToast({ title: "请先选择图片!" }) } } filePath:要读取的文件的路径 (本地路径) encoding:指定读取文件的字符编码(ascii,base64,binary,hex......) 第三步:执行上传,把图片数组传输给后端即可 // 执行上传 upCont: function (baseImg) { const that = this; wx.request({ url: "上传地址", method: "POST", data: { imglist: baseImg }, success: function (res) { if (res.data.code == 200) { wx.showModal({ title: "提示", content: "提交成功,棒棒哒!" }) // 清空当前数据 that.data.imgList = []; } else { wx.showModal({ title: "提示", content: "上传失败!" }) } } }) } 删除功能:被选中图片移除当前图片数组 // 删除图片(选中图片移除) delImg: function(e) { const that = this; const index = e.currentTarget.dataset.index; // 当前点击图片索引 that.data.imgList.splice(index, 1); that.setData({ imgList: that.data.imgList }) } tips:点击提交按钮后可以增加显示loading提示框:wx.showLoading(),返回结果后隐藏loading提示框:wx.hideLoading(),此方法可以避免重复点击! 完整代码: 1.js代码(直接复制文中代码即可) 2.wxml <view class="img-list"> <view class="txt">图片 {{imgList.length}} / {{maxImg}}</view> <view class="list"> <!-- 图片展示列表 --> <view class="li" wx:for="{{imgList}}" wx:key="index"> <image class="file" src="{{item}}"></image> <!-- 删除图片 --> <image class="close" src="/images/close.png" data-index="{{index}}" bindtap="delImg"></image> </view> <!-- 添加图片 --> <view class="li" bindtap="selectPictures"> <image class="file" src="/images/upload.jpg"></image> </view> </view> </view> <view class="btn" bindtap="conversionAddress">提 交</view> 3.wxss .img-list{ width: 700rpx; margin: 0 auto;} .img-list .txt{ width: 680rpx; padding: 40rpx 0 20rpx; margin: 0 auto; color: #b2b2b2;} .img-list .list{ width: 700rpx; overflow: hidden;} .img-list .list .li{ width: 160rpx; margin: 10rpx 0 0 10rpx; height: 160rpx; border: 1rpx solid #fff; float: left; position: relative;} .img-list .list .li:last-child{ border: 1rpx solid #f7f7f7;} .img-list .list .li .file{ display: block; width: 160rpx; height: 160rpx;} .img-list .list .li .close{ position: absolute; top: 0; right: 0; width: 44rpx; height: 44rpx; background: #fff;} .btn{ background: #f60; width: 680rpx; border-radius: 10rpx; line-height: 88rpx; color: #fff; text-align: center; margin: 50rpx auto 0;} 效果图: [图片]
2020-03-12 - 【笔记】如何生成固定页面的小程序码
在群里经常看到有人问,怎么生成特定页面的小程序码,本文针对这个问题总结下 第一步,登录小程序后台,右上角看到我标记的①了吗,点击下 [图片] 占位 第二步输入自己的小程序名称 [图片] 占位 第三步,按照提示,在手机上取固定的页面链接 [图片] 占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位占位 第四步,手机上操作,右下角看到了吗"复制链接"四个字按钮 [图片] 占位 这个时候就可以提取某个页面的页面链接,粘贴到第三步中的文本框内,点击确定即可完成,生成的小程序码如下所示 [图片]
2020-03-21 - 【文章】优秀文章推送
前言 开发小程序时间不久,看的文章可能大家都看过了,所以此文就送给在小程序开发路上刚刚起跑的一些小伙伴,大佬勿喷 文章来源各大社区或博客 来自掘金 [译] 前端项目中常见的 CSS 问题 [译]一行css代码搞定响应式布局 📝你本可以少写些 if-else 小程序开发经验:多页面数据同步 --腾讯IVWEB团队 手把手教会你小程序登录鉴权 用wxDraw.js制作酷炫的小程序canvas动画『wxDraw 小程序界的zrender』 小程序多端框架全面测评 --凹凸实验室 来自知乎 有用!关于微信小程序,那些开发文档没有告诉你的 开发“小程序”必备书单 来自简书 微信小程序资源整理 小程序的常用居中弹性布局样式整理 最后 看过的文章真的不多,还得加油啊😥!就先分享这些,如果还行记的点赞哦!后期有文章在加吧 欢迎在评论区将你的好文分享一下
2019-06-21 - mina-lazy-image: 图片懒加载自定义组件
Github: https://github.com/alexayan/mina-lazy-image 功能 图片在视口中出现才进行加载显示,优化页面性能 使用方法 安装组件 [代码]npm install --save mina-lazy-image [代码] 在页面的 json 配置文件中添加 mina-lazy-image 使用此组件需要依赖小程序基础库 2.2.2 版本,同时依赖开发者工具的 npm 构建。具体详情可查阅官方 npm 文档。 [代码]{ "usingComponents": { "mina-lazy-image": "mina-lazy-image/index" } } [代码] WXML 文件中引用 mina-lazy-image [代码]<mina-lazy-image src="{{src}}" mode="widthFIx" image-class="custom-class-name"/> [代码] mina-lazy-image 的属性介绍如下: 字段名 类型 必填 描述 src String 是 图片链接 placeholder String 否 占位图片链接 mode String 否 请参考 image 组件 mode 属性 webp Number 否 请参考 image 组件 webp 属性 showMenuByLongpress Boolean 否 请参考 image 组件 show-menu-by-longpress 属性 styles String 否 设置图片样式 viewport Object 否 默认为 {bottom: 0},配置图片显示区域 mina-lazy-image 外部样式类 [代码]image-class[代码], [代码]image-container-class[代码]
2020-01-09