uniapp+h5 公众号实现分享海报绘制

封装Canvas画布hooks

js 复制代码
import { } from "vue"

// ==================== 常量定义 ====================

/**
 * 默认 canvas 宽度
 * @type {number}
 */
const defaultWidth = 750

/**
 * 默认 canvas 高度
 * @type {number}
 */
const defaultHeigth = 1334

/**
 * 默认背景颜色
 * @type {string}
 */
const defaultRectBgColor = '#fff'

/**
 * 创建微信小程序 canvas 实例
 * @param {string} id - canvas 节点的 id 选择器
 * @param {object} that - 组件实例(用于 createSelectorQuery)
 * @param {number} width - canvas 宽度,默认 750
 * @param {number} height - canvas 高度,默认 1334
 * @returns {Promise<{canvas: object, ctx: object}>} 返回包含 canvas 实例和 2d 上下文的对象
 */
const createCanvasMiniWX = (id, that, width = defaultWidth, height = defaultHeigth) => {
	return new Promise((resolve, reject) => {
		const selectQuery = uni.createSelectorQuery().in(that)
		selectQuery.select(id)
			.fields({
				node: true,
				size: true
			})
			.exec((res) => {
				const canvas = res[0].node
				if (!canvas) {
					return reject()
				}
				canvas.width = width
				canvas.height = height
				const ctx = canvas.getContext('2d')
				resolve({
					canvas,
					ctx
				})
			})
	})
}

/**
 * 创建 Web/H5 环境 canvas 实例
 * @param {number} width - canvas 宽度,默认 750
 * @param {number} height - canvas 高度,默认 1334
 * @returns {Promise<{canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D}>} 返回包含 canvas 元素和 2d 上下文的对象
 */
const createCanvasWEB = (width = defaultWidth, height = defaultHeigth) => {
	return new Promise((resolve, reject) => {
		const canvas = document.createElement('canvas')
		canvas.width = width
		canvas.height = height
		canvas.style.display = 'none'
		canvas.style.backgroundColor = '#ffffff'
		document.body.appendChild(canvas)
		const ctx = canvas.getContext('2d')

		// // 设置填充样式为白色
		// ctx.fillStyle = 'white';
		// // 填充整个画布
		// ctx.fillRect(0, 0, canvas.width, canvas.height);

		resolve({
			ctx,
			canvas
		})
	})
}

/**
 * 在微信小程序环境中创建图片对象
 * @param {string} src - 图片地址
 * @param {object} canvas - canvas 实例
 * @returns {Promise<Image>} 返回加载完成的图片对象
 */
const createImageMiniWX = (src, canvas) => {
	return new Promise((resolve, reject) => {
		const image = canvas.createImage()
		image.src = src
		image.crossOrigin = 'anonymous'
		// 图片加载完成回调
		image.onload = () => {
			resolve(image)
		}
		image.onerror = (err) => {
			reject(err)
		}
	})
}

/**
 * 在 Web/H5 环境中创建图片对象
 * @param {string} src - 图片地址
 * @returns {Promise<HTMLImageElement>} 返回加载完成的图片对象
 * @description 会为 HTTP 链接添加时间戳参数以避免缓存问题
 */
const createImageWEB = (src) => {
	return new Promise((resolve, reject) => {
		const image = new Image()
		let url = src.split('?')[0]
		if (url && url.startsWith('http')) {
			url = url + '?timstamp=' + Date.now()
			// url = url + '?x-oss-process=image/format,jpg/resize,w_500,h_500/quality,q_100'

		}
		image.src = url
		image.crossOrigin = 'anonymous'

		// 图片加载完成回调
		image.onload = () => {
			resolve(image)
		}
		image.onerror = (err) => {
			reject(err)
		}
	})
}

/**
 * 跨平台创建 canvas 实例(兼容微信小程序和 H5)
 * @param {string} id - canvas 节点的 id 选择器(小程序端必需)
 * @param {object} that - 组件实例(小程序端必需)
 * @param {number} width - canvas 宽度,默认 750
 * @param {number} height - canvas 高度,默认 1334
 * @returns {Promise<{canvas: object|HTMLCanvasElement, ctx: object|CanvasRenderingContext2D}>} 返回包含 canvas 实例和 2d 上下文的对象
 */
