要求能够实现将个人照片、信息,展示到小程序页面中,并可以导出。但是因为后端没有一个单独的图片服务器,并且图片是以一个blob对象捎带在响应数据中的,因此还要考虑blob数据转换成文件(图片)。
可以拆解一下功能逻辑:
1、发起请求,保存图片到本地路径,或者本地临时路径。
2、绘制。
3、绑定按键,点击即可导出文件(图片)。

其中1、3都有uniapp和小程序都有自带的api。
2的话主要是考虑canvas,主要有两种实现思路,一种是通过wxml导出,一种是直接在canvas上绘制。其中wxml导出是不大好实现的,通过查资料发现了有wxml2canvas这样的插件,可以阉割地实现这样一个功能。

下面分步来实现功能:
1、请求数据,这边比较简单,需要注意的就是同步异步的问题,这里处理地比较简单,用了await强制整个过程都是同步的。
写入文件:首先将blob这种二进制流放到一个缓冲区,然后写到一个路径,这里用到了一个小程序的缓存,然后同时做了一个随机文件名的处理,防止命名的碰撞。注意这里当头像数据存储完毕后,直接开始画了头像,是因为这里我发现wxml2canvas只有对text类型的节点处理的比较好,其他类型(图片、矩形)等处理起来不如直接用canvas快,所以这里图片和文本是分开做的,图片是用的原生canvas绘制(因为实现起来比较简单,所以这里也不会过多提及),文本是依靠wxml来实现的。

            async getData() {await uni.request({url: 'http://localhost:8081/teacher/selectTeacherElectronicResumeInfo',data: {tea_id: this.tea_id},method: 'POST'}).then(res => {this.resumeInfo = res[1].data.data})await uni.request({url: 'http://localhost:8081/teacher/findTeaPhoto',data: {tea_id: this.tea_id},method: 'POST'}).then(async res => {this.img = uni.base64ToArrayBuffer(res[1].data.data)var save = wx.getFileSystemManager()var rand = Math.random()this.path = wx.env.USER_DATA_PATH + `/pic${Math.random()}.png`console.log(this.path)console.log('tempPath->', this.path)await save.writeFile({filePath: this.path,data: this.img,encoding: 'binary',success: (res) => {console.log('success')this.drawAvatar()},fail: () => {console.log('fail')},complete: () => {// console.log('complete')}})})}

2、绘制
通过1,知道图片是单独使用原生canvas实现的,所以绘制部分只需要使用wxml以及wxml2canvas导出即可,这里贴一下各部分的代码。
通过代码也可以看到实现的是比较粗糙,线条是通过破折号来实现的。

<template><view><view class="share__canvas share__canvas1 canvas" id="wxml"><view class="content-box-first"><view class="draw_canvas title" data-type="text" data-text="个人信息">个人信息</view><view class="draw_canvas" data-type="text" :data-text="`姓名:${resumeInfo.tea.teaName}`">{{resumeInfo.tea.teaName}}</view><view class="draw_canvas" data-type="text" :data-text="`性别:${resumeInfo.tea.teaGender}`">{{resumeInfo.tea.teaGender}}</view><view class="draw_canvas" data-type="text" :data-text="`电子邮箱:${resumeInfo.tea.mailAddress}`">{{resumeInfo.tea.mailAddress}}</view><view class="draw_canvas" data-type="text" :data-text="`联系电话:${resumeInfo.tea.teaTelNum}`">{{resumeInfo.tea.teaTelNum}}</view><view class="draw_canvas" data-type="text" :data-text="`研究方向:${resumeInfo.tea.searchDirection}`">{{resumeInfo.tea.searchDirection}}</view><view class="draw_canvas" data-type="text" :data-text="`所属学院:${resumeInfo.tea.teaDname}`">{{resumeInfo.tea.teaDname}}</view><view class="draw_canvas" data-type="text" :data-text="`学历:${resumeInfo.tea.highestEdu}`">{{resumeInfo.tea.highestEdu}}</view><view class="draw_canvas" data-type="text" :data-text="`职称:${resumeInfo.tea.position}`">{{resumeInfo.tea.position}}</view></view><view class="draw_canvas line" data-text="—————————————————————" data-type="text"></view><view class="content-box-other"><view class="draw_canvas title" data-type="text" data-text="获得奖项">获得奖项</view><view v-for="(item,index) in resumeInfo.award" :key="index"><view class="draw_canvas" data-type="text" :data-text="`${item.awardName}-${item.awardSetDep}`">{{`${item.awardName}-${item.awardSetDep}`}}</view></view></view><view class="draw_canvas line" data-text="—————————————————————" data-type="text"></view><view class="content-box-other"><view class="draw_canvas title" data-type="text" data-text="主持项目">主持项目</view><view v-for="(item,index) in resumeInfo.project" :key="index"><view class="draw_canvas long-text" data-type="text" :data-text="`${item.proName}-${item.proGrade}`">{{`${item.proName}-${item.proGrade}`}}</view></view></view><view class="draw_canvas line" data-text="—————————————————————" data-type="text"></view><view class="content-box-other"><view class="draw_canvas title" data-type="text" data-text="发表论文">发表论文</view><view v-for="(item,index) in resumeInfo.paper" :key="index"><view class="draw_canvas long-text" data-type="text" :data-text="`${item.paperTitle}-${item.colMethod}`">{{`${item.paperTitle}-${item.colMethod}`}}</view></view></view><view class="draw_canvas line" data-text="—————————————————————" data-type="text"></view><view class="content-box-other"><view class="draw_canvas title" data-type="text" data-text="主持项目">主持项目</view><view v-for="(item,index) in resumeInfo.project" :key="index"><view class="draw_canvas long-text" data-type="text" :data-text="`${item.proName}-${item.proGrade}`">{{`${item.proName}-${item.proGrade}`}}</view></view></view><view class="draw_canvas line" data-text="—————————————————————" data-type="text"></view><view class="content-box-other"><view class="draw_canvas title" data-type="text" data-text="学术专著">学术专著</view><view v-for="(item,index) in resumeInfo.book" :key="index"><view class="draw_canvas" data-type="text" :data-text="`《${item.monographName}》-${item.press}`">{{`《${item.monographName}》-${item.press}`}}</view></view></view><view class="draw_canvas line" data-text="—————————————————————" data-type="text"></view><view class="content-box-other"><view class="draw_canvas title" data-type="text" data-text="发明专利">发明专利</view><view v-for="(item,index) in resumeInfo.patent" :key="index"><view class="draw_canvas" data-type="text" :data-text="`${item.patName}-${item.patType}-${item.state}`">{{`${item.patName}-${item.patType}-${item.state}`}}</view></view></view><view class="draw_canvas line" data-text="—————————————————————" data-type="text"></view></view><canvas canvas-id="resume" id="resume-box"></canvas><button id="footer-btn-save" @click="getResume">生成简历</button></view>
</template>
drawResume() {var systemInfo = uni.getSystemInfoSync()this.drawCanvasImage = new Wxml2Canvas({width: systemInfo.screenWidth, // 宽, 以iphone6为基准,传具体数值,其他机型自动适配height: systemInfo.screenHeight*1.5, // 高element: 'resume',background: '#ffffff',})let data = {list: [{type: 'wxml',class: '.share__canvas1 .draw_canvas', // draw_canvas指定待绘制的元素limit: '.share__canvas1', // 限定绘制元素的范围,取指定元素与它的相对位置x: 0,y: 0}]}this.drawCanvasImage.draw(data);}

本来想使用scss的嵌套的特性,结果发现实际上多分出来几个类就行,比如title、item

<style lang="scss">.line{font-weight: bolder ;color: grey;margin-left: 50rpx;}#resume-box {height: 150vh;width: 100vw;top: 0;}.long-text{font-size: 24rpx;}.item{font-size: 28rpx;color:#303030;}.title{margin-top:20rpx;font-size: 40rpx;font-weight: bold;text-decoration:underline}#wxml {position: fixed;top: 0;height: 150vh;visibility: hidden;}#footer-btn-save {height: 100rpx;line-height: 100rpx;}
.content-box-first{margin-top: 300rpx;margin-left: 50rpx;width: 100vh;
}
.content-box-other{margin-top: 50rpx;margin-left: 50rpx;width: 100vh;
}
</style>

3、导出图片
直接调用wx的api即可

 getResume() {wx.canvasToTempFilePath({canvasId: 'resume',fileType: 'png',quality: 1,success: (res) => {wx.saveImageToPhotosAlbum({filePath: res.tempFilePath,success: () => {console.log('保存成功')},fail: () => {},complete: () => {}})},fail: () => {console.log()},complete: () => {}})}

大致可以实现成这样,实际上如果仔细设计一下ui,可以更好看,但没必要。

这里贴一下完整代码

<template><view><view class="share__canvas share__canvas1 canvas" id="wxml"><view class="content-box-first"><view class="draw_canvas title" data-type="text" data-text="个人信息">个人信息</view><view class="draw_canvas" data-type="text" :data-text="`姓名:${resumeInfo.tea.teaName}`">{{resumeInfo.tea.teaName}}</view><view class="draw_canvas" data-type="text" :data-text="`性别:${resumeInfo.tea.teaGender}`">{{resumeInfo.tea.teaGender}}</view><view class="draw_canvas" data-type="text" :data-text="`电子邮箱:${resumeInfo.tea.mailAddress}`">{{resumeInfo.tea.mailAddress}}</view><view class="draw_canvas" data-type="text" :data-text="`联系电话:${resumeInfo.tea.teaTelNum}`">{{resumeInfo.tea.teaTelNum}}</view><view class="draw_canvas" data-type="text" :data-text="`研究方向:${resumeInfo.tea.searchDirection}`">{{resumeInfo.tea.searchDirection}}</view><view class="draw_canvas" data-type="text" :data-text="`所属学院:${resumeInfo.tea.teaDname}`">{{resumeInfo.tea.teaDname}}</view><view class="draw_canvas" data-type="text" :data-text="`学历:${resumeInfo.tea.highestEdu}`">{{resumeInfo.tea.highestEdu}}</view><view class="draw_canvas" data-type="text" :data-text="`职称:${resumeInfo.tea.position}`">{{resumeInfo.tea.position}}</view></view><view class="draw_canvas line" data-text="—————————————————————" data-type="text"></view><view class="content-box-other"><view class="draw_canvas title" data-type="text" data-text="获得奖项">获得奖项</view><view v-for="(item,index) in resumeInfo.award" :key="index"><view class="draw_canvas" data-type="text" :data-text="`${item.awardName}-${item.awardSetDep}`">{{`${item.awardName}-${item.awardSetDep}`}}</view></view></view><view class="draw_canvas line" data-text="—————————————————————" data-type="text"></view><view class="content-box-other"><view class="draw_canvas title" data-type="text" data-text="主持项目">主持项目</view><view v-for="(item,index) in resumeInfo.project" :key="index"><view class="draw_canvas long-text" data-type="text" :data-text="`${item.proName}-${item.proGrade}`">{{`${item.proName}-${item.proGrade}`}}</view></view></view><view class="draw_canvas line" data-text="—————————————————————" data-type="text"></view><view class="content-box-other"><view class="draw_canvas title" data-type="text" data-text="发表论文">发表论文</view><view v-for="(item,index) in resumeInfo.paper" :key="index"><view class="draw_canvas long-text" data-type="text" :data-text="`${item.paperTitle}-${item.colMethod}`">{{`${item.paperTitle}-${item.colMethod}`}}</view></view></view><view class="draw_canvas line" data-text="—————————————————————" data-type="text"></view><view class="content-box-other"><view class="draw_canvas title" data-type="text" data-text="主持项目">主持项目</view><view v-for="(item,index) in resumeInfo.project" :key="index"><view class="draw_canvas long-text" data-type="text" :data-text="`${item.proName}-${item.proGrade}`">{{`${item.proName}-${item.proGrade}`}}</view></view></view><view class="draw_canvas line" data-text="—————————————————————" data-type="text"></view><view class="content-box-other"><view class="draw_canvas title" data-type="text" data-text="学术专著">学术专著</view><view v-for="(item,index) in resumeInfo.book" :key="index"><view class="draw_canvas" data-type="text" :data-text="`《${item.monographName}》-${item.press}`">{{`《${item.monographName}》-${item.press}`}}</view></view></view><view class="draw_canvas line" data-text="—————————————————————" data-type="text"></view><view class="content-box-other"><view class="draw_canvas title" data-type="text" data-text="发明专利">发明专利</view><view v-for="(item,index) in resumeInfo.patent" :key="index"><view class="draw_canvas" data-type="text" :data-text="`${item.patName}-${item.patType}-${item.state}`">{{`${item.patName}-${item.patType}-${item.state}`}}</view></view></view><view class="draw_canvas line" data-text="—————————————————————" data-type="text"></view></view><canvas canvas-id="resume" id="resume-box"></canvas><button id="footer-btn-save" @click="getResume">生成简历</button></view>
</template><script>import Wxml2Canvas from 'wxml2canvas'export default {data() {return {img: '',imgUrl: '',width: uni.getSystemInfoSync().screenWidth,height: uni.getSystemInfoSync().screenHeight,ctx: uni.createCanvasContext('resume'),resumeInfo: {},tea_id: 2007020301}},methods: {drawResume() {var systemInfo = uni.getSystemInfoSync()this.drawCanvasImage = new Wxml2Canvas({width: systemInfo.screenWidth, // 宽, 以iphone6为基准,传具体数值,其他机型自动适配height: systemInfo.screenHeight*1.5, // 高element: 'resume',background: '#ffffff',})let data = {list: [{type: 'wxml',class: '.share__canvas1 .draw_canvas', // draw_canvas指定待绘制的元素limit: '.share__canvas1', // 限定绘制元素的范围,取指定元素与它的相对位置x: 0,y: 0}]}this.drawCanvasImage.draw(data);},getResume() {wx.canvasToTempFilePath({canvasId: 'resume',fileType: 'png',quality: 1,success: (res) => {wx.saveImageToPhotosAlbum({filePath: res.tempFilePath,success: () => {console.log('保存成功')},fail: () => {},complete: () => {}})},fail: () => {console.log()},complete: () => {}})},async getData() {await uni.request({url: 'http://localhost:8081/teacher/selectTeacherElectronicResumeInfo',data: {tea_id: this.tea_id},method: 'POST'}).then(res => {this.resumeInfo = res[1].data.data})await uni.request({url: 'http://localhost:8081/teacher/findTeaPhoto',data: {tea_id: this.tea_id},method: 'POST'}).then(async res => {this.img = uni.base64ToArrayBuffer(res[1].data.data)var save = wx.getFileSystemManager()var rand = Math.random()this.path = wx.env.USER_DATA_PATH + `/pic${Math.random()}.png`console.log(this.path)console.log('tempPath->', this.path)await save.writeFile({filePath: this.path,data: this.img,encoding: 'binary',success: (res) => {console.log('success')this.drawAvatar()},fail: () => {console.log('fail')},complete: () => {// console.log('complete')}})})},drawAvatar() {this.imgUrl = this.pathconsole.log('imgUrl', this.imgUrl)this.ctx.drawImage(this.imgUrl, 0.36 * this.width, 20, 100, 120)this.ctx.draw(true)}},async created() {this.getData()setTimeout(()=>{this.drawResume();console.log(this.resumeInfo)},500)},destroyed(){wx.removeSavedFile({filePath:this.path})}}
</script><style lang="scss">.line{font-weight: bolder ;color: grey;margin-left: 50rpx;}#resume-box {height: 150vh;width: 100vw;top: 0;}.long-text{font-size: 24rpx;}.item{font-size: 28rpx;color:#303030;}.title{margin-top:20rpx;font-size: 40rpx;font-weight: bold;text-decoration:underline}#wxml {position: fixed;top: 0;height: 150vh;visibility: hidden;}#footer-btn-save {height: 100rpx;line-height: 100rpx;}
.content-box-first{margin-top: 300rpx;margin-left: 50rpx;width: 100vh;
}
.content-box-other{margin-top: 50rpx;margin-left: 50rpx;width: 100vh;
}
</style>

然后学生版简历和教师版简历是分开实现的,用的是纯原生的canvas来做的。基本思路和教师端一致的,无非就是绘制的时候,主要选择了绝对的定位方式,对数据的行数有着极高的要求。贴一下代码,,可以看到代码行数明显多于上面。主要是后端接口返了一个很大的数据集合,需要从里面筛选,教师版的简历,是因为拿到接口以后才开始设计的,省去了很多遍历的工作。这里要注意去限制一下数据的长度和条数,不然就会和线条重叠了。

<template><view><canvas canvas-id='myResume' style="height:100vh;width:750rpx"></canvas><button id="button" @click="handler">导出图片</button></view>
</template><script>export default {data() {return {stu_id: 201656788,resumeInfo: {avatarUrl: 'http://127.0.0.1:9999/pic?pic=.png', //头像文件,这里可以给一个blob,我可以转成本地文件//下面这三个就是字面意思name: 'zzy',email: '',telephone: '15692373380',education: {undergraduate: { //表示本科阶段的教育经历begin: '2018年', //开始时间end: '2022年', //毕业时间school: '山东大学', //学校名称college: '软件学院', //学院名称major: '软件工程' //专业},graduate: { //表示研究生阶段的教育经历begin: '2018年',end: '2022年',school: '山东大学',college: '软件学院',major: '软件工程'}},researchInterest: '复制粘贴与调参', //研究方向findings: [ //论文或者叫成果,超过5篇取5篇文章名就行,小于等于5篇有多少给多少'《摸鱼与划水》','《期末三天突击学习法》','《学习娱乐两边误》','《吃遍山东大学食堂》','《大学如何摸鱼三年》']}}},methods: {handler() {wx.canvasToTempFilePath({canvasId: 'myResume',fileType: 'jpg',quality: 1,success: (res) => {wx.saveImageToPhotosAlbum({filePath: res.tempFilePath,success: () => {console.log('保存成功')},fail: () => {},complete: () => {}})},fail: () => {},complete: () => {}})}},async created() {this.resumeInfo.education.undergraduate = uni.getStorageSync('undergraduateInfo')await uni.request({url: 'http://localhost:8081//student///selectStudentElectronicResumeInfo',data: {stu_id: this.stu_id}}).then(res => {//科研成果部分res = res[1].dataconsole.log(res)var books = []for (var i = 0; i < res.data.book.length; i++) {if (res.data.book[i].monographName.length > 15) {books.push(`《${res.data.book[i].monographName.slice(0,14)}...》`)} else {books.push(`《${res.data.book[i].monographName}》`)}}for (var i = 0; i < res.data.paper.length; i++) {if (res.data.paper[i].paperTitle.length > 15) {books.push(`《${res.data.paper[i].paperTitle.slice(0,14)}...》`)} else {books.push(`《${res.data.paper[i].paperTitle}》`)}}for (var i = 0; i < res.data.patent.length; i++) {if (res.data.patent[i].patName.length > 15) {books.push(`《${res.data.patent[i].patName.slice(0,14)}...》`)} else {books.push(`《${res.data.patent[i].patName}》`)}}if (books.length < 5) {this.resumeInfo.findings = books} else {this.resumeInfo.findings = books.slice(0, 5)}//個人信息部分this.resumeInfo.name = res.data.stu.stuNamethis.resumeInfo.email = res.data.stu.mailAddressthis.resumeInfo.telephone = res.data.stu.stuTelNumthis.resumeInfo.education.graduate.begin = res.data.stu.stuGrade + '年'this.resumeInfo.education.graduate.end = Number(res.data.stu.stuGrade) + 3 + '' + '年'this.resumeInfo.education.graduate.college = res.data.stu.stuDname + '学院'this.resumeInfo.education.graduate.major = res.data.stu.stuMajorthis.resumeInfo.researchInterest = res.data.stu.stuMajorthis.resumeInfo.avatarUrl = res.data.stu.stuPhotoconsole.log(this.resumeInfo.avatarUrl)})var width, height, imgawait wx.getSystemInfo().then(res => {width = res.windowWidthheight = res.windowHeight})var ctx = wx.createCanvasContext('myResume')ctx.setFillStyle('#595959') //文字颜色:默认黑色var linesLocation = [30, 280, 330] //控制三大部分的距离var linesWords = ['教育经历', '研究方向', '科研成果']// wx.downloadFile({//     url:this.resumeInfo.avatarUrl,//     success:(res)=>{//         img = res.tempFilePath//         ctx.drawImage(img,0,0,20,20)//         console.log(res.tempFilePath)//     }// })ctx.setFontSize(25)ctx.fillText(this.resumeInfo.name, width * 0.32, height * 0.06)ctx.setFontSize(15)ctx.fillText('邮箱地址:' + this.resumeInfo.email, width * 0.32, height * 0.11)ctx.fillText('电话号码:' + this.resumeInfo.telephone, width * 0.32, height * 0.15)this.resumeInfo.avatarUrl = uni.base64ToArrayBuffer(this.resumeInfo.avatarUrl)var imgSrc = this.resumeInfo.avatarUrlvar save = wx.getFileSystemManager()var rand = Math.random()var path = wx.env.USER_DATA_PATH + `/pic${Math.random()}.png`console.log(path)save.writeFile({filePath: path,data: this.resumeInfo.avatarUrl,encoding: 'binary',success: (res) => {console.log(path)}})ctx.drawImage(path, 0.06 * width, 10, 72, 90)save.removeSavedFile({filePath: path})for (var i = 0; i < linesLocation.length; i++) {ctx.setFillStyle('#595959')ctx.fillRect(width * 0.01, 75 + linesLocation[i], width * 0.98, 8)ctx.fillRect(width * 0.025, 90 + linesLocation[i], width * 0.95, 2)ctx.beginPath()ctx.moveTo(0.04 * width, 100 + linesLocation[i])ctx.lineTo(0.04 * width + 50, 100 + linesLocation[i])ctx.lineTo(0.04 * width + 65, 110 + linesLocation[i])ctx.lineTo(0.04 * width + 50, 120 + linesLocation[i])ctx.lineTo(0.04 * width, 120 + linesLocation[i])ctx.closePath()ctx.fill()ctx.arc(0.04 * width + 80, 110 + linesLocation[i], 8, 0, Math.PI * 2, true)ctx.fill()ctx.globalCompositeOperation = 'sourse-out'}for (var i = 0; i < linesWords.length; i++) {ctx.setFillStyle('#FFFFFF')ctx.setFontSize(12)ctx.fillText(linesWords[i], 0.04 * width + 2, 120 + linesLocation[i] - 5)}ctx.setFillStyle('#595959')ctx.setFontSize(18)ctx.fillText('本科学历', 0.3 * width, 120 + linesLocation[0] - 5)ctx.setFontSize(15)ctx.fillText(`时间:${this.resumeInfo.education.undergraduate.begin}-${this.resumeInfo.education.undergraduate.end}`,0.3 * width,120 + linesLocation[0] + 20)ctx.fillText(`学校:${this.resumeInfo.education.undergraduate.school}`,0.3 * width,120 + linesLocation[0] + 40)ctx.fillText(`学院:${this.resumeInfo.education.undergraduate.college}`,0.3 * width,120 + linesLocation[0] + 60)ctx.fillText(`专业:${this.resumeInfo.education.undergraduate.major}`,0.3 * width,120 + linesLocation[0] + 80)var educationOffset = 120ctx.setFontSize(18)ctx.fillText('研究生学历', 0.3 * width, 120 + linesLocation[0] - 5 + educationOffset)ctx.setFontSize(15)ctx.fillText(`时间:${this.resumeInfo.education.graduate.begin}-${this.resumeInfo.education.graduate.end}`,0.3 * width,120 + linesLocation[0] + 20 + educationOffset)ctx.fillText(`学校:${this.resumeInfo.education.graduate.school}`,0.3 * width,120 + linesLocation[0] + 40 + educationOffset)ctx.fillText(`学院:${this.resumeInfo.education.graduate.college}`,0.3 * width,120 + linesLocation[0] + 60 + educationOffset)ctx.fillText(`专业:${this.resumeInfo.education.graduate.major}`,0.3 * width,120 + linesLocation[0] + 80 + educationOffset)ctx.setFontSize(18)ctx.fillText(this.resumeInfo.researchInterest,0.3 * width,120 + linesLocation[1])ctx.setFontSize(15)for (var i = 0; i < this.resumeInfo.findings.length; i++) {ctx.fillText(this.resumeInfo.findings[i],0.3 * width,115 + linesLocation[2] + i * 30)}//调用draw()开始绘制setTimeout(() => {ctx.draw()}, 3000)}}
</script>
<style>#button {color: white;background-color: #007AFF;height: 50rpx;line-height: 50rpx;width: 375rpx;}
</style>

最后导出的图片的效果是下面这样

最后,说一下这两种实现的优劣。
依靠wxml实现的简历可以做到对内容高度的自适应,语义化比较明显,可以使用css来对文本进行控制;缺点就是不大会去画图片,因此只能依靠原生canvas去绘制头像和一些线条(当然例子里只绘制了线条)。
靠纯canvas实现就是麻烦一点,但是现在想了一下发现让页面留一个canvasContext类型的变量,并且封装一些函数来进行常用的操作,比如绘制线条和文字,理论上是可以通过计算来达到自适应的布局的。

最最最后,统计一下使用的api和插件
微信开放平台 这里主要是一些文件操作
wxml2canvas
如果是pc或者h5做电子简历会更容易,因为有dom-to-canvas这样的插件。小程序的能力相比web还是差了很多的

【项目难点】使用canvas完成电子简历功能相关推荐

  1. 接入微信电子发票java_Android app对接微信电子发票功能

    最新项目需要对接微信的电子发票功能 业务场景如下图所示: 业务场景 参考官方给出的文档提示 先搞清楚整个业务流程 业务流程 搞清楚大致的业务流程之后  可以开始对接微信平台了 1  第一步 必备数据 ...

  2. 把view或者div绘制 canvas ,导出图片功能实现完整源码附效果图(兼容H5和小程序)

    先看下效果图:(上面灰色块内的用div和CSS写出来的,然后绘制到canvas) 实现此功能需要使用到一个微信小程序的插件,插件官方文档地址: wxml-to-canvas | 微信开放文档 本博客代 ...

  3. html实现照片添加功能,HTML5 Canvas调用手机拍照功能实现图片上传功能(图文详解上篇)...

    这篇文章主要为大家详细介绍了HTML5 Canvas,和jquery技术,调用手机拍照功能实现图片上传,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 因为最近一段时间,一直在弄微信项目,其中涉及到 ...

  4. django 实现电子支付功能

    思路:调用第三方支付 API 接口实现支付功能.本来想用支付宝来实现第三方网站的支付功能的,但是在实际操作中发现支付宝没有 Python 接口,网上虽然有他人二次封装的的 Python 接口,但是对我 ...

  5. wps如何自己制作流程图_WPS小技巧——如何制作炫酷的个性电子简历

    大家好,上期内容我们介绍了一个在Word文档中,快速改变顺序排列的小技巧,那今天,再来给大家分享一个利用Excel快速制作电子简历的实用方法吧~ 首先,选中部分空白单元格,然后给它添加一个外边框来粗化 ...

  6. Codesys电子凸轮功能的设计与可视化仿真

    1.序言 在机械设计中,凸轮机构可以完成各种复杂的运动,包括直线运动.摆动.等速运动和不等速运动,能够应用于各个行业:电子凸轮(英文简称ECAM)是利用构造的凸轮曲线来模拟机械凸轮,以达到机械凸轮系统 ...

  7. 前端,通过面试去学习,开放问题(个人对前端发展的理解、项目难点、项目亮点、最复杂的逻辑、团队协作冲突问题、HR面试问题)

    开放问题 这一篇去整理开放问题,可以说面试中比较难回答.不容易回答好.最耗时间的问题就是这些开放问题,更能提现一个人的水平和解决问题能力吧 试着整理一下 个人对前端发展的理解 前端的话,前几年流行大前 ...

  8. DCWriter 电子病历文档编辑器的 电子病历功能规范对照表

    DCWriter 电子病历文档编辑器的 电子病历功能规范对照表 DCWriter能实现国家制定的<电子病历功能规范>中的针对病历文档编辑器而制定的大部分功能需求,主要有: 电子病历功能规范 ...

  9. 利用canvas绘制电子证书

    利用canvas绘制电子证书 <canvas>标签是HTML中的一个图形容器,其实你也可以把它理解成为一个程序员的画图工具,它只要提供的是2D绘制功能.我们可以通过利用JavaScript ...

最新文章

  1. HDU 1754 I Hate It
  2. 将Linux脚本中的正常输出,警告,错误等信息输出到文件中
  3. 从java到c_Binder机制,从Java到C (4. Parcel)
  4. 什么是Hack技术?
  5. 动感英语笔记_【共同战“疫”】(一)滨海新区大港西苑小学英语教师团队
  6. Jenkins的卸载
  7. 僵尸进程和孤儿进程 转载
  8. Java使用代理服务器
  9. 如何检查Django版本
  10. ZOJ 3686 A Simple Tree Problem
  11. 使用Kotlin进行Android开发
  12. 新版FireFox使用NPAPI插件的办法
  13. linux下运行hadoop,Linux命令行下运行Hadoop单元测试
  14. 倒立摆 adams matlab,基于ADAMS与MATLAB联合仿真地倒立摆设计毕业论文.docx
  15. JavaScript重定向Referer丢失
  16. canvas 擦除动画_HTML5 实现橡皮擦的擦除效果
  17. Markdown pad2 使用本地图片
  18. vue日历排班组件_vue之手把手教你写日历组件
  19. python导入excel数据到mysql
  20. java wtc_java通过wtc调用tuxedo服务超时

热门文章

  1. 马云收购士兰微_在当下的中国,这家民企十分难得,芯片设计制造封装样样都做...
  2. Springboot 个性化配置SpringMVC
  3. 协方差矩阵计算性质推导
  4. 海天味业股票可以长期投资吗
  5. 安卓接收消息通知——从后台发出到显示消息弹窗详解
  6. 美国佐治亚理工学院计算机博士,美国佐治亚理工学院机械工程博士PHD全奖
  7. “肢解”自动化立体仓库:分分钟了解透彻
  8. PPT文件取消限制密码
  9. TensorRT及PyCUDA安装记录
  10. AMF目前有两种版本,AMF0和AMF3