完成canvas生成图片下载

This commit is contained in:
kron
2025-06-21 03:34:10 +08:00
parent 34d4a1bed8
commit c1148d6e51
7 changed files with 213 additions and 88 deletions

View File

@@ -52,6 +52,7 @@ onMounted(() => {
align-items: center;
width: 72vw;
height: 60px;
/* margin-top: var(--status-bar-height); */
padding-left: 15px;
}
.back-btn {

View File

@@ -1,136 +1,73 @@
{
"pages": [
{
"path": "pages/index",
"style": {
"navigationBarTitleText": "首页"
}
"path": "pages/index"
},
{
"path": "pages/my-device",
"style": {
"navigationBarTitleText": "我的设备"
}
"path": "pages/image-share"
},
{
"path": "pages/device-intro",
"style": {
"navigationBarTitleText": "智能弓箭"
}
"path": "pages/my-device"
},
{
"path": "pages/user",
"style": {
"navigationBarTitleText": "用户信息"
}
"path": "pages/device-intro"
},
{
"path": "pages/orders",
"style": {
"navigationBarTitleText": "订单"
}
"path": "pages/user"
},
{
"path": "pages/order-detail",
"style": {
"navigationBarTitleText": "订单详情"
}
"path": "pages/orders"
},
{
"path": "pages/be-vip",
"style": {
"navigationBarTitleText": "会员"
}
"path": "pages/order-detail"
},
{
"path": "pages/grade-intro",
"style": {
"navigationBarTitleText": "等级介绍"
}
"path": "pages/be-vip"
},
{
"path": "pages/rank-intro",
"style": {
"navigationBarTitleText": "段位介绍"
}
"path": "pages/grade-intro"
},
{
"path": "pages/my-growth",
"style": {
"navigationBarTitleText": "我的成长"
}
"path": "pages/rank-intro"
},
{
"path": "pages/first-try",
"style": {
"navigationBarTitleText": "新手试炼"
}
"path": "pages/my-growth"
},
{
"path": "pages/practise",
"style": {
"navigationBarTitleText": "个人练习"
}
"path": "pages/first-try"
},
{
"path": "pages/practise-one",
"style": {
"navigationBarTitleText": "个人单组练习"
}
"path": "pages/practise"
},
{
"path": "pages/practise-two",
"style": {
"navigationBarTitleText": "个人耐力挑战"
}
"path": "pages/practise-one"
},
{
"path": "pages/friend-battle",
"style": {
"navigationBarTitleText": "好友约战"
}
"path": "pages/practise-two"
},
{
"path": "pages/battle-room",
"style": {
"navigationBarTitleText": "对战房间"
}
"path": "pages/friend-battle"
},
{
"path": "pages/ranking",
"style": {
"navigationBarTitleText": "排行赛"
}
"path": "pages/battle-room"
},
{
"path": "pages/rank-list",
"style": {
"navigationBarTitleText": "排行榜"
}
"path": "pages/ranking"
},
{
"path": "pages/team-match",
"style": {
"navigationBarTitleText": "排位赛"
}
"path": "pages/rank-list"
},
{
"path": "pages/melee-match",
"style": {
"navigationBarTitleText": "排位赛"
}
"path": "pages/team-match"
},
{
"path": "pages/match-detail",
"style": {
"navigationBarTitleText": "排位赛"
}
"path": "pages/melee-match"
},
{
"path": "pages/battle-result",
"style": {
"navigationBarTitleText": "对战结果"
}
"path": "pages/match-detail"
},
{
"path": "pages/battle-result"
}
],
"globalStyle": {

136
src/pages/image-share.vue Normal file
View File

@@ -0,0 +1,136 @@
<script setup>
import { onMounted } from "vue";
import Container from "@/components/Container.vue";
import { renderText, renderRankTitle, renderLine } from "@/util.js";
const saveImage = () => {
uni.canvasToTempFilePath({
canvasId: "shareCanvas",
success: (res) => {
const tempFilePath = res.tempFilePath;
// 保存图片到相册
uni.saveImageToPhotosAlbum({
filePath: tempFilePath,
success: () => {
uni.showToast({ title: "保存成功" });
},
fail: () => {
uni.showToast({ title: "保存失败", icon: "error" });
},
});
},
});
};
onMounted(() => {
var ctx = uni.createCanvasContext("shareCanvas");
const width = 302;
const height = 535;
ctx.drawImage("../static/share-bg.png", 0, 0, 302, 534);
ctx.drawImage("../static/avatar.png", 20, 20, 35, 35);
ctx.drawImage("../static/avatar-frame.png", 15, 15, 45, 45);
renderText(ctx, "用户名称", 14, "#fff", 70, 35);
renderRankTitle(ctx, "钻石1级");
ctx.drawImage(
"../static/first-try-title.png",
(width - 200) / 2,
155,
200,
53
);
renderText(ctx, "正式开启弓箭手之路", 22, "#fff", 53, 230);
renderText(ctx, "共", 16, "#fff", 124, 300);
renderText(ctx, "50", 16, "#fed847", 144, 300);
renderText(ctx, "环", 16, "#fff", 166, 300);
renderLine(ctx, 80);
renderLine(ctx, 192);
for (let i = 0; i < 6; i++) {
ctx.drawImage("../static/score-bg.png", 16 + i * 46, 320, 40, 40);
renderText(ctx, "9", 25, "#fed847", 29 + i * 46, 349);
}
for (let i = 0; i < 6; i++) {
ctx.drawImage("../static/score-bg.png", 16 + i * 46, 366, 40, 40);
renderText(ctx, "9", 25, "#fed847", 29 + i * 46, 395);
}
ctx.drawImage(
"../static/device-icon.png",
width * 0.06,
height * 0.87,
55,
55
);
renderText(ctx, "射灵平台", 16, "#fff", width * 0.28, height * 0.9);
renderText(
ctx,
"快加入我们一起玩吧~",
11,
"rgba(255, 255, 255, 0.5)",
width * 0.28,
height * 0.93
);
renderText(
ctx,
"后羿就是这样练成的",
11,
"rgba(255, 255, 255, 0.5)",
width * 0.28,
height * 0.96
);
ctx.drawImage("../static/qr-code.png", width * 0.75, height * 0.86, 60, 60);
ctx.draw();
});
</script>
<template>
<Container>
<view class="content">
<canvas
style="width: 302px; height: 534px"
canvas-id="shareCanvas"
id="firstCanvas"
></canvas>
<view class="footer">
<view>
<image src="../static/wechat-outline.png" mode="widthFix" />
<text>微信好友</text>
</view>
<view>
<image src="../static/wechat-moment.png" mode="widthFix" />
<text>朋友圈</text>
</view>
<view @click="saveImage">
<image src="../static/download.png" mode="widthFix" />
<text>保存到相册</text>
</view>
</view>
</view>
</Container>
</template>
<style scoped>
.content {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 20px;
}
.footer {
width: 100%;
display: flex;
justify-content: space-around;
margin-top: 50px;
}
.footer > view {
display: flex;
flex-direction: column;
align-items: center;
color: #fff;
font-size: 12px;
}
.footer > view > image {
width: 45px;
margin-bottom: 10px;
}
</style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
src/static/qr-code.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

51
src/util.js Normal file
View File

@@ -0,0 +1,51 @@
export function renderLine(ctx, from) {
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = "rgba(255, 255, 255, 0.3)";
const length = 35;
ctx.moveTo(from, 293);
ctx.lineTo(from + length, 293);
ctx.stroke();
}
export function renderText(ctx, text, size, color, x, y) {
ctx.setFontSize(size);
ctx.setFillStyle(color);
ctx.fillText(text, x, y);
}
export function renderRankTitle(ctx, text) {
const fontSize = 10;
const textWidth = ctx.measureText(text).width;
const padding = 8; // 文字与背景边缘的间距
const radius = 10; // 圆角半径
const textX = 75;
const textY = 55;
const x = textX - padding; // 文字 x 坐标减去内边距
const y = textY - fontSize - padding / 2 + 1; // 文字 y 坐标减去字体大小和内边距
const width = textWidth + padding * 2 - 15; // 背景宽度
const height = fontSize + padding + 1; // 背景高度
// 开始绘制圆角矩形
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.fillStyle = "#5F51FF";
ctx.fill();
ctx.setFontSize(fontSize);
ctx.setFillStyle("rgba(255, 255, 255, 0.9)");
ctx.fillText(text, textX, textY); // 绘制文字
}