fix: lucky page
This commit is contained in:
@@ -23,17 +23,39 @@
|
||||
<view class="lucky-card" id="lucky-card">
|
||||
<!-- 头部渐变区 -->
|
||||
<view class="card-header">
|
||||
<view class="header-decor left">福</view>
|
||||
<view class="header-decor right">禧</view>
|
||||
<!-- Top Bar: User Info & Date -->
|
||||
<view class="header-top-bar">
|
||||
<view
|
||||
class="user-info-row"
|
||||
v-if="userInfo && (userInfo.avatarUrl || userInfo.nickName)"
|
||||
>
|
||||
<image
|
||||
class="user-avatar"
|
||||
:src="
|
||||
userInfo.avatarUrl || '/static/images/default-avatar.png'
|
||||
"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<text class="user-name">{{
|
||||
userInfo.nickName || "好运用户"
|
||||
}}</text>
|
||||
</view>
|
||||
<view class="date-tag">{{ currentDateStr }}</view>
|
||||
</view>
|
||||
|
||||
<!-- Main Content: Score & Word -->
|
||||
<view class="header-main">
|
||||
<text class="header-label">今日好运指数</text>
|
||||
<view class="score-wrap">
|
||||
<text class="score">{{ resultData.score }}</text>
|
||||
<text class="percent">%</text>
|
||||
</view>
|
||||
<text class="lucky-word">{{ resultData.luckyWord }}</text>
|
||||
</view>
|
||||
|
||||
<view class="tag-year">{{ currentDateStr }}</view>
|
||||
<!-- Decorators -->
|
||||
<view class="header-decor left-bottom">福</view>
|
||||
<view class="header-decor right-bottom">禧</view>
|
||||
</view>
|
||||
|
||||
<!-- 内容区 -->
|
||||
@@ -122,10 +144,13 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, getCurrentInstance } from "vue";
|
||||
import { ref, getCurrentInstance, computed } from "vue";
|
||||
import calendar from "@/utils/lunar.js";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const userStore = useUserStore();
|
||||
const userInfo = computed(() => userStore.userInfo);
|
||||
const popup = ref(null);
|
||||
const isAnimating = ref(true);
|
||||
const isFlipping = ref(false);
|
||||
@@ -135,7 +160,7 @@ const currentDateStr = ref("");
|
||||
|
||||
// 画布相关
|
||||
const canvasWidth = ref(600);
|
||||
const canvasHeight = ref(1000);
|
||||
const canvasHeight = ref(1100);
|
||||
|
||||
const resultData = ref({
|
||||
score: 88,
|
||||
@@ -204,63 +229,125 @@ const startAnimation = () => {
|
||||
}, 1800);
|
||||
};
|
||||
|
||||
const onSaveImage = () => {
|
||||
const onSaveImage = async () => {
|
||||
uni.showLoading({ title: "生成图片中..." });
|
||||
|
||||
let avatarPath = "/static/images/default-avatar.png"; // Default or fallback
|
||||
if (userInfo.value && userInfo.value.avatarUrl) {
|
||||
// Basic check for remote URL
|
||||
if (
|
||||
userInfo.value.avatarUrl.startsWith("http") ||
|
||||
userInfo.value.avatarUrl.startsWith("//")
|
||||
) {
|
||||
try {
|
||||
const [err, res] = await uni.downloadFile({
|
||||
url: userInfo.value.avatarUrl,
|
||||
});
|
||||
if (!err && res.statusCode === 200) {
|
||||
avatarPath = res.tempFilePath;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Avatar download failed", e);
|
||||
}
|
||||
} else {
|
||||
avatarPath = userInfo.value.avatarUrl;
|
||||
}
|
||||
}
|
||||
|
||||
const ctx = uni.createCanvasContext("luckyCanvas", proxy);
|
||||
const W = canvasWidth.value;
|
||||
const H = canvasHeight.value;
|
||||
const cardH = 850; // 卡片主体高度
|
||||
|
||||
// 1. 绘制背景
|
||||
ctx.setFillStyle("#ffffff");
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
|
||||
// 2. 绘制卡片头部渐变
|
||||
const grd = ctx.createLinearGradient(0, 0, 0, 360);
|
||||
const headerH = 460;
|
||||
const grd = ctx.createLinearGradient(0, 0, 0, headerH);
|
||||
grd.addColorStop(0, "#d84315");
|
||||
grd.addColorStop(1, "#ffca28");
|
||||
ctx.setFillStyle(grd);
|
||||
ctx.fillRect(0, 0, W, 360);
|
||||
ctx.fillRect(0, 0, W, headerH);
|
||||
|
||||
// 3. 头部装饰文字
|
||||
ctx.setFillStyle("rgba(255, 255, 255, 0.6)");
|
||||
ctx.setFontSize(24);
|
||||
ctx.fillText("福", 30, 40);
|
||||
ctx.fillText("禧", W - 50, 40);
|
||||
// --- Top Bar ---
|
||||
const topY = 40;
|
||||
const avatarSize = 64;
|
||||
|
||||
// 4. 头部内容
|
||||
// 绘制用户头像 (Top Left)
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.arc(
|
||||
40 + avatarSize / 2,
|
||||
topY + avatarSize / 2,
|
||||
avatarSize / 2,
|
||||
0,
|
||||
2 * Math.PI,
|
||||
);
|
||||
ctx.clip();
|
||||
ctx.drawImage(avatarPath, 40, topY, avatarSize, avatarSize);
|
||||
ctx.restore();
|
||||
|
||||
// 绘制用户昵称
|
||||
ctx.setTextAlign("left");
|
||||
ctx.setFillStyle("#ffffff");
|
||||
ctx.setFontSize(26);
|
||||
ctx.font = "bold 26px sans-serif";
|
||||
ctx.fillText(
|
||||
userInfo.value?.nickName || "好运用户",
|
||||
40 + avatarSize + 16,
|
||||
topY + 42,
|
||||
);
|
||||
|
||||
// 绘制日期 (Top Right)
|
||||
const dateStr = currentDateStr.value || "2026 CNY SPECIAL";
|
||||
ctx.setFontSize(22);
|
||||
ctx.font = "normal 22px sans-serif";
|
||||
const dateWidth = ctx.measureText(dateStr).width + 30;
|
||||
const dateX = W - 40 - dateWidth;
|
||||
const dateY = topY + 12; // box top
|
||||
// date bg
|
||||
ctx.setFillStyle("rgba(255, 255, 255, 0.2)");
|
||||
roundRect(ctx, dateX, dateY, dateWidth, 40, 20);
|
||||
ctx.fill();
|
||||
// date text
|
||||
ctx.setFillStyle("#ffffff");
|
||||
ctx.fillText(dateStr, dateX + 15, dateY + 28);
|
||||
|
||||
// --- Main Content (Centered) ---
|
||||
const centerX = W / 2;
|
||||
|
||||
// Label
|
||||
ctx.setTextAlign("center");
|
||||
ctx.setFillStyle("rgba(255, 255, 255, 0.9)");
|
||||
ctx.setFontSize(24);
|
||||
ctx.fillText("今日好运指数", W / 2, 80);
|
||||
ctx.font = "normal 24px sans-serif";
|
||||
ctx.fillText("今日好运指数", centerX, 180);
|
||||
|
||||
// 分数
|
||||
// Score
|
||||
ctx.setFillStyle("#ffffff");
|
||||
ctx.setFontSize(120);
|
||||
ctx.font = "bold 120px sans-serif";
|
||||
ctx.fillText(resultData.value.score + "%", W / 2, 200);
|
||||
ctx.setFontSize(140);
|
||||
ctx.font = "bold 140px sans-serif";
|
||||
ctx.fillText(resultData.value.score + "%", centerX, 310);
|
||||
|
||||
// 幸运词
|
||||
ctx.setFontSize(48);
|
||||
ctx.font = "bold 48px sans-serif";
|
||||
ctx.fillText(resultData.value.luckyWord, W / 2, 280);
|
||||
// Lucky Word
|
||||
ctx.setFontSize(52);
|
||||
ctx.font = "bold 52px sans-serif";
|
||||
ctx.fillText(resultData.value.luckyWord, centerX, 390);
|
||||
|
||||
// 日期标签背景
|
||||
const dateStr = currentDateStr.value || "2026 CNY SPECIAL";
|
||||
ctx.setFillStyle("rgba(0, 0, 0, 0.15)");
|
||||
const dateWidth = ctx.measureText(dateStr).width + 40;
|
||||
roundRect(ctx, W / 2 - dateWidth / 2, 310, dateWidth, 34, 17);
|
||||
ctx.fill();
|
||||
|
||||
// 日期文字
|
||||
ctx.setFillStyle("#ffffff");
|
||||
ctx.setFontSize(20);
|
||||
ctx.fillText(dateStr, W / 2, 334);
|
||||
// Decorators (Bottom Corners)
|
||||
ctx.setFillStyle("rgba(255, 255, 255, 0.4)");
|
||||
ctx.setFontSize(24);
|
||||
ctx.font = "normal 24px sans-serif";
|
||||
ctx.setTextAlign("left");
|
||||
ctx.fillText("福", 40, headerH - 20);
|
||||
ctx.setTextAlign("right");
|
||||
ctx.fillText("禧", W - 40, headerH - 20);
|
||||
|
||||
// 5. 绘制内容区 (宜/忌)
|
||||
const gridY = 400;
|
||||
const boxW = (W - 64 - 24) / 2; // (600 - padding*2 - gap)/2
|
||||
const gridH = 140; // 增加高度防止内容溢出
|
||||
const gridY = 500;
|
||||
const boxW = (W - 64 - 24) / 2;
|
||||
const gridH = 140;
|
||||
|
||||
// 宜
|
||||
drawBox(ctx, 32, gridY, boxW, gridH, "#fbfbfb", "#f5f5f5");
|
||||
@@ -268,7 +355,6 @@ const onSaveImage = () => {
|
||||
ctx.setFontSize(24);
|
||||
ctx.setFillStyle("#d81e06");
|
||||
ctx.font = "bold 24px sans-serif";
|
||||
// 图标模拟
|
||||
ctx.fillText("✔ 今日宜", 56, gridY + 44);
|
||||
|
||||
ctx.setFontSize(22);
|
||||
@@ -295,8 +381,8 @@ const onSaveImage = () => {
|
||||
);
|
||||
|
||||
// 6. 幸运元素
|
||||
const elY = 570; // 下移,避免与上面重叠
|
||||
const elH = 160; // 增加高度
|
||||
const elY = 670;
|
||||
const elH = 160;
|
||||
drawBox(ctx, 32, elY, W - 64, elH, "#fbfbfb", "#f5f5f5");
|
||||
|
||||
// 标题
|
||||
@@ -306,15 +392,14 @@ const onSaveImage = () => {
|
||||
ctx.fillText("★ 幸运元素", 56, elY + 46);
|
||||
|
||||
// 元素内容
|
||||
const contentW = W - 64; // 内容区域总宽度
|
||||
const colW = contentW / 3; // 三等分
|
||||
const startX = 32; // 起始X坐标
|
||||
const contentW = W - 64;
|
||||
const colW = contentW / 3;
|
||||
const startX = 32;
|
||||
|
||||
// 调整Y坐标,确保不重叠
|
||||
const labelY = elY + 90;
|
||||
const valY = elY + 126;
|
||||
|
||||
// 颜色 (第一列)
|
||||
// 颜色
|
||||
ctx.setTextAlign("center");
|
||||
ctx.setFontSize(20);
|
||||
ctx.setFillStyle("#999999");
|
||||
@@ -326,7 +411,7 @@ const onSaveImage = () => {
|
||||
ctx.font = "bold 26px sans-serif";
|
||||
ctx.fillText(resultData.value.luckyColor, startX + colW * 0.5, valY);
|
||||
|
||||
// 数字 (第二列)
|
||||
// 数字
|
||||
ctx.setFontSize(20);
|
||||
ctx.setFillStyle("#999999");
|
||||
ctx.font = "normal 20px sans-serif";
|
||||
@@ -337,7 +422,7 @@ const onSaveImage = () => {
|
||||
ctx.font = "bold 26px sans-serif";
|
||||
ctx.fillText(resultData.value.luckyNumber, startX + colW * 1.5, valY);
|
||||
|
||||
// 方向 (第三列)
|
||||
// 方向
|
||||
ctx.setFontSize(20);
|
||||
ctx.setFillStyle("#999999");
|
||||
ctx.font = "normal 20px sans-serif";
|
||||
@@ -348,7 +433,7 @@ const onSaveImage = () => {
|
||||
ctx.font = "bold 26px sans-serif";
|
||||
ctx.fillText(resultData.value.luckyDirection, startX + colW * 2.5, valY);
|
||||
|
||||
// 分隔线 1
|
||||
// 分隔线
|
||||
ctx.setStrokeStyle("#eeeeee");
|
||||
ctx.setLineWidth(2);
|
||||
ctx.beginPath();
|
||||
@@ -358,7 +443,6 @@ const onSaveImage = () => {
|
||||
ctx.lineTo(startX + colW, lineBottom);
|
||||
ctx.stroke();
|
||||
|
||||
// 分隔线 2
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(startX + colW * 2, lineTop);
|
||||
ctx.lineTo(startX + colW * 2, lineBottom);
|
||||
@@ -369,11 +453,10 @@ const onSaveImage = () => {
|
||||
ctx.setFontSize(22);
|
||||
ctx.setFillStyle("#999999");
|
||||
ctx.font = "italic 22px sans-serif";
|
||||
// 语录换行处理,下移坐标
|
||||
wrapTextCentered(ctx, `“${resultData.value.quote}”`, W / 2, 780, W - 80, 30);
|
||||
wrapTextCentered(ctx, `“${resultData.value.quote}”`, W / 2, 880, W - 80, 30);
|
||||
|
||||
// 8. 底部区域 (Footer)
|
||||
const footerY = 850;
|
||||
const footerY = 960;
|
||||
|
||||
// 分隔线
|
||||
ctx.setStrokeStyle("#f0f0f0");
|
||||
@@ -395,9 +478,7 @@ const onSaveImage = () => {
|
||||
ctx.font = "normal 20px sans-serif";
|
||||
ctx.fillText("2026 CNY SPECIAL · 新春助手", 40, footerY + 100);
|
||||
|
||||
// 底部右侧二维码 (占位图)
|
||||
// 假设二维码在 static/icon/yunshi.png 或 logo.png
|
||||
// 实际开发中应替换为小程序码
|
||||
// 底部右侧二维码
|
||||
ctx.drawImage("/static/logo.png", W - 140, footerY + 25, 100, 100);
|
||||
|
||||
// 绘制
|
||||
@@ -506,7 +587,7 @@ defineExpose({ open, close });
|
||||
/* 动画容器 */
|
||||
.animation-container {
|
||||
width: 600rpx;
|
||||
height: 850rpx;
|
||||
height: 920rpx; /* Updated height */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@@ -616,39 +697,70 @@ defineExpose({ open, close });
|
||||
|
||||
.lucky-card {
|
||||
width: 100%;
|
||||
height: 850rpx;
|
||||
height: 920rpx; /* Increased height */
|
||||
background: #fff;
|
||||
border-radius: 40rpx;
|
||||
overflow: hidden;
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.card-header {
|
||||
height: 360rpx;
|
||||
height: 460rpx; /* Increased height */
|
||||
background: linear-gradient(180deg, #d84315 0%, #ffca28 100%);
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 30rpx 40rpx;
|
||||
box-sizing: border-box;
|
||||
color: #fff;
|
||||
|
||||
.header-decor {
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
font-size: 24rpx;
|
||||
opacity: 0.6;
|
||||
.header-top-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
&.left {
|
||||
left: 30rpx;
|
||||
.user-info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.user-avatar {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.8);
|
||||
margin-right: 16rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
&.right {
|
||||
right: 30rpx;
|
||||
|
||||
.user-name {
|
||||
font-size: 26rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.date-tag {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
padding: 8rpx 20rpx;
|
||||
border-radius: 30rpx;
|
||||
font-size: 22rpx;
|
||||
color: #fff;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.header-main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.header-label {
|
||||
font-size: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
margin-bottom: 8rpx;
|
||||
opacity: 0.9;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
@@ -660,32 +772,39 @@ defineExpose({ open, close });
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
.score {
|
||||
font-size: 120rpx;
|
||||
font-size: 140rpx; /* Bigger */
|
||||
font-weight: bold;
|
||||
text-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
text-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.percent {
|
||||
font-size: 40rpx;
|
||||
margin-left: 4rpx;
|
||||
margin-left: 8rpx;
|
||||
font-weight: 500;
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
.lucky-word {
|
||||
font-size: 48rpx;
|
||||
font-size: 52rpx;
|
||||
font-weight: bold;
|
||||
letter-spacing: 4rpx;
|
||||
letter-spacing: 6rpx;
|
||||
text-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.tag-year {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
padding: 8rpx 24rpx;
|
||||
border-radius: 30rpx;
|
||||
font-size: 20rpx;
|
||||
letter-spacing: 2rpx;
|
||||
.header-decor {
|
||||
position: absolute;
|
||||
bottom: 20rpx;
|
||||
font-size: 24rpx;
|
||||
opacity: 0.4;
|
||||
|
||||
&.left-bottom {
|
||||
left: 40rpx;
|
||||
}
|
||||
&.right-bottom {
|
||||
right: 40rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user