feat: card music
This commit is contained in:
@@ -30,6 +30,13 @@ export const getCardTemplateContentList = async (page = 1) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getCardMusicList = async () => {
|
||||||
|
return request({
|
||||||
|
url: "/api/blessing/card/music/list",
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const getCardTemplateTitleList = async (page = 1) => {
|
export const getCardTemplateTitleList = async (page = 1) => {
|
||||||
return request({
|
return request({
|
||||||
url: "/api/blessing/card/template-title/list?page=" + page,
|
url: "/api/blessing/card/template-title/list?page=" + page,
|
||||||
|
|||||||
@@ -26,6 +26,13 @@
|
|||||||
|
|
||||||
<!-- 预览卡片 -->
|
<!-- 预览卡片 -->
|
||||||
<view class="card-preview">
|
<view class="card-preview">
|
||||||
|
<view
|
||||||
|
class="music-control"
|
||||||
|
@tap.stop="openBgmList"
|
||||||
|
:class="{ playing: isBgmPlaying }"
|
||||||
|
>
|
||||||
|
<uni-icons type="headphones" size="20" color="#fff"></uni-icons>
|
||||||
|
</view>
|
||||||
<view class="premium-tag">
|
<view class="premium-tag">
|
||||||
<uni-icons type="info" size="12" color="#fff"></uni-icons>
|
<uni-icons type="info" size="12" color="#fff"></uni-icons>
|
||||||
<text>分享或保存即可去除水印</text>
|
<text>分享或保存即可去除水印</text>
|
||||||
@@ -432,6 +439,51 @@
|
|||||||
@logind="handleLogind"
|
@logind="handleLogind"
|
||||||
:share-token="shareToken"
|
:share-token="shareToken"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- Music List Popup -->
|
||||||
|
<uni-popup ref="bgmPopup" type="bottom">
|
||||||
|
<view class="bgm-popup">
|
||||||
|
<view class="bgm-header">
|
||||||
|
<text class="bgm-title">选择背景音乐</text>
|
||||||
|
<view class="bgm-close" @tap="closeBgmList">✕</view>
|
||||||
|
</view>
|
||||||
|
<scroll-view scroll-y class="bgm-scroll">
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in bgms"
|
||||||
|
:key="index"
|
||||||
|
class="bgm-item"
|
||||||
|
:class="{ active: currentBgmIndex === index && isBgmPlaying }"
|
||||||
|
@tap="selectBgm(index)"
|
||||||
|
>
|
||||||
|
<view class="bgm-info">
|
||||||
|
<uni-icons
|
||||||
|
:type="
|
||||||
|
currentBgmIndex === index && isBgmPlaying
|
||||||
|
? 'sound-filled'
|
||||||
|
: 'sound'
|
||||||
|
"
|
||||||
|
size="18"
|
||||||
|
:color="
|
||||||
|
currentBgmIndex === index && isBgmPlaying ? '#ff3b30' : '#333'
|
||||||
|
"
|
||||||
|
></uni-icons>
|
||||||
|
<text class="bgm-name">{{ item.name }}</text>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
v-if="currentBgmIndex === index && isBgmPlaying"
|
||||||
|
class="bgm-playing-icon"
|
||||||
|
>
|
||||||
|
<view class="bar bar1"></view>
|
||||||
|
<view class="bar bar2"></view>
|
||||||
|
<view class="bar bar3"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
<view class="bgm-footer" @tap="turnOffBgm">
|
||||||
|
<text class="turn-off-text">关闭音乐</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</uni-popup>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -446,6 +498,7 @@ import {
|
|||||||
getCardTemplateList,
|
getCardTemplateList,
|
||||||
getCardTemplateContentList,
|
getCardTemplateContentList,
|
||||||
getCardTemplateTitleList,
|
getCardTemplateTitleList,
|
||||||
|
getCardMusicList,
|
||||||
} from "@/api/make";
|
} from "@/api/make";
|
||||||
import { abilityCheck, getShareReward, msgCheckApi } from "@/api/system";
|
import { abilityCheck, getShareReward, msgCheckApi } from "@/api/system";
|
||||||
import {
|
import {
|
||||||
@@ -455,6 +508,8 @@ import {
|
|||||||
onReachBottom,
|
onReachBottom,
|
||||||
onShow,
|
onShow,
|
||||||
onPullDownRefresh,
|
onPullDownRefresh,
|
||||||
|
onUnload,
|
||||||
|
onHide,
|
||||||
} from "@dcloudio/uni-app";
|
} from "@dcloudio/uni-app";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import LoginPopup from "@/components/LoginPopup/LoginPopup.vue";
|
import LoginPopup from "@/components/LoginPopup/LoginPopup.vue";
|
||||||
@@ -484,6 +539,98 @@ const titleState = ref({
|
|||||||
scale: 1,
|
scale: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const bgms = ref([]);
|
||||||
|
const currentBgmIndex = ref(0);
|
||||||
|
const isBgmPlaying = ref(false);
|
||||||
|
const innerAudioContext = uni.createInnerAudioContext();
|
||||||
|
const bgmPopup = ref(null);
|
||||||
|
|
||||||
|
const initBgm = () => {
|
||||||
|
if (bgms.value.length > 0) {
|
||||||
|
playBgm(0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openBgmList = () => {
|
||||||
|
bgmPopup.value.open();
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeBgmList = () => {
|
||||||
|
bgmPopup.value.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectBgm = (index) => {
|
||||||
|
if (index === currentBgmIndex.value && isBgmPlaying.value) {
|
||||||
|
// 如果点击当前正在播放的,暂停
|
||||||
|
// pauseBgm();
|
||||||
|
// 这里用户可能想重播,或者什么都不做。暂时什么都不做
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
playBgm(index);
|
||||||
|
closeBgmList();
|
||||||
|
};
|
||||||
|
|
||||||
|
const turnOffBgm = () => {
|
||||||
|
stopBgm();
|
||||||
|
currentBgmIndex.value = -1; // -1 表示关闭
|
||||||
|
closeBgmList();
|
||||||
|
uni.showToast({ title: "已关闭音乐", icon: "none" });
|
||||||
|
};
|
||||||
|
|
||||||
|
const playBgm = (index) => {
|
||||||
|
if (index < 0 || index >= bgms.value.length) return;
|
||||||
|
|
||||||
|
// 更新 index
|
||||||
|
currentBgmIndex.value = index;
|
||||||
|
|
||||||
|
innerAudioContext.stop();
|
||||||
|
innerAudioContext.src = bgms.value[index].musicUrl;
|
||||||
|
innerAudioContext.loop = true;
|
||||||
|
innerAudioContext.autoplay = true;
|
||||||
|
innerAudioContext.play();
|
||||||
|
|
||||||
|
// 重新绑定事件(防止丢失)
|
||||||
|
innerAudioContext.onPlay(() => {
|
||||||
|
isBgmPlaying.value = true;
|
||||||
|
});
|
||||||
|
innerAudioContext.onPause(() => {
|
||||||
|
isBgmPlaying.value = false;
|
||||||
|
});
|
||||||
|
innerAudioContext.onStop(() => {
|
||||||
|
isBgmPlaying.value = false;
|
||||||
|
});
|
||||||
|
innerAudioContext.onError((res) => {
|
||||||
|
console.error("BGM Error:", res.errMsg);
|
||||||
|
isBgmPlaying.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopBgm = () => {
|
||||||
|
innerAudioContext.stop();
|
||||||
|
isBgmPlaying.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
onUnload(() => {
|
||||||
|
innerAudioContext.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
onHide(() => {
|
||||||
|
innerAudioContext.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
onShow(() => {
|
||||||
|
// 页面回到前台时,如果之前是播放状态(且不是 Off),尝试恢复播放
|
||||||
|
// 但由于 onHide 停止了,isBgmPlaying 变成了 false
|
||||||
|
// 这里可以根据需求决定是否恢复。
|
||||||
|
// 为了简单,我们只在 onLoad 初始化。如果用户想听,需要手动点。
|
||||||
|
// 或者我们可以记录一个 shouldPlay 状态。
|
||||||
|
// 鉴于用户需求是“点击切换”,我们保持简单。
|
||||||
|
// 但要注意:initBgm 在 onLoad 调用,onShow 也会调用 syncUserInfo 等。
|
||||||
|
// 我们可以把 initBgm 放在 onLoad。
|
||||||
|
syncUserInfo();
|
||||||
|
// ... existing onShow logic ...
|
||||||
|
});
|
||||||
|
|
||||||
const titleStyle = computed(() => {
|
const titleStyle = computed(() => {
|
||||||
return {
|
return {
|
||||||
transform: `translate(${titleState.value.offsetX}rpx, ${titleState.value.offsetY}rpx) scale(${titleState.value.scale})`,
|
transform: `translate(${titleState.value.offsetX}rpx, ${titleState.value.offsetY}rpx) scale(${titleState.value.scale})`,
|
||||||
@@ -744,6 +891,7 @@ onLoad((options) => {
|
|||||||
getTemplateList();
|
getTemplateList();
|
||||||
getTemplateContentList();
|
getTemplateContentList();
|
||||||
getTemplateTitleList();
|
getTemplateTitleList();
|
||||||
|
getMusicList();
|
||||||
if (options.shareToken) {
|
if (options.shareToken) {
|
||||||
shareToken.value = options.shareToken;
|
shareToken.value = options.shareToken;
|
||||||
}
|
}
|
||||||
@@ -884,6 +1032,12 @@ const getTemplateList = async (isLoadMore = false) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getMusicList = async () => {
|
||||||
|
const res = await getCardMusicList();
|
||||||
|
bgms.value = res || [];
|
||||||
|
initBgm();
|
||||||
|
};
|
||||||
|
|
||||||
const getTemplateTitleList = async (isLoadMore = false) => {
|
const getTemplateTitleList = async (isLoadMore = false) => {
|
||||||
if (loadingTitles.value || (!hasMoreTitles.value && isLoadMore)) return;
|
if (loadingTitles.value || (!hasMoreTitles.value && isLoadMore)) return;
|
||||||
|
|
||||||
@@ -2083,4 +2237,152 @@ function drawRoundRect(ctx, x, y, w, h, r, color) {
|
|||||||
left: -9999px;
|
left: -9999px;
|
||||||
top: -9999px;
|
top: -9999px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.music-control {
|
||||||
|
position: absolute;
|
||||||
|
top: 24rpx;
|
||||||
|
left: 24rpx;
|
||||||
|
width: 64rpx;
|
||||||
|
height: 64rpx;
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 20;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.music-control.playing {
|
||||||
|
background: rgba(255, 59, 48, 0.8);
|
||||||
|
border-color: rgba(255, 59, 48, 0.5);
|
||||||
|
animation: music-rotate 4s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes music-rotate {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bgm-popup {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 24rpx 24rpx 0 0;
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
max-height: 60vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bgm-header {
|
||||||
|
padding: 30rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-bottom: 1rpx solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bgm-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bgm-close {
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #999;
|
||||||
|
padding: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bgm-scroll {
|
||||||
|
max-height: 500rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bgm-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 30rpx;
|
||||||
|
border-bottom: 1rpx solid #f5f5f5;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bgm-item:active {
|
||||||
|
background: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bgm-item.active {
|
||||||
|
background: #fff5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bgm-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bgm-name {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bgm-item.active .bgm-name {
|
||||||
|
color: #ff3b30;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bgm-playing-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 4rpx;
|
||||||
|
height: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar {
|
||||||
|
width: 4rpx;
|
||||||
|
background: #ff3b30;
|
||||||
|
animation: equalize 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar1 {
|
||||||
|
animation-delay: 0s;
|
||||||
|
height: 60%;
|
||||||
|
}
|
||||||
|
.bar2 {
|
||||||
|
animation-delay: 0.2s;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.bar3 {
|
||||||
|
animation-delay: 0.4s;
|
||||||
|
height: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes equalize {
|
||||||
|
0% {
|
||||||
|
height: 40%;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
height: 40%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bgm-footer {
|
||||||
|
padding: 30rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-top: 1rpx solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.turn-off-text {
|
||||||
|
color: #666;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user