feat: make gpage

This commit is contained in:
zzc
2026-01-21 23:58:21 +08:00
parent 25de38a1f5
commit 6742776e64
2 changed files with 132 additions and 68 deletions

View File

@@ -15,3 +15,10 @@ export const updateCard = async (data) => {
data, data,
}); });
}; };
export const getCardTemplateList = async (page = 1) => {
return request({
url: "/api/blessing/card/template/list?page=" + page,
method: "GET",
});
};

View File

@@ -2,7 +2,11 @@
<view class="make-page" :style="{ paddingTop: getBavBarHeight() + 'px' }"> <view class="make-page" :style="{ paddingTop: getBavBarHeight() + 'px' }">
<!-- 预览卡片 --> <!-- 预览卡片 -->
<view class="card-preview"> <view class="card-preview">
<image class="card-bg" :src="currentTemplate.cover" mode="aspectFill" /> <image
class="card-bg"
:src="currentTemplate?.imageUrl"
mode="aspectFill"
/>
<view class="card-overlay"> <view class="card-overlay">
<view class="title"> <view class="title">
<text class="main">新春快乐</text> <text class="main">新春快乐</text>
@@ -31,6 +35,22 @@
<view class="editor-panel"> <view class="editor-panel">
<view class="drag-handle"></view> <view class="drag-handle"></view>
<!-- 底部操作 -->
<view class="bottom-actions">
<button class="btn secondary" @tap="preview">
<uni-icons type="cloud-download" size="20" color="#888"></uni-icons>
<view>保存</view>
</button>
<button open-type="share" class="btn primary" @tap="shareOrSave">
<uni-icons
type="paperplane-filled"
size="20"
color="#fff"
></uni-icons>
<view>分享给好友</view>
</button>
</view>
<!-- 功能入口 --> <!-- 功能入口 -->
<view class="tools"> <view class="tools">
<view <view
@@ -49,24 +69,34 @@
<view v-if="activeTool === 'template'" class="section"> <view v-if="activeTool === 'template'" class="section">
<view class="section-title"> <view class="section-title">
<text>热门模板</text> <text>热门模板</text>
<text class="more" @tap="showMore">查看更多 ></text>
</view> </view>
<scroll-view scroll-x class="tpl-scroll" show-scrollbar="false"> <scroll-view
<view class="tpl-wrap"> scroll-y
class="tpl-scroll"
show-scrollbar="false"
@scrolltolower="loadMoreTemplates"
>
<view class="tpl-grid">
<view <view
v-for="(tpl, i) in templates" v-for="(tpl, i) in templates"
:key="i" :key="i"
class="tpl-card" class="tpl-card"
:class="{ selected: tpl.id === currentTemplate.id }" :class="{ selected: tpl?.id === currentTemplate?.id }"
@tap="applyTemplate(tpl)" @tap="applyTemplate(tpl)"
> >
<image :src="tpl.cover" class="tpl-cover" mode="aspectFill" /> <image :src="tpl.imageUrl" class="tpl-cover" mode="aspectFill" />
<view class="tpl-name">{{ tpl.name }}</view> <view class="tpl-name">{{ tpl.name }}</view>
<view v-if="tpl.id === currentTemplate.id" class="tpl-check" <view v-if="tpl?.id === currentTemplate?.id" class="tpl-check"
></view ></view
> >
</view> </view>
</view> </view>
<view v-if="loadingTemplates" class="loading-more">加载中...</view>
<view
v-else-if="!hasMoreTemplates && templates.length > 0"
class="no-more"
>没有更多了</view
>
</scroll-view> </scroll-view>
</view> </view>
@@ -154,22 +184,6 @@
<button class="btn" @tap="toggleAvatarDecor">切换挂饰</button> <button class="btn" @tap="toggleAvatarDecor">切换挂饰</button>
</view> </view>
</view> </view>
<!-- 底部操作 -->
<view class="bottom-actions">
<button class="btn secondary" @tap="preview">
<uni-icons type="cloud-download" size="20" color="#888"></uni-icons>
<view>保存</view>
</button>
<button open-type="share" class="btn primary" @tap="shareOrSave">
<uni-icons
type="paperplane-filled"
size="20"
color="#fff"
></uni-icons>
<view>分享给好友</view>
</button>
</view>
</view> </view>
<canvas <canvas
@@ -183,11 +197,14 @@
<script setup> <script setup>
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import { getBavBarHeight, getDeviceInfo } from "@/utils/system"; import { getBavBarHeight, getDeviceInfo } from "@/utils/system";
import { createCardTmp, updateCard } from "@/api/make"; import { createCardTmp, getCardTemplateList } from "@/api/make";
import { createCardShareToken } from "@/api/card"; import { createCardShareToken } from "@/api/card";
import { onShareAppMessage, onLoad } from "@dcloudio/uni-app"; import { onShareAppMessage, onLoad } from "@dcloudio/uni-app";
import { useUserStore } from "@/stores/user"; import { useUserStore } from "@/stores/user";
const templatePage = ref(1);
const loadingTemplates = ref(false);
const hasMoreTemplates = ref(true);
const userStore = useUserStore(); const userStore = useUserStore();
const cardId = ref(""); const cardId = ref("");
@@ -195,11 +212,11 @@ const targetName = ref("祝您");
const signatureName = ref(userStore?.userInfo?.nickName || "xxx"); const signatureName = ref(userStore?.userInfo?.nickName || "xxx");
const userAvatar = ref( const userAvatar = ref(
userStore?.userInfo?.avatarUrl || userStore?.userInfo?.avatarUrl ||
"https://file.lihailezzc.com/resource/b48c41054c2633c478463ac1b1f1ca23.png" "https://file.lihailezzc.com/resource/b48c41054c2633c478463ac1b1f1ca23.png",
); );
const blessingText = ref( const blessingText = ref(
"岁末将至敬颂冬绥。平安喜乐万事胜意。祝您2026年大吉大利一马当先前程似锦龙马精神阖家安康" "岁末将至敬颂冬绥。平安喜乐万事胜意。祝您2026年大吉大利一马当先前程似锦龙马精神阖家安康",
); );
const textColors = ["#ffffff", "#ff3b30", "#F5A623", "#8B572A", "#000000"]; const textColors = ["#ffffff", "#ff3b30", "#F5A623", "#8B572A", "#000000"];
@@ -217,6 +234,7 @@ const greetingLib = [
onLoad((options) => { onLoad((options) => {
cardId.value = "69674f307307beac4519025f"; cardId.value = "69674f307307beac4519025f";
// createCard(); // createCard();
getTemplateList();
}); });
const createCard = async () => { const createCard = async () => {
@@ -230,6 +248,53 @@ const createCard = async () => {
} }
}; };
const getTemplateList = async (isLoadMore = false) => {
if (loadingTemplates.value || (!hasMoreTemplates.value && isLoadMore)) return;
loadingTemplates.value = true;
try {
const res = await getCardTemplateList(templatePage.value);
// 兼容数组或对象列表格式
const list = Array.isArray(res) ? res : res.list || [];
if (list.length > 0) {
if (isLoadMore) {
templates.value = [...templates.value, ...list];
} else {
templates.value = list;
// 初始加载时设置第一个为当前选中
if (list.length > 0 && !currentTemplate.value) {
currentTemplate.value = list[0];
}
}
// 判断是否还有更多
if (typeof res.hasNext !== "undefined") {
hasMoreTemplates.value = res.hasNext;
} else {
// 如果没有 hasNext 字段,根据返回数量简单判断
hasMoreTemplates.value = list.length >= 8; // 假设每页 10 条
}
if (hasMoreTemplates.value) {
templatePage.value++;
}
} else {
if (!isLoadMore) templates.value = [];
hasMoreTemplates.value = false;
}
} catch (error) {
console.error("加载模板失败:", error);
} finally {
loadingTemplates.value = false;
}
};
const loadMoreTemplates = () => {
getTemplateList(true);
};
onShareAppMessage(async () => { onShareAppMessage(async () => {
const deviceInfo = getDeviceInfo(); const deviceInfo = getDeviceInfo();
const shareTokenRes = await createCardShareToken({ const shareTokenRes = await createCardShareToken({
@@ -262,33 +327,12 @@ const selectGreeting = (text) => {
const tools = [ const tools = [
{ type: "template", text: "模板", icon: "▦" }, { type: "template", text: "模板", icon: "▦" },
{ type: "text", text: "文字", icon: "文" }, { type: "text", text: "文字", icon: "文" },
{ type: "image", text: "图片/背景", icon: "图" }, // { type: "image", text: "图片/背景", icon: "图" },
{ type: "avatar", text: "头像挂饰", icon: "饰" }, // { type: "avatar", text: "头像挂饰", icon: "饰" },
]; ];
const activeTool = ref("template"); const activeTool = ref("template");
const templates = ref([ const templates = ref([]);
{
id: 1,
name: "金典红金",
cover: "https://file.lihailezzc.com/20260109082842_666_1.jpg",
},
{
id: 2,
name: "富贵花开",
cover: "https://file.lihailezzc.com/20260108222141_644_1.jpg",
},
{
id: 3,
name: "大气字法",
cover: "https://file.lihailezzc.com/20260108222141_644_1.jpg",
},
{
id: 4,
name: "萌趣马年",
cover: "https://file.lihailezzc.com/20260109082842_666_1.jpg",
},
]);
const currentTemplate = ref(templates.value[0]); const currentTemplate = ref(templates.value[0]);
@@ -359,7 +403,7 @@ const saveByCanvas = async (save = true) => {
// 1⃣ 画背景 // 1⃣ 画背景
// ⭐ 先加载背景图 // ⭐ 先加载背景图
const [bgPath, avatarPath] = await Promise.all([ const [bgPath, avatarPath] = await Promise.all([
loadImage(currentTemplate.value.cover), loadImage(currentTemplate?.value?.imageUrl),
loadImage(userAvatar.value), loadImage(userAvatar.value),
]); ]);
@@ -552,7 +596,7 @@ function drawUserBubble(ctx, options) {
bubbleWidth, bubbleWidth,
bubbleHeight, bubbleHeight,
bubbleHeight / 2, bubbleHeight / 2,
bubbleColor bubbleColor,
); );
// 2⃣ 绘制头像 // 2⃣ 绘制头像
@@ -566,7 +610,7 @@ function drawUserBubble(ctx, options) {
avatarY + avatarSize / 2, avatarY + avatarSize / 2,
avatarSize / 2, avatarSize / 2,
0, 0,
Math.PI * 2 Math.PI * 2,
); );
ctx.clip(); ctx.clip();
ctx.drawImage(avatarPath, avatarX, avatarY, avatarSize, avatarSize); ctx.drawImage(avatarPath, avatarX, avatarY, avatarSize, avatarSize);
@@ -767,46 +811,59 @@ function drawRoundRect(ctx, x, y, w, h, r, color) {
} }
.tpl-scroll { .tpl-scroll {
margin-top: 12rpx; margin-top: 12rpx;
height: 600rpx; /* 增加高度以展示纵向列表 */
} }
.tpl-wrap { .tpl-grid {
display: flex; display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16rpx;
padding-bottom: 20rpx;
} }
.tpl-card { .tpl-card {
width: 180rpx; width: 100%; /* 自适应 grid 宽度 */
height: 240rpx; border-radius: 12rpx;
border-radius: 18rpx;
overflow: hidden; overflow: hidden;
background: #fff; background: #fff;
margin-right: 16rpx;
position: relative; position: relative;
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.06); box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
} }
.tpl-card.selected { .tpl-card.selected {
outline: 4rpx solid #ff3b30; outline: 4rpx solid #ff3b30;
} }
.tpl-cover { .tpl-cover {
width: 100%; width: 100%;
height: 160rpx; height: 368rpx;
} }
.tpl-name { .tpl-name {
font-size: 22rpx; font-size: 20rpx;
color: #333; color: #333;
padding: 8rpx 12rpx; padding: 6rpx 8rpx;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
.tpl-check { .tpl-check {
position: absolute; position: absolute;
right: 10rpx; right: 6rpx;
top: 10rpx; top: 6rpx;
width: 36rpx; width: 32rpx;
height: 36rpx; height: 32rpx;
border-radius: 50%; border-radius: 50%;
background: #ff3b30; background: #ff3b30;
color: #fff; color: #fff;
font-size: 22rpx; font-size: 20rpx;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.loading-more,
.no-more {
text-align: center;
font-size: 22rpx;
color: #999;
padding: 10rpx 0;
}
/* 文字编辑区 */ /* 文字编辑区 */
.text-edit-section { .text-edit-section {