export const createCanvas = (id, that, width = defaultWidth, height = defaultHeigth) => {
	// #ifdef MP-WEIXIN
	return createCanvasMiniWX(id, that, width, height)
	// #endif

	// #ifdef H5
	return createCanvasWEB(width, height)
	// #endif
}

/**
 * 跨平台创建图片对象(兼容微信小程序和 H5)
 * @param {string} src - 图片地址
 * @param {object} canvas - canvas 实例(小程序端必需)
 * @returns {Promise<Image|HTMLImageElement>} 返回加载完成的图片对象
 */
export const createImage = (src, canvas) => {
	// #ifdef MP-WEIXIN
	return createImageMiniWX(src, canvas)
	// #endif

	// #ifdef H5
	return createImageWEB(src)
	// #endif
}

/**
 * 绘制圆形图片
 * @param {object} ctx - canvas 2d 绘图上下文
 * @param {Image|HTMLImageElement} image - 图片对象
 * @param {object} position - 位置和尺寸配置 {x, y, width, height}
 * @param {boolean} rect - 是否绘制矩形背景,默认 false
 * @param {string} bgColor - 背景颜色,默认 '#fff'
 */
export const drawCircleImage = (ctx, image, position, rect = false, bgColor = defaultRectBgColor) => {
	const {
		x,
		y,
		width,
		height
	} = position
	if (rect) {
		ctx.beginPath()
		ctx.fillStyle = bgColor
		ctx.fillRect(x, y, width, height)
	}


	const r = width / 2
	ctx.save()
	ctx.beginPath()
	ctx.arc(x + r, y + r, r, 0, 2 * Math.PI)
	ctx.clip()
	ctx.drawImage(image, x, y, width, height)
	ctx.restore()
}

/**
 * 绘制图片(支持圆形或矩形)
 * @param {object} canvas - canvas 实例
 * @param {object} ctx - canvas 2d 绘图上下文
 * @param {string} url - 图片地址
 * @param {object} position - 位置和尺寸配置 {x, y, width, height}
 * @param {object} ext - 扩展配置 {circle: 是否圆形, rect: 是否绘制背景, bgColor: 背景颜色}
 */
export const drawImage = async (canvas, ctx, url, position, ext = {}) => {
	const {
		circle,
		rect,
		bgColor
	} = ext
	const image = await createImage(url, canvas)
	if (circle) {
		drawCircleImage(ctx, image, position, rect, bgColor)
	} else {
		const {
			x,
			y,
			width,
			height
		} = position
		ctx.drawImage(image, x, y, width, height)
	}
}

/**
 * 绘制单行文本
 * @param {object} ctx - canvas 2d 绘图上下文
 * @param {string} text - 文本内容
 * @param {object} position - 位置和样式配置 {x, y, size, color, baseLine, maxWidth}
 * @param {boolean} throughtLine - 是否绘制删除线
 */
export const drawText = (ctx, text, position, throughtLine) => {
	const color = position?.color || '#333'
	const size = position?.size || 24
	const x = position?.x || 0
	const y = position?.y || 0
	const baseLine = position?.baseLine || 'top'
	const maxWidth = position?.maxWidth

	ctx.fillStyle = color
	ctx.font = `${size}px seri`
	ctx.textBaseline = baseLine
	ctx.fillText(text, x, y)

	if (!throughtLine) return
	const textWidth = ctx.measureText(text).width
	const lineY = y + size / 2
	ctx.beginPath()
	ctx.moveTo(x, lineY)
	ctx.lineTo(x + textWidth, lineY)
	ctx.stroke()
	ctx.beginPath()
}

/**
 * 绘制多行文本(支持自动换行和省略号)
 * @param {object} ctx - canvas 2d 绘图上下文
 * @param {string} str - 文本内容
 * @param {object} position - 位置和样式配置 {x, y, size, color, maxWidth, lineHeight, line, baseLine}
 * @description 当文本行数超过指定行数时,最后一行自动添加省略号
 */
