gameSeriveUniapp/src/pages-user/player/list.vue
2026-01-12 16:48:28 +08:00

355 lines
8.2 KiB
Vue

<template>
<view class="player-list">
<navbar title="代练列表" />
<view class="content">
<!-- 搜索和筛选 -->
<view class="search-section">
<view class="search-bar">
<text class="search-icon">🔍</text>
<input
class="search-input"
v-model="searchKeyword"
placeholder="搜索代练昵称或游戏"
placeholder-class="placeholder"
@confirm="handleSearch"
/>
</view>
<!-- 筛选项 -->
<view class="filter-bar">
<view class="filter-item" @click="showGameFilter = true">
<text class="filter-text">{{ selectedGame || '游戏' }}</text>
<text class="arrow">▼</text>
</view>
<view class="filter-item" @click="showSortFilter = true">
<text class="filter-text">{{ selectedSort }}</text>
<text class="arrow">▼</text>
</view>
<view class="filter-item" :class="{ active: onlineOnly }" @click="toggleOnline">
<text class="filter-text">仅在线</text>
</view>
</view>
</view>
<!-- 代练列表 -->
<scroll-view class="list-scroll" scroll-y @scrolltolower="loadMore">
<view class="player-list-content">
<player-card
v-for="player in filteredPlayers"
:key="player.id"
:player="player"
@click="goToPlayerDetail"
/>
<!-- 空状态 -->
<empty v-if="filteredPlayers.length === 0" icon="👤" text="暂无代练" description="没有找到符合条件的代练" />
<!-- 加载更多 -->
<view class="load-more" v-if="hasMore && filteredPlayers.length > 0">
<text>加载更多...</text>
</view>
</view>
</scroll-view>
</view>
<!-- 游戏筛选弹窗 -->
<view class="filter-modal" v-if="showGameFilter" @click="showGameFilter = false">
<view class="filter-content" @click.stop>
<view class="filter-title">选择游戏</view>
<view class="filter-options">
<view
class="filter-option"
:class="{ active: selectedGame === '' }"
@click="selectGame('')"
>
全部游戏
</view>
<view
class="filter-option"
v-for="game in games"
:key="game"
:class="{ active: selectedGame === game }"
@click="selectGame(game)"
>
{{ game }}
</view>
</view>
</view>
</view>
<!-- 排序筛选弹窗 -->
<view class="filter-modal" v-if="showSortFilter" @click="showSortFilter = false">
<view class="filter-content" @click.stop>
<view class="filter-title">排序方式</view>
<view class="filter-options">
<view
class="filter-option"
v-for="sort in sortOptions"
:key="sort.value"
:class="{ active: selectedSort === sort.label }"
@click="selectSort(sort)"
>
{{ sort.label }}
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useServiceStore, usePlayerStore } from '@/store/modules/service'
import type { Player } from '@/types'
import Navbar from '@/components/navbar/index.vue'
import PlayerCard from '@/components/player-card/index.vue'
import Empty from '@/components/empty/index.vue'
const serviceStore = useServiceStore()
const playerStore = usePlayerStore()
const searchKeyword = ref('')
const selectedGame = ref('')
const selectedSort = ref('综合排序')
const onlineOnly = ref(false)
const hasMore = ref(false)
const showGameFilter = ref(false)
const showSortFilter = ref(false)
const players = ref<Player[]>([])
// 游戏列表
const games = ref(['王者荣耀', '英雄联盟', '和平精英', '原神', 'CF'])
// 排序选项
const sortOptions = ref([
{ label: '综合排序', value: 'default' },
{ label: '评分最高', value: 'rating' },
{ label: '接单最多', value: 'orders' },
{ label: '完成率最高', value: 'completeRate' }
])
// 过滤后的代练列表
const filteredPlayers = computed(() => {
let result = [...players.value]
// 搜索关键词过滤
if (searchKeyword.value) {
const keyword = searchKeyword.value.toLowerCase()
result = result.filter(
p => p.name.toLowerCase().includes(keyword) || p.gameName.toLowerCase().includes(keyword)
)
}
// 游戏过滤
if (selectedGame.value) {
result = result.filter(p => p.gameName === selectedGame.value)
}
// 仅在线过滤
if (onlineOnly.value) {
result = result.filter(p => p.isOnline)
}
// 排序
if (selectedSort.value === '评分最高') {
result.sort((a, b) => b.rating - a.rating)
} else if (selectedSort.value === '接单最多') {
result.sort((a, b) => b.orderCount - a.orderCount)
} else if (selectedSort.value === '完成率最高') {
result.sort((a, b) => b.completeRate - a.completeRate)
}
return result
})
// 搜索
const handleSearch = () => {
console.log('搜索:', searchKeyword.value)
}
// 选择游戏
const selectGame = (game: string) => {
selectedGame.value = game
showGameFilter.value = false
}
// 选择排序
const selectSort = (sort: { label: string; value: string }) => {
selectedSort.value = sort.label
showSortFilter.value = false
}
// 切换仅在线
const toggleOnline = () => {
onlineOnly.value = !onlineOnly.value
}
// 跳转到代练详情
const goToPlayerDetail = (player: Player) => {
uni.navigateTo({ url: `/pages-user/player/detail?id=${player.id}` })
}
// 加载更多
const loadMore = () => {
if (hasMore.value) {
console.log('加载更多代练...')
}
}
onMounted(async () => {
try {
players.value = await playerStore.getPlayerList()
} catch (error) {
uni.showToast({ title: '加载失败', icon: 'none' })
}
})
</script>
<style lang="scss" scoped>
.player-list {
min-height: 100vh;
background: $uni-bg-color-grey;
}
.content {
height: calc(100vh - 88rpx);
display: flex;
flex-direction: column;
}
// 搜索区域
.search-section {
background: #fff;
padding: 20rpx 24rpx;
border-bottom: 1rpx solid $uni-border-color-light;
}
.search-bar {
display: flex;
align-items: center;
gap: 16rpx;
padding: 16rpx 24rpx;
background: $uni-bg-color-grey;
border-radius: 48rpx;
margin-bottom: 20rpx;
.search-icon {
font-size: 32rpx;
}
.search-input {
flex: 1;
font-size: 28rpx;
color: $uni-text-color;
}
.placeholder {
color: $uni-text-color-placeholder;
}
}
.filter-bar {
display: flex;
gap: 16rpx;
}
.filter-item {
display: flex;
align-items: center;
gap: 8rpx;
padding: 12rpx 24rpx;
background: $uni-bg-color-grey;
border-radius: 32rpx;
font-size: 24rpx;
.filter-text {
color: $uni-text-color-grey;
}
.arrow {
font-size: 20rpx;
color: $uni-text-color-placeholder;
}
&.active {
background: rgba(102, 126, 234, 0.1);
.filter-text {
color: $uni-color-primary;
font-weight: bold;
}
}
}
// 列表区域
.list-scroll {
flex: 1;
padding: 24rpx;
}
.player-list-content {
min-height: 100%;
}
// 加载更多
.load-more {
padding: 32rpx;
text-align: center;
font-size: 24rpx;
color: $uni-text-color-placeholder;
}
// 筛选弹窗
.filter-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: flex-end;
z-index: 9999;
}
.filter-content {
width: 100%;
max-height: 70vh;
background: #fff;
border-radius: 24rpx 24rpx 0 0;
padding: 32rpx;
padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
}
.filter-title {
font-size: 32rpx;
font-weight: bold;
color: $uni-text-color;
margin-bottom: 32rpx;
text-align: center;
}
.filter-options {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.filter-option {
padding: 24rpx;
background: $uni-bg-color-grey;
border-radius: $uni-border-radius-base;
font-size: 28rpx;
color: $uni-text-color-grey;
text-align: center;
&.active {
background: rgba(102, 126, 234, 0.1);
color: $uni-color-primary;
font-weight: bold;
}
}
</style>