uniapp使用Canvas生成电子名片

发布于:2025-06-02 ⋅ 阅读:(26) ⋅ 点赞:(0)

uniapp使用Canvas生成电子名片

工作中有生成电子名片的一个需求,刚刚好弄了发一下分享分享



前言

先看效果
在这里插入图片描述


一、上代码?

不对不对应该是上才艺,哈哈哈

	<template>
		<view class="container">
			<view class="head" style="height: 7rem;">
				<view style="color:#fff;position: fixed;top: 4rem;left: 1rem;">
					<br>
					<text style="font-size: 11px;"></text>
				</view>
			</view>
			<view style="width: 100%; height: 7.5rem;"></view>
			<scroll-view scroll-y style="height: 100vh;" class="content">
				<view style="padding-top: 30rpx;">
					<!-- 立体名片卡片 -->
					<view class="card-container" id="cardContainer">
						<view class="business-card">
							<!-- 顶部彩色边缘标签 -->
							<view class="card-edge"></view>

							<view class="card-content">
								<view class="card-title">裤架工厂</view>
								<view class="card-subtitle">专做软体沙发</view>

								<view class="card-details">
									<view class="detail-item">
										<view>高级产品设计师</view>
									</view>
									<view class="detail-item">
										<view>创新事业部 | ABC科技</view>
									</view>
									<view class="detail-item">
										<view>工厂地址:上海浦东新区三林东地铁左边大厦23</view>
									</view>
								</view>

							</view>

							<!-- 双二维码容器 -->
							<view class="qrcode-column">
								<view class="qrcode-item">
									<view class="card-image-container">
										<image class="card-image"
											src="https://linda-uni.oss-cn-guangzhou.aliyuncs.com/images/and/app_20230925_20250529092745.png"
											mode="aspectFit"></image>
									</view>
									<view class="qrcode-label">微信联系</view>
								</view>

								<view class="qrcode-item">
									<view class="card-image-container">
										<image class="card-image"
											src="https://linda-uni.oss-cn-guangzhou.aliyuncs.com/images/and/app_20230925_20250529092745.png"
											mode="aspectFit"></image>
									</view>
									<view class="qrcode-label">官方网站</view>
								</view>
							</view>

							<!-- 角落装饰 -->
							<view class="card-corner corner-tl"></view>
							<view class="card-corner corner-br"></view>
						</view>
					</view>

					<view class="save-btn" @click="saveCardImage">
						<text>保存名片图片</text>
					</view>
				</view>
			</scroll-view>

			<canvas canvas-id="cardCanvas"
				:style="{ position: 'absolute', left: '-9999px', width: canvasWidth + 'px', height: canvasHeight + 'px' }"></canvas>
		</view>
	</template>
	<script>
		export default {
			data() {
				return {
					canvasWidth: 1100,
					canvasHeight: 630,
					dpr: 1,
					qrcode1: 'https://linda-uni.oss-cn-guangzhou.aliyuncs.com/images/and/app_20230925_20250529092745.png',
					qrcode2: 'https://linda-uni.oss-cn-guangzhou.aliyuncs.com/images/and/app_20230925_20250529092745.png',
					backgroundImage: 'https://linda-uni.oss-cn-guangzhou.aliyuncs.com/images/and/550e8a1a-e450-4880-9441-33608476b32a.jpg'
				}
			},
			mounted() {

			},
			methods: {
				// 保存名片为图片   
				async saveCardImage() {
					uni.showLoading({
						title: '生成中...',
						mask: true
					});

					try {
						// 1. 绘制Canvas
						await this.drawCardCanvas();

						// 2. 生成临时图片路径
						const tempPath = await new Promise((resolve, reject) => {
							uni.canvasToTempFilePath({
								canvasId: 'cardCanvas',
								quality: 1,
								fileType: 'png',
								width: this.canvasWidth * this.dpr,
								height: this.canvasHeight * this.dpr,
								destWidth: this.canvasWidth * this.dpr,
								destHeight: this.canvasHeight * this.dpr,
								success: res => resolve(res.tempFilePath),
								fail: err => reject(err)
							});
						});

						// 3. 保存到相册
						await new Promise((resolve, reject) => {
							uni.saveImageToPhotosAlbum({
								filePath: tempPath,
								success: () => resolve(),
								fail: err => reject(err)
							});
						});

						uni.hideLoading();
						uni.showToast({
							title: '保存成功',
							icon: 'success'
						});
					} catch (err) {
						uni.hideLoading();
						uni.showToast({
							title: '保存失败: ' + (err.errMsg || err),
							icon: 'none'
						});
						console.error('保存失败:', err);
					}
				},

				// 绘制名片到Canvas
				async drawCardCanvas() {
					const ctx = uni.createCanvasContext('cardCanvas', this);
					const dpr = this.dpr;
					const width = this.canvasWidth;
					const height = this.canvasHeight;
					const padding = 30 * dpr;

					// 清除画布
					ctx.clearRect(0, 0, width, height);

					// 1. 绘制背景
					// ctx.setFillStyle('#1F1F1F');
					// ctx.fillRect(0, 0, width, height);

					// 2. 绘制名片背景
					const cardWidth = width - padding * 2;
					const cardHeight = height - padding * 2;

					// 绘制名片背景
					if (this.backgroundImage) {
						// 使用图片背景
						try {
							await this.drawRoundedImage(
								ctx,
								this.backgroundImage,
								padding,
								padding,
								cardWidth,
								cardHeight,
								20 * dpr
							);
						} catch (e) {
							console.error('背景图片加载失败:', e);
							// 回退到默认背景
							this.drawDefaultBackground(ctx, padding, cardWidth, cardHeight);
						}
					} else {
						// 使用颜色背景
						this.drawDefaultBackground(ctx, padding, cardWidth, cardHeight);
					}



					// // 渐变背景
					// const gradient = ctx.createLinearGradient(0, 0, cardWidth, cardHeight);   
					// gradient.addColorStop(0, '#302f30');
					// gradient.addColorStop(1, '#282728');
					// ctx.setFillStyle(gradient);

					// // 圆角矩形
					// this.drawRoundedRect(ctx, padding, padding, cardWidth, cardHeight, 20 * dpr);
					// ctx.fill();

					// 3. 顶部彩色边缘(带圆角)
					const topHeight = 16 * dpr; // 边缘高度
					const topRadius = 20 * dpr; // 圆角半径

					// 创建渐变
					const edgeGradient = ctx.createLinearGradient(padding, padding, padding + cardWidth, padding);
					edgeGradient.addColorStop(0, '#ff9a9e');
					edgeGradient.addColorStop(0.3, '#fad0c4');
					edgeGradient.addColorStop(0.6, '#a18cd1');
					edgeGradient.addColorStop(1, '#fbc2eb');
					ctx.setFillStyle(edgeGradient);

					// 绘制带圆角的顶部边缘(仅左上和右上圆角)
					ctx.beginPath();
					// 从左上圆角结束点开始
					ctx.moveTo(padding + topRadius, padding);
					// 绘制上边缘线
					ctx.lineTo(padding + cardWidth - topRadius, padding);
					// 绘制右上圆角
					ctx.arcTo(
						padding + cardWidth, padding,
						padding + cardWidth, padding + topHeight,
						topRadius
					);
					// 绘制右边缘
					ctx.lineTo(padding + cardWidth, padding + topHeight);
					// 绘制下边缘(向左)
					ctx.lineTo(padding, padding + topHeight);
					// 绘制左边缘(向上)
					ctx.lineTo(padding, padding + topRadius);
					// 绘制左上圆角
					ctx.arcTo(
						padding, padding,
						padding + topRadius, padding,
						topRadius
					);
					ctx.closePath();
					ctx.fill();

					// 4. 设置字体  
					ctx.setFontSize(40 * dpr);
					ctx.setFillStyle('#f0f0f0');
					ctx.fillText('裤架工厂', padding + 40 * dpr, padding + 80 * dpr);

					ctx.setFontSize(28 * dpr);
					ctx.setFillStyle('#CCCCCC');
					ctx.fillText('专做软体沙发', padding + 40 * dpr, padding + 130 * dpr);

					// 绘制下划线
					ctx.setStrokeStyle('#ff9a9e');
					ctx.setLineWidth(6 * dpr);
					ctx.beginPath();
					ctx.moveTo(padding + 40 * dpr, padding + 90 * dpr);
					ctx.lineTo(padding + 120 * dpr, padding + 90 * dpr);
					ctx.stroke();

					// 5. 绘制详情信息
					const details = [
						'高级产品设计师',
						'创新事业部 | ABC科技',
						'工厂地址:上海浦东新区三林东地铁左边大厦23'
					];

					ctx.setFontSize(28 * dpr);
					ctx.setFillStyle('#b0b0b0');
					details.forEach((text, i) => {
						ctx.fillText(text, padding + 40 * dpr, padding + 200 * dpr + i * 50 * dpr);
					});

					// 6. 绘制二维码 (修改部分开始)
					const qrSize = cardWidth * 0.12; // 缩小二维码尺寸
					const qrY = padding + 350 * dpr;
					const qrSpacing = 30 * dpr; // 二维码间距
					const qrRadius = 20 * dpr; // 圆角半径

					const wxX = padding + 40 * dpr;
					await this.drawRoundedImage(
						ctx,
						this.qrcode1,
						wxX,
						qrY,
						qrSize,
						qrSize,
						qrRadius
					);

					// 官网二维码 (靠左,在微信二维码右侧)
					const webX = wxX + qrSize + qrSpacing;
					await this.drawRoundedImage(
						ctx,
						this.qrcode2,
						webX,
						qrY,
						qrSize,
						qrSize,
						qrRadius
					);

					ctx.setFontSize(24 * dpr);
					ctx.setFillStyle('#e0e0e0');

					// 微信文字居中
					ctx.setTextAlign('center');
					ctx.fillText('客户群', wxX + qrSize / 2, qrY + qrSize + 30 * dpr);

					// 官网文字居中
					ctx.setTextAlign('center');
					ctx.fillText('国内站', webX + qrSize / 2, qrY + qrSize + 30 * dpr);

					// 恢复对齐方式
					ctx.setTextAlign('left');

					// 执行绘制
					ctx.draw();

					// 返回绘制完成的Promise
					return new Promise(resolve => {
						setTimeout(resolve, 500);
					});
				},

				// 新增方法:绘制圆角图片
				async drawRoundedImage(ctx, src, x, y, width, height, radius) {
					return new Promise((resolve, reject) => {
						uni.getImageInfo({
							src: src,
							success: (res) => {
								// 保存当前状态
								ctx.save();

								// 创建圆角矩形路径   
								this.drawRoundedRect(ctx, x, y, width, height, radius);
								ctx.clip(); // 裁剪为圆角矩形

								// 绘制图片
								ctx.drawImage(res.path, x, y, width, height);

								// 恢复状态
								ctx.restore();
								resolve();
							},
							fail: reject
						});
					});
				},

				// 工具方法:绘制圆角矩形
				drawRoundedRect(ctx, x, y, width, height, radius) {
					ctx.beginPath();
					ctx.moveTo(x + radius, y);
					ctx.lineTo(x + width - radius, y);
					ctx.arcTo(x + width, y, x + width, y + radius, radius);
					ctx.lineTo(x + width, y + height - radius);
					ctx.arcTo(x + width, y + height, x + width - radius, y + height, radius);
					ctx.lineTo(x + radius, y + height);
					ctx.arcTo(x, y + height, x, y + height - radius, radius);
					ctx.lineTo(x, y + radius);
					ctx.arcTo(x, y, x + radius, y, radius);
					ctx.closePath();
				},


				drawDefaultBackground(ctx, padding, cardWidth, cardHeight) {
					const gradient = ctx.createLinearGradient(0, 0, cardWidth, cardHeight);
					gradient.addColorStop(0, '#302f30');
					gradient.addColorStop(1, '#282728');
					ctx.setFillStyle(gradient);
					this.drawRoundedRect(ctx, padding, padding, cardWidth, cardHeight, 20);
					ctx.fill();
				},

				// 工具方法:绘制装饰角
				drawCorner(ctx, x, y, size, position) {
					ctx.setStrokeStyle('#ff9a9e');
					ctx.setLineWidth(3 * this.dpr);
					ctx.beginPath();

					// 圆角半径
					const radius = 12 * this.dpr;

					if (position === 'tl') {
						// 左上角 - 包围式圆角
						ctx.moveTo(x - size, y);
						ctx.lineTo(x, y);
						ctx.lineTo(x, y - size);

						// 添加圆角效果
						ctx.moveTo(x, y - radius);
						ctx.arc(x + radius, y - radius, radius, Math.PI, 1.5 * Math.PI);
					} else if (position === 'br') {
						// 右下角 - 包围式圆角
						ctx.moveTo(x + size, y);
						ctx.lineTo(x, y);
						ctx.lineTo(x, y + size);

						// 添加圆角效果
						ctx.moveTo(x, y + radius);
						ctx.arc(x - radius, y + radius, radius, 0.5 * Math.PI, Math.PI);
					}

					ctx.stroke();
				},

				// 工具方法:绘制图片(支持网络图片)
				drawImage(ctx, src, x, y, width, height) {
					return new Promise((resolve, reject) => {
						uni.getImageInfo({
							src: src,
							success: res => {
								ctx.drawImage(res.path, x, y, width, height);
								resolve();
							},
							fail: (err) => {
								console.error('图片加载失败:', err);
								reject(err);
							}
						});
					});
				}
			},
		}
	</script>

	<style>
		page {
			background-color: #1F1F1F;
		}

		.container {
			position: relative;
			height: 100vh;
			width: 100vw;
			overflow: hidden;
		}

		.head {
			width: 100%;
			position: fixed;
			top: 0px;
			left: 0%;
			z-index: 99999;
		}

		/* 新增样式 */
		.card-container {
			position: relative;
			width: 88%;
			margin: 0 auto 40rpx;
			perspective: 1000px;
		}

		.business-card {
			background: linear-gradient(145deg, #302f30, #282728);
			border-radius: 20rpx;
			padding: 30rpx;
			/* display: flex; */
			justify-content: space-between;
			box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.4);
			position: relative;
			overflow: hidden;
			transform: translateY(0);
			transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
		}

		.business-card:active {
			transform: translateY(-10rpx);
			box-shadow: 0 15rpx 40rpx rgba(0, 0, 0, 0.6);
		}

		/* 边缘装饰效果 */
		.card-edge {
			position: absolute;
			top: 0;
			left: 0;
			width: 100%;
			height: 16rpx;
			background: linear-gradient(90deg, #ff9a9e, #fad0c4, #a18cd1, #fbc2eb);
			border-radius: 20rpx 20rpx 0 0;
		}

		.card-corner {
			position: absolute;
			width: 40rpx;
			height: 40rpx;
			border: 3rpx solid #ff9a9e;
		}

		.corner-tl {
			top: 20rpx;
			left: 20rpx;
			border-right: none;
			border-bottom: none;
			border-top-left-radius: 12rpx;
		}

		.corner-br {
			bottom: 20rpx;
			right: 20rpx;
			border-left: none;
			border-top: none;
			border-bottom-right-radius: 12rpx;
		}

		.card-content {
			flex: 1;
			padding-right: 20rpx;
		}

		.card-title {
			font-size: 40rpx;
			font-weight: bold;
			color: #f0f0f0;
			margin-bottom: 16rpx;
			position: relative;
			display: inline-block;
		}

		.card-title::after {
			content: '';
			position: absolute;
			bottom: -8rpx;
			left: 0;
			width: 80rpx;
			height: 6rpx;
			background: linear-gradient(90deg, #ff9a9e, #fad0c4);
			border-radius: 4rpx;
		}

		.card-subtitle {
			font-size: 28rpx;
			color: #CCCCCC;
			margin: 30rpx 0 20rpx;
			line-height: 1.5;
		}

		.card-details {
			display: flex;
			flex-direction: column;
			gap: 20rpx;
			margin-top: 30rpx;
		}

		.detail-item {
			display: flex;
			align-items: center;
			font-size: 28rpx;
			color: #b0b0b0;
		}

		.detail-icon {
			width: 50rpx;
			color: #ff9a9e;
			font-size: 32rpx;
			margin-right: 15rpx;
		}

		.card-image-container {
			flex-shrink: 0;
			width: 180rpx;
			height: 180rpx;
			border-radius: 15rpx;
			overflow: hidden;
			box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.3);
			border: 2rpx solid rgba(255, 255, 255, 0.1);
			position: relative;
			background: #fff;
			display: flex;
			align-items: center;
			justify-content: center;
			transition: all 0.3s ease;
		}

		.card-image-container:active {
			transform: scale(1.05);
			box-shadow: 0 12rpx 25rpx rgba(0, 0, 0, 0.5);
		}

		.card-image {
			width: 90%;
			height: 90%;
			display: block;
		}

		/* 双二维码样式 */
		.qrcode-column {
			display: flex;
			/* flex-direction: column; */
			/* align-items: center; */
			/* justify-content: center; */
			flex-shrink: 0;
			width: 100%;
			gap: 25rpx;
			margin-top: 20rpx;
		}

		.qrcode-item {
			display: flex;
			flex-direction: column;
			align-items: center;
		}

		.qrcode-label {
			color: #e0e0e0;
			font-size: 24rpx;
			text-align: center;
			margin-top: 10rpx;
			font-weight: 500;
		}

		.save-btn {
			margin: 40rpx auto;
			padding: 20rpx 40rpx;
			background: linear-gradient(90deg, #ff9a9e, #a18cd1);
			color: white;
			border-radius: 50rpx;
			width: 60%;
			text-align: center;
			font-weight: bold;
			box-shadow: 0 8rpx 20rpx rgba(161, 140, 209, 0.4);
		}
	</style>

总结

这里面就js 里面的有用,其他的我也没有删除,要用的可以自己删除就可以了,go,卷起来


网站公告

今日签到

点亮在社区的每一天
去签到