export const drawTextMutilLine = (ctx, str, position) => {
	const {
		x,
		y,
		size,
		blod,
		color,
		maxWidth,
		lineHeight,
		line,
		baseLine
	} = position
	ctx.textBaseline = baseLine
	// 字体
	ctx.font = size + 'px seri'
	// 颜色
	ctx.fillStyle = color || '#333'
	// 文本处理
	let strArr = str.split('');
	let row = [];
	let temp = '';
	for (let i = 0; i < strArr.length; i++) {
		if (ctx.measureText(temp).width < maxWidth) {
			temp += strArr[i];
		} else {
			i--; //这里添加了i-- 是为了防止字符丢失,效果图中有对比
			row.push(temp);
			temp = '';
		}
	}
	row.push(temp); // row有多少项则就有多少行

	//如果数组长度大于2,现在只需要显示两行则只截取前两项,把第二行结尾设置成'...'
	if (row.length > line) {
		let rowCut = row.slice(0, line);
		let rowPart = rowCut[line - 1];
		let test = '';
		let empty = [];
		for (let i = 0; i < rowPart.length; i++) {
			if (ctx.measureText(test).width < maxWidth) {
				test += rowPart[i];
			} else {
				break;
			}
		}
		empty.push(test);
		let group = empty[0] + '...' //这里只显示两行,超出的用...表示
		rowCut.splice(line - 1, 1, group);
		row = rowCut;
	}
	// 把文本绘制到画布中
	for (let i = 0; i < row.length; i++) {
		// 一次渲染一行
		ctx.fillText(row[i], x, y + i * lineHeight, maxWidth);
	}
	// 保存当前画布状态
	ctx.save()
	// 将之前在绘图上下文中的描述(路径、变形、样式)画到 canvas 中。
	// ctx.draw()
}

// 绘制背景
const drawBg = (ctx, bgColor) => {
	ctx.beginPath()
	ctx.fillStyle = bgColor
	ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height)
}


/**
 * Canvas 绘图组合 hook(主入口函数)
 * @param {string} id - canvas 节点的 id 选择器(小程序端必需)
 * @param {object} that - 组件实例(小程序端必需)
 * @param {number} width - canvas 宽度,默认 750
 * @param {number} height - canvas 高度,默认 1334
 * @returns {Promise<object>} 返回包含 canvas、ctx 及各种绘图方法的对象
 * @description 提供统一的绘图接口,返回方法包含:
 *   - createImageFn: 创建图片对象
 *   - drawCircleImageFn: 绘制圆形图片
 *   - drawImageFn: 绘制普通图片
 *   - drawTextFn: 绘制单行文本
 *   - drawTextMutilLineFn: 绘制多行文本
 */
export const useCanvas = async (id, that, width = defaultWidth, height = defaultHeigth) => {
	let canvas, ctx;
	const res = await createCanvas(id, that, width, height)
	canvas = res.canvas
	ctx = res.ctx
	return {
		canvas,
		ctx,
		createImageFn: (url) => createImage(url, canvas),
		drawCircleImageFn: (image, position, rect = true, bgColor = defaultRectBgColor) => drawCircleImage(ctx,
			image, position, rect = true, bgColor = defaultRectBgColor),
		drawImageFn: (url, position, ext = {}) => drawImage(canvas, ctx, url, position, ext),
		drawTextFn: (text, position, throughtLine) => drawText(ctx, text, position, throughtLine),
		drawTextMutilLineFn: (text, position) => drawTextMutilLine(ctx, text, position),
		drawBgFn: (bgColor) => drawBg(ctx, bgColor),
	}
}
export default useCanvas

操作文档

useCanvas 使用文档

一个跨平台的 Canvas 绘图工具,兼容微信小程序和 H5 环境,用于绘制海报、图片、文本等。

