Files
YwxAppThink/addon/wxchat/YwxMakeFriends/pages/ent/ent.uvue
T

897 lines
22 KiB
Plaintext

<template>
<view class="page">
<!-- 顶部导航栏 -->
<view class="page-nav">
<view class="page-nav-left">
<view class="tabsbox">
<view v-for="(tab, index) in filters" :key="index" class="tabsbox-item"
:class="{ 'tabsbox-active': tabActive == index }" @click="switchTab(index)">
<text class="tabsbox-txt"
:class="{'tabsbox-active-txt': tabActive == index }">{{ tab.text }}</text>
<!-- <text v-if="tab.count" class="ywx-tabs-badge">{{ tab.count }}</text> -->
</view>
</view>
</view>
<view class="page-nav-right">
<view class="icon-btn" @click="onSearchClick">
<uni-icons type="search" size="32" color="#333"></uni-icons>
</view>
<view class="icon-btn" @click="onMessageClick">
<uni-icons type="chat" size="32" color="#333"></uni-icons>
<text v-if="unreadCount > 0" class="message-badge">{{ unreadCount > 99 ? '99+' : unreadCount }}
</text>
</view>
</view>
</view>
<view class="page-body" style=" padding-bottom: 90rpx;">
<!-- 轮播图 -->
<swiper class="banner-swiper" :indicator-dots="true" :autoplay="true" :interval="3000" :duration="500">
<swiper-item v-for="(banner, index) in banners" :key="index" @click="onBannerClick(banner)">
<image class="banner-image" :src="banner.image" mode="aspectFill"></image>
<text v-if="banner.tag" class="banner-tag">{{ banner.tag }}</text>
</swiper-item>
</swiper>
<!-- 快速入口 -->
<!-- <scroll-view class="quick-entrance" scroll-x>
<view v-for="(entrance, index) in quickEntrances" :key="index" class="entrance-item"
@click="onEntranceClick(entrance)">
<view class="entrance-icon" :style="{ backgroundColor: entrance.bgColor }">
<uni-icons :type="entrance.icon" size="28"
:color="entrance.iconColor != null || '#fff'"></uni-icons>
</view>
<text class="entrance-text">{{ entrance.text }}</text>
<view v-if="entrance.hot" class="hot-badge">HOT</view>
</view>
</scroll-view> -->
<!-- 筛选栏 -->
<!-- <view class="filter-bar">
<view v-for="(filter, index) in filters" :key="index" class="filter-item"
:class="{ active: tabActive == index }" @click="changeFilter(index)">
<text class="filter-text">{{ filter.text }}</text>
<view v-if="filter.count" class="filter-count">{{ filter.count }}</view>
</view>
<view class="filter-more" @click="showMoreFilters">
<uni-icons type="more" size="20" color="#666"></uni-icons>
</view>
</view> -->
<!-- 用户列表 -->
<scroll-view class="user-list" direction="vertical" :show-scrollbar="false" :refresher-enabled="true"
:refresher-triggered="refreshing" @refresherrefresh="onRefresh" @scrolltolower="onLoadMore">
<!-- 直播 -->
<view v-if="tabActive == 0" class="section">
<view class="section-header">
<text class="section-title">语音房</text>
<text class="section-subtitle">{{ voiceRooms.length }}个房间在线</text>
</view>
<view class="voice-room-list">
<view v-for="room in voiceRooms" :key="room.id" class="voice-room" @click="joinVoiceRoom(room)">
<view class="room-header">
<image class="room-cover" :src="room.cover" mode="aspectFill"></image>
<view class="room-status">
<uni-icons type="sound" size="16" color="#fff"></uni-icons>
<text class="room-status-text">{{ room.onlineCount }}/{{ room.maxCount }}</text>
</view>
<text v-if="room.hot" class="room-hot">🔥{{ room.hot }}</text>
</view>
<view class="room-info">
<text class="room-title">{{ room.title }}</text>
<view class="room-tags">
<text v-for="(tag, tagIndex) in room.tags" :key="tagIndex" class="room-tag">
{{ tag }}
</text>
</view>
<view class="room-users">
<view v-for="(user, userIndex) in room.users" :key="userIndex"
class="room-user-avatar">
<image class="user-avatar-small" :src="user.avatar" mode="aspectFill"></image>
</view>
<text v-if="room.users.length < room.onlineCount" class="room-user-more">
+{{ room.onlineCount - room.users.length }}
</text>
</view>
</view>
</view>
</view>
</view>
<!-- 语音 -->
<view v-if="tabActive == 1" class="section">
<view class="section-header">
<text class="section-title">游戏中心</text>
<text class="section-subtitle">边玩边交友</text>
</view>
<view class="game-list">
<view v-for="game in games" :key="game.id" class="game-item" @click="playGame(game)">
<view class="game-cover">
<image class="game-image" :src="game.image" mode="aspectFill"></image>
<view v-if="game.playing" class="game-playing">
<uni-icons type="play" size="16" color="#fff"></uni-icons>
<text class="playing-text">{{ formatNumber(game.playing) }}在线</text>
</view>
</view>
<view class="game-info">
<view class="game-header">
<text class="game-title">{{ game.title }}</text>
<view v-if="game.reward" class="game-reward">+{{ game.reward }}</view>
</view>
<text class="game-desc">{{ game.description }}</text>
<view class="game-tags">
<view v-for="(tag, tagIndex) in game.tags" :key="tagIndex" class="game-tag">
{{ tag }}
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 畅聊 -->
<view v-if="tabActive == 2" class="section">
<view class="section-header">
<text class="section-title">游戏中心</text>
<text class="section-subtitle">边玩边交友</text>
</view>
<view class="game-list">
<view v-for="game in games" :key="game.id" class="game-item" @click="playGame(game)">
<view class="game-cover">
<image class="game-image" :src="game.image" mode="aspectFill"></image>
<view v-if="game.playing" class="game-playing">
<uni-icons type="play" size="16" color="#fff"></uni-icons>
<text class="playing-text">{{ formatNumber(game.playing) }}在线</text>
</view>
</view>
<view class="game-info">
<view class="game-header">
<text class="game-title">{{ game.title }}</text>
<view v-if="game.reward" class="game-reward">+{{ game.reward }}</view>
</view>
<text class="game-desc">{{ game.description }}</text>
<view class="game-tags">
<view v-for="(tag, tagIndex) in game.tags" :key="tagIndex" class="game-tag">
{{ tag }}
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 游戏 -->
<view v-if="tabActive == 3" class="section">
<view class="section-header">
<text class="section-title">游戏中心</text>
<text class="section-subtitle">边玩边交友</text>
</view>
<view class="game-list">
<view v-for="game in games" :key="game.id" class="game-item" @click="playGame(game)">
<view class="game-cover">
<image class="game-image" :src="game.image" mode="aspectFill"></image>
<view v-if="game.playing" class="game-playing">
<uni-icons type="play" size="16" color="#fff"></uni-icons>
<text class="playing-text">{{ formatNumber(game.playing) }}在线</text>
</view>
</view>
<view class="game-info">
<view class="game-header">
<text class="game-title">{{ game.title }}</text>
<view v-if="game.reward" class="game-reward">+{{ game.reward }}</view>
</view>
<text class="game-desc">{{ game.description }}</text>
<view class="game-tags">
<view v-for="(tag, tagIndex) in game.tags" :key="tagIndex" class="game-tag">
{{ tag }}
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 加载状态 -->
<!-- <view class="load-status">
<uni-load-more :status="loadStatus" />
</view> -->
</scroll-view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, reactive, computed, onMounted } from 'vue'
import { IBanner, IFilter, IVoiceRoom, IGame, IMenu } from '@/types/entType.uts'
// 响应式数据
const unreadCount = ref(3)
const refreshing = ref(false)
const loading = ref(false)
const noMore = ref(false)
const page = ref(1)
const tabActive = ref(0)
// 轮播图数据
const banners = reactive<IBanner[]>([
{
id: 1,
image: 'https://images.unsplash.com/photo-1511795409834-ef04bbd61622?ixlib=rb-4.0.3&auto=format&fit=crop&w=1200&q=80',
tag: '新活动',
url: '/pages/activity/activity'
},
{
id: 2,
image: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?ixlib=rb-4.0.3&auto=format&fit=crop&w=1200&q=80',
tag: 'VIP专区',
url: '/pages/vip/vip'
},
{
id: 3,
image: 'https://images.unsplash.com/photo-1529626455594-4ff0802cfb7e?ixlib=rb-4.0.3&auto=format&fit=crop&w=1200&q=80',
tag: '热门推荐',
url: '/pages/hot/hot'
}
] as IBanner[])
// 快速入口数据
//语音 畅聊 游戏
const quickEntrances = reactive<IMenu[]>([
{ id: 2, icon: 'sound', text: '语音房', bgColor: '#5AC8FA', url: '/pages/voice-room/voice-room', hot: true },
{ id: 3, icon: 'videocam', text: '视频', bgColor: '#FF9500', url: '/pages/video/video' },
{ id: 4, icon: 'game', text: '游戏', bgColor: '#34C759', url: '/pages/game/game' },
{ id: 5, icon: 'gift', text: '礼物', bgColor: '#AF52DE', url: '/pages/gift/gift' },
{ id: 6, icon: 'location', text: '同城', bgColor: '#5856D6', url: '/pages/city/city' },
{ id: 7, icon: 'star', text: '推荐', bgColor: '#FFCC00', url: '/pages/recommend/recommend' },
{ id: 8, icon: 'fire', text: '热门', bgColor: '#FF3B30', url: '/pages/hot/hot' }
] as IMenu[])
// 筛选数据
const filters = reactive<IFilter[]>([
{ id: 0, text: '附近', count: 128 },
{ id: 1, text: '推荐', count: 56 },
{ id: 2, text: '语音房', count: 12 },
{ id: 3, text: '游戏', count: 8 }
] as IFilter[])
// 语音房数据
const voiceRooms = reactive<IVoiceRoom[]>([
{
id: 301,
title: '情感聊天室',
cover: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
onlineCount: 8,
maxCount: 10,
hot: 125,
tags: ['情感', '聊天', '治愈'],
users: [
{ id: 1, avatar: 'https://randomuser.me/api/portraits/women/44.jpg' },
{ id: 2, avatar: 'https://randomuser.me/api/portraits/men/32.jpg' },
{ id: 3, avatar: 'https://randomuser.me/api/portraits/women/68.jpg' }
]
},
{
id: 302,
title: '游戏开黑房',
cover: 'https://images.unsplash.com/photo-1550745165-9bc0b252726f?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
onlineCount: 6,
maxCount: 8,
hot: 89,
tags: ['游戏', '开黑', '娱乐'],
users: [
{ id: 4, avatar: 'https://randomuser.me/api/portraits/men/75.jpg' },
{ id: 5, avatar: 'https://randomuser.me/api/portraits/women/65.jpg' }
]
},
{
id: 303,
title: '音乐分享会',
cover: 'https://images.unsplash.com/photo-1511379938547-c1f69419868d?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
onlineCount: 4,
maxCount: 6,
hot: 67,
tags: ['音乐', '分享', '放松'],
users: [
{ id: 6, avatar: 'https://randomuser.me/api/portraits/women/23.jpg' },
{ id: 7, avatar: 'https://randomuser.me/api/portraits/men/22.jpg' }
]
}
])
// 游戏数据
const games = reactive<IGame[]>([
{
id: 401,
title: '你画我猜',
image: 'https://images.unsplash.com/photo-1511512578047-dfb367046420?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
description: '经典互动游戏,发挥你的想象力',
playing: 1250,
reward: 50,
tags: ['互动', '休闲', '社交']
},
{
id: 402,
title: '真心话大冒险',
image: 'https://images.unsplash.com/photo-1535223289827-42f1e9919769?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
description: '增进了解的经典游戏',
playing: 890,
reward: 30,
tags: ['互动', '冒险', '社交']
},
{
id: 403,
title: '狼人杀',
image: 'https://images.unsplash.com/photo-1542751371-adc38448a05e?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
description: '考验推理和口才的社交游戏',
playing: 1560,
reward: 80,
tags: ['推理', '策略', '社交']
}
])
// 计算属性
const loadStatus = computed(() => {
if (loading.value) return 'loading'
if (noMore.value) return 'noMore'
return 'more'
})
// 加载数据
const loadData = () => {
console.log('加载数据...')
// 这里可以调用API加载数据
}
// 页面加载
onMounted(() => {
console.log('娱乐交友页面加载完成')
loadData()
})
// 格式化距离
const formatDistance = (distance : number) : string => {
if (distance < 1) {
return '<1km'
} else if (distance < 10) {
return `${distance.toFixed(1)}km`
} else {
return `${Math.floor(distance)}km`
}
}
// 格式化数字
const formatNumber = (num : number) : string => {
if (num >= 10000) {
return (num / 10000).toFixed(1) + 'w'
} else if (num >= 1000) {
return (num / 1000).toFixed(1) + 'k'
}
return num.toString()
}
// 点击搜索
const onSearchClick = () => {
console.log('点击搜索')
uni.navigateTo({
url: '/pages/search/search'
})
}
// 点击消息
const onMessageClick = () => {
console.log('点击消息')
unreadCount.value = 0
uni.navigateTo({
url: '/pages/message/message'
})
}
// 点击轮播图
const onBannerClick = (banner : IBanner) => {
console.log('点击轮播图:', banner.id)
uni.navigateTo({
url: banner.url
})
}
// 点击快速入口
const onEntranceClick = (entrance : IMenu) => {
console.log('点击快速入口:', entrance.text)
if (entrance.url != null) {
uni.navigateTo({
url: entrance.url
})
} else {
uni.showToast({
title: `进入${entrance.text}`,
icon: 'none',
duration: 1500
})
}
}
// 切换筛选
const switchTab = (index : number) => {
console.log('切换筛选:', filters[index].text)
tabActive.value = index
page.value = 1
noMore.value = false
// 这里应该重新加载对应筛选的数据
}
// 显示更多筛选
const showMoreFilters = () => {
console.log('显示更多筛选')
uni.showActionSheet({
itemList: ['最新', '热门', '在线', 'VIP'],
success: (res) => {
console.log('选择了筛选:', res.tapIndex)
}
})
}
// 加入语音房
const joinVoiceRoom = (room : IVoiceRoom) => {
console.log('加入语音房:', room.title)
uni.showModal({
title: '加入语音房',
content: `确定要加入"${room.title}"吗?`,
success: (res) => {
if (res.confirm) {
uni.showToast({
title: '正在加入房间...',
icon: 'none'
})
// 实际应该跳转到语音房页面
}
}
})
}
// 玩游戏
const playGame = (game : IGame) => {
console.log('玩游戏:', game.title)
uni.showToast({
title: `开始${game.title}`,
icon: 'none',
duration: 1500
})
// 实际应该跳转到游戏页面
}
// 下拉刷新
const onRefresh = () => {
console.log('下拉刷新')
refreshing.value = true
setTimeout(() => {
console.log('刷新完成')
refreshing.value = false
page.value = 1
noMore.value = false
uni.showToast({
title: '刷新成功',
icon: 'success',
duration: 2000
})
}, 1500)
}
// 上拉加载更多
const onLoadMore = () => {
console.log('上拉加载更多')
if (loading.value || noMore.value) return
loading.value = true
setTimeout(() => {
page.value++
// 模拟没有更多数据
if (page.value >= 3) {
noMore.value = true
}
loading.value = false
uni.showToast({
title: '加载成功',
icon: 'none',
duration: 1500
})
}, 1000)
}
// 语音通话
const onVoiceCall = () => {
console.log('语音通话')
uni.showActionSheet({
itemList: ['随机语音', '好友语音', '创建语音房'],
success: (res) => {
const actions = ['randomVoice', 'friendVoice', 'createRoom']
const action = actions[res.tapIndex]
console.log('选择了:', action)
}
})
}
// 视频通话
const onVideoCall = () => {
console.log('视频通话')
uni.showModal({
title: '视频通话',
content: '开始视频通话需要消耗流量,是否继续?',
confirmText: '开始通话',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: '/pages/video-call/video-call'
})
}
}
})
}
</script>
<style>
.header-right {
flex-flow: row nowrap;
display: flex;
}
.icon-btn {
position: relative;
width: 60rpx;
height: 60rpx;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
background-color: #f8f8f8;
}
.message-badge {
position: absolute;
top: -5rpx;
right: -5rpx;
min-width: 32rpx;
height: 32rpx;
line-height: 32rpx;
text-align: center;
background-color: #FF3B30;
color: #fff;
font-size: 20rpx;
border-radius: 16rpx;
padding: 0 8rpx;
}
/* 轮播图 */
.banner-swiper {
height: 300rpx;
margin: 10rpx;
border-radius: 20rpx;
overflow: hidden;
}
.banner-image {
width: 100%;
height: 100%;
}
.banner-tag {
position: absolute;
top: 20rpx;
right: 20rpx;
padding: 8rpx 20rpx;
background-color: rgba(255, 107, 107, 0.9);
color: #fff;
font-size: 24rpx;
border-radius: 20rpx;
}
/* 筛选栏 */
.filter-bar {
display: flex;
align-items: center;
flex-flow: row nowrap;
padding: 10rpx 20rpx;
background-color: #fff;
margin: 10rpx;
border-radius: 20rpx;
box-shadow: 0 5rpx 20rpx rgba(0, 0, 0, 0.05);
}
.filter-item {
display: flex;
align-items: center;
padding: 15rpx 30rpx;
margin-right: 20rpx;
border-radius: 40rpx;
background-color: #f8f8f8;
transition: all 0.3s ease;
}
.filter-item.active {
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
color: #fff;
}
.filter-item.active .filter-count {
background-color: rgba(255, 255, 255, 0.2);
color: #fff;
}
.filter-text {
font-size: 28rpx;
font-weight: 500;
}
.filter-count {
margin-left: 10rpx;
padding: 4rpx 12rpx;
background-color: #e0e0e0;
color: #666;
font-size: 20rpx;
border-radius: 20rpx;
}
.filter-more {
width: 60rpx;
height: 60rpx;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
background-color: #f8f8f8;
margin-left: auto;
}
/* 用户列表 */
.user-list {
flex: 1;
padding: 10rpx 20rpx;
border-radius: 20rpx;
}
.section {
border-radius: 20rpx;
box-shadow: 0 5rpx 20rpx rgba(0, 0, 0, 0.05);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.section-subtitle {
font-size: 24rpx;
color: #999;
}
/* 语音房列表 */
.voice-room-list {
display: flex;
flex-direction: column;
}
.voice-room {
margin-bottom: 15rpx;
background-color: #f8f8f8;
border-radius: 20rpx;
overflow: hidden;
transition: transform 0.3s ease;
}
.voice-room:active {
transform: scale(0.99);
}
.room-header {
position: relative;
height: 200rpx;
}
.room-cover {
width: 100%;
height: 100%;
background-color: #e0e0e0;
}
.room-status {
position: absolute;
bottom: 20rpx;
right: 20rpx;
display: flex;
align-items: center;
padding: 8rpx 20rpx;
background-color: rgba(0, 0, 0, 0.7);
border-radius: 20rpx;
}
.room-status .uni-icons {
margin-right: 8rpx;
}
.room-hot {
position: absolute;
top: 20rpx;
left: 20rpx;
padding: 8rpx 20rpx;
background-color: rgba(255, 107, 107, 0.9);
color: #fff;
font-size: 20rpx;
border-radius: 20rpx;
}
.room-info {
padding: 30rpx;
}
.room-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 15rpx;
}
.room-tags {
display: flex;
flex-flow: row wrap;
margin-bottom: 20rpx;
}
.room-tag {
padding: 8rpx 20rpx;
background-color: #e0e0e0;
color: #666;
font-size: 20rpx;
border-radius: 20rpx;
}
.room-users {
display: flex;
flex-flow: row wrap;
align-items: center;
}
.room-user-avatar {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
overflow: hidden;
border: 4rpx solid #fff;
margin-right: -10rpx;
}
.room-user-avatar:first-child {
margin-left: 0;
}
.user-avatar-small {
width: 100%;
height: 100%;
background-color: #e0e0e0;
}
.room-user-more {
width: 60rpx;
height: 60rpx;
line-height: 60rpx;
text-align: center;
background-color: #f0f0f0;
color: #999;
font-size: 20rpx;
border-radius: 50%;
border: 4rpx solid #fff;
margin-left: 10rpx;
}
/* 游戏列表 */
.game-list {
display: flex;
flex-direction: column;
}
.game-item {
display: flex;
background-color: #f8f8f8;
border-radius: 20rpx;
overflow: hidden;
transition: transform 0.3s ease;
margin-top: 15rpx;
}
.game-item:active {
transform: scale(0.99);
}
.game-cover {
position: relative;
height: 200rpx;
}
.game-image {
width: 100%;
height: 100%;
background-color: #e0e0e0;
}
.game-playing {
position: absolute;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 10rpx;
background-color: rgba(0, 0, 0, 0.7);
color: #fff;
font-size: 20rpx;
}
.game-playing .uni-icons {
margin-right: 8rpx;
}
.game-info {
flex: 1;
padding: 30rpx;
}
.game-header {
display: flex;
align-items: center;
flex-flow: row wrap;
justify-content: space-between;
margin-bottom: 10rpx;
}
.game-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.game-reward {
padding: 8rpx 20rpx;
background-color: #FF9500;
color: #fff;
font-size: 20rpx;
border-radius: 20rpx;
}
.game-desc {
font-size: 24rpx;
color: #666;
margin-bottom: 15rpx;
line-height: 1.4;
}
.game-tags {
display: flex;
flex-flow: row wrap;
flex-wrap: wrap;
}
.game-tag {
padding: 8rpx 20rpx;
background-color: #e0e0e0;
color: #666;
font-size: 20rpx;
border-radius: 20rpx;
}
/* 加载状态 */
.load-status {
padding: 30rpx 0;
display: flex;
justify-content: center;
}
</style>