注意:
- 小程序后台设置:downloadFile合法域名
思路:
- 添加canvas
<canvas id="canvas" canvas-id="canvas" style="width: 300px; height: 500px"></canvas>
- 获取canvas实例
uni.createCanvasContext('canvas')
- 获取图片信息
uni.getImageInfo
- 保存图片
uni.canvasToTempFilePath
uni.saveImageToPhotosAlbum
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
// Poster.vue <template> <view class="flex-center col h-100"> <view class="relative" style="width: 300px; height: 500px"> <canvas id="canvas" canvas-id="canvas" style="width: 300px; height: 500px"></canvas> </view> <view class="flex-center absolute w-100 save-btn b-0 pb-50 mb-50" style="z-index: 11111"> <view class="common-btn color3 align-center" @click="saveImg"> 保存图片</view> </view> </view> </template> <script setup lang="ts"> import { onLoad } from '@dcloudio/uni-app' import { reactive } from 'vue' import { getData, getQrcode } from '@/api/index' import { useStoreHooks } from '@/hooks/' import ApiType from '@/typings/api' import Image from '@/utils/image' const { state } = useStoreHooks() let data = reactive({ score: {} as ApiType.ExerciseData, ctx: uni.createCanvasContext('canvas') }) onLoad(async () => { uni.showLoading({ title: '加载中...' }) getData({ uid: state.user.userInfo.uid, token: state.user.userInfo.token }).then(res => { data.score = res.data.data const p1 = Image.getImageInfo(Image.getImageUrl('hb.png')) const p2 = Image.getImageInfo(state.user.userInfo.head_url as string) const p3 = Image.getImageInfo(Image.getImageUrl('scj.png')) const p4 = getQrcodeTmp() Promise.all([p1, p2, p3, p4]).then(res => { data.ctx.drawImage(res[0], 0, 0, 300, 500) data.ctx.save() data.ctx.beginPath() data.ctx.arc(70, 110, 30, 0, 2 * Math.PI) data.ctx.clip() data.ctx.drawImage(res[1], 40, 80, 60, 60) data.ctx.restore() data.ctx.save() data.ctx.drawImage(res[2], 120, 180, 60, 60) data.ctx.save() data.ctx.beginPath() data.ctx.arc(150, 370, 35, 0, 2 * Math.PI) data.ctx.setFillStyle('#fff') data.ctx.fill() data.ctx.beginPath() data.ctx.arc(150, 370, 30, 0, 2 * Math.PI) data.ctx.clip() data.ctx.drawImage(res[3], 120, 340, 60, 60) data.ctx.restore() data.ctx.save() drawText() uni.hideLoading() }) }) }) const drawText = async () => { data.ctx.setFillStyle('#f1f3f2') data.ctx.setFontSize(16) data.ctx.setTextAlign('left') data.ctx.fillText(state.user.userInfo.name as string, 120, 105) data.ctx.setFillStyle('#F0F0F0') data.ctx.setFontSize(15) data.ctx.setTextAlign('center') data.ctx.fillText('您已完成全部答题', 150, 265) data.ctx.setFillStyle('#F0F0F0') data.ctx.setFontSize(15) data.ctx.setTextAlign('center') data.ctx.fillText( `答题成绩100`, 150, 285 ) data.ctx.setFillStyle('#5F715D') data.ctx.setFontSize(10) data.ctx.setTextAlign('center') data.ctx.fillText('长按识别 去看看', 150, 425) data.ctx.draw() } const saveImg = () => { uni.canvasToTempFilePath({ canvasId: 'myCanvas', success: res => { uni.saveImageToPhotosAlbum({ filePath: res.tempFilePath, success: () => { uni.showToast({ title: '保存成功' }) }, fail: () => { uni.showToast({ title: '保存失败', icon: 'error' }) } }) } }) } const getQrcodeTmp = (): Promise<string> => new Promise(resolve => { getQrcode({ path: '/pages/index/index' }).then(res => { const src = res.data.data.qc_img_base64 Image.getBase64ImageInfo(src).then(res => resolve(res)) }) }) </script> <style lang="scss" scoped> .content { padding: 20% 10%; } .avatar { overflow: hidden; width: 128rpx; height: 128rpx; margin: 20rpx; border-radius: 50%; } .info { color: #f1f3f2 !important; font-size: 30rpx; letter-spacing: 2px; line-height: 20px; text-align: center; padding-bottom: 30%; } .color1 { color: #f1f3f2 !important; } .color2 { color: #a1a9a1 !important; } .color3 { color: #bab6a8 !important; } </style> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
// image.ts namespace Image { /** * 获取图片全路径 * @param name 图片名 * @returns */ export const getImageUrl = (name: string): string => import.meta.env.VITE_IMG + name /** * 获取图片信息 * @param src 图片路径 * @returns */ export const getImageInfo = (src: string): Promise<string> => new Promise((resolve, reject) => { uni.getImageInfo({ src, success: res => resolve(res.path), fail: err => reject(err) }) }) /** * 将 base64 图片数据转本地图片 * @param base64Data base64图片数据 * @returns */ export const getBase64ImageInfo = (base64Data: string): Promise<string> => { const fs = uni.getFileSystemManager() return new Promise((resolve, reject) => { const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64Data) || [] if (!format) { reject(new Error('ERROR_BASE64SRC_PARSE')) } const FILE_BASE_NAME = 'tmp_base64src' const filePath = `${wx.env.USER_DATA_PATH}/${FILE_BASE_NAME}.${format}` const buffer = uni.base64ToArrayBuffer(bodyData) fs.writeFile({ filePath, data: buffer, encoding: 'binary', success: () => resolve(filePath), fail: () => reject(new Error('ERROR_BASE64SRC_WRITE')) }) }) } /** * 删除本地临时文件 * @param filePath 临时图片地址 * @returns */ export const removeTmpImage = (filePath: string): Promise<void> => new Promise(() => { const fs = uni.getFileSystemManager() fs.unlink({ filePath, success: res => console.log(res), fail: e => console.log(e) }) }) } export default Image |