|
|
@@ -27,16 +27,21 @@ const roundRect = (ctx, x, y, w, h, r) => {
|
|
|
* 微信小程序 Canvas 2D 中不能用 uni.createImage()
|
|
|
*/
|
|
|
const createCanvasImage = (canvas, src) => {
|
|
|
-
|
|
|
return new Promise((resolve) => {
|
|
|
if (!src || !canvas) return resolve(null)
|
|
|
- const img = canvas.createImage()
|
|
|
- img.onload = () => resolve(img)
|
|
|
- img.onerror = () => {
|
|
|
- console.warn('图片加载失败:', src)
|
|
|
- resolve(null)
|
|
|
+ try {
|
|
|
+
|
|
|
+ const img = canvas.createImage()
|
|
|
+ img.onload = () => resolve(img)
|
|
|
+ img.onerror = () => {
|
|
|
+ console.warn('图片加载失败:', src)
|
|
|
+ resolve(null)
|
|
|
+ }
|
|
|
+ img.src = src
|
|
|
+ } catch (e) {
|
|
|
+ return resolve(null)
|
|
|
}
|
|
|
- img.src = src
|
|
|
+
|
|
|
})
|
|
|
}
|
|
|
|
|
|
@@ -44,7 +49,9 @@ const downloadImage = async (url) => {
|
|
|
try {
|
|
|
if (!url || !url.startsWith('http')) return url
|
|
|
// 使用 getImageInfo 获取图片本地临时路径(微信原生能力,通过 request 域名即可,无需 downloadFile 白名单)
|
|
|
- const res = await uni.getImageInfo({ src: url })
|
|
|
+ const res = await uni.getImageInfo({
|
|
|
+ src: url
|
|
|
+ })
|
|
|
if (res && res.path) return res.path
|
|
|
return null
|
|
|
} catch (e) {
|
|
|
@@ -61,16 +68,24 @@ const resolveImageSrc = async (src) => {
|
|
|
if (src.startsWith('http')) {
|
|
|
try {
|
|
|
// getImageInfo 走 request 域名,无需额外配置 downloadFile 白名单
|
|
|
- const res = await uni.getImageInfo({ src })
|
|
|
+ const res = await uni.getImageInfo({
|
|
|
+ src
|
|
|
+ })
|
|
|
if (res && res.path) return res.path
|
|
|
return null
|
|
|
- } catch { return null }
|
|
|
+ } catch {
|
|
|
+ return null
|
|
|
+ }
|
|
|
}
|
|
|
if (src.startsWith('/') || src.startsWith('.')) {
|
|
|
try {
|
|
|
- const res = await uni.getImageInfo({ src })
|
|
|
+ const res = await uni.getImageInfo({
|
|
|
+ src
|
|
|
+ })
|
|
|
return res.path
|
|
|
- } catch { return null }
|
|
|
+ } catch {
|
|
|
+ return null
|
|
|
+ }
|
|
|
}
|
|
|
return src
|
|
|
}
|
|
|
@@ -155,7 +170,8 @@ const fillTextWrap = (ctx, text, x, y, maxWidth, lineHeight, align = 'left') =>
|
|
|
ctx.textAlign = align
|
|
|
ctx.textBaseline = 'top'
|
|
|
const chars = text.split('')
|
|
|
- let line = '', cy = y
|
|
|
+ let line = '',
|
|
|
+ cy = y
|
|
|
for (let i = 0; i < chars.length; i++) {
|
|
|
const test = line + chars[i]
|
|
|
if (ctx.measureText(test).width > maxWidth && i > 0) {
|
|
|
@@ -174,7 +190,10 @@ export const generateCardPoster = async (cardInfo = {}, qrInfo = {}) => {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
const query = uni.createSelectorQuery()
|
|
|
query.select('#posterCanvas')
|
|
|
- .fields({ node: true, size: true })
|
|
|
+ .fields({
|
|
|
+ node: true,
|
|
|
+ size: true
|
|
|
+ })
|
|
|
.exec(async (res) => {
|
|
|
if (!res || !res[0]) {
|
|
|
reject(new Error('Canvas 节点未找到'))
|
|
|
@@ -198,13 +217,21 @@ export const generateCardPoster = async (cardInfo = {}, qrInfo = {}) => {
|
|
|
|
|
|
// ===== 2. 装饰圆点 =====
|
|
|
ctx.fillStyle = 'rgba(255,255,255,0.10)'
|
|
|
- ctx.beginPath(); ctx.arc(620, 160, 100, 0, Math.PI * 2); ctx.fill()
|
|
|
- ctx.beginPath(); ctx.arc(680, 320, 60, 0, Math.PI * 2); ctx.fill()
|
|
|
- ctx.beginPath(); ctx.arc(-20, 640, 80, 0, Math.PI * 2); ctx.fill()
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.arc(620, 160, 100, 0, Math.PI * 2);
|
|
|
+ ctx.fill()
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.arc(680, 320, 60, 0, Math.PI * 2);
|
|
|
+ ctx.fill()
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.arc(-20, 640, 80, 0, Math.PI * 2);
|
|
|
+ ctx.fill()
|
|
|
|
|
|
// ===== 3. 白色卡片(去掉白色背景,只保留圆角裁剪区域)=====
|
|
|
- const cardX = 20, cardY = 60
|
|
|
- const cardW = W - 40, cardH = 480
|
|
|
+ const cardX = 20,
|
|
|
+ cardY = 60
|
|
|
+ const cardW = W - 40,
|
|
|
+ cardH = 480
|
|
|
const innerPad = 16
|
|
|
const innerX = cardX + innerPad
|
|
|
const innerY = cardY + innerPad
|
|
|
@@ -213,7 +240,8 @@ export const generateCardPoster = async (cardInfo = {}, qrInfo = {}) => {
|
|
|
|
|
|
// 背景图(静态资源直接传路径,canvas.createImage 支持加载本地包内资源)
|
|
|
try {
|
|
|
- const cardBg = await createCanvasImage(canvas, '/static/image/home/usecard-bg.png')
|
|
|
+ const cardBg = await createCanvasImage(canvas,
|
|
|
+ '/static/image/home/usecard-bg.png')
|
|
|
if (cardBg) {
|
|
|
ctx.save()
|
|
|
roundRect(ctx, innerX, innerY, innerW, innerH, 16)
|
|
|
@@ -281,7 +309,8 @@ export const generateCardPoster = async (cardInfo = {}, qrInfo = {}) => {
|
|
|
const companyName = cardInfo.companyName || '公司名称'
|
|
|
const companyX = innerX + 44
|
|
|
const companyY = nameY + 60
|
|
|
- const companyEndY = fillTextWrap(ctx, companyName, companyX, companyY, companyMaxW, 36, 'left')
|
|
|
+ const companyEndY = fillTextWrap(ctx, companyName, companyX, companyY,
|
|
|
+ companyMaxW, 36, 'left')
|
|
|
|
|
|
// ===== 7. 联系方式 =====
|
|
|
const contactY = Math.max(companyEndY + 24, innerY + 160)
|
|
|
@@ -289,13 +318,18 @@ export const generateCardPoster = async (cardInfo = {}, qrInfo = {}) => {
|
|
|
const contactGap = 60
|
|
|
|
|
|
// 预加载联系方式图标
|
|
|
- const phoneIcon = await createCanvasImage(canvas, '/static/image/public/phone-icon.png')
|
|
|
- const emailIcon = await createCanvasImage(canvas, '/static/image/public/email-icon.png')
|
|
|
- const addressIcon = await createCanvasImage(canvas, '/static/image/public/address-icon.png')
|
|
|
+ const phoneIcon = await createCanvasImage(canvas,
|
|
|
+ '/static/image/public/phone-icon.png')
|
|
|
+ const emailIcon = await createCanvasImage(canvas,
|
|
|
+ '/static/image/public/email-icon.png')
|
|
|
+ const addressIcon = await createCanvasImage(canvas,
|
|
|
+ '/static/image/public/address-icon.png')
|
|
|
|
|
|
drawContactRow(ctx, phoneIcon, cardInfo.phonenumber, contactX, contactY + 20)
|
|
|
- drawContactRow(ctx, emailIcon, cardInfo.email, contactX, contactY + 20 + contactGap)
|
|
|
- drawContactRow(ctx, addressIcon, cardInfo.companyAddress, contactX, contactY + 20 + contactGap * 2)
|
|
|
+ drawContactRow(ctx, emailIcon, cardInfo.email, contactX, contactY + 20 +
|
|
|
+ contactGap)
|
|
|
+ drawContactRow(ctx, addressIcon, cardInfo.companyAddress, contactX, contactY +
|
|
|
+ 20 + contactGap * 2)
|
|
|
|
|
|
// ===== 8. 导出 =====
|
|
|
setTimeout(() => {
|
|
|
@@ -335,4 +369,7 @@ export const savePosterToAlbum = (tempFilePath) => {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
-export default { generateCardPoster, savePosterToAlbum }
|
|
|
+export default {
|
|
|
+ generateCardPoster,
|
|
|
+ savePosterToAlbum
|
|
|
+}
|