需要添加直播接口
This commit is contained in:
1113
pages/anchor/index.nvue
Normal file
1113
pages/anchor/index.nvue
Normal file
File diff suppressed because it is too large
Load Diff
818
pages/audience/index.nvue
Normal file
818
pages/audience/index.nvue
Normal file
@@ -0,0 +1,818 @@
|
||||
<template>
|
||||
<view class="live-container" @click="handleHideInput" :style="{
|
||||
height: systemInfo?.windowHeight + 'px',
|
||||
width: systemInfo?.safeArea?.width + 'px',
|
||||
}">
|
||||
<!-- 主播画面区域 -->
|
||||
<view class="live-content">
|
||||
<LiveStreamView v-if="liveID.length > 0" :liveID="liveID" :isAnchor="false" :templateLayout="templateLayout"
|
||||
:currentLoginUserId="currentLoginUserId" :onStreamViewClick="ShowAudienceViewClickPanel"
|
||||
:enableClickPanel="true" :isLiving="true">
|
||||
</LiveStreamView>
|
||||
|
||||
<!-- 顶部信息栏 -->
|
||||
<view class="live-header">
|
||||
<view class="header-left" @click="showAnchorInfoDrawer">
|
||||
<view class="stream-info">
|
||||
<image class="avatar" :src="currentLive?.liveOwner?.avatarURL || defaultAvatarURL" mode="aspectFill" />
|
||||
<view class="stream-details">
|
||||
<text class="stream-title"
|
||||
:numberOfLines="1">{{ currentLive?.liveOwner?.userName || currentLive?.liveOwner?.userID}}</text>
|
||||
</view>
|
||||
<!-- <view
|
||||
class="follow-btn"
|
||||
:class="{ 'followed': isFollowed }"
|
||||
@click.stop="handleFollowClick"
|
||||
>
|
||||
<text :style="isFollowed ? 'color: #338aff; font-size: 28rpx;' : 'color: #fff; font-size: 28rpx;'">
|
||||
{{ isFollowed ? '已关注' : '关注' }}
|
||||
</text>
|
||||
</view> -->
|
||||
</view>
|
||||
</view>
|
||||
<view class="header-right">
|
||||
<view class="participants" @click="showAudienceList">
|
||||
<view v-for="(user, index) in audienceList.slice(0, 2)">
|
||||
<image class="participant-avatar" :src="user?.avatarURL || defaultAvatarURL" mode="aspectFill" />
|
||||
</view>
|
||||
<view class="participant-count">
|
||||
<text class="count-text">{{ audienceList.length }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="control-icons" @click.stop="navigateBack()">
|
||||
<!-- <image class="control-icon" src="/static/images/live-share.png" /> -->
|
||||
<image class="control-icon" src="/static/images/close.png" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="live-network-container" @tap="isShowLiveStatusInfoCard = true">
|
||||
<image class="live-network" src="/static/images/network-good.png" alt="" />
|
||||
<text class="live-timer">{{ liveDurationText }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 聊天消息列表 -->
|
||||
<BarrageList mode="audience" :bottomPx="safeArea.height * 1/8" @itemTap="audienceOperator" ref="barrageListRef" />
|
||||
|
||||
<!-- 底部互动区域 -->
|
||||
<view class="footer">
|
||||
<BarrageInput></BarrageInput>
|
||||
<view class="action-buttons">
|
||||
<image class="action-btn" @click="showNetworkQualityPanel()" src="/static/images/dashboard.png" />
|
||||
<image class="action-btn" @click="showGiftPicker()" src="/static/images/live-gift.png" />
|
||||
<image class="action-btn" :class="{ 'disabled': shouldDisableCoGuestButton }"
|
||||
v-if="templateLayout !== 200 && uni.$localGuestStatus === 'IDLE'" @click="handleCoGuestButtonClick"
|
||||
src="/static/images/link-guest.png" />
|
||||
<image class="action-btn" v-if="templateLayout !== 200 && uni.$localGuestStatus === 'USER_APPLYING'"
|
||||
@click="ShowCoGuestRequestPanel()" src="/static/images/live-request.png" />
|
||||
<image class="action-btn" v-if="templateLayout !== 200 && uni.$localGuestStatus === 'CONNECTED'"
|
||||
@click="ShowCoGuestRequestPanel()" src="/static/images/live-disconnect.png" />
|
||||
<Like />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<UserInfoPanel v-model="isShowUserInfoPanel" :userInfo="clickUserInfo" :isShowAnchor="isShowAnchorInfo">
|
||||
</UserInfoPanel>
|
||||
<LiveAudienceList v-model="isShowAudienceList"></LiveAudienceList>
|
||||
<CoGuestRequestPanel v-model="isShowCoGuestRequestPanel" :liveID="currentLive.liveID" :userID="currentLoginUserId"
|
||||
:seatIndex="seatIndex"></CoGuestRequestPanel>
|
||||
|
||||
|
||||
<GiftPicker v-model="isShowGiftPicker" :onGiftSelect="showGiftToast"></GiftPicker>
|
||||
<GiftPlayer ref="giftPlayerRef" v-model="isLargeSizeGiftPlayer" :url="giftInfo?.resourceURL" :safeArea="safeArea"
|
||||
@finished="svgaPlayerFinished" />
|
||||
|
||||
<NetworkQualityPanel v-model="isShowNewWorkPanel"></NetworkQualityPanel>
|
||||
</view>
|
||||
</view>
|
||||
<LiveStatusInfoCard v-model="isShowLiveStatusInfoCard" videoQuality="4K" audioMode="高保真人声" :audioVolume="70"
|
||||
:latency="45" :downLoss="0" :upLoss="2" />
|
||||
<ActionSheet v-model="isShowExitSheet" :title="exitSheetTitle" :itemList="exitSheetItems"
|
||||
@select="onExitSheetSelect" />
|
||||
<ActionSheet v-model="isShowCoGuestSheet" :title="coGuestSheetTitle" :itemList="coGuestSheetItems"
|
||||
@select="onCoGuestSheetSelect" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import { ref, onMounted, computed, onUnmounted, watch, nextTick } from 'vue';
|
||||
import UserInfoPanel from '@/uni_modules/tuikit-atomic-x/components/LiveStreamView/UserInfoPanel.nvue';
|
||||
import LiveAudienceList from '@/uni_modules/tuikit-atomic-x/components/LiveAudienceList/LiveAudienceList.nvue';
|
||||
import CoGuestRequestPanel from '@/uni_modules/tuikit-atomic-x/components/CoGuestPanel/CoGuestRequestPanel.nvue';
|
||||
import GiftPicker from '@/uni_modules/tuikit-atomic-x/components/GiftPicker.nvue';
|
||||
import NetworkQualityPanel from '@/uni_modules/tuikit-atomic-x/components/NetworkQualityPanel.nvue'
|
||||
import LiveStatusInfoCard from '@/uni_modules/tuikit-atomic-x/components/LiveStatusInfoCard.nvue';
|
||||
import Like from '@/uni_modules/tuikit-atomic-x/components/Like.nvue';
|
||||
import LiveStreamView from '@/uni_modules/tuikit-atomic-x/components/LiveStreamView/LiveStreamView.nvue';
|
||||
import BarrageInput from '@/uni_modules/tuikit-atomic-x/components/BarrageInput.vue';
|
||||
import BarrageList from '@/uni_modules/tuikit-atomic-x/components/BarrageList.nvue';
|
||||
import GiftPlayer from '@/uni_modules/tuikit-atomic-x/components/GiftPlayer/GiftPlayer.nvue';
|
||||
import { giftService } from '@/uni_modules/tuikit-atomic-x/components/GiftPlayer/giftService'
|
||||
import { useBarrageState } from "@/uni_modules/tuikit-atomic-x/state/BarrageState";
|
||||
import { useLiveListState } from "@/uni_modules/tuikit-atomic-x/state/LiveListState";
|
||||
import { useLiveSeatState } from "@/uni_modules/tuikit-atomic-x/state/LiveSeatState";
|
||||
import { useLiveAudienceState } from '@/uni_modules/tuikit-atomic-x/state/LiveAudienceState';
|
||||
import { useCoGuestState } from '@/uni_modules/tuikit-atomic-x/state/CoGuestState';
|
||||
import { useLoginState } from "@/uni_modules/tuikit-atomic-x/state/LoginState";
|
||||
import { useGiftState } from "@/uni_modules/tuikit-atomic-x/state/GiftState";
|
||||
import { useCoHostState } from "@/uni_modules/tuikit-atomic-x/state/CoHostState";
|
||||
import ActionSheet from '@/components/ActionSheet.nvue'
|
||||
uni.$localGuestStatus = 'IDLE'
|
||||
const { loginUserInfo } = useLoginState()
|
||||
const { messageList, sendTextMessage, sendCustomMessage } = useBarrageState(uni?.$liveID);
|
||||
const { joinLive, createLive, fetchLiveList, liveList, leaveLive, currentLive, addLiveListListener, removeLiveListListener } = useLiveListState(uni?.$liveID);
|
||||
const { seatList, addLiveSeatEventListener, removeLiveSeatEventListener } = useLiveSeatState(uni?.$liveID);
|
||||
const { audienceList } = useLiveAudienceState(uni?.$liveID);
|
||||
const { disconnect, connected, cancelApplication } = useCoGuestState(uni?.$liveID)
|
||||
const { addGiftListener, removeGiftListener } = useGiftState(uni?.$liveID);
|
||||
const { connected: hostConnected } = useCoHostState(uni?.$liveID)
|
||||
|
||||
const dom = uni.requireNativePlugin('dom')
|
||||
const systemInfo = ref({});
|
||||
const safeArea = ref({
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: 375,
|
||||
height: 750,
|
||||
});
|
||||
const isShowUserInfoPanel = ref(false);
|
||||
const isShowAudienceList = ref(false);
|
||||
const isShowCoGuestRequestPanel = ref(false);
|
||||
const isShowGiftPicker = ref(false);
|
||||
const isShowLiveStatusInfoCard = ref(false);
|
||||
const isLargeSizeGiftPlayer = ref(false);
|
||||
const giftInfo = ref({});
|
||||
const isShowNewWorkPanel = ref(false)
|
||||
const selectedAudience = ref({});
|
||||
const defaultCoverURL = '/static/images/default-background.jpg';
|
||||
const defaultAvatarURL = 'https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_01.png';
|
||||
|
||||
// 连麦按钮禁用状态
|
||||
const isCoGuestButtonDisabled = ref(false);
|
||||
|
||||
// 计算连麦按钮是否应该被禁用
|
||||
const shouldDisableCoGuestButton = computed(() => {
|
||||
// 当 hostConnected 数组长度大于0时,按钮置灰
|
||||
return hostConnected.value.length > 0;
|
||||
});
|
||||
|
||||
const liveID = ref('');
|
||||
const isFollowed = ref(false);
|
||||
const inputValue = ref("");
|
||||
// Removed direct svga-player ref; handled inside GiftPlayer
|
||||
const liveDuration = ref(0); // 秒
|
||||
const liveDurationText = ref('00:00:00');
|
||||
let timer : any = null;
|
||||
const currentLoginUserId = ref();
|
||||
const clickUserInfo = ref({});
|
||||
const templateLayout = ref(600);
|
||||
const seatIndex = ref(-1);
|
||||
const barrageListRef = ref();
|
||||
const giftPlayerRef = ref();
|
||||
const { showGift, onGiftFinished } = giftService({
|
||||
roomId: uni?.$liveID,
|
||||
giftPlayerRef,
|
||||
})
|
||||
const isShowAnchorInfo = ref(true)
|
||||
|
||||
// action sheets
|
||||
const isShowExitSheet = ref(false)
|
||||
const isShowCoGuestSheet = ref(false)
|
||||
const exitSheetTitle = ref('')
|
||||
const exitSheetItems = ref(['退出直播间'])
|
||||
const coGuestSheetItems = ref(['取消连麦申请'])
|
||||
const coGuestSheetTitle = ref('')
|
||||
|
||||
|
||||
// 监听座位变化:当自身不在 seatList 时,将本地连麦状态重置为 IDLE
|
||||
watch(connected, (newList, oldList) => {
|
||||
const list = Array.isArray(newList) ? newList : [];
|
||||
const hasSelfInConnected = list.some(item => item?.userID === uni.$userID);
|
||||
if (!hasSelfInConnected) {
|
||||
uni.$localGuestStatus = 'IDLE'
|
||||
}
|
||||
}, { deep: true, immediate: true })
|
||||
watch(() => loginUserInfo.value?.userID, (newUserId, oldUserId) => {
|
||||
if (newUserId) {
|
||||
currentLoginUserId.value = newUserId;
|
||||
}
|
||||
}, { immediate: true, deep: true });
|
||||
|
||||
// 页面加载
|
||||
onLoad((options) => {
|
||||
console.warn('Live page onLoad = ', options);
|
||||
liveID.value = options?.liveID;
|
||||
|
||||
if (liveID.value) {
|
||||
joinLive({
|
||||
liveID: liveID.value,
|
||||
success: () => {
|
||||
liveDuration.value = 0;
|
||||
updateLiveDurationText();
|
||||
timer = setInterval(() => {
|
||||
updateLiveDurationText();
|
||||
}, 1000);
|
||||
|
||||
templateLayout.value = currentLive.value?.seatLayoutTemplateID || templateLayout.value;
|
||||
console.log('joinLive success templateLayout: ', templateLayout.value);
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({ icon: 'none', title: "直播已结束" });
|
||||
setTimeout(() => uni.redirectTo({ url: `/pages/livelist/index` }), 500);
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
uni.showToast({ title: 'liveID 为空', icon: 'none' });
|
||||
});
|
||||
|
||||
watch(currentLive, (newVal, oldVal) => {
|
||||
if (newVal) {
|
||||
templateLayout.value = newVal.seatLayoutTemplateID || templateLayout.value;
|
||||
console.log(`currentLive change: ${JSON.stringify(newVal)}`);
|
||||
}
|
||||
});
|
||||
|
||||
function updateLiveDurationText() {
|
||||
// 如果 currentLive 存在且有 createTime,则基于创建时间计算
|
||||
if (currentLive.value && currentLive.value.createTime) {
|
||||
const currentTime = Math.floor(Date.now() / 1000); // 当前时间戳(秒)
|
||||
const createTime = Math.floor(currentLive.value.createTime / 1000); // 直播间创建时间(秒)
|
||||
const duration = Math.max(0, currentTime - createTime); // 直播时长(秒)
|
||||
|
||||
const h = String(Math.floor(duration / 3600)).padStart(2, '0');
|
||||
const m = String(Math.floor((duration % 3600) / 60)).padStart(2, '0');
|
||||
const s = String(duration % 60).padStart(2, '0');
|
||||
liveDurationText.value = `${h}:${m}:${s}`;
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer) clearInterval(timer);
|
||||
removeLiveListListener('onLiveEnded', handleLiveEnded)
|
||||
removeLiveListListener('onKickedOutOfLive', handleKickedOutOfLive)
|
||||
removeGiftListener(uni.$liveID, 'onReceiveGift', handleReceiveGift)
|
||||
removeLiveSeatEventListener(uni.$liveID, 'onLocalCameraOpenedByAdmin', handleLocalCameraOpenedByAdmin)
|
||||
removeLiveSeatEventListener(uni.$liveID, 'onLocalCameraClosedByAdmin', handleLocalCameraClosedByAdmin)
|
||||
removeLiveSeatEventListener(uni.$liveID, 'onLocalMicrophoneOpenedByAdmin', handleLocalMicrophoneOpenedByAdmin)
|
||||
removeLiveSeatEventListener(uni.$liveID, 'onLocalMicrophoneClosedByAdmin', handleLocalMicrophoneClosedByAdmin)
|
||||
|
||||
});
|
||||
onMounted(() => {
|
||||
uni.setKeepScreenOn({
|
||||
keepScreenOn: true,
|
||||
});
|
||||
uni.getSystemInfo({
|
||||
success: (res) => {
|
||||
systemInfo.value = res;
|
||||
safeArea.value = res.safeArea;
|
||||
}
|
||||
});
|
||||
addLiveListListener('onLiveEnded', handleLiveEnded)
|
||||
addLiveListListener('onKickedOutOfLive', handleKickedOutOfLive)
|
||||
addGiftListener(uni.$liveID, 'onReceiveGift', handleReceiveGift)
|
||||
addLiveSeatEventListener(uni.$liveID, 'onLocalCameraOpenedByAdmin', handleLocalCameraOpenedByAdmin)
|
||||
addLiveSeatEventListener(uni.$liveID, 'onLocalCameraClosedByAdmin', handleLocalCameraClosedByAdmin)
|
||||
addLiveSeatEventListener(uni.$liveID, 'onLocalMicrophoneOpenedByAdmin', handleLocalMicrophoneOpenedByAdmin)
|
||||
addLiveSeatEventListener(uni.$liveID, 'onLocalMicrophoneClosedByAdmin', handleLocalMicrophoneClosedByAdmin)
|
||||
});
|
||||
|
||||
const handleLocalCameraOpenedByAdmin = {
|
||||
|
||||
callback: (event) => {
|
||||
uni.showToast({
|
||||
title: `您已被解除禁画`,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
const handleLocalCameraClosedByAdmin = {
|
||||
callback: (event) => {
|
||||
uni.showToast({
|
||||
title: `您已被禁画`,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
const handleLocalMicrophoneOpenedByAdmin = {
|
||||
callback: (event) => {
|
||||
uni.showToast({
|
||||
title: `您已被解除静音`,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
const handleLocalMicrophoneClosedByAdmin = {
|
||||
callback: (event) => {
|
||||
uni.showToast({
|
||||
title: `您已被静音`,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
const handleLiveEnded = {
|
||||
callback: (event) => {
|
||||
if (timer) clearInterval(timer);
|
||||
removeLiveListListener('onLiveEnded', handleLiveEnded)
|
||||
removeLiveListListener('onKickedOutOfLive', handleKickedOutOfLive)
|
||||
removeGiftListener(uni.$liveID, 'onReceiveGift', handleReceiveGift)
|
||||
removeLiveSeatEventListener(uni.$liveID, 'onLocalCameraOpenedByAdmin', handleLocalCameraOpenedByAdmin)
|
||||
removeLiveSeatEventListener(uni.$liveID, 'onLocalCameraClosedByAdmin', handleLocalCameraClosedByAdmin)
|
||||
removeLiveSeatEventListener(uni.$liveID, 'onLocalMicrophoneOpenedByAdmin', handleLocalMicrophoneOpenedByAdmin)
|
||||
removeLiveSeatEventListener(uni.$liveID, 'onLocalMicrophoneClosedByAdmin', handleLocalMicrophoneClosedByAdmin)
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title: '直播已结束'
|
||||
})
|
||||
uni.$liveID = ''
|
||||
uni.redirectTo({
|
||||
url: `/pages/livelist/index`,
|
||||
delta: 1,
|
||||
success: () => {
|
||||
console.log('返回成功');
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('返回失败', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
const handleKickedOutOfLive = {
|
||||
callback: (event) => {
|
||||
if (timer) clearInterval(timer);
|
||||
removeLiveListListener('onLiveEnded', handleLiveEnded)
|
||||
removeLiveListListener('onKickedOutOfLive', handleKickedOutOfLive)
|
||||
removeGiftListener(uni.$liveID, 'onReceiveGift', handleReceiveGift)
|
||||
removeLiveSeatEventListener(uni.$liveID, 'onLocalCameraOpenedByAdmin', handleLocalCameraOpenedByAdmin)
|
||||
removeLiveSeatEventListener(uni.$liveID, 'onLocalCameraClosedByAdmin', handleLocalCameraClosedByAdmin)
|
||||
removeLiveSeatEventListener(uni.$liveID, 'onLocalMicrophoneOpenedByAdmin', handleLocalMicrophoneOpenedByAdmin)
|
||||
removeLiveSeatEventListener(uni.$liveID, 'onLocalMicrophoneClosedByAdmin', handleLocalMicrophoneClosedByAdmin)
|
||||
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title: '被踢出直播间'
|
||||
})
|
||||
uni.$liveID = ''
|
||||
uni.redirectTo({
|
||||
url: `/pages/livelist/index`,
|
||||
delta: 1,
|
||||
success: () => {
|
||||
console.log('返回成功');
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('返回失败', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const handleReceiveGift = {
|
||||
callback: (event) => {
|
||||
const res = JSON.parse(event)
|
||||
if (res.sender.userID !== uni.$userID) {
|
||||
showGiftToast(res?.gift || {}, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
const audienceCount = computed(() => audienceList.value?.length);
|
||||
|
||||
watch(liveList, (newValue, oldValue) => {
|
||||
for (let i = 0; i < (oldValue || []).length; i++) {
|
||||
const liveNewInfo = (newValue || [])[i];
|
||||
const liveOldInfo = (oldValue || [])[i];
|
||||
if (!liveNewInfo || !liveOldInfo) continue;
|
||||
|
||||
if (!liveOldInfo?.isMessageDisable && liveNewInfo?.isMessageDisable) {
|
||||
uni.showToast({
|
||||
title: `${liveNewInfo?.liveOwner?.userName}被禁言`,
|
||||
icon: 'none',
|
||||
duration: 2000,
|
||||
position: 'center',
|
||||
});
|
||||
}
|
||||
}
|
||||
}, { immediate: true, deep: true });
|
||||
|
||||
|
||||
const audienceOperator = (message : any) => {
|
||||
console.warn(`click message: ${JSON.stringify(message)}`);
|
||||
if (message?.sender?.userID === loginUserInfo.value.userID) {
|
||||
return;
|
||||
}
|
||||
|
||||
clickUserInfo.value = { ...message?.sender || {}, liveID: uni?.$liveID };
|
||||
console.warn(`click message clickUserInfo: ${JSON.stringify(clickUserInfo.value)}`);
|
||||
isShowAnchorInfo.value = false
|
||||
showUserInfoPanel();
|
||||
};
|
||||
const handleHideInput = () => {
|
||||
uni.hideKeyboard()
|
||||
}
|
||||
|
||||
const navigateBack = () => {
|
||||
if (uni.$localGuestStatus === 'CONNECTED') {
|
||||
exitSheetItems.value = ['断开连麦', '退出直播间']
|
||||
exitSheetTitle.value = '当前处于连麦状态,是否需要「断开连麦」或「退出直播间」'
|
||||
}
|
||||
isShowExitSheet.value = true
|
||||
};
|
||||
|
||||
const onExitSheetSelect = (res : { tapIndex : number }) => {
|
||||
const index = res.tapIndex
|
||||
// 当处于连麦,索引0是“断开连麦”,索引1是“退出直播间”;否则只有“退出直播间”在索引0
|
||||
if (uni.$localGuestStatus === 'CONNECTED' && index === 0) {
|
||||
disconnect({
|
||||
liveID: uni?.$liveID,
|
||||
})
|
||||
exitSheetItems.value = ['退出直播间']
|
||||
exitSheetTitle.value = ''
|
||||
uni.$localGuestStatus = 'IDLE'
|
||||
return
|
||||
}
|
||||
if ((uni.$localGuestStatus === 'CONNECTED' && index === 1) || (uni.$localGuestStatus !== 'CONNECTED' && index === 0)) {
|
||||
leaveLive({
|
||||
success: () => {
|
||||
uni.$liveID = ''
|
||||
uni.redirectTo({
|
||||
url: `/pages/livelist/index`,
|
||||
delta: 1,
|
||||
animationType: 'pop-out',
|
||||
animationDuration: 300,
|
||||
success: () => {
|
||||
console.log('返回成功');
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('返回失败', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const ShowAudienceViewClickPanel = (userInfo) => {
|
||||
if (!userInfo) return;
|
||||
if (userInfo?.userID === currentLive.value.liveOwner.userID) return
|
||||
console.warn(`ShowAudienceViewClickPanel userID: ${userInfo?.userID}, currentLoginUserId: ${currentLoginUserId?.value}`);
|
||||
clickUserInfo.value = userInfo;
|
||||
isShowAnchorInfo.value = false
|
||||
showUserInfoPanel();
|
||||
};
|
||||
|
||||
const showAnchorInfoDrawer = () => {
|
||||
isShowAnchorInfo.value = true
|
||||
clickUserInfo.value = { ...(currentLive?.value.liveOwner || {}), liveID: currentLive?.value.liveID || '' }
|
||||
showUserInfoPanel()
|
||||
}
|
||||
|
||||
const showUserInfoPanel = () => {
|
||||
isShowUserInfoPanel.value = true;
|
||||
};
|
||||
|
||||
const showAudienceList = () => {
|
||||
isShowAudienceList.value = true;
|
||||
};
|
||||
|
||||
// 处理连麦按钮点击事件
|
||||
const handleCoGuestButtonClick = () => {
|
||||
if (shouldDisableCoGuestButton.value) {
|
||||
return; // 如果按钮被禁用,直接返回
|
||||
}
|
||||
ShowCoGuestRequestPanel();
|
||||
};
|
||||
|
||||
const ShowCoGuestRequestPanel = () => {
|
||||
if (uni.$localGuestStatus === 'CONNECTED' || uni.$localGuestStatus === 'USER_APPLYING') {
|
||||
if (uni.$localGuestStatus === 'USER_APPLYING') {
|
||||
coGuestSheetItems.value = ['取消连麦申请']
|
||||
} else if (uni.$localGuestStatus === 'CONNECTED') {
|
||||
coGuestSheetItems.value = ['断开连麦']
|
||||
}
|
||||
isShowCoGuestSheet.value = true
|
||||
} else {
|
||||
isShowCoGuestRequestPanel.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
const onCoGuestSheetSelect = (res : { tapIndex : number }) => {
|
||||
if (uni.$localGuestStatus === 'CONNECTED') {
|
||||
if (res.tapIndex === 0) {
|
||||
disconnect({
|
||||
liveID: uni?.$liveID,
|
||||
})
|
||||
uni.$localGuestStatus = 'IDLE'
|
||||
}
|
||||
return
|
||||
}
|
||||
if (uni.$localGuestStatus === 'USER_APPLYING') {
|
||||
if (res.tapIndex === 0) {
|
||||
cancelApplication({
|
||||
liveID: uni?.$liveID,
|
||||
})
|
||||
uni.$localGuestStatus = 'IDLE'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const selectParticipant = (participant) => {
|
||||
console.warn('选择参与者:', participant);
|
||||
selectedAudience.value = participant;
|
||||
// 可以在这里添加更多逻辑,比如显示参与者详情
|
||||
};
|
||||
|
||||
const showGiftPicker = () => {
|
||||
isShowGiftPicker.value = true;
|
||||
};
|
||||
|
||||
const showNetworkQualityPanel = () => {
|
||||
isShowNewWorkPanel.value = true;
|
||||
}
|
||||
|
||||
const handleFollowClick = () => {
|
||||
isFollowed.value = !isFollowed.value;
|
||||
uni.showToast({
|
||||
title: isFollowed.value ? '已关注' : '已取消关注',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
});
|
||||
};
|
||||
|
||||
// 显示礼物提示
|
||||
const showGiftToast = async (giftData ?: any, isOnlyDisplay : boolean = false) => {
|
||||
if (!giftData) return;
|
||||
|
||||
const giftDataCopy = {
|
||||
...giftData,
|
||||
resourceURL: giftData.resourceURL ? String(giftData.resourceURL) : '',
|
||||
name: giftData.name ? String(giftData.name) : '',
|
||||
giftID: giftData.giftID || 0,
|
||||
};
|
||||
|
||||
giftInfo.value = giftDataCopy;
|
||||
|
||||
const isFromSelf = !giftData?.sender || giftData?.sender?.userID === uni.$userID;
|
||||
|
||||
|
||||
showGift(giftDataCopy, {
|
||||
onlyDisplay: isOnlyDisplay,
|
||||
isFromSelf: isFromSelf
|
||||
});
|
||||
|
||||
// 使用 BarrageList 的 showToast 方法显示礼物提示
|
||||
if (barrageListRef.value) {
|
||||
barrageListRef.value.showToast({
|
||||
name: giftData?.sender?.userName || giftData?.sender?.userID || '',
|
||||
avatarURL: giftData?.sender?.avatarURL || '',
|
||||
desc: giftData?.name || '',
|
||||
iconURL: giftData?.iconURL || '',
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const svgaPlayerFinished = () => {
|
||||
isLargeSizeGiftPlayer.value = false;
|
||||
onGiftFinished();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.live-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
width: 750rpx;
|
||||
background: rgba(15, 16, 20, 1);
|
||||
}
|
||||
|
||||
.live-content {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
width: 750rpx;
|
||||
}
|
||||
|
||||
.live-background {
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.live-header {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 32rpx;
|
||||
margin-top: 80rpx;
|
||||
width: 750rpx;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stream-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
padding: 6rpx 10rpx;
|
||||
border-radius: 40rpx;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 58rpx;
|
||||
height: 58rpx;
|
||||
border-radius: 30rpx;
|
||||
border-width: 2rpx;
|
||||
border-color: #ffffff;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.stream-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.stream-title {
|
||||
color: #ffffff;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4rpx;
|
||||
width: 120rpx;
|
||||
height: 40rpx;
|
||||
lines: 1;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
width: 300rpx;
|
||||
padding-left: 40rpx;
|
||||
}
|
||||
|
||||
.participants {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.participant-avatar {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border-radius: 24rpx;
|
||||
border-width: 2rpx;
|
||||
border-color: #ffffff;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.participant-count {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border-radius: 24rpx;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.count-text {
|
||||
color: #ffffff;
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.control-icons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.control-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.footer {
|
||||
flex: 1;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 80rpx;
|
||||
padding-left: 32rpx;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 686rpx;
|
||||
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
position: relative;
|
||||
background: rgba(34, 38, 46, 0.5);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 72rpx;
|
||||
padding-left: 40rpx;
|
||||
color: #ffffff;
|
||||
font-size: 28rpx;
|
||||
width: 260rpx;
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
}
|
||||
|
||||
.input-prefix {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
top: 50rpx;
|
||||
left: 230rpx;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.input-emoji {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
position: fixed;
|
||||
right: 40rpx;
|
||||
bottom: 80rpx;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
.action-btn.disabled {
|
||||
opacity: 0.5;
|
||||
filter: grayscale(100%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.follow-btn {
|
||||
padding: 0 16rpx;
|
||||
height: 56rpx;
|
||||
background: #338aff;
|
||||
color: #fff;
|
||||
border-radius: 32rpx;
|
||||
font-size: 28rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/* width: 88rpx; */
|
||||
}
|
||||
|
||||
.follow-btn.followed {
|
||||
background: #fff;
|
||||
color: #338aff;
|
||||
border: 2rpx solid #338aff;
|
||||
}
|
||||
|
||||
.live-network {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
}
|
||||
|
||||
.live-network-container {
|
||||
position: fixed;
|
||||
top: 200rpx;
|
||||
right: 30rpx;
|
||||
width: 180rpx;
|
||||
height: 40rpx;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 45rpx;
|
||||
flex-direction: row;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.live-timer {
|
||||
color: #fff;
|
||||
font-size: 24rpx;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -8,7 +8,10 @@
|
||||
{ name: '朋友圈', icon: 'circle' },
|
||||
{ name: '线上商城', icon: 'mall' },
|
||||
{ name: '我的拼团', icon: 'team' },
|
||||
{ name: '项目入口', icon: 'project' }
|
||||
{ name: '项目入口', icon: 'project' },
|
||||
// #ifdef APP-PLUS
|
||||
{ name: '直播列表', icon: 'liveStream' }
|
||||
// #endif
|
||||
]
|
||||
|
||||
const onGo = item => {
|
||||
@@ -36,6 +39,10 @@
|
||||
navigateTo('/pages/shop-together/index')
|
||||
return
|
||||
}
|
||||
if (item === 'liveStream') {
|
||||
navigateTo('/pages/discover/livelist/index')
|
||||
return
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
252
pages/discover/livelist/index.nvue
Normal file
252
pages/discover/livelist/index.nvue
Normal file
@@ -0,0 +1,252 @@
|
||||
<template>
|
||||
<view
|
||||
style="position: relative; height: 160rpx; padding-top: 80rpx; background-color: #fff; display: flex; flex-direction: row; justify-content: center; align-items: center;">
|
||||
<image style="width: 21rpx; height: 34rpx; position: absolute; left: 60rpx;" src="/static/images/back-black.png"
|
||||
@tap="handleGoBack" />
|
||||
<text>在线直播</text>
|
||||
<image style="width: 36rpx; height: 36rpx; position: absolute; right: 60rpx;" src="/static/images/refresh.png"
|
||||
@tap="handlePageRefresh" />
|
||||
</view>
|
||||
<view class="home-container" :style="{ height: safeArea.height + 'px' }">
|
||||
<live-list />
|
||||
<!-- 创建房间按钮 -->
|
||||
<!-- <view class="home-footer">
|
||||
<view class="create-btn" @click="goAnchorPage()">
|
||||
<image style="width: 36rpx; height: 36rpx; margin-right: 10rpx;" src="/static/images/create-live.png" />
|
||||
<text class="btn-text">开2直播</text>
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reLaunch } from '@/utils/router';
|
||||
import {
|
||||
ref,
|
||||
onMounted,
|
||||
watch
|
||||
} from 'vue';
|
||||
import {
|
||||
onLoad,
|
||||
onShow
|
||||
} from '@dcloudio/uni-app';
|
||||
|
||||
import LiveList from '@/uni_modules/tuikit-atomic-x/components/LiveList.nvue';
|
||||
import {
|
||||
useLiveListState
|
||||
} from "@/uni_modules/tuikit-atomic-x/state/LiveListState";
|
||||
const {
|
||||
fetchLiveList,
|
||||
liveListCursor
|
||||
} = useLiveListState();
|
||||
|
||||
const safeArea = ref({
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: 375,
|
||||
height: 750,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
uni.getSystemInfo({
|
||||
success: (res) => {
|
||||
safeArea.value = res.safeArea;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
onShow(() => {
|
||||
console.warn(`home onShow`);
|
||||
needRefresh.value = true;
|
||||
});
|
||||
|
||||
const goAnchorPage = () => {
|
||||
uni.redirectTo({
|
||||
url: '/pages/anchor/index'
|
||||
});
|
||||
}
|
||||
|
||||
const handleGoBack = () => {
|
||||
console.log('返回')
|
||||
reLaunch('/pages/discover/discover')
|
||||
}
|
||||
|
||||
const handlePageRefresh = () => {
|
||||
const params = {
|
||||
cursor: '',
|
||||
count: 20,
|
||||
success: () => {
|
||||
fetchLiveListRecursively(liveListCursor.value);
|
||||
}
|
||||
};
|
||||
fetchLiveList(params);
|
||||
}
|
||||
|
||||
const fetchLiveListRecursively = (cursor) => {
|
||||
const params = {
|
||||
cursor: cursor,
|
||||
count: 20,
|
||||
success: () => {
|
||||
if (liveListCursor.value) {
|
||||
fetchLiveListRecursively(liveListCursor.value);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '刷新完成'
|
||||
});
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error(`fetchLiveListRecursively failed, err: ${JSON.stringify(err)}`);
|
||||
}
|
||||
};
|
||||
fetchLiveList(params);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.home-container {
|
||||
flex: 1;
|
||||
background-color: #F2F5FC;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 32rpx;
|
||||
background-color: #ffffff;
|
||||
border-bottom-width: 1rpx;
|
||||
border-bottom-color: #f0f0f0;
|
||||
position: relative;
|
||||
height: 120rpx;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.header-center {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.title-underline {
|
||||
width: 60rpx;
|
||||
height: 4rpx;
|
||||
background-color: #007AFF;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 80rpx;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.help-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.user-bar {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding-top: 32rpx;
|
||||
padding-bottom: 16rpx;
|
||||
padding-left: 32rpx;
|
||||
padding-right: 32rpx;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 32rpx;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 28rpx;
|
||||
color: #222;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.user-id {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.user-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.icon-help {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.home-footer {
|
||||
position: absolute;
|
||||
bottom: 60rpx;
|
||||
width: 750rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.create-btn {
|
||||
background-color: #0468FC;
|
||||
border-radius: 999px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 25rpx 60rpx;
|
||||
}
|
||||
|
||||
|
||||
.btn-text {
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
</style>
|
||||
193
pages/liveend/index.nvue
Normal file
193
pages/liveend/index.nvue
Normal file
@@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<view class="container" :style="{ height: safeArea.height + 'px'}">
|
||||
<image @tap="handleToLive" class="back-btn" src="/static/images/close.png" mode="aspectFit" />
|
||||
<view class="header">
|
||||
<text class="title">直播已结束</text>
|
||||
</view>
|
||||
<view class="stats-card">
|
||||
<view class="stats-row">
|
||||
<view class="stats-item">
|
||||
<text class="stats-value">{{ formattedDuration }}</text>
|
||||
<text class="stats-label">直播时长</text>
|
||||
</view>
|
||||
<view class="stats-item">
|
||||
<text class="stats-value">{{ summaryData?.totalViewers }}</text>
|
||||
<text class="stats-label">累计观看</text>
|
||||
</view>
|
||||
<view class="stats-item">
|
||||
<text class="stats-value">{{ summaryData?.totalMessageSent }}</text>
|
||||
<text class="stats-label">消息数量</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stats-row">
|
||||
<view class="stats-item">
|
||||
<text class="stats-value">{{ summaryData?.totalGiftCoins }}</text>
|
||||
<text class="stats-label">礼物收入</text>
|
||||
</view>
|
||||
<view class="stats-item">
|
||||
<text class="stats-value">{{ summaryData?.totalGiftUniqueSenders }}</text>
|
||||
<text class="stats-label">送礼人数</text>
|
||||
</view>
|
||||
<view class="stats-item">
|
||||
<text class="stats-value">{{ summaryData?.totalLikesReceived }}</text>
|
||||
<text class="stats-label">点赞数量</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
onMounted,
|
||||
computed
|
||||
} from 'vue';
|
||||
import {
|
||||
onLoad
|
||||
} from '@dcloudio/uni-app';
|
||||
|
||||
const safeArea = ref({
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: 375,
|
||||
height: 750,
|
||||
});
|
||||
|
||||
const summaryData = ref()
|
||||
|
||||
|
||||
onLoad((options) => {
|
||||
summaryData.value = uni.$summaryData
|
||||
})
|
||||
|
||||
// 计算属性: 格式化后的直播时长
|
||||
const formattedDuration = computed(() => {
|
||||
return formatDuration(summaryData.value?.totalDuration);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
uni.getSystemInfo({
|
||||
success: (res) => {
|
||||
safeArea.value = res.safeArea;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const handleToLive = () => {
|
||||
uni.redirectTo({
|
||||
url: '/pages/discover/livelist/index',
|
||||
success() {
|
||||
console.log('跳转成功');
|
||||
},
|
||||
fail(err) {
|
||||
console.error('跳转失败:', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 格式化直播时长(输入单位为毫秒)
|
||||
const formatDuration = (milliseconds) => {
|
||||
// 处理无效输入
|
||||
if (!milliseconds || milliseconds <= 0 || isNaN(milliseconds)) {
|
||||
return '00:00:00';
|
||||
}
|
||||
|
||||
// 将毫秒转换为秒(向下取整)
|
||||
const totalSeconds = Math.floor(milliseconds / 1000);
|
||||
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
const remainingSeconds = totalSeconds % 60;
|
||||
|
||||
// 始终显示 HH:MM:SS 格式
|
||||
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
flex: 1;
|
||||
background-color: rgba(19, 20, 23, 1);
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
/* 顶部对齐 */
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-top: 300rpx;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
position: absolute;
|
||||
top: 100rpx;
|
||||
right: 80rpx;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #fff;
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.time {
|
||||
color: #bdbdbd;
|
||||
font-size: 26rpx;
|
||||
margin-bottom: 40rpx;
|
||||
margin-left: 32rpx;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stats-card {
|
||||
/* position: absolute; */
|
||||
/* top: 450rpx; */
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgba(43, 44, 48, 1);
|
||||
border-radius: 24rpx;
|
||||
padding: 40rpx 0;
|
||||
width: 700rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.2);
|
||||
margin-top: 80rpx;
|
||||
/* 与header间距 */
|
||||
}
|
||||
|
||||
.stats-row {
|
||||
width: 700rpx;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
margin: 24rpx;
|
||||
}
|
||||
|
||||
.stats-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.stats-item {
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stats-value {
|
||||
color: #fff;
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stats-label {
|
||||
color: #bdbdbd;
|
||||
font-size: 22rpx;
|
||||
margin-top: 8rpx;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@@ -11,7 +11,7 @@
|
||||
url: '/pages/my-index/wallet/index'
|
||||
},
|
||||
{ name: '我的团队', icon: 'team', url: '/pages/my-index/my-team' },
|
||||
{ name: '群创建直播', icon: 'videocam', url: '' },
|
||||
// { name: '群创建直播', icon: 'videocam', url: '' },
|
||||
{
|
||||
name: '会议记录',
|
||||
icon: 'meeting',
|
||||
|
||||
Reference in New Issue
Block a user