diff --git a/api/make.js b/api/make.js index 62de816..1de83af 100644 --- a/api/make.js +++ b/api/make.js @@ -29,3 +29,11 @@ export const getCardTemplateContentList = async () => { method: "GET", }); }; + +export const getCardTemplateTitleList = async (page = 1) => { + return request({ + url: "/api/blessing/card/template-title/list?page=" + page, + method: "GET", + }); +}; + diff --git a/pages/make/index.vue b/pages/make/index.vue index 81d6f91..d916880 100644 --- a/pages/make/index.vue +++ b/pages/make/index.vue @@ -9,10 +9,17 @@ /> 年禧集.马年春节祝福 - + + + + + + + + + + + + 选择标题 + + + + + + + + + 加载中... + 没有更多了 + + + + + 标题位置 (上下) + + + + 标题位置 (左右) + + + + 标题缩放 + + + + + + 选好标题了,去选模板 + + + + @@ -293,22 +380,6 @@ - - - - - - { getTemplateList(); getTemplateContentList(); + getTemplateTitleList(); }); onShow(() => { @@ -481,6 +564,8 @@ onShow(() => { onReachBottom(() => { if (activeTool.value === "template") { loadMoreTemplates(); + } else if (activeTool.value === "title") { + loadMoreTitles(); } }); @@ -536,6 +621,48 @@ const getTemplateList = async (isLoadMore = false) => { } }; +const getTemplateTitleList = async (isLoadMore = false) => { + if (loadingTitles.value || (!hasMoreTitles.value && isLoadMore)) return; + + loadingTitles.value = true; + try { + const res = await getCardTemplateTitleList(titlePage.value); + const list = Array.isArray(res) ? res : res.list || []; + + if (list.length > 0) { + if (isLoadMore) { + titles.value = [...titles.value, ...list]; + } else { + titles.value = list; + if (list.length > 0 && !currentTitle.value) { + currentTitle.value = list[0]; + } + } + + if (typeof res.hasNext !== "undefined") { + hasMoreTitles.value = res.hasNext; + } else { + hasMoreTitles.value = list.length >= 8; + } + + if (hasMoreTitles.value) { + titlePage.value++; + } + } else { + if (!isLoadMore) titles.value = []; + hasMoreTitles.value = false; + } + } catch (error) { + console.error("加载标题失败:", error); + } finally { + loadingTitles.value = false; + } +}; + +const loadMoreTitles = () => { + getTemplateTitleList(true); +}; + const getTemplateContentList = async () => { const res = await getCardTemplateContentList(); if (res.length) { @@ -603,8 +730,9 @@ const selectGreeting = (text) => { const tools = [ { type: "template", text: "1. 选模板", icon: "🎨", step: 1 }, - { type: "text", text: "2. 改文字", icon: "✍️", step: 2 }, - { type: "position", text: "3. 调位置", icon: "🎯", step: 3 }, + { type: "title", text: "2. 选标题", icon: "🧧", step: 2 }, + { type: "text", text: "3. 改文字", icon: "✍️", step: 3 }, + { type: "position", text: "4. 调位置", icon: "🎯", step: 4 }, ]; const activeTool = ref("template"); @@ -659,7 +787,7 @@ const preview = async () => { return; } const tempPath = await saveByCanvas(true); - id = createCard(); + const id = createCard(); shareOrSave(id); saveRecordRequest(tempPath, id, "card_generate"); @@ -728,9 +856,10 @@ const saveByCanvas = async (save = true) => { try { // 1️⃣ 画背景 // ⭐ 先加载背景图 - const [bgImg, avatarImg] = await Promise.all([ + const [bgImg, avatarImg, titleImg] = await Promise.all([ loadCanvasImage(currentTemplate?.value?.imageUrl), loadCanvasImage(userAvatar.value), + currentTitle.value ? loadCanvasImage(currentTitle.value.imageUrl) : Promise.resolve(null), ]); ctx.drawImage(bgImg, 0, 0, W, H); @@ -739,17 +868,14 @@ const saveByCanvas = async (save = true) => { ctx.fillStyle = "rgba(0,0,0,0.08)"; ctx.fillRect(0, 0, W, H); - // 3️⃣ 标题 - // ctx.fillStyle = "#ffffff"; - // ctx.font = "42px sans-serif"; // 默认字体 - // ctx.textAlign = "center"; - // ctx.textBaseline = "alphabetic"; // Canvas 2D 默认是 alphabetic - // ctx.fillText("新春快乐", W / 2, 120); - - // ctx.font = "22px sans-serif"; - // ctx.globalAlpha = 0.9; - // ctx.fillText("2026 YEAR OF THE HORSE", W / 2, 165); - // ctx.globalAlpha = 1; + // 3️⃣ 标题图片 + if (titleImg) { + const titleW = titleImg.width * titleScale.value; + const titleH = titleImg.height * titleScale.value; + const titleX = (W - titleW) / 2 + titleOffsetX.value; + const titleY = 40 + titleOffsetY.value; + ctx.drawImage(titleImg, titleX, titleY, titleW, titleH); + } // 4️⃣ 祝福语气泡 drawBubbleText(ctx, { @@ -1130,7 +1256,7 @@ function drawRoundRect(ctx, x, y, w, h, r, color) { .step-bar { display: flex; justify-content: space-between; - padding: 30rpx 60rpx 10rpx; + padding: 20rpx 60rpx 10rpx; position: relative; } .step-item { @@ -1236,6 +1362,23 @@ function drawRoundRect(ctx, x, y, w, h, r, color) { position: relative; box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06); } +.title-card { + height: 120rpx; + display: flex; + align-items: center; + justify-content: center; + padding: 10rpx; +} +.title-cover { + width: 100%; + height: 100%; +} +.selected-title-img { + position: absolute; + width: 400rpx; + z-index: 2; + transition: transform 0.1s; +} .tpl-card.selected { outline: 4rpx solid #ff3b30; } @@ -1423,33 +1566,53 @@ function drawRoundRect(ctx, x, y, w, h, r, color) { } /* 按钮区 */ -.bottom-actions { +.main-actions { display: flex; align-items: center; justify-content: center; - padding: 20rpx 24rpx 30rpx; + padding: 24rpx 32rpx 32rpx; + background: #fff; + position: relative; } .btn { + flex: 1; display: flex; align-items: center; justify-content: center; - height: 88rpx; - border-radius: 999rpx; - padding: 0 40rpx; - font-size: 28rpx; + height: 96rpx; + border-radius: 48rpx; + margin: 0 12rpx; + font-size: 30rpx; + font-weight: 600; + transition: all 0.2s active; + position: relative; + overflow: hidden; + border: none; } -.btn view { - margin-left: 10rpx; +.btn::after { + border: none; } +.btn:active { + transform: scale(0.96); + opacity: 0.9; +} + .btn.secondary { - background: #fff; - color: #000; - border: 1px solid #ccc; + background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); + color: #333; + box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.05), + inset 0 0 0 2rpx #eee; } + .btn.primary { - background: #ff3b30; + background: linear-gradient(135deg, #ff6b66 0%, #ff3b30 100%); color: #fff; - box-shadow: 0 12rpx 24rpx rgba(255, 59, 48, 0.35); + box-shadow: 0 12rpx 30rpx rgba(255, 59, 48, 0.3); + text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1); +} + +.btn view { + margin-left: 12rpx; } .hidden-canvas { position: fixed;