微信小程序使用canvas画动态表格并导出图片
效果图:
wxml:
<view> <view class="search_box"> <view class="selectBox_time"> <picker class="time-tilte-text" mode="date" fields="day" value="{{date}}" start="2020-01-01" bindchange="getDateTime" bindcancel="cancelDate"> <view class="selectDate"> <text>{{queryDay}}</text> <text class="selectIcon">﹀</text> </view> </picker> </view> <view class="btn" bindtap="saveExcel"><i-icon type="search" />导出清单</view> <view class="btn" style="background: #15B628;color: #fff;" bindtap="saveImg"><i-icon type="add" />保存图片</view> </view> <canvas id="myCanvas" type="2d" style=" width: 100vw; height: calc(100vh - 80px); background: #fff;" /> </view>
.intro { margin: 30px; text-align: center; } .search_box { width: 96%; margin: 10px 2% 10px 2%; height: 40px; /* padding-top: 10px; */ line-height: 40px; } .selectBox_time { width: 44%; float: left; font-size: 28rpx; border: 1rpx solid #eee; border-radius: 10px; position: relative; } .selectDate text { margin-left: 4px; } .selectIcon{ position: absolute; right: 10px; color: #c0c4cc; font-size: 12px; top: 6rpx; } .btn { width: 23%; background: #eee; text-align: center; margin-left: 10px; float: right; font-size: 26rpx; color: #000; border-radius: 8px; }
js:
const app = getApp() const utils = require('../../common/util.js'); Page({ /** * 页面的初始数据 */ data: { shareImgSrc:'', deviceWidth:'', deviceHeight:'', tokenInfo:'', queryDay:'请选择时间', mgridArr: [], headers: [] }, /** * 生命周期函数--监听页面加载 */ onLoad() { let that = this var tokenInfo = wx.getStorageSync('tokenInfo') let month = utils.formatDate(new Date()); const data = month.substring(0, 10) this.setData({ queryDay: data, tokenInfo:tokenInfo }) wx.getSystemInfo({ success(res) { that.setData({ deviceWidth: res.windowWidth, deviceHeight: res.windowHeight }) } }) this.queryDossierDayDataTable() }, // 选择日期 getDateTime: function(e){ this.setData({ queryDay: e.detail.value }) this.queryDossierDayDataTable() }, // 取消时间 cancelDate: function(){ this.setData({ queryDay: '请选择时间' }) }, // 站点日报 queryDossierDayDataTable(){ let queryDay = this.data.queryDay == '请选择时间' ? null : this.data.queryDay if(queryDay == '' || queryDay == null ){ wx.showToast({ title: '请选择日期', icon: 'none', duration: 2000 }) }else{ let params = { queryDay: this.data.queryDay, isMonth: false } var that = this; app.$post.post(`地址`, params, true).then(res => { if(res.code == 0){ if(res.data){ let headers = res.data.headers let mgrid_COUNT =res.data.mgrid_COUNT let mgridArr = res.data.mgrid let len = mgridArr[0].length - headers.length mgridArr.forEach(item => { item.splice(-len) }) let lastArr = mgridArr.concat(mgrid_COUNT) that.setData({ mgridArr: lastArr, headers: headers }) that.drawCanvas() }else{ wx.showToast({ title: "这一天没有检测数据", icon: 'none' }) } }else{ wx.showToast({ title: res.msg, icon: 'none' }) } }) } }, //适配方法封装 /**canvas绘制文字适配 * @params coordinate 坐标值或大小 */ fitSize: function (coordinate) { let that = this; let deviceWidth = that.data.deviceWidth;//这个windowWidth指的是该设备宽度,可以onLoad监听页面加载中获取 let v = 375 / deviceWidth;//375是设计稿的大小,得到的v值是:设计稿和设备宽度的比例关系,也可理解成在设计稿的大小基础上放大或缩小的倍数 return coordinate / v;//返回的是当前坐标值或者大小与v的比例 }, drawCanvas(){ let that = this wx.createSelectorQuery() .select('#myCanvas') // 在 WXML 中填入的 id .fields({ node: true, size: true }) .exec((res) => { // Canvas 对象 const header = that.data.headers const mgrid = that.data.mgridArr const canvas = res[0].node // 渲染上下文 const ctx = canvas.getContext('2d') // Canvas 画布的实际绘制宽高 const width = res[0].width const height = res[0].height // const height = mgrid.length * 20 +150 // 初始化画布大小 const dpr = wx.getWindowInfo().pixelRatio canvas.width = width * dpr canvas.height = height * dpr ctx.scale(dpr, dpr) // 清空画布 ctx.clearRect(0, 0, width, height) canvas.width=455*2; canvas.height=mgrid.length*20+580; // canvas.style.border="1px solid #ccc"; var rectH=20; //单元格高度 // var rectW=65; //单元格宽度 var rectW=canvas.width / header.length ctx.scale(1,1) ctx.lineWidth = 1; ctx.strokeStyle = "#ccc"; ctx.textAlign = "center"; ctx.fillStyle="#FFFFFF"; //头部 var title = that.data.tokenInfo.orgName + ' 全车影像检测日报' ctx.font = `normal normal normal ${parseInt(that.fitSize(16))}px PingFang SC`; ctx.fillStyle="#000000"; ctx.fillText(title,455, 40) ctx.font = `normal normal normal ${parseInt(that.fitSize(12))}px PingFang SC`; ctx.fillText(that.data.queryDay, 455, 60) ctx.font = `normal normal normal ${parseInt(that.fitSize(8))}px PingFang SC`; ctx.fillStyle = '#15B628' ctx.fillRect(30, 80, 40, 10) ctx.fillText('配件检测正常', 105, 88) ctx.fillStyle = '#267ef0' ctx.fillRect(150, 80, 40, 10) ctx.fillText('配件异常已销售', 235, 88) ctx.fillStyle = '#ff4d00' ctx.fillRect(280, 80, 40, 10) ctx.fillText('配件异常未销售', 365, 88) for(let i = 0; i < header.length; i++){ ctx.fillStyle = "#000"; ctx.font = `normal normal normal ${parseInt(that.fitSize(7))}px PingFang SC`; ctx.textAlign = "center"; ctx.fillText(header[i], (rectW/2) + (rectW*i), 125); } for (let i = 0; i < mgrid.length; i++) { if( i < mgrid.length-4){ for(let j = 0; j < mgrid[0].length; j++){ ctx.fillStyle = "#333"; ctx.font = `normal normal normal ${parseInt(that.fitSize(7))}px PingFang SC`; ctx.textAlign = "center"; if(mgrid[i][j] == "-"){ ctx.fillText(" ", (rectW/2) + (rectW*j), rectH * i + 145) }else{ if(mgrid[i][j] == 0){ ctx.fillStyle = '#15B628' ctx.fillRect((rectW/4) + (rectW*j), rectH * i + 135, rectW/2, 10) }else if(mgrid[i][j] == 1){ ctx.fillStyle = '#267ef0' ctx.fillRect((rectW/4) + (rectW*j), rectH * i + 135, rectW/2, 10) }else if(mgrid[i][j] == 2){ ctx.fillStyle = '#ff4d00' ctx.fillRect((rectW/4) + (rectW*j), rectH * i + 135, rectW/2, 10) }else{ ctx.fillText(mgrid[i][j], (rectW/2) + (rectW*j), rectH * i + 145) } // ctx.fillText(mgrid[i][j], 32 + (65*j), rectH * i + 35) // ctx.drawImage(code, 32 + (65*j), rectH * i + 35, 14, 14) } } }else{ for(let j = 0; j < mgrid[0].length; j++){ ctx.fillStyle = "#000"; ctx.font = `normal normal normal ${parseInt(that.fitSize(7))}px PingFang SC`; ctx.textAlign = "center"; if(mgrid[i][j] == null){ ctx.fillText(" ", rectW/2 + (rectW*j), rectH * i + 145); }else{ ctx.fillText(mgrid[i][j], rectW/2 + (rectW*j), rectH * i + 145); } } } } for(var i= 0;i<header.length+2;i++){ // 画竖线 ctx.moveTo(rectW*i,110); ctx.lineTo(rectW*i, rectH*(mgrid.length + 1)+110); // 画横线 // ctx.moveTo(0,rectH*i+90); // ctx.lineTo(canvas.width,rectH*i+90); ctx.stroke(); } for(var i= 0;i<mgrid.length+2;i++){ // 画竖线 // ctx.moveTo(rectW*i,90); // ctx.lineTo(rectW*i,canvas.height); // 画横线 ctx.moveTo(0,rectH*i+110); ctx.lineTo(canvas.width,rectH*i+110); ctx.stroke(); } // 生成图片 wx.canvasToTempFilePath({ canvas, success: res => { // 生成的图片临时文件路径 const tempFilePath = res.tempFilePath that.setData({ //关键 赋值给变量 shareImgSrc: res.tempFilePath }) }, }) }) }, //保存图片 saveImg: function (){ var that = this; wx.saveImageToPhotosAlbum({ //shareImgSrc为canvas赋值的图片路径 filePath: that.data.shareImgSrc, success(res) { wx.showModal({ title: '保存成功', content: '图片成功保存到相册啦,去发图噻~', showCancel: false, confirmText: '确认', confirmColor: '#21e6c1', success: function (res) { if (res.confirm) { console.log('用户点击确定'); } } }) } }) }, saveExcel(){ wx.showToast({ title: "功能正在开发中,敬请期待", icon: 'none' }) }, /** * 生命周期函数--监听页面初次渲染完成 */ onReady() { }, /** * 生命周期函数--监听页面显示 */ onShow() { }, /** * 生命周期函数--监听页面隐藏 */ onHide() { }, /** * 生命周期函数--监听页面卸载 */ onUnload() { }, /** * 页面相关事件处理函数--监听用户下拉动作 */ onPullDownRefresh() { }, /** * 页面上拉触底事件的处理函数 */ onReachBottom() { }, /** * 用户点击右上角分享 */ // onShareAppMessage() {} })