feat: card music
This commit is contained in:
@@ -26,6 +26,13 @@
|
||||
|
||||
<!-- 预览卡片 -->
|
||||
<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">
|
||||
<uni-icons type="info" size="12" color="#fff"></uni-icons>
|
||||
<text>分享或保存即可去除水印</text>
|
||||
@@ -432,6 +439,51 @@
|
||||
@logind="handleLogind"
|
||||
: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>
|
||||
</template>
|
||||
|
||||
@@ -446,6 +498,7 @@ import {
|
||||
getCardTemplateList,
|
||||
getCardTemplateContentList,
|
||||
getCardTemplateTitleList,
|
||||
getCardMusicList,
|
||||
} from "@/api/make";
|
||||
import { abilityCheck, getShareReward, msgCheckApi } from "@/api/system";
|
||||
import {
|
||||
@@ -455,6 +508,8 @@ import {
|
||||
onReachBottom,
|
||||
onShow,
|
||||
onPullDownRefresh,
|
||||
onUnload,
|
||||
onHide,
|
||||
} from "@dcloudio/uni-app";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import LoginPopup from "@/components/LoginPopup/LoginPopup.vue";
|
||||
@@ -484,6 +539,98 @@ const titleState = ref({
|
||||
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(() => {
|
||||
return {
|
||||
transform: `translate(${titleState.value.offsetX}rpx, ${titleState.value.offsetY}rpx) scale(${titleState.value.scale})`,
|
||||
@@ -744,6 +891,7 @@ onLoad((options) => {
|
||||
getTemplateList();
|
||||
getTemplateContentList();
|
||||
getTemplateTitleList();
|
||||
getMusicList();
|
||||
if (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) => {
|
||||
if (loadingTitles.value || (!hasMoreTitles.value && isLoadMore)) return;
|
||||
|
||||
@@ -2083,4 +2237,152 @@ function drawRoundRect(ctx, x, y, w, h, r, color) {
|
||||
left: -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>
|
||||
|
||||
Reference in New Issue
Block a user