Резюме:Необходимые навыки для разработки небольших программ...
FundebugПерепечатано с разрешения, авторские права принадлежат оригинальному автору.
Прошло много времени с тех пор, как были написаны требования к проекту, но я все еще хочу вернуться и подвести итоги, одно - пересмотреть и оптимизировать проект, а другое - сделать запись питов, чтобы избежать подобных проблем в будущем.
нужно
Используйте мощные социальные возможности WeChat для достижения цели расщепления с помощью небольших программ и привлечения новых пользователей.
В результате плакат выглядит следующим образом:
анализ спроса
1. Используя API, официально предоставленный апплетом, вы можете напрямую поделиться им и переслать его в группу WeChat, чтобы открыть апплет. 2. Используйте апплет для создания плаката, сохраните изображение в альбом и поделитесь им с кругом друзей.Пользователи могут долго нажимать идентификационный QR-код, чтобы подписаться на официальный аккаунт, или открыть апплет для достижения цели расщепления.
План реализации
1. Как анализировать
Считаю, что у всех должна быть подобная путаница, то есть как нарисовать постер по дизайну изделия.На самом деле я не знала с чего начать в то время.Поделилась в кругу друзей. Но рисуемые картинки имеют не только текст, но и числа, картинки, QR-коды и т. д., и все они живые.Как это сгенерировать динамически? Подумав об этом, мне нужно понемногу рисовать текст, цифры и фоновые изображения на холсте, чтобы я мог, наконец, синтезировать картинку через API и экспортировать ее в альбом мобильного телефона.
2. Проблемы, которые необходимо решить
- Динамическое получение и рисование QR-кодов (включая создание QR-кодов для мини-программ, QR-кодов официальной учетной записи и QR-кодов для открытия веб-страниц)
- Как нарисовать фоновое изображение и получить информацию об изображении
- Сохраните нарисованную картинку в локальный альбом
- Обрабатывать, отменяет ли пользователь авторизацию для сохранения в альбом
3. Этапы реализации
Здесь я специально записываю поднятые выше вопросы и описываю примерный процесс реализации.
①Сначала создайте холст холста, я установил отрицательное положение холста, чтобы предотвратить его отображение на странице, потому что я пытаюсь динамически отображать и скрывать холст, оценивая условия, при рисовании будут проблемы, поэтому я Использование При этом методе также необходимо задать размер холста.
<canvas canvas-id="myCanvas" style="width: 690px;height:1085px;position: fixed;top: -10000px;"></canvas>
②После создания холста сначала нарисуйте фоновое изображение.Поскольку фоновое изображение размещается локально, я получаю атрибут canvas-id компонента
const ctx = wx.createCanvasContext('myCanvas')
ctx.drawImage('/img/study/shareimg.png', 0, 0, 690, 1085)
③ После создания фонового изображения нарисуйте аватар, текст и цифры на фоновом изображении. Получите информацию об аватаре через getImageInfo.Здесь следует отметить, что имя домена загрузки должно быть настроено до того, как полученный сетевой образ начнет действовать.В частности, настройте его в фоновых настройках апплета.
Чтобы получить адрес аватара, сначала измерьте размер аватара на холсте, а также координаты осей X и Y. Результатом [0] здесь является адрес изображения, возвращенный пакетом обещания.
let headImg = new Promise(function (resolve) {
wx.getImageInfo({
src: `${app.globalData.baseUrl2}${that.data.currentChildren.headImg}`,
success: function (res) {
resolve(res.path)
},
fail: function (err) {
console.log(err)
wx.showToast({
title: '网络错误请重试',
icon: 'loading'
})
}
})
})
let avatarurl_width = 60, //绘制的头像宽度
avatarurl_heigth = 60, //绘制的头像高度
avatarurl_x = 28, //绘制的头像在画布上的位置
avatarurl_y = 36; //绘制的头像在画布上的位置
ctx.save(); // 先保存状态 已便于画完圆再用
ctx.beginPath(); //开始绘制
//先画个圆 前两个参数确定了圆心 (x,y) 坐标 第三个参数是圆的半径 四参数是绘图方向 默认是false,即顺时针
ctx.arc(avatarurl_width / 2 + avatarurl_x, avatarurl_heigth / 2 + avatarurl_y, avatarurl_width / 2, 0, Math.PI * 2, false);
ctx.clip(); //画了圆 再剪切 原始画布中剪切任意形状和尺寸。一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内
ctx.drawImage(result[0], avatarurl_x, avatarurl_y, avatarurl_width, avatarurl_heigth); // 推进去图片
Вот пример того, как рисовать текст.Например, если я хочу нарисовать следующее «слово», мне нужно динамически получить общую ширину предыдущего количества слов, чтобы я мог установить координату по оси x для "слово". Тут я изначально хотел передатьmeasureTextдля измерения ширины шрифта, но значение ширины, полученное впервые на стороне iOS, неверно.По этому вопросу я также поднимал его в сообществе разработчиков WeChatbug, поэтому я хочу использовать другой метод для достижения этого, то есть сначала получить значение ширины слова при нормальных обстоятельствах, а затем умножить его на общее количество слов, чтобы получить общую ширину.Можно попробовать самому .
let allReading = 97 / 6 / app.globalData.ratio * wordNumber.toString().length + 325;
ctx.font = 'normal normal 30px sans-serif';
ctx.setFillStyle('#ffffff')
ctx.fillText('字', allReading, 150);
④Отрисовка QR-кода официального аккаунта аналогична получению аватара, он также возвращает сетевой адрес изображения через интерфейс, а затем получает информацию об изображении QR-кода официального аккаунта через getImageInfo
⑤ Как нарисовать код апплета, конкретный документ официального веб-сайта также дает интерфейс для создания бесконечного кода апплета, с помощью сгенерированного апплета вы можете открыть любую страницу апплета, и QR-код действует постоянно, и какой интерфейс QR-кода апплета называется Для различных сценариев приложений вы можете увидеть, что говорится в официальной документации, то есть внешний интерфейс вызывает код апплета, возвращаемый внутренним интерфейсом, путем передачи параметров, а затем рисует его на холсте (и рисунок аватарка и официальный аккаунт двухмерно написаны на ней).один и тот же код)
ctx.drawImage('小程序码的本地地址', x轴, Y轴, 宽, 高)
⑥ После окончательного рисунка превратите холст в картину и вернитесь по адресу изображения.
wx.canvasToTempFilePath({
canvasId: 'myCanvas',
success: function (res) {
canvasToTempFilePath = res.tempFilePath // 返回的图片地址保存到一个全局变量里
that.setData({
showShareImg: true
})
wx.showToast({
title: '绘制成功',
})
},
fail: function () {
wx.showToast({
title: '绘制失败',
})
},
complete: function () {
wx.hideLoading()
wx.hideToast()
}
})
⑦Сохранение в системный альбом; сначала определите, открыл ли пользователь авторизованный пользователем альбом, и обработайте результаты в различных ситуациях. Например, если пользователь авторизуется по нормальной логике, то проблем нет, но если некоторые пользователи нажмут отменить авторизацию, что делать, если нет, то будут определенные проблемы. Таким образом, после того, как пользователь щелкнет, чтобы отменить авторизацию, появится всплывающее приглашение.Когда он снова щелкнет, он автоматически перейдет к настройкам, чтобы помочь пользователю открыть авторизацию, чтобы достичь цели сохранения ее в альбом и поделиться кругом друзей.
// 获取用户是否开启用户授权相册
if (!openStatus) {
wx.openSetting({
success: (result) => {
if (result) {
if (result.authSetting["scope.writePhotosAlbum"] === true) {
openStatus = true;
wx.saveImageToPhotosAlbum({
filePath: canvasToTempFilePath,
success() {
that.setData({
showShareImg: false
})
wx.showToast({
title: '图片保存成功,快去分享到朋友圈吧~',
icon: 'none',
duration: 2000
})
},
fail() {
wx.showToast({
title: '保存失败',
icon: 'none'
})
}
})
}
}
},
fail: () => { },
complete: () => { }
});
} else {
wx.getSetting({
success(res) {
// 如果没有则获取授权
if (!res.authSetting['scope.writePhotosAlbum']) {
wx.authorize({
scope: 'scope.writePhotosAlbum',
success() {
openStatus = true
wx.saveImageToPhotosAlbum({
filePath: canvasToTempFilePath,
success() {
that.setData({
showShareImg: false
})
wx.showToast({
title: '图片保存成功,快去分享到朋友圈吧~',
icon: 'none',
duration: 2000
})
},
fail() {
wx.showToast({
title: '保存失败',
icon: 'none'
})
}
})
},
fail() {
// 如果用户拒绝过或没有授权,则再次打开授权窗口
openStatus = false
console.log('请设置允许访问相册')
wx.showToast({
title: '请设置允许访问相册',
icon: 'none'
})
}
})
} else {
// 有则直接保存
openStatus = true
wx.saveImageToPhotosAlbum({
filePath: canvasToTempFilePath,
success() {
that.setData({
showShareImg: false
})
wx.showToast({
title: '图片保存成功,快去分享到朋友圈吧~',
icon: 'none',
duration: 2000
})
},
fail() {
wx.showToast({
title: '保存失败',
icon: 'none'
})
}
})
}
},
fail(err) {
console.log(err)
}
})
}
Суммировать
На данный момент все шаги реализованы.При рисовании будут встречаться некоторые данные, возвращаемые фоном асинхронных запросов, поэтому я инкапсулировал их с помощью promise, async и await, чтобы гарантировать, что экспортируемая информация об изображении будет полной. В процессе рисования я столкнулся с некоторыми ямками. Например, масштаб изображения, экспортированного в начале, неверен, и ширина текста, измеренная с помощью MeasureText, неверна, а цвет текста на экспортированном изображении может быть неправильным при многократном рисовании (может быть из-за сети). причины). Если вы тоже столкнулись с ямами, вы можете обсудить это вместе и сделать запись, полный код прикреплен ниже.
import regeneratorRuntime from '../../utils/runtime.js' // 引入模块
const app = getApp(),
api = require('../../service/http.js');
var ctx = null, // 创建canvas对象
canvasToTempFilePath = null, // 保存最终生成的导出的图片地址
openStatus = true; // 声明一个全局变量判断是否授权保存到相册
// 获取微信公众号二维码
getCode: function () {
return new Promise(function (resolve, reject) {
api.fetch('/wechat/open/getQRCodeNormal', 'GET').then(res => {
console.log(res, '获取微信公众号二维码')
if (res.code == 200) {
console.log(res.content, 'codeUrl')
resolve(res.content)
}
}).catch(err => {
console.log(err)
})
})
},
// 生成海报
async createCanvasImage() {
let that = this;
// 点击生成海报数据埋点
that.setData({
generateId: '点击生成海报'
})
if (!ctx) {
let codeUrl = await that.getCode()
wx.showLoading({
title: '绘制中...'
})
let code = new Promise(function (resolve) {
wx.getImageInfo({
src: codeUrl,
success: function (res) {
resolve(res.path)
},
fail: function (err) {
console.log(err)
wx.showToast({
title: '网络错误请重试',
icon: 'loading'
})
}
})
})
let headImg = new Promise(function (resolve) {
wx.getImageInfo({
src: `${app.globalData.baseUrl2}${that.data.currentChildren.headImg}`,
success: function (res) {
resolve(res.path)
},
fail: function (err) {
console.log(err)
wx.showToast({
title: '网络错误请重试',
icon: 'loading'
})
}
})
})
Promise.all([headImg, code]).then(function (result) {
const ctx = wx.createCanvasContext('myCanvas')
console.log(ctx, app.globalData.ratio, 'ctx')
let canvasWidthPx = 690 * app.globalData.ratio,
canvasHeightPx = 1085 * app.globalData.ratio,
avatarurl_width = 60, //绘制的头像宽度
avatarurl_heigth = 60, //绘制的头像高度
avatarurl_x = 28, //绘制的头像在画布上的位置
avatarurl_y = 36, //绘制的头像在画布上的位置
codeurl_width = 80, //绘制的二维码宽度
codeurl_heigth = 80, //绘制的二维码高度
codeurl_x = 588, //绘制的二维码在画布上的位置
codeurl_y = 984, //绘制的二维码在画布上的位置
wordNumber = that.data.wordNumber, // 获取总阅读字数
// nameWidth = ctx.measureText(that.data.wordNumber).width, // 获取总阅读字数的宽度
// allReading = ((nameWidth + 375) - 325) * 2 + 380;
// allReading = nameWidth / app.globalData.ratio + 325;
allReading = 97 / 6 / app.globalData.ratio * wordNumber.toString().length + 325;
console.log(wordNumber, wordNumber.toString().length, allReading, '获取总阅读字数的宽度')
ctx.drawImage('/img/study/shareimg.png', 0, 0, 690, 1085)
ctx.save(); // 先保存状态 已便于画完圆再用
ctx.beginPath(); //开始绘制
//先画个圆 前两个参数确定了圆心 (x,y) 坐标 第三个参数是圆的半径 四参数是绘图方向 默认是false,即顺时针
ctx.arc(avatarurl_width / 2 + avatarurl_x, avatarurl_heigth / 2 + avatarurl_y, avatarurl_width / 2, 0, Math.PI * 2, false);
ctx.clip(); //画了圆 再剪切 原始画布中剪切任意形状和尺寸。一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内
ctx.drawImage(result[0], avatarurl_x, avatarurl_y, avatarurl_width, avatarurl_heigth); // 推进去图片
ctx.restore(); //恢复之前保存的绘图上下文状态 可以继续绘制
ctx.setFillStyle('#ffffff'); // 文字颜色
ctx.setFontSize(28); // 文字字号
ctx.fillText(that.data.currentChildren.name, 103, 78); // 绘制文字
ctx.font = 'normal bold 44px sans-serif';
ctx.setFillStyle('#ffffff'); // 文字颜色
ctx.fillText(wordNumber, 325, 153); // 绘制文字
ctx.font = 'normal normal 30px sans-serif';
ctx.setFillStyle('#ffffff')
ctx.fillText('字', allReading, 150);
ctx.font = 'normal normal 24px sans-serif';
ctx.setFillStyle('#ffffff'); // 文字颜色
ctx.fillText('打败了全国', 26, 190); // 绘制文字
ctx.font = 'normal normal 24px sans-serif';
ctx.setFillStyle('#faed15'); // 文字颜色
ctx.fillText(that.data.percent, 154, 190); // 绘制孩子百分比
ctx.font = 'normal normal 24px sans-serif';
ctx.setFillStyle('#ffffff'); // 文字颜色
ctx.fillText('的小朋友', 205, 190); // 绘制孩子百分比
ctx.font = 'normal bold 32px sans-serif';
ctx.setFillStyle('#333333'); // 文字颜色
ctx.fillText(that.data.singIn, 50, 290); // 签到天数
ctx.fillText(that.data.reading, 280, 290); // 阅读时长
ctx.fillText(that.data.reading, 508, 290); // 听书时长
// 书籍阅读结构
ctx.font = 'normal normal 28px sans-serif';
ctx.setFillStyle('#ffffff'); // 文字颜色
ctx.fillText(that.data.bookInfo[0].count, 260, 510);
ctx.fillText(that.data.bookInfo[1].count, 420, 532);
ctx.fillText(that.data.bookInfo[2].count, 520, 594);
ctx.fillText(that.data.bookInfo[3].count, 515, 710);
ctx.fillText(that.data.bookInfo[4].count, 492, 828);
ctx.fillText(that.data.bookInfo[5].count, 348, 858);
ctx.fillText(that.data.bookInfo[6].count, 212, 828);
ctx.fillText(that.data.bookInfo[7].count, 148, 726);
ctx.fillText(that.data.bookInfo[8].count, 158, 600);
ctx.font = 'normal normal 18px sans-serif';
ctx.setFillStyle('#ffffff'); // 文字颜色
ctx.fillText(that.data.bookInfo[0].name, 232, 530);
ctx.fillText(that.data.bookInfo[1].name, 394, 552);
ctx.fillText(that.data.bookInfo[2].name, 496, 614);
ctx.fillText(that.data.bookInfo[3].name, 490, 730);
ctx.fillText(that.data.bookInfo[4].name, 466, 850);
ctx.fillText(that.data.bookInfo[5].name, 323, 878);
ctx.fillText(that.data.bookInfo[6].name, 184, 850);
ctx.fillText(that.data.bookInfo[7].name, 117, 746);
ctx.fillText(that.data.bookInfo[8].name, 130, 621);
ctx.drawImage(result[1], codeurl_x, codeurl_y, codeurl_width, codeurl_heigth); // 绘制头像
ctx.draw(false, function () {
// canvas画布转成图片并返回图片地址
wx.canvasToTempFilePath({
canvasId: 'myCanvas',
success: function (res) {
canvasToTempFilePath = res.tempFilePath
that.setData({
showShareImg: true
})
console.log(res.tempFilePath, 'canvasToTempFilePath')
wx.showToast({
title: '绘制成功',
})
},
fail: function () {
wx.showToast({
title: '绘制失败',
})
},
complete: function () {
wx.hideLoading()
wx.hideToast()
}
})
})
})
}
},
// 保存到系统相册
saveShareImg: function () {
let that = this;
// 数据埋点点击保存学情海报
that.setData({
saveId: '保存学情海报'
})
// 获取用户是否开启用户授权相册
if (!openStatus) {
wx.openSetting({
success: (result) => {
if (result) {
if (result.authSetting["scope.writePhotosAlbum"] === true) {
openStatus = true;
wx.saveImageToPhotosAlbum({
filePath: canvasToTempFilePath,
success() {
that.setData({
showShareImg: false
})
wx.showToast({
title: '图片保存成功,快去分享到朋友圈吧~',
icon: 'none',
duration: 2000
})
},
fail() {
wx.showToast({
title: '保存失败',
icon: 'none'
})
}
})
}
}
},
fail: () => { },
complete: () => { }
});
} else {
wx.getSetting({
success(res) {
// 如果没有则获取授权
if (!res.authSetting['scope.writePhotosAlbum']) {
wx.authorize({
scope: 'scope.writePhotosAlbum',
success() {
openStatus = true
wx.saveImageToPhotosAlbum({
filePath: canvasToTempFilePath,
success() {
that.setData({
showShareImg: false
})
wx.showToast({
title: '图片保存成功,快去分享到朋友圈吧~',
icon: 'none',
duration: 2000
})
},
fail() {
wx.showToast({
title: '保存失败',
icon: 'none'
})
}
})
},
fail() {
// 如果用户拒绝过或没有授权,则再次打开授权窗口
openStatus = false
console.log('请设置允许访问相册')
wx.showToast({
title: '请设置允许访问相册',
icon: 'none'
})
}
})
} else {
// 有则直接保存
openStatus = true
wx.saveImageToPhotosAlbum({
filePath: canvasToTempFilePath,
success() {
that.setData({
showShareImg: false
})
wx.showToast({
title: '图片保存成功,快去分享到朋友圈吧~',
icon: 'none',
duration: 2000
})
},
fail() {
wx.showToast({
title: '保存失败',
icon: 'none'
})
}
})
}
},
fail(err) {
console.log(err)
}
})
}
},