直播间需要添加签到功能

This commit is contained in:
cbb
2026-01-13 17:56:19 +08:00
parent 06e026c8b8
commit c139fcf501
16 changed files with 313 additions and 171 deletions

View File

@@ -17,7 +17,8 @@
const silentLogin = async () => { const silentLogin = async () => {
if (token.value) { if (token.value) {
loginTencentIM() loginTencentIM()
reLaunch('/TUIKit/components/TUIConversation/index') // reLaunch('/TUIKit/components/TUIConversation/index')
return return
} }

View File

@@ -627,7 +627,6 @@
item.modifyMessage({ item.modifyMessage({
cloudCustomData: JSON.stringify(newMessage) cloudCustomData: JSON.stringify(newMessage)
}) })
return
receiveRedEnvelope({ receiveRedEnvelope({
redPacketId: data.id redPacketId: data.id
}) })

View File

@@ -889,7 +889,7 @@
reason: '' reason: ''
} }
console.log(options, '===') console.log(options, '===')
return
await TUIGroupService.deleteGroupMember(options) await TUIGroupService.deleteGroupMember(options)
} }

View File

@@ -212,6 +212,7 @@ export const getUserMomentsList = data => {
return http({ return http({
url: '/api/service/userMoments/list', url: '/api/service/userMoments/list',
method: 'get', method: 'get',
loading: false,
data data
}) })
} }

View File

@@ -99,9 +99,9 @@ export const imDataStartLive = (roomId) => {
} }
/** 结束直播 */ /** 结束直播 */
export const imDataEndLive = (roomId) => { export const imDataEndLive = (roomId, viewers) => {
return http({ return http({
url: `/api/service/imLiveRoom/${roomId}`, url: `/api/service/imLiveRoom/${roomId}/${viewers}`,
method: 'delete' method: 'delete'
}) })
} }

View File

@@ -12,6 +12,8 @@
type: String, type: String,
default: '' default: ''
}) })
const emit = defineEmits(['search'])
</script> </script>
<template> <template>
@@ -27,7 +29,7 @@
:placeholder="props.placeholder" :placeholder="props.placeholder"
class="search-box" class="search-box"
/> />
<button class="search-btn">搜索</button> <button class="search-btn" @click="emit('search')">搜索</button>
</view> </view>
</template> </template>

View File