功能特性

  • ✅ 跨平台支持(微信小程序 + H5)
  • ✅ 图片绘制(支持圆形/矩形)
  • ✅ 文本绘制(单行/多行自动换行)
  • ✅ 省略号处理(超出行数自动添加 ...
  • ✅ 删除线支持
  • ✅ 自动处理跨域图片加载

快速开始

基础用法

javascript 复制代码
import { useCanvas } from '@/hooks/useCanvas.js'

// 在组件中使用
const { canvas, ctx, drawImageFn, drawTextFn } = await useCanvas(
  'myCanvas',  // canvas 节点 id
  this,        // 组件实例
  750,         // 宽度(可选,默认 750)
  1334         // 高度(可选,默认 1334)
)

// 绘制图片
await drawImageFn('https://example.com/image.png', {
  x: 0,
  y: 0,
  width: 750,
  height: 400
})

// 绘制文本
drawTextFn('Hello World', {
  x: 100,
  y: 500,
  size: 32,
  color: '#333333'
})

API 文档

主入口函数

useCanvas(id, that, width, height)

初始化 Canvas 绘图环境,返回绘图对象和方法。

参数 类型 必填 说明
id string canvas 节点的 id 选择器(小程序端必需)
that object 组件实例(小程序端必需)
width number canvas 宽度,默认 750
height number canvas 高度,默认 1334

返回值:

javascript 复制代码
{
  canvas: Canvas实例,
  ctx: CanvasRenderingContext2D实例,
  createImageFn: Function,       // 创建图片对象
  drawCircleImageFn: Function,   // 绘制圆形图片
  drawImageFn: Function,         // 绘制图片
  drawTextFn: Function,          // 绘制单行文本
  drawTextMutilLineFn: Function  // 绘制多行文本
}

绘图方法

1. 绘制图片
javascript 复制代码
await drawImageFn(url, position, ext)

绘制普通图片或圆形图片。

参数 类型 必填 说明
url string 图片地址
position object 位置和尺寸配置
ext object 扩展配置

position 配置:

属性 类型 说明
x number x 坐标
y number y 坐标
width number 宽度
height number 高度

ext 配置:

属性 类型 默认值 说明
circle boolean false 是否绘制圆形图片
rect boolean false 是否绘制背景矩形
bgColor string '#fff' 背景颜色

示例:

javascript 复制代码
// 普通图片
await drawImageFn('https://example.com/bg.png', {
  x: 0,
  y: 0,
  width: 750,
  height: 1334
})

// 圆形头像
await drawImageFn('https://example.com/avatar.png', {
  x: 50,
  y: 100,
  width: 200,
  height: 200
}, {
  circle: true,
  rect: true,
  bgColor: '#f5f5f5'
})

2. 绘制圆形图片
javascript 复制代码
drawCircleImageFn(image, position, rect, bgColor)

直接绘制圆形图片(需要先创建图片对象)。

参数 类型 默认值 说明
image Image 图片对象
position object 位置和尺寸配置 {x, y, width, height}
rect boolean true 是否绘制矩形背景
bgColor string '#fff' 背景颜色

示例:

javascript 复制代码
const img = await createImageFn('https://example.com/avatar.png')
drawCircleImageFn(img, {
  x: 50,
  y: 100,
  width: 200,
  height: 200
}, true, '#f5f5f5')

3. 绘制单行文本
javascript 复制代码
drawTextFn(text, position, throughtLine)

绘制单行文本,支持删除线。

参数 类型 默认值 说明
text string 文本内容
position object 位置和样式配置
throughtLine boolean false 是否绘制删除线

position 配置:

属性 类型 默认值 说明
x number 0 x 坐标
y number 0 y 坐标
size number 24 字体大小
color string '#333' 字体颜色
baseLine string 'top' 文本基线对齐方式
maxWidth number - 最大宽度(可选)

示例:

javascript 复制代码
// 普通文本
drawTextFn('欢迎来到物业系统', {
  x: 50,
  y: 300,
  size: 36,
  color: '#333333'
})

// 带删除线的文本
drawTextFn('原价: ¥999', {
  x: 50,
  y: 400,
  size: 28,
  color: '#999999'
}, true)

4. 绘制多行文本
javascript 复制代码
drawTextMutilLineFn(text, position)

绘制多行文本,支持自动换行和省略号处理。

参数 类型 说明
text string 文本内容
position object 位置和样式配置

position 配置:

属性 类型 默认值 说明
x number 0 x 坐标
y number 0 y 坐标(起始位置)
size number - 字体大小(必填)
color string '#333' 字体颜色
maxWidth number - 最大宽度(必填)
lineHeight number - 行高(必填)
line number - 最大行数(必填)
baseLine string - 文本基线对齐方式

示例:

javascript 复制代码
// 最多显示 2 行,超出显示省略号
drawTextMutilLineFn('这是一段很长的文本内容,用于演示多行文本自动换行和省略号的功能...', {
  x: 50,
  y: 500,
  size: 28,
  color: '#666666',
  maxWidth: 650,
  lineHeight: 40,
  line: 2,
  baseLine: 'top'
})

5. 创建图片对象
javascript 复制代码
const img = await createImageFn(url)

创建并加载图片对象(一般配合 drawCircleImageFn 使用)。

参数 类型 说明
url string 图片地址

注意: H5 环境下会自动为 HTTP 链接添加时间戳参数以避免缓存问题。


完整示例

生成海报示例

vue 复制代码
<template>
  <view>
    <canvas id="posterCanvas" type="2d"></canvas>
    <image v-if="posterUrl" :src="posterUrl"></image>
  </view>
</template>

<script>
import { useCanvas } from '@/hooks/useCanvas.js'

export default {
  data() {
    return {
      posterUrl: ''
    }
  },
  async mounted() {
    await this.drawPoster()
  },
  methods: {
    async drawPoster() {
      const { canvas, ctx, drawImageFn, drawTextFn, drawTextMutilLineFn } = await useCanvas(
        'posterCanvas',
        this,
        750,
        1334
      )

      // 1. 绘制背景图
      await drawImageFn('https://example.com/bg.png', {
        x: 0,
        y: 0,
        width: 750,
        height: 1334
      })

      // 2. 绘制圆形头像
      await drawImageFn('https://example.com/avatar.png', {
        x: 50,
        y: 80,
        width: 200,
        height: 200
      }, {
        circle: true,
        rect: true,
        bgColor: '#fff'
      })

      // 3. 绘制用户名
      drawTextFn('张三', {
        x: 280,
        y: 120,
        size: 36,
        color: '#333333'
      })

      // 4. 绘制多行描述
      drawTextMutilLineFn('这是一段很长的描述文本,用于演示多行自动换行功能...', {
        x: 50,
        y: 320,
        size: 28,
        color: '#666666',
        maxWidth: 650,
        lineHeight: 40,
        line: 3,
        baseLine: 'top'
      })

      // 5. 绘制商品图
      await drawImageFn('https://example.com/product.png', {
        x: 50,
        y: 450,
        width: 650,
        height: 500
      })

      // 6. 绘制价格
      drawTextFn('¥99.00', {
        x: 50,
        y: 1000,
        size: 48,
        color: '#ff0000'
      })

      // 7. 生成图片
      uni.canvasToTempFilePath({
        canvas: canvas,
        success: (res) => {
          this.posterUrl = res.tempFilePath
        }
      }, this)
    }
  }
}
</script>

常量配置

常量 说明
defaultWidth 750 默认 canvas 宽度
defaultHeigth 1334 默认 canvas 高度
defaultRectBgColor '#fff' 默认背景颜色

注意事项

  1. 小程序端需要传入组件实例 :使用 uni.createSelectorQuery().in(that) 查询节点
  2. 图片跨域问题 :图片地址需要支持跨域访问,H5 会自动处理 crossOrigin = 'anonymous'
  3. 异步加载 :图片绘制是异步的,需要使用 await 等待加载完成
  4. Canvas 节点类型 :小程序端需要设置 type="2d" 属性

平台差异

功能 微信小程序 H5
Canvas 创建 canvas.createImage() new Image()
节点查询 uni.createSelectorQuery().in(that) document.createElement('canvas')
跨域图片 需配置下载域名 需服务端支持 CORS
缓存处理 - 自动添加时间戳

常见问题

Q: 小程序端 canvas 为空?

A: 检查 canvas 节点是否设置了 type="2d",并确保节点已渲染完成再调用。

Q: 图片加载失败?

A: 检查图片地址是否正确,小程序端需在后台配置 downloadFile 合法域名。

Q: 文本显示位置不对?

A: 检查 baseLine 参数,默认是 'top',根据需要调整为 'middle''bottom'


更新日志

  • v1.0.0 - 初始版本,支持基础图片和文本绘制

项目实际使用

这里记录原本项目中使用方法,不一定是最优写发,可更具自己项目来实现
invite-apply.vue

html 复制代码
<template>
	<invite-apply-canvas v-if="showCanvas" :userInfo="userInfo" :data="data" :url="url" @finished="onFinished"
						 @qrcodeFinished="onQrcodeFinished" @error="onError"></invite-apply-canvas>
</template>
<script setup>
import storePoster from "@/store/poster"
import storeUserInfo from "@/store/userInfo"
import {
	watch,
	computed,
	defineProps
} from "vue"
import useHomeData from '@/hooks/useHomeData.js'

const { userInfo } = storeUserInfo()
// 获取家庭信息
const { userHomeInfo, getHomeInfoData } = useHomeData()
getHomeInfoData()

const {
	applyPoster
} = storePoster()


const props = defineProps({
	data: {
		type: Object,
		default: () => ({})
	}
})


const url = computed(() => {
	if (props.data.id) {
		let origin = window.location.origin
		return `${origin}/#/pagesFamily/apply/apply?id=${props.data.id}`
	}
	return ''
})

const showCanvas = computed(() => {
	const bool = url.value && !applyPoster.value.loading && !applyPoster.value.url
	// if(!!bool) {
	// 	applyPoster.value.loading = true
	// }
	console.log(bool)
	return bool
})

const onFinished = (url) => {
	applyPoster.value.url = url
	applyPoster.value.loading = false
}
const onQrcodeFinished = (url) => {
	applyPoster.value.qrcode = url
	// console.log('家庭二维码绘制成功')
}
const onError = (err) => {
	applyPoster.value.loading = false
}



</script>

<style lang="scss">
.comp-invite-canvas {
	width: 622rpx;
	margin: 0 auto;

	.canvas {
		position: fixed;
		left: 100vw;
	}

	.canvas-img-box {
		width: 100%;
		border-radius: 24rpx;
		overflow: hidden;

		.canvas-img {
			width: 100%;
		}

		.no-canvas-img-box {
			text-align: center;
			font-size: 28rpx;
			font-weight: bold;
			line-height: 1118rpx;
			letter-spacing: 2rpx;
			background: #fff;
			// background-image: url('http://cdn.jiyouzhenxuan.com/upload/tjgyq-hb.png');
			// background-repeat: no-repeat;
			// background-size: 100%;
		}
	}

	.tip {
		text-align: center;
		font-size: 32rpx;
		font-weight: bold;
		color: #bcbcbc;
		line-height: 68rpx;
	}
}
</style>

invite-apply-canvas

html 复制代码
<template>
	<view class="comp-invite-canvas">
		<canvas class="canvas" :style="{ 'backgroundColor': '#fff' }" :width="750" :height="1334"
				canvas-id="canvas-invite-home" id="canvas-invite-home"></canvas>
		<my-canvas-qrcode @catchError="onError" :data="url" @finished="onFinished"></my-canvas-qrcode>
	</view>
</template>

<script>
import useCanvas from "@/hooks/useCanvas.js"
export default {
	emits: ['finished', 'err', 'qrcodeFinished'],
	props: {
		userInfo: {
			type: Object,
			default: () => ({})
		},
		data: {
			type: Object,
			default: () => ({})
		},
		url: {
			type: String,
			required: true
		},
		recommendInfo: {
			type: Object,
			default: () => ({})
		},
		platformInfo: {
			type: Object,
			default: () => ({})
		}
	},
	data() {
		return {
			// 家庭分享
			bg: 'http://cdn.joytup.com/poster/xq-share-poster.png',
			icon: 'https://cdn.joytup.com/poster/home-share-icon.png',
			logo: 'https://cdn.joytup.com/plg-logo.jpg',
			tip: '长按识别加入我们的小区',
			qrcodeImg: '',
			canvasImg: '',
			position: {
				img: {
					width: 670,
					height: 1078,
					x: 0,
					y: 0,
				},
				bg: {
					width: 670,
					// height: 1078,
					height: 890,
					x: 0,
					y: 0,
				},
				qrcode: {
					width: 140,
					height: 140,
					x: 502,
					y: 914,
				},
				logo: {
					width: 40,
					height: 40,
					x: 552,
					y: 964,
				},
				name: {
					size: 32,
					x: 102,
					y: 942,
					color: '#19191A',
					maxWidth: 400
				},
				headImgUrl: {
					width: 64,
					height: 64,
					x: 24,
					y: 950,
				},
				tip: {
					size: 24,
					x: 100,
					y: 994,
					color: '#97979B'
				},
				icon: {
					width: 40,
					height: 20,
					x: 370,
					y: 1000,
				},
			}
		}
	},
	mounted() {
	},
	methods: {
		onError(err) {
			this.$emit('err', err)
		},
		onFinished(url) {
			this.qrcodeImg = url
			this.$emit('qrcodeFinished', url)
			this.draw()
		},
		async draw() {
			let bool = true
			const {
				canvas,
				ctx,
				drawImageFn,
				drawTextFn,
				drawCircleImageFn,
				drawBgFn
			} = await useCanvas('canvas-invite-home', this, 670, 1078).catch(() => {
				bool = false
			})
			if (!bool) return

			await drawBgFn('#fff')
			await drawImageFn(this.bg, this.position.bg).catch((err) => {
			})
			await drawImageFn(this.qrcodeImg, this.position.qrcode).catch((err) => {
			})
			await drawImageFn(this.logo, this.position.logo, {
				circle: true,
				rect: true,
			}).catch((err) => {
			})
			// 分享小区信息
			if (this.data.logo) {
				await drawImageFn(this.data.logo, this.position.headImgUrl, {
					circle: false,
					rect: false,
					bgColor: '#fff'
				})
			}

			drawTextFn(this.data.name, this.position.name)
			drawTextFn(this.tip, this.position.tip)
			await drawImageFn(this.icon, this.position.icon).catch((err) => {
			})



			// const imgurl = canvas.toDataURL("image/jpeg", 1.0)
			const imgurl = canvas.toDataURL()
			this.canvasImg = imgurl
			this.$emit('finished', imgurl)
		}
	}
}
</script>

<style lang="scss">
.comp-invite-canvas {
	width: 622rpx;
	margin: 0 auto;

	.canvas {
		position: fixed;
		left: 100vw;
	}

	.canvas-img-box {
		width: 100%;
		border-radius: 24rpx;
		overflow: hidden;

		.canvas-img {
			width: 100%;
		}

		.no-canvas-img-box {
			text-align: center;
			font-size: 28rpx;
			font-weight: bold;
			line-height: 1118rpx;
			letter-spacing: 2rpx;
			background: #fff;
			// background-image: url('http://cdn.jiyouzhenxuan.com/upload/tjgyq-hb.png');
			// background-repeat: no-repeat;
			// background-size: 100%;
		}
	}

	.tip {
		text-align: center;
		font-size: 32rpx;
		font-weight: bold;
		color: #bcbcbc;
		line-height: 68rpx;
	}
}
</style>
```
显示页面
```html
<template>
    <view class="page-invite">
        <my-navigation></my-navigation>
        <view class="flex-between-center" style="margin: 0 24rpx 24rpx;">
            <view class=""></view>
            <view class="refresh flex-center" @click="refresh">
                <view class="">重新绘制</view>
            </view>
        </view>
        <view class="canvas-img-box">
            <image v-if="inviteUrl" class="canvas-img" :src="inviteUrl" mode="widthFix"></image>
            <view v-else class="no-canvas-img-box flex-center">
                正在绘制海报,绘制海报过程较慢,请耐心等待!如遇到长时间未响应或邀请海报异常,请点击 "重新绘制" ,或点击右上角 "..." 分享邀请链接!
            </view>
        </view>
        <view class="tip">
            长按图片保存
        </view>
        <invite-apply :data="communityData" />
    </view>
</template>

<script setup>
import storeRecommendInfo from "@/store/recommendInfo.js"
import {
    onLoad
} from "@dcloudio/uni-app"
import {
    ref,
    computed,
    watch
} from "vue"
import {
    getPlatformInfo
} from "@/api/common.js"
import useHideMenuItem from "@/hooks/useHideMenuItem"
import storePoster from "@/store/poster"
import onShare from "@/assets/third/share"
import storeUserInfo from "@/store/userInfo"
import useHomeData from '@/hooks/useHomeData.js'
import { compoundDetail } from "@/api/property.js"

// 获取家庭信息
const { userHomeInfo, getHomeInfoData } = useHomeData()
getHomeInfoData()

useHideMenuItem()
const { userInfo } = storeUserInfo()
const {
    applyPoster,
} = storePoster()
const {
    recommendInfo
} = storeRecommendInfo()

const options = ref({})

// 小区详情信息
const communityData = ref({})

const getPropertyInfo = async () => {
    const res = await compoundDetail({ id: options.value.id })
    communityData.value = res.data || {}
}


const inviteUrl = computed(() => {
    return applyPoster.value?.url
})

const url = computed(() => {
    if (userHomeInfo.value.id) {
        let origin = window.location.origin
        return `${origin}/#/pagesFamily/apply/apply?id=${userHomeInfo.value.id}`
    }
    return ''
})

const refresh = () => {
    applyPoster.value.url = ''
    applyPoster.value.loading = false
}

onLoad((option) => {
    options.value = option
    if (!applyPoster.value.url) {
        applyPoster.value.loading = false
    }

    refresh()
    getPropertyInfo()
})
watch(
    () => [userInfo.value, url.value],
    () => {
        if (userInfo.value?.id && url.value) {
            const data = {
                title: '申请邀请',
                desc: userInfo.value.agentName + '邀请您加入小区',
                imgUrl: userInfo.value.headImgUrl,
                link: url.value,
            }
            onShare(data)
        }
    }, {
    immediate: true
}
)
</script>

<style lang="scss">
.refresh {
    padding: 4rpx 16rpx;
    line-height: 32rpx;
    color: #333;
    font-size: 24rpx;
    font-weight: 400;
    border: 1px solid #ccc;
    border-radius: 24rpx;
    // background: $color-primary;
    // color: #fff;
}

.page-invite {
    .invite-panel {
        border-radius: 24rpx;
        overflow: hidden;
    }
}

.canvas-img-box {
    width: 622rpx;
    margin: 0 auto;
    // width: 100%;
    border-radius: 24rpx;
    overflow: hidden;

    .canvas-img {
        width: 100%;
        // background-color: #fff;
    }

    .no-canvas-img-box {
        padding: 24rpx;
        height: 1118rpx;
        text-align: center;
        font-size: 28rpx;
        font-weight: 500;
        line-height: 48rpx;
        letter-spacing: 2rpx;
        background: #fff;
    }
}

.tip {
    text-align: center;
    font-size: 32rpx;
    font-weight: bold;
    color: #bcbcbc;
    line-height: 68rpx;
}
</style>
```

## 实现效果图 
显示生成的图片地址,图片可以直接长按下载或者识别二维码,这里的二维码不是固定的,又是生成的,使用uview的二维码组件生成获取链接,载使用画布绘制
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/f1943832497341a7aff54f5b1c93308d.png)
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/cc2e1ce6ad6c43e4affcf5bf35b8338e.png)
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/ee9b3fb99afc44d59a88222acc1f3189.png)

还可以画整个图片背景的,这里不贴代码,直接贴长按保存的图片
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6a9c9e9e57eb47f2bb2356a7fb086c5e.png)
相关推荐
2501_916007472 小时前
没有 Mac 用户如何上架 App Store,IPA生成、证书与描述文件管理、跨平台上传
android·macos·ios·小程序·uni-app·iphone·webview
wangjun51592 小时前
uniapp uni.downloadFile 偶发性下载文件失败 无响应
uni-app
2501_9151063221 小时前
当 Perfdog 开始收费之后,我重新整理了一替代方案
android·ios·小程序·https·uni-app·iphone·webview
2501_915918411 天前
中小团队发布,跨平台 iOS 上架,证书、描述文件创建管理,测试分发一体化方案
android·ios·小程序·https·uni-app·iphone·webview
家里有只小肥猫1 天前
uniApp打包ios报错
ios·uni-app
jingling5551 天前
uniapp | 基于高德地图实现位置选择功能(安卓端)
android·前端·javascript·uni-app
某公司摸鱼前端1 天前
前端一键部署网站至服务器FTP
前端·javascript·uni-app
爱怪笑的小杰杰1 天前
UniApp 桌面应用实现 Android 开机自启动(无原生插件版)
android·java·uni-app
m0_647057961 天前
uniapp使用rich-text流式 Markdown 换行问题与解决方案
前端·javascript·uni-app