- 为什么图片链接可正常访问但image组件加载不出来图片?
因为 image 控件的图片拉取本质上是 web 上的 backgroundImage,很多时候是由于图片不规范(content-type / length / 是否302跳转等 )导致拉取不成功,最终表现为加载不出图片。关于这一块我们在持续优化中
2021-12-17 - 数组增删改查
排序: loadData:function(){ let todo = wx.getStorageSync(‘yList’); if(todo){ this.setData({yList: todo.sort(function (a, b) { return a.date<b.date?1:-1; //升序改成> 数组按日期排序 }) }); } this.loadTotal();加载数量统计 }, 合计: loadTotal:function(){ let todoName = this.data.yList.length; let todoNumber = 0; let i=0; while(i < todoName){ todoNumber= todoNumber+parseInt(this.data.yList[i].number) ; i++; 传统的计算,应该用es6的简单吧 } this.setData({totalName:todoName,totalNumber:todoNumber}); }, 搜索: seachName:function(e){ console.log(e.target.dataset.name) var arr=this.data.yList arr = this.data.yList.filter(function(x) { return x.name == e.target.dataset.name })查询name字段,然后让查询出来的数组,加载到一个新的列表里,老的列表隐藏,返回时清空新的列表,老的列表显示 this.setData({showNewBtn:false,showList:false,showBackBtn:true,seachList:arr,seach:e.target.dataset.name}) }, 新增: submitNew:function(){ let arr=this.data.yList; let i=0; arr.unshift({ id:new Date().getTime(), name:this.data.name, number:this.data.number, date:this.data.date, note:this.data.note, state:’’ })在前面新增一条数据 this.setData({yList:arr,name:’’,note:’’}); this.save(); }, 删除: submitDel:function(){ let id=this.data.id; 绝对id,这个是个时间戳,如果是下标读取,正常没问题当搜索出来的,点击删除就出问题了,删除了只是搜索出来的下标,不是原数组的下标。 let arr=this.data.yList; let that=this; wx.showModal({ title: [代码]${this.data.name} ${this.data.number}个[代码], content: [代码]${this.data.date} ${this.data.note}[代码], confirmText: “删除”, cancelText: “返回”, success: function (res) { if (res.confirm) { let i=that.data.yList.findIndex(item => item.id === id);找到这条数据 arr.splice(i,1);删除这条数据 that.setData({yList:arr,id:’’}) that.hideEdit(); that.save(); that.hideBack(); }else{ console.log(‘返回’) } } }); }, 编辑: submitEdit:function(){ let id=this.data.id; let i=this.data.yList.findIndex(item => item.id === id);找到这条数据的下标 let arr=this.data.yList; arr[i].name = this.data.name; arr[i].number = this.data.number; arr[i].date = this.data.date; arr[i].note = this.data.note; arr[i].state = this.data.state;改写这条数据的值 this.setData({yList:arr,id:’’,seachList:[],seach:’’}); wx.showToast({title: ‘更新成功’,icon: ‘success’,duration: 500}); this.hideEdit(); this.save(); }, 增删改查就这样了。
2020-01-13 - 新的canvas 初次尝试 (1)---清除画布
新的canvas(特指基础库2.9.0之后)绘图性能很强。流畅度和HTML差别不大。运行效果令人欢欣鼓舞。 [代码]<canvas type="2d" id="canvas" /> [代码] 在本人项目中正好有图片编辑的功能要使用canvas。 那么我们先梳理一下需求 编辑图片在图片上进行描边操作 描边不满意,清除掉重新描边 尽量减少页面绘制时间。提高用户体验 针对以上需求,经过多轮测试,我们制定了以下计划 在用户选择完图片后,使用image标签显示图片,减少drawImaged的次数 在image标签上层覆盖一个透明的canvas,然后在canvas上进行描边操作 在2.9.0但是随之而来的就是原有的canvas接口部分不能使用。比如canvas.draw()在新的canvas不能使用。想通过canvas.draw()来清除画布的方式不在适用于新的canvas。 但是好在官方发布的接口公测公告中告诉我们 [代码]为了提高 Canvas 组件的性能,我们计划在小程序基础库 v2.9.0 正式开放一套全新的 Canvas 接口。该接口符合 HTML Canvas 2D 的标准 [代码] 好吧 HTML Canvas 2D标准。那么我们照着这个标准来进行画布的清除吧。 Round One 我们都知道 ctx.clearRect(0,0,canvas.width,canvas.height),可以清除画布区域内所有内容。但是重新开始绘制的时候,原来被清除的内容又神奇的回到了画布上 [代码]ctx.clearRect() [代码] 运行效果 浏览器 — fail 开发工具 — fail 基础库2.8.3 — fail 基础库2.9.0 — fail Round Two 改变画布状态,强制画布初始化。 除了2.9.0 其他版本运行完美。作为小程序,我们要考虑大多数用户的运行环境,所以该方案也行不通 [代码]canvas.height = canvas.height [代码] 运行效果 浏览器 — pass 开发工具 — pass 基础库2.8.3 — pass 基础库2.9.0 — fail Round Three 改变绘制的颜色叠加方式 [代码]ctx.globalCompositeOperation = 'destination-out'; ctx.beginPath(); ctx.fillStyle = 'red'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fill(); ctx.globalCompositeOperation = 'source-over'; [代码] 运行效果 浏览器 — pass 开发工具 — pass 基础库2.8.3 — pass 基础库2.9.0 — pass 代码 如下 https://developers.weixin.qq.com/s/ZZOQ2nmu71cM Game Over
2019-10-25 - 小程序批量删除云数据库里的数据
我们用云开发的云数据库存数据,难免会遇到数据过多,或者一些过时数据要删除的需求。之前云开发删除数据库只能一条条的删除。要想批量删除很麻烦,近期云开发推出了批量删除数据的方法。甚至可以稍微改造下实现数据库里某个集合(表)里所有数据的删除操作。 老规矩,先看效果图 如删除工资表中2019年9月份的工资 [图片] 可以看到我们成功删除7条数据。 删除所有的工资数据 [图片] 可以看到我们把工资表里768条数据,全部删除了。 接下来我们就来看下具体的实现代码 一,先看官方文档如何写的 [图片] 通过上图可以看到,我们既可以删除单条,又可以删除多条。 [图片] 通过上图可以看到,我们只能结合where语句才能实现批量删除。 再来看下官方给的demo [图片] 一看我们就能知道这是写在云函数里的。所以我们批量删除数据库里的数据,必须是通过云函数来实现批量。 官方文档的地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-server-api/database/collection.remove.html 二,我们就结合具体业务来实现批量删除 1, 首先看下我们的工资表里,有yuefen这个字段 [图片] 比如我们2019年11月所有的人工资填写有误,我们想批量的删除所有 yuefen为 2019.11的数据,对应的代码如下图红色框里的代码。 [图片] 2,作为一个业务代码,我们肯定要把数据做活 所以定义一个输入框,用来输入你要删除的月份。如下图所示 [图片] 3,删除所有数据 同样的我们想删除所有数据,也比较依赖where。那门我们删除所有数据,该怎么匹配where语句呢。翻看官方文档,可以看到官方文档有判断某一个字段是否有值。所以我们编写的删除所有数据的代码如下。 [图片] 这样我们就可以通过判断月份存在,就可以删除所有数据了,因为所有的数据都有月份的。 这样我们就可以实现小程序数据库里数据的批量删除操作了,官方其实也有为我们提供批量更新的操作,感兴趣的同学去官方文档看下就可以了。 https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-server-api/database/collection.update.html [图片] 完整的云函数源码直接给大家贴出来吧。 [代码]const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const db = cloud.database() exports.main = async(event, context) => { let { type, yuefen } = event try { if (type == 'all') { const _ = db.command return await db.collection('gongzi').where({ yuefen: _.exists(true) //只要月份字段存在,就删除 }).remove() } else { return await db.collection('gongzi').where({ yuefen: yuefen }).remove() } } catch (e) { console.error(e) } } [代码] 后面我会写更多关于小程序,云开发,云数据库的文章,请持续关注。
2019-11-20 - 云函数获取openid
代码如下: app.js: //如果担心openid的安全,就用这个函数 getCloudOpenid: async function () { return this.openid = this.openid || (await wx.cloud.callFunction({name: 'login'})).result.OPENID }, //最佳方案。 getOpenid: async function () { (this.openid = this.openid || wx.getStorageSync('openid')) || wx.setStorageSync('openid', await this.getCloudOpenid()) return this.openid }, 任何page: onLoad: async function () { console.log(this.openid = await getApp().getOpenid()) }, //在本page的其他函数里获得openid。 yourFunc: function(){ console.log(this.openid) } 云函数login: const cloud = require('wx-server-sdk') cloud.init() exports.main=async()=>{return cloud.getWXContext()}
2020-10-18 - 小程序奇技淫巧之 -- globalDataBehavior管理全局状态
Behaviors 自定义组件中,提供了[代码]behaviors[代码]的使用和定义。 从官方文档我们能看到: [代码]behaviors[代码]是用于组件间代码共享的特性,类似于一些编程语言中的“mixins”或“traits”。 每个[代码]behavior[代码]可以包含一组属性、数据、生命周期函数和方法,组件引用它时,它的属性、数据和方法会被合并到组件中,生命周期函数也会在对应时机被调用。每个组件可以引用多个[代码]behavior[代码]。 简单来说,我们能通过[代码]behaviors[代码]来重构[代码]Component[代码]的能力。Behavior的用处很多,前面也有介绍 computed 计算属性、watch 观察属性的实现,都是使用的 Behavior。 全局状态管理 我们希望全局共享一些数据状态,如果只是通过一个文件的方式进行维护,那么我们无法在状态更新的时候及时地同步到页面。我们需要额外调用 setData 才能更新页面中的 data 数据,才能告诉渲染层这块的数据渲染需要变更,而很多的 Store 状态管理库也是通过这样的方式实现的(事件通知 + setData + 全局状态)。 在小程序 Behavior 能力的支持下,我们可以通过一个全局的 globalData Behavior 注入到每个需要用到的 Component 中,这样就可以在需要的页面中直接引入该 Behavior,就能获取到了。不啰嗦,Behavior的实现如下: [代码]// globalDataStore 用来全局记录 globalData,为了跨页面同步 globalData 用 export let globalDataStore = {}; // 获取本地的 gloabalData 缓存 try { const gloabalData = wx.getStorageSync("gloabalData"); // 有缓存的时候加上 if (gloabalData) { globalDataStore = { ...gloabalData }; } } catch (error) { console.error("gloabalData getStorageSync error", "e =", error); } // globalCount 用来全局记录 setGlobalData 的调用次数,为了在 B 页面回到 A 页面的时候, // 检查页面 __setGlobalDataCount 和 globalCount 是否一致来判断在 B 页面是否有 setGlobalData, // 以此来同步 globalData let globalCount = 0; export default Behavior({ data: { globalData: Object.assign({}, globalDataStore) }, lifetimes: { attached() { // 页面 onLoad 的时候同步一下 globalCount this.__setGlobalDataCount = globalCount; // 同步 globalDataStore 的内容 this.setData({ globalData: Object.assign( {}, this.data.globalData || {}, globalDataStore ) }); } }, pageLifetimes: { show() { // 为了在 B 页面回到 A 页面的时候,检查页面 __setGlobalDataCount 和 globalCount 是否一致来判断在 B 页面是否有 setGlobalData if (this.__setGlobalDataCount != globalCount) { // 同步 globalData this.__setGlobalDataCount = globalCount; this.setGlobalData(Object.assign({}, globalDataStore)); } } }, methods: { // setGlobalData 实现,主要内容为将 globalDataStore 的内容设置进页面的 data 的 globalData 属性中。 setGlobalData(obj: any) { globalCount = globalCount + 1; this.__setGlobalDataCount = this.__setGlobalDataCount + 1; obj = obj || {}; let outObj = Object.keys(obj).reduce((sum, key) => { let _key = "globalData." + key; sum[_key] = obj[key]; return sum; }, {}); this.setData(outObj, () => { globalDataStore = this.data.globalData; }); }, // setGlobalDataAndStorage 实现,先调用 setGlobalData,然后存到 storage 里 setGlobalDataAndStorage(obj: any) { this.setGlobalData(obj); try { let gloabalData = wx.getStorageSync("gloabalData"); // 有缓存的时候加上 if (gloabalData) { gloabalData = { ...gloabalData, ...obj }; } else { gloabalData = { ...obj }; } wx.setStorageSync("gloabalData", gloabalData); } catch (e) { console.error("gloabalData setStorageSync error", "e =", e); } } } }); [代码] 显然,该 Behavior 主要提供了几个能力: 会在小程序 data 添加 globalData 的属性,在 WXML 文件中可以直接通过[代码]{{globalData.xxxx}}[代码]获取到 提供[代码]setGlobalData()[代码]方法,用于更新全局状态 提供[代码]setGlobalDataAndStorage()[代码]方法,用于更新全局状态,同时写入缓存(会在下次启动应用的时候自动获取缓存数据) 这样,我们在初始化 Component 的时候直接引入就可以使用: [代码]Component({ // 在behaviors中引入globalDataBehavior behaviors: [globalDataBehavior], // 其他选项 methods: { test() { // 使用this.setGlobalData可以更新全局的数据状态 this.setGlobalData({ test: "hello world" }); // 使用this.setGlobalDataAndStorage可以更新全局的数据状态,并写入缓存 // 下次globalDataBehavior会默认从缓存中获取 this.setGlobalDataAndStorage({ test: "hello world" }); } } }); [代码] 在引入了 globalDataBehavior 之后,我们的 WXML 就可以直接使用了: [代码]<view>{{ globalData.test }}</view> [代码] 页面如何使用 Behavior [代码]Component[代码]是[代码]Page[代码]的超集,因此可以使用[代码]Component[代码]构造器构造页面。 看看官方文档:事实上,小程序的页面也可以视为自定义组件。因而,页面也可以使用[代码]Component[代码]构造器构造,拥有与普通组件一样的定义段与实例方法。但此时要求对应[代码]json[代码]文件中包含[代码]usingComponents[代码]定义段。 更详细的使用方法,在 computed 计算属性、watch 观察属性两篇文章中也有描述,大家可以自行参考。 或者直接查看最终的项目代码:wxapp-typescript-demo。 参考 Component构造器 behaviors 结束语 Behavior 其实是很强大的一个能力,我们能用它来对自己的小程序做很多的能力拓展,缺啥补啥,还可以“混入”给每个 Component 每个方法打入日志,就不用每个组件自己手动打印代码拉。
2019-12-10 - 基于小程序·云开发的HTTPAPI实现图片上传功能
基于HTTPAPI实现内容管理的图片上传功能 目前基于小程序·云开发可以快速的实现无服务器的小程序快速开发。但是小程序开发工具自带的管理工具,并不方便进行内容管理。因此为了更高效的管理小程序,需要开发后台管理工具,以方便在电脑端进行内容管理。基于此,会陆续连载更新基于小程序·云开发HTTPAPI进行后台管理工具相关开发的一些经验文章。 前端:elment-admin 后端:node.js koa 前端使用elment-ui el-upload组件实现上传功能 对于elment-admin以及elment-ui不熟悉的请自行去官网学习。 elment-admin是基于elment-ui组件构建的后台管理系统UI,功能强大,方便快速构建后台管理系统。 elment-admin:https://github.com/PanJiaChen/vue-element-admin elment-ui:https://element.eleme.io/#/zh-CN/guide/design [代码]<template> <div class="dashboard-container"> <div> <el-upload class="upload-demo" action="http://localhost:3000/upload/imgs" :before-upload="beforeUploadFile" > <el-button size="small" type="primary">点击上传</el-button> <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div> </el-upload> </div> </div> </template> [代码] 后端基于node koa的后端图片上传功能代码: 学习技术的最好方法就是官方文档,因此要想熟练的使用httpapi构建小程序的后端管理系统,就必须先熟悉官方文档。 小程序官方文档链接:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/basis/getting-started.html 要想使用Httpapi去实现图片上传功能,我们需要先获取访问请求中的参数access_token。access_token获取的官方文档内容: [图片] 通过官方文档可知,传递参数appid和secret,发送请求到文档中的地址,可以获取access_token数据。 access_token获取,存储以及定时更新的相关代码如下: [代码]const rp = require('request-promise') const fs = require('fs') //引用文件操作函数 const path = require('path') //引入文件路径函数 //获取文件的绝对路径 const filePath = path.resolve(__dirname, './access_token.json') //小程序的APPID和Secret const APPID = '小程序的APPID' const Secret = '小程序的Secret' //获取Token的URL const URL = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APPID}&secret=${Secret}` //从小程序端获取Token函数 const updateAccessToken = async () => { const resStr = await rp(URL) //将字符串类型转为对象 const res = JSON.parse(resStr) if (res.access_token) { //写入文件 fs.writeFileSync(filePath, JSON.stringify({ access_token: res.access_token, //从小程序服务器获取的token createTime: new Date() //获取token时,对应的时间 })) } else { //如果文件不存在,则重新调用获取token函数,重新执行写入 await updateAccessToken() } } //查询Token函数返回给响应的函数去调用云函数 const getAccessToken = async () => { try { //读取文件 const readRes = fs.readFileSync(filePath, 'utf8') const readObj = JSON.parse(readRes) //获取access_token.json中的createtime的时间 const createTime = new Date(readObj.createTime).getTime() //获取当前时间 const nowTime = new Date().getTime() //判断Token的失效是否大于2小时,如果大于2小时则需要重新获取Token,然后在读取Token数据 if ((nowTime - createTime) / 1000 / 60 / 60 >= 2) { await updateAccessToken() await getAccessToken() } //返回Token数据 return readObj.access_token } catch (error) { //如果access_token文件不存在,则读取报错,此时需要重新从服务器获取token数据,再读取token数据 await updateAccessToken() await getAccessToken() } } //定时更新Token setInterval(async () => { await updateAccessToken() }, (7200 - 300) * 1000) module.exports = getAccessToken; [代码] 获取文件上传链接 从官方文档可知,传递access_token和env参数,访问文档中的请求地址,获取上传文件所需的参数数据。 [图片] 从官方文档可知,返回的上传数据中包含如下内容: authorization字段 token字段 cos_file_id字段 url字段 上传文件到云存储。 按照官方文档,访问返回的url字段的链接,并传递相关参数,将图片上传到云存储。 [图片] 图片上传功能的代码如下: [代码]const rp = require('request-promise') const fs = require('fs') //引用文件操作函数 const getAccessToken = require('./getAccessToken') const cloudStorage = { // 1、请求地址 async upload(ctx){ console.log(1,ctx) const ACCESS_TOKEN = await getAccessToken() const file = ctx.request.files.file const path = `test/${Date.now()}-${Math.random()}-${file.name}` var options = { method: 'POST', uri: `https://api.weixin.qq.com/tcb/uploadfile?access_token=${ACCESS_TOKEN}`, body: { env: 'miniprogram-dev-y7a0a', path, }, json: true // Automatically stringifies the body to JSON }; //请求返回的参数 const info = await rp(options) .then(res => { // POST succeeded... return res }) .catch(err => { // POST failed... console.log(err) }); // 2、上传文件到云存储 const params ={ method: 'POST', uri:info.url, header:{ 'content-type':'multipart/form-data' }, formData:{ key:path, Signature:info.authorization, 'x-cos-security-token':info.token, 'x-cos-meta-fileid':info.cos_file_id, file:fs.createReadStream(file.path) }, json: true // Automatically stringifies the body to JSON } await rp(params) return info.file_id } } module.exports=cloudStorage; [代码] 后续会陆续更新相关技术文章,来帮助大家快速熟悉和使用小程序·云开发实现小程序的快速开发,以及基于HTTPAPI实现小程序后端管理系统的开发。
2019-10-11