@@ -550,14 +550,16 @@
clearAudioEffectSet() clearAudioEffectSet()
clearBeautyPanelSet() clearBeautyPanelSet()
uni.$summaryData = summaryData.value uni.$summaryData = summaryData.value
console.warn(` 退出直播 imDataEndLive`); console.warn(` 退出直播===直播间人数`, audienceCount.value);
imDataEndLive(roomDataId.value, audienceCount.value).then(() => {
endLive({ uni.redirectTo({ url: '/pages/liveend/index' });
success: () => { })
console.warn(` 退出直播 imDataEndLive`); // endLive({
uni.redirectTo({ url: '/pages/liveend/index' }); // success: () => {
}, // console.warn(` 退出直播 imDataEndLive`);
}); // uni.redirectTo({ url: '/pages/liveend/index' });
// },
// });
} }
} }
@@ -569,60 +571,67 @@
isShowLiveMoreActionsPanel.value = true; isShowLiveMoreActionsPanel.value = true;
}; };
const roomDataId = ref('')
const startLive = async () => { const startLive = async () => {
// try { try {
// console.log('点击开始直播') console.log('点击开始直播')
// const data = { const data = {
// coverUrl: coverURL.value, coverUrl: coverURL.value,
// roomName: liveTitle.value, roomName: liveTitle.value,
// groupId: groupId.value groupId: groupId.value
// } }
// const roomData = await imAddLive(data) const roomData = await imAddLive(data)
// const res = await imDataStartLive(roomData.data.roomId) const roomId = roomData.data.roomId
// console.log(res) uni.$liveID = roomId
// const params = { liveID.value = roomId
// cursor: "", // 首次拉起传空不能是null),然后根据回调数据的cursor确认是否拉完 const res = await imDataStartLive(roomId)
// count: 20, // 分页拉取的个数 console.log(roomData, '========11111')
// }; console.log(res, '========22222')
// fetchLiveList(params); roomDataId.value = roomId
// openLocalCamera({ isFront: isFrontCamera.value }); const params = {
// openLocalMicrophone(); cursor: "", // 首次拉起传空不能是null),然后根据回调数据的cursor确认是否拉完
// setLocalVideoMuteImage(); count: 20, // 分页拉取的个数
// isStartLive.value = true; };
// } catch (err) { joinLive({ liveID: roomId })
// console.log(err, '====22') fetchLiveList(params);
// } openLocalCamera({ isFront: isFrontCamera.value });
openLocalMicrophone();
createLive({ setLocalVideoMuteImage();
liveInfo: { isStartLive.value = true;
liveID: uni?.$liveID, } catch (err) {
liveName: liveTitle.value, console.log(err, '====22')
coverURL: coverURL.value, }
isSeatEnabled: true,
seatMode: 'APPLY',
maxSeatCount: 0,
isPublicVisible: liveMode.value === '公开',
keepOwnerOnSeat: true,
seatLayoutTemplateID: templateLayout.value,
},
success: () => {
const params = {
cursor: "", // 首次拉起传空不能是null),然后根据回调数据的cursor确认是否拉完
count: 20, // 分页拉取的个数
};
fetchLiveList(params);
openLocalCamera({ isFront: isFrontCamera.value }); // createLive({
openLocalMicrophone(); // liveInfo: {
setLocalVideoMuteImage(); // liveID: uni?.$liveID,
}, // liveName: liveTitle.value,
fail: (errCode, errMsg) => { // coverURL: coverURL.value,
uni.showToast({ // isSeatEnabled: true,
title: '创建直播间失败', // seatMode: 'APPLY',
}); // maxSeatCount: 0,
}, // isPublicVisible: liveMode.value === '公开',
}); // keepOwnerOnSeat: true,
isStartLive.value = true; // seatLayoutTemplateID: templateLayout.value,
// },
// success: () => {
// const params = {
// cursor: "", // 首次拉起传空不能是null),然后根据回调数据的cursor确认是否拉完
// count: 20, // 分页拉取的个数
// };
// fetchLiveList(params);
// openLocalCamera({ isFront: isFrontCamera.value });
// openLocalMicrophone();
// setLocalVideoMuteImage();
// },
// fail: (errCode, errMsg) => {
// uni.showToast({
// title: '创建直播间失败',
// });
// },
// });
// isStartLive.value = true;
}; };
const ShowAnchorViewClickPanel = (userInfo) => { const ShowAnchorViewClickPanel = (userInfo) => {

View File

@@ -427,16 +427,17 @@
disconnect({ disconnect({
liveID: uni?.$liveID, liveID: uni?.$liveID,
}) })
exitSheetItems.value = ['退出直播间'] exitSheetItems.value = ['退2出直播间']
exitSheetTitle.value = '' exitSheetTitle.value = ''
uni.$localGuestStatus = 'IDLE' uni.$localGuestStatus = 'IDLE'
return return
} }
if ((uni.$localGuestStatus === 'CONNECTED' && index === 1) || (uni.$localGuestStatus !== 'CONNECTED' && index === 0)) { if ((uni.$localGuestStatus === 'CONNECTED' && index === 1) || (uni.$localGuestStatus !== 'CONNECTED' && index === 0)) {
// imDataEndLive(liveID.value, 0)
leaveLive({ leaveLive({
success: () => { success: () => {
// imDataEndLive(liveID.value)
uni.$liveID = '' uni.$liveID = ''
uni.redirectTo({ uni.redirectTo({
url: `/pages/discover/livelist/index`, url: `/pages/discover/livelist/index`,

View File

@@ -25,6 +25,7 @@ text-transform: none;`
const MAX_SCROLL = 446 const MAX_SCROLL = 446
const paging = ref(null) const paging = ref(null)
const listLoading = ref(true)
const cbNavBar = ref({}) const cbNavBar = ref({})
const dataList = ref([]) const dataList = ref([])
const topIcon = reactive({ const topIcon = reactive({
@@ -54,24 +55,36 @@ text-transform: none;`
try { try {
const res = await getUserMomentsList({ const res = await getUserMomentsList({
pageNum, pageNum,
pageSize pageSize,
targetUserId: userInfo.value.userId
}) })
paging.value.complete(res.rows) const list = res.rows.map(item => {
return {
...item,
commentList: item.comments
}
})
paging.value.complete(list)
listLoading.value = false
} catch (error) { } catch (error) {
paging.value.complete(false) paging.value.complete(false)
} }
} }
const onLike = async item => { const onLike = async item => {
await likeUserMoments(item.id) const res = await likeUserMoments(item.id)
// item.likeCount += 1 if (res.data) {
item.likeCount += 1
} else {
item.likeCount -= 1
}
} }
/** 点击查看大图 */ /** 点击查看大图 */
const onImage = current => { const onImage = (urls, current = 0) => {
uni.previewImage({ uni.previewImage({
urls: [current], // 图片路径数组(本地或网络) urls, // 图片路径数组(本地或网络)
current: current // 当前显示的图片(可选,默认为第一张) current // 当前显示的图片(可选,默认为第一张)
}) })
closeComment() closeComment()
} }
@@ -87,9 +100,11 @@ text-transform: none;`
console.log('发布评论') console.log('发布评论')
const data = { const data = {
content: contentData.value, content: contentData.value,
id: item.id id: item.id,
momentId: item.id
} }
const res = await addUserMomentsComment(data) const res = await addUserMomentsComment(data)
item.commentList.push(res.data)
closeComment() closeComment()
} }
@@ -117,6 +132,7 @@ text-transform: none;`
safe-area-inset-bottom safe-area-inset-bottom
use-safe-area-placeholder use-safe-area-placeholder
:show-loading-more-no-more-view="false" :show-loading-more-no-more-view="false"
:auto="false"
@query="getData" @query="getData"
@scroll="onScroll" @scroll="onScroll"
> >
@@ -146,39 +162,51 @@ text-transform: none;`
</template> </template>
<view class="top-bg-img"> <view class="top-bg-img">
<image <image
:src="userInfo.avatar" :src="userInfo?.avatar"
mode="aspectFill" mode="aspectFill"
class="img" class="img"
@click="onImage(userInfo.avatar)" @click="onImage([userInfo?.avatar])"
></image> ></image>
<!-- 用户信息 --> <!-- 用户信息 -->
<view class="user-info"> <view class="user-info">
<text>{{ userInfo.userName }}</text> <text>{{ userInfo?.userName }}</text>
<image <image
:src="userInfo.avatar" :src="userInfo?.avatar"
mode="aspectFill" mode="aspectFill"
class="avatar" class="avatar"
@click="onImage(userInfo.avatar)" @click="onImage([userInfo?.avatar])"
></image> ></image>
</view> </view>
</view> </view>
<!-- 动态列表 --> <!-- 动态列表 -->
<view class="dynamic-list" @click="closeComment"> <view v-if="!listLoading" class="dynamic-list" @click="closeComment">
<view v-for="item in dataList" :key="item.id" class="list"> <view v-for="item in dataList" :key="item.id" class="list">
<image <image
src="https://img1.baidu.com/it/u=2645961124,1296922095&fm=253&app=138&f=JPEG?w=800&h=1530" :src="item.avatar"
mode="aspectFill" mode="aspectFill"
class="avatar" class="avatar"
></image> ></image>
<view class="content"> <view class="content">
<text class="name">名字</text> <text class="name">{{ item.userName }}</text>
<text class="text">{{ item.content }}</text> <text class="text">{{ item.content }}</text>
<view class="img-list"> <view
v-if="item.images.length > 0"
:class="item.images.length === 1 ? 'one-img' : 'img-list'"
>
<image <image
src="https://p4.itc.cn/images01/20220619/46660ed163164c14be90e605a73ee5e8.jpeg" v-for="(img, index) in item.images"
:key="index"
:src="img.imageUrl"
lazy-load
mode="aspectFill" mode="aspectFill"
class="item-img" class="item-img"
@click="
onImage(
item.images.map(v => v.imageUrl),
index
)
"
></image> ></image>
</view> </view>
<!-- 地址 --> <!-- 地址 -->
@@ -223,10 +251,14 @@ text-transform: none;`
<button @click.stop="onComment(item)">发布</button> <button @click.stop="onComment(item)">发布</button>
</view> </view>
<!-- 评论内容 --> <!-- 评论内容 -->
<view class="comment"> <view v-if="item.commentList.length > 0" class="comment">
<view v-for="item in 3" :key="item" class="comment-item"> <view
<text>Admin:</text> v-for="(c, i) in item.commentList"
<text>确实的很好值得推荐的很好</text> :key="i"
class="comment-item"
>
<text>{{ c.userName }}:</text>
<text>{{ c.content }}</text>
</view> </view>
</view> </view>
</view> </view>

View File

@@ -29,9 +29,14 @@
/** 连续天数 */ /** 连续天数 */
const continuousDays = ref(0) const continuousDays = ref(0)
/** 给数子补零 */
const padZero = num => {
return num < 10 ? `0${num}` : num
}
const getData = async msg => { const getData = async msg => {
const res = await getSignList({ const res = await getSignList({
signMonth: `${currentYear.value}-${currentMonth.value}` signMonth: `${currentYear.value}-${padZero(currentMonth.value)}`
}) })
dateList.value = generateCalendar( dateList.value = generateCalendar(
@@ -199,7 +204,9 @@
</view> </view>
<!-- 切换 --> <!-- 切换 -->
<view class="switch-box"> <view class="switch-box">
<text class="date">{{ currentYear }}{{ currentMonth }}</text> <text class="date">
{{ currentYear }}{{ padZero(currentMonth) }}
</text>
<view class="btn"> <view class="btn">
<uni-icons <uni-icons
type="left" type="left"

View File

@@ -64,6 +64,16 @@
margin: 16rpx 0; margin: 16rpx 0;
} }
.img-list { .img-list {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 14rpx;
.item-img {
width: 180rpx;
height: 180rpx;
border-radius: 8rpx;
}
}
.one-img {
.item-img { .item-img {
width: 410rpx; width: 410rpx;
height: 250rpx; height: 250rpx;

View File

@@ -8,7 +8,7 @@
onLoad(() => { onLoad(() => {
// 3秒后跳转 // 3秒后跳转
indexGo.value = setTimeout(() => { indexGo.value = setTimeout(() => {
reLaunch('/pages/news-list/news-list') reLaunch('/TUIKit/components/TUIConversation/index')
}, 3000) }, 3000)
}) })

View File

@@ -4,11 +4,14 @@
import { getCategory, getProductList } from '@/api/mall' import { getCategory, getProductList } from '@/api/mall'
import { navigateTo } from '@/utils/router' import { navigateTo } from '@/utils/router'
const paging = ref(null)
/** 顶部分类选项 */ /** 顶部分类选项 */
const topNavOptions = ref([]) const topNavOptions = ref([])
const formData = reactive({ const formData = reactive({
name: '', name: '',
type: '0' type: '',
pageNum: 1,
pageSize: 15
}) })
/** 商品列表 */ /** 商品列表 */
const cardList = ref([]) const cardList = ref([])
@@ -18,14 +21,24 @@
topNavOptions.value = res.data topNavOptions.value = res.data
} }
const getListData = async () => { const getListData = async (pageNum, pageSize) => {
const res = await getProductList() try {
cardList.value = res.rows const res = await getProductList({
console.log(res.rows) pageNum,
pageSize,
categoryId: formData.type,
productName: formData.name
})
paging.value.complete(res.rows)
} catch (error) {
paging.value.complete(false)
}
} }
const onTop = value => { const onTop = value => {
formData.type = value formData.type = value
formData.name = ''
getListData(1, formData.pageSize)
} }
const onGo = item => { const onGo = item => {
@@ -34,53 +47,77 @@
onLoad(async () => { onLoad(async () => {
await categoryList() await categoryList()
await getListData() // await getListData(1, formData.pageSize)
}) })
</script> </script>
<template> <template>
<view class="mall-list"> <z-paging
<view class="top-box"> ref="paging"
<cb-search v-model="formData.name"></cb-search> v-model="cardList"
<view class="top-options"> :default-page-no="formData.pageNum"
<view :default-page-size="formData.pageSize"
v-for="(item, index) in topNavOptions" safe-area-inset-bottom
:key="index" use-safe-area-placeholder
:class="{ active: item.id === formData.type }" :show-loading-more-no-more-view="false"
class="text" :paging-style="{ 'background-color': '#f9f9f9' }"
@click="onTop(item.id)" @query="getListData"
> >
{{ item.categoryName }} <view class="mall-list">
</view> <view class="top-box">
</view> <cb-search
</view> v-model="formData.name"
@search="getListData()"
<!-- 商品卡片 --> ></cb-search>
<view class="card-list"> <view class="top-options">
<view <view
v-for="item in cardList" :class="{ active: formData.type === '' }"
:key="item.id" class="text"
class="card-item" @click="onTop('')"
@click="onGo(item)" >
> 全部
<image </view>
:src="item.mainImage" <view
mode="scaleToFill" v-for="(item, index) in topNavOptions"
class="imghead" :key="index"
></image> :class="{ active: item.id === formData.type }"
<text class="title">{{ item.productName }}</text> class="text"
<view class="price"> @click="onTop(item.id)"
<view class="num-box"> >
<text class="num"></text> {{ item.categoryName }}
<text class="num">{{ item.minPrice }}</text>
</view> </view>
<!-- <text class="buy">好评率99%</text> -->
</view> </view>
<!-- 拼单数量 --> </view>
<text class="bottom-name">拼单数量:{{ item.salesCount }}</text>
<!-- 商品卡片 -->
<view class="card-list">
<view
v-for="item in cardList"
:key="item.id"
class="card-item"
@click="onGo(item)"
>
<image
:src="item.mainImage"
mode="scaleToFill"
class="imghead"
></image>
<text class="title">{{ item.productName }}</text>
<view class="price">
<view class="num-box">
<text class="num"></text>
<text class="num">{{ item.minPrice }}</text>
</view>
<!-- <text class="buy">好评率99%</text> -->
</view>
<!-- 拼单数量 -->
<text class="bottom-name">
拼单数量:{{ item.salesCount }}
</text>
</view>
</view> </view>
</view> </view>
</view> </z-paging>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -17,11 +17,11 @@
icon: 'meeting', icon: 'meeting',
url: '/pages/my-index/meeting-record/index' url: '/pages/my-index/meeting-record/index'
}, },
{ // {
name: '我的朋友圈', // name: '我的朋友圈',
icon: 'circle', // icon: 'circle',
url: '/pages/discover/dynamic/dynamic' // url: '/pages/discover/dynamic/dynamic'
}, // },
{ {
name: '我的收藏', name: '我的收藏',
icon: 'collection', icon: 'collection',

View File

@@ -87,19 +87,25 @@ export const useUserStore = defineStore('user', () => {
const show = await showDialog('提示', '确定要退出登录吗?') const show = await showDialog('提示', '确定要退出登录吗?')
if (show) { if (show) {
await userLogout() await userLogout()
userInfo.value = null await logout()
clearToken()
removeUserInfoData()
removeSig()
await TUILogin.logout().then(() => {
reLaunch('/pages/login/login')
})
// #ifdef APP-PLUS
await useLoginState().logout()
// #endif
} }
} }
/**
* 退出登录(不带提示)
*/
const logout = async () => {
userInfo.value = null
clearToken()
removeUserInfoData()
removeSig()
await TUILogin.logout().then(() => {
reLaunch('/pages/login/login')
})
// #ifdef APP-PLUS
await useLoginState().logout()
// #endif
}
/** 刷新用户信息(如用户信息被修改) */ /** 刷新用户信息(如用户信息被修改) */
const refreshUserInfo = async () => { const refreshUserInfo = async () => {
const res = await getUserData() const res = await getUserData()
@@ -119,6 +125,7 @@ export const useUserStore = defineStore('user', () => {
return { return {
userInfo, userInfo,
tencentUserSig, tencentUserSig,
logout,
refreshUserInfo, refreshUserInfo,
fetchUserInfo, fetchUserInfo,
loginTencentIM, loginTencentIM,

View File

@@ -1,7 +1,11 @@
import { getToken, removeToken } from './storage' import { useUserStore } from '../stores/user'
import { getToken } from './storage'
const BASE_URL = import.meta.env.VITE_SYSTEM_URL const BASE_URL = import.meta.env.VITE_SYSTEM_URL
// 防止多个 401 同时触发登出和跳转
let isHandling401 = false
/** /**
* 网络请求封装 * 网络请求封装
* @param {Object} options 请求参数 * @param {Object} options 请求参数
@@ -50,12 +54,17 @@ const request = options => {
if (response.data.code === 200) { if (response.data.code === 200) {
resolve(response.data) resolve(response.data)
} else { } else {
handleError(response.data.code, response.data) // 注意:这里也要 reject否则调用方无法感知失败
const err = handleError(response.data.code, response.data)
reject(err || response.data)
// handleError(response.data.code, response.data)
} }
} else { } else {
// 状态码错误处理 // 状态码错误处理
handleError(response.statusCode, response.data) // handleError(response.statusCode, response.data)
reject(response) // reject(response)
const err = handleError(response.statusCode, response.data)
reject(err || response)
} }
}, },
fail: error => { fail: error => {
@@ -84,21 +93,46 @@ const request = options => {
* @param {Object} data 响应数据 * @param {Object} data 响应数据
*/ */
const handleError = (statusCode, data) => { const handleError = (statusCode, data) => {
// 如果是 401 且正在处理中,直接返回(避免重复处理)
if (statusCode === 401) {
if (isHandling401) {
return new Error('Unauthorized')
}
isHandling401 = true
uni.showModal({
title: '提示',
content: '登录已过期,请重新登录',
showCancel: false,
success: async () => {
await useUserStore().logout()
// 可选:跳转登录页
uni.redirectTo({ url: '/pages/login/index' })
console.log('登录已过期,====')
},
complete: () => {
// 重置标志,允许下次 401 处理(比如用户重新登录后再次过期)
isHandling401 = false
}
})
return new Error('Unauthorized')
}
switch (statusCode) { switch (statusCode) {
case 401: // case 401:
uni.showModal({ // console.log('登录已过期,====')
title: '提示', // uni.showModal({
content: '登录已过期,请重新登录', // title: '提示',
showCancel: false, // content: '登录已过期,请重新登录',
success: () => { // showCancel: false,
// 清除本地存储的token并跳转到登录页 // success: () => {
removeToken() // useUserStore().logout()
uni.navigateTo({ // // uni.navigateTo({
url: '/pages/login/index' // // url: '/pages/login/index'
}) // // })
} // }
}) // })
break // break
case 403: case 403:
uni.showToast({ uni.showToast({
title: '没有权限访问', title: '没有权限访问',
@@ -131,6 +165,8 @@ const handleError = (statusCode, data) => {
mask: true mask: true
}) })
} }
return new Error(`Request failed with code: ${statusCode}`)
} }
export default request export default request