581 lines
14 KiB
Vue
581 lines
14 KiB
Vue
<template>
|
||
<view class="game-detail-page">
|
||
<!-- 顶部导航 -->
|
||
<view class="nav-bar">
|
||
<view class="nav-back" @click="goBack">
|
||
<uni-icons type="arrowleft" size="24" color="#fff"></uni-icons>
|
||
</view>
|
||
<view class="nav-title">对局详情</view>
|
||
<view class="nav-actions">
|
||
<uni-icons type="more" size="24" color="#fff"></uni-icons>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 玩家列表容器 - 添加滚动条功能 -->
|
||
<view class="player-list-container">
|
||
<!-- 水平滚动容器 -->
|
||
<scroll-view
|
||
class="player-scroll-view"
|
||
scroll-x="true"
|
||
:scroll-left="scrollLeft"
|
||
@scroll="onScroll"
|
||
:show-scrollbar="false"
|
||
scroll-with-animation
|
||
>
|
||
<view class="player-list-content">
|
||
<!-- 玩家信息表头 -->
|
||
<view class="player-header">
|
||
<view class="header-row">
|
||
<view class="header-cell header-label">玩家 ({{ players.length }}位)</view>
|
||
<view class="player-columns">
|
||
<view class="player-column" v-for="player in players" :key="player.id">
|
||
<view class="player-header-info">
|
||
<!-- 统一头像容器样式 -->
|
||
<view class="avatar-container">
|
||
<image class="player-avatar" :src="player.avatar" mode="aspectFill"></image>
|
||
</view>
|
||
<text class="player-name">{{ player.name }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 对局详情表格 -->
|
||
<view class="detail-table">
|
||
<!-- 总分行 -->
|
||
<view class="table-row">
|
||
<view class="row-label">总分</view>
|
||
<view class="row-cells">
|
||
<view class="score-cell" v-for="player in players" :key="player.id">
|
||
{{ formatScore(player.totalScore) }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 每局得分行 -->
|
||
<view class="table-row" v-for="(round, roundIndex) in gameRounds" :key="roundIndex">
|
||
<view class="row-label">第{{ roundIndex + 1 }}局</view>
|
||
<view class="row-cells">
|
||
<view class="score-cell" v-for="player in players" :key="player.id">
|
||
{{ formatScore(getPlayerRoundScore(roundIndex, player.id)) }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 自定义可拖动滚动条 -->
|
||
<view class="custom-scrollbar" v-if="showScrollIndicator">
|
||
<view
|
||
class="scroll-track"
|
||
@touchstart="onTrackTouchStart"
|
||
@touchmove.prevent="onTrackTouchMove"
|
||
@touchend="onTrackTouchEnd"
|
||
:id="trackId"
|
||
>
|
||
<view
|
||
class="scroll-thumb"
|
||
:style="{
|
||
left: thumbPosition + '%',
|
||
width: thumbWidth + '%'
|
||
}"
|
||
@touchstart="onThumbTouchStart"
|
||
@touchmove.prevent="onThumbTouchMove"
|
||
@touchend="onThumbTouchEnd"
|
||
></view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部分享按钮 -->
|
||
<view class="action-buttons">
|
||
<button class="share-btn" @click="shareDetail">分享对局详情</button>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted, computed, nextTick, watch } from 'vue'
|
||
import { onShow } from '@dcloudio/uni-app'
|
||
|
||
// 玩家数据
|
||
const players = ref([])
|
||
// 对局记录
|
||
const gameRounds = ref([])
|
||
|
||
// 滚动条相关数据
|
||
const scrollLeft = ref(0)
|
||
const scrollWidth = ref(0)
|
||
const scrollViewWidth = ref(0)
|
||
const trackId = ref('scroll-track-' + Date.now())
|
||
|
||
// 滚动条拖动状态
|
||
const isDragging = ref(false)
|
||
const dragStartX = ref(0)
|
||
const dragStartScrollLeft = ref(0)
|
||
const trackRect = ref({ left: 0, width: 0 })
|
||
|
||
// 计算属性:是否显示滚动条指示器
|
||
const showScrollIndicator = computed(() => {
|
||
return scrollWidth.value > scrollViewWidth.value
|
||
})
|
||
|
||
// 计算属性:滚动条滑块宽度
|
||
const thumbWidth = computed(() => {
|
||
if (scrollWidth.value === 0) return 100
|
||
const widthRatio = (scrollViewWidth.value / scrollWidth.value) * 100
|
||
return Math.max(20, Math.min(100, widthRatio)) // 最小宽度20%,最大100%
|
||
})
|
||
|
||
// 计算属性:滚动条滑块位置
|
||
const thumbPosition = computed(() => {
|
||
if (scrollWidth.value === 0 || scrollWidth.value <= scrollViewWidth.value) return 0
|
||
const maxScroll = scrollWidth.value - scrollViewWidth.value
|
||
return (scrollLeft.value / maxScroll) * (100 - thumbWidth.value)
|
||
})
|
||
|
||
// 格式化分数显示
|
||
const formatScore = (score) => {
|
||
if (score === 0) return '0'
|
||
return score > 0 ? `+${score}` : `${score}`
|
||
}
|
||
|
||
// 获取玩家在某一局的得分
|
||
const getPlayerRoundScore = (roundIndex, playerId) => {
|
||
if (!gameRounds.value[roundIndex]) return 0
|
||
const playerScore = gameRounds.value[roundIndex].find(item => item.playerId === playerId)
|
||
return playerScore ? playerScore.score : 0
|
||
}
|
||
|
||
// 计算玩家总分
|
||
const calculateTotalScores = () => {
|
||
players.value.forEach(player => {
|
||
let total = 0
|
||
gameRounds.value.forEach(round => {
|
||
const roundScore = round.find(item => item.playerId === player.id)
|
||
if (roundScore) {
|
||
total += roundScore.score
|
||
}
|
||
})
|
||
player.totalScore = total
|
||
})
|
||
}
|
||
|
||
// 页面加载时初始化数据
|
||
onMounted(() => {
|
||
// 从本地存储获取玩家数据和对局记录
|
||
const savedPlayers = uni.getStorageSync('players')
|
||
const savedRounds = uni.getStorageSync('gameRounds')
|
||
|
||
if (savedPlayers) {
|
||
players.value = savedPlayers
|
||
} else {
|
||
// 如果没有数据,使用默认数据
|
||
players.value = [
|
||
{
|
||
id: 1,
|
||
name: '玩家80061',
|
||
avatar: 'https://ts1.tc.mm.bing.net/th/id/OIP-C.QQG4bvcAR3CJ0WeQULA9UQAAAA?w=275&h=211&c=8&rs=1&qlt=90&o=6&cb=ucfimgc1&dpr=1.5&pid=3.1&rm=2',
|
||
totalScore: 0
|
||
},
|
||
{
|
||
id: 2,
|
||
name: '2',
|
||
avatar: 'https://tse2-mm.cn.bing.net/th/id/OIP-C.Pbhgd_vCFFNQWXi7y-HynAAAAA?w=209&h=209&c=7&r=0&o=7&cb=ucfimgc2&dpr=1.5&pid=1.7&rm=3',
|
||
totalScore: 0
|
||
}
|
||
]
|
||
}
|
||
|
||
if (savedRounds) {
|
||
gameRounds.value = savedRounds
|
||
calculateTotalScores()
|
||
} else {
|
||
// 如果没有数据,使用默认数据
|
||
gameRounds.value = [
|
||
[
|
||
{ playerId: 1, score: 3 },
|
||
{ playerId: 2, score: -3 }
|
||
]
|
||
]
|
||
calculateTotalScores()
|
||
}
|
||
|
||
// 监听玩家数量变化
|
||
watch(() => players.value.length, () => {
|
||
setTimeout(() => {
|
||
updateScrollDimensions()
|
||
}, 300)
|
||
})
|
||
|
||
// 更新滚动区域尺寸
|
||
setTimeout(() => {
|
||
updateScrollDimensions()
|
||
}, 1000)
|
||
})
|
||
|
||
onShow(() => {
|
||
// 更新滚动区域
|
||
setTimeout(() => {
|
||
updateScrollDimensions()
|
||
getTrackRect()
|
||
}, 300)
|
||
})
|
||
|
||
// 滚动事件处理
|
||
const onScroll = (e) => {
|
||
scrollLeft.value = e.detail.scrollLeft
|
||
}
|
||
|
||
// 更新滚动区域尺寸
|
||
const updateScrollDimensions = () => {
|
||
console.log('开始更新滚动区域尺寸...')
|
||
|
||
nextTick(() => {
|
||
setTimeout(() => {
|
||
const query = uni.createSelectorQuery()
|
||
query.select('.player-scroll-view').boundingClientRect()
|
||
query.select('.player-list-content').boundingClientRect()
|
||
query.exec((res) => {
|
||
if (res[0] && res[1]) {
|
||
scrollViewWidth.value = res[0].width
|
||
scrollWidth.value = res[1].width
|
||
|
||
console.log('滚动区域计算完成:', {
|
||
scrollViewWidth: scrollViewWidth.value,
|
||
scrollWidth: scrollWidth.value,
|
||
showScrollIndicator: scrollWidth.value > scrollViewWidth.value,
|
||
playersCount: players.value.length
|
||
})
|
||
|
||
// 强制更新显示状态
|
||
if (scrollWidth.value > scrollViewWidth.value) {
|
||
console.log('需要显示滚动条')
|
||
}
|
||
} else {
|
||
console.error('获取滚动区域尺寸失败')
|
||
}
|
||
})
|
||
}, 100)
|
||
})
|
||
}
|
||
|
||
// 获取轨道位置信息
|
||
const getTrackRect = () => {
|
||
return new Promise((resolve) => {
|
||
setTimeout(() => {
|
||
const query = uni.createSelectorQuery()
|
||
query.select('.custom-scrollbar .scroll-track').boundingClientRect()
|
||
query.exec((res) => {
|
||
if (res[0]) {
|
||
trackRect.value = {
|
||
left: res[0].left,
|
||
width: res[0].width
|
||
}
|
||
resolve(trackRect.value)
|
||
} else {
|
||
resolve(null)
|
||
}
|
||
})
|
||
}, 100)
|
||
})
|
||
}
|
||
|
||
// 滚动条滑块触摸开始
|
||
const onThumbTouchStart = async (e) => {
|
||
isDragging.value = true
|
||
dragStartX.value = e.touches[0].clientX
|
||
dragStartScrollLeft.value = scrollLeft.value
|
||
|
||
await getTrackRect()
|
||
|
||
e.stopPropagation()
|
||
}
|
||
|
||
// 滚动条滑块触摸移动
|
||
const onThumbTouchMove = (e) => {
|
||
if (!isDragging.value) return
|
||
|
||
const deltaX = e.touches[0].clientX - dragStartX.value
|
||
const maxScroll = Math.max(0, scrollWidth.value - scrollViewWidth.value)
|
||
|
||
if (maxScroll > 0 && trackRect.value.width > 0) {
|
||
const scrollPercent = deltaX / trackRect.value.width
|
||
const newScrollLeft = dragStartScrollLeft.value + (scrollPercent * maxScroll)
|
||
scrollLeft.value = Math.max(0, Math.min(maxScroll, newScrollLeft))
|
||
}
|
||
|
||
e.stopPropagation()
|
||
}
|
||
|
||
// 滚动条滑块触摸结束
|
||
const onThumbTouchEnd = (e) => {
|
||
isDragging.value = false
|
||
e.stopPropagation()
|
||
}
|
||
|
||
// 滚动条轨道触摸开始
|
||
const onTrackTouchStart = async (e) => {
|
||
const rect = await getTrackRect()
|
||
if (!rect) return
|
||
|
||
const clickX = e.touches[0].clientX - rect.left
|
||
const thumbWidthPx = (thumbWidth.value / 100) * rect.width
|
||
const thumbCenter = (thumbPosition.value / 100) * rect.width + thumbWidthPx / 2
|
||
|
||
const maxScroll = Math.max(0, scrollWidth.value - scrollViewWidth.value)
|
||
|
||
if (maxScroll > 0) {
|
||
if (clickX < thumbCenter) {
|
||
scrollLeft.value = Math.max(0, scrollLeft.value - scrollViewWidth.value * 0.8)
|
||
} else {
|
||
scrollLeft.value = Math.min(maxScroll, scrollLeft.value + scrollViewWidth.value * 0.8)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 滚动条轨道触摸移动
|
||
const onTrackTouchMove = (e) => {
|
||
e.stopPropagation()
|
||
}
|
||
|
||
// 滚动条轨道触摸结束
|
||
const onTrackTouchEnd = (e) => {
|
||
e.stopPropagation()
|
||
}
|
||
|
||
// 返回上一页
|
||
const goBack = () => {
|
||
uni.navigateBack()
|
||
}
|
||
|
||
// 分享对局详情
|
||
const shareDetail = () => {
|
||
uni.showToast({
|
||
title: '分享功能待实现',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.game-detail-page {
|
||
background-color: #fff;
|
||
min-height: 100vh;
|
||
padding-bottom: 200rpx; /* 给底部按钮留出空间 */
|
||
}
|
||
|
||
.nav-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 20rpx 30rpx;
|
||
background-color: #41479b;
|
||
color: #fff;
|
||
|
||
.nav-back, .nav-actions {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.nav-title {
|
||
font-size: 32rpx;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
|
||
/* 玩家列表容器 */
|
||
.player-list-container {
|
||
padding: 0 30rpx 30rpx;
|
||
position: relative;
|
||
}
|
||
|
||
/* 滚动视图样式 */
|
||
.player-scroll-view {
|
||
width: 100%;
|
||
white-space: nowrap;
|
||
|
||
// 隐藏原生滚动条
|
||
::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
}
|
||
|
||
.player-list-content {
|
||
display: inline-block;
|
||
min-width: 100%;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.player-header {
|
||
background-color: #f5f5f5;
|
||
border-bottom: 1rpx solid #eee;
|
||
|
||
.header-row {
|
||
display: flex;
|
||
padding: 20rpx 0;
|
||
|
||
.header-label {
|
||
width: 200rpx;
|
||
flex: none;
|
||
font-size: 28rpx;
|
||
color: #000;
|
||
font-weight: 500;
|
||
display: flex;
|
||
align-items: center;
|
||
padding-left: 20rpx;
|
||
}
|
||
|
||
.player-columns {
|
||
display: flex;
|
||
}
|
||
|
||
.player-column {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
width: 140rpx;
|
||
flex-shrink: 0;
|
||
padding: 0 10rpx;
|
||
}
|
||
|
||
.player-header-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
|
||
/* 统一头像容器样式 */
|
||
.avatar-container {
|
||
position: relative;
|
||
width: 110rpx;
|
||
height: 110rpx;
|
||
margin-bottom: 10rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.player-avatar {
|
||
width: 100rpx;
|
||
height: 100rpx;
|
||
border-radius: 50%;
|
||
border: 5rpx solid #fff;
|
||
box-shadow: 0 6rpx 20rpx rgba(65, 71, 155, 0.3);
|
||
background: linear-gradient(135deg, #41479b 0%, #8b91e2 100%);
|
||
transition: all 0.3s ease;
|
||
}
|
||
}
|
||
|
||
.player-name {
|
||
font-size: 24rpx;
|
||
color: #000;
|
||
text-align: center;
|
||
word-break: break-all;
|
||
white-space: normal;
|
||
max-width: 120rpx;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.detail-table {
|
||
.table-row {
|
||
display: flex;
|
||
padding: 20rpx 0;
|
||
border-bottom: 1rpx solid #eee;
|
||
|
||
&:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.row-label {
|
||
width: 200rpx;
|
||
flex: none;
|
||
font-size: 28rpx;
|
||
color: #000;
|
||
display: flex;
|
||
align-items: center;
|
||
font-weight: 500;
|
||
padding-left: 20rpx;
|
||
}
|
||
|
||
.row-cells {
|
||
display: flex;
|
||
|
||
.score-cell {
|
||
width: 140rpx;
|
||
flex-shrink: 0;
|
||
text-align: center;
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 0 10rpx;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 自定义可拖动滚动条样式 - 完全复制单人房间的样式 */
|
||
.custom-scrollbar {
|
||
margin-top: 20rpx;
|
||
padding: 0 20rpx;
|
||
|
||
.scroll-track {
|
||
height: 12rpx;
|
||
background-color: #f0f0f0;
|
||
border-radius: 6rpx;
|
||
position: relative;
|
||
|
||
// 添加点击区域扩展
|
||
&::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: -10rpx;
|
||
bottom: -10rpx;
|
||
left: 0;
|
||
right: 0;
|
||
}
|
||
}
|
||
|
||
.scroll-thumb {
|
||
position: absolute;
|
||
height: 100%;
|
||
background-color: #41479b;
|
||
border-radius: 6rpx;
|
||
transition: all 0.1s ease;
|
||
|
||
// 添加悬停效果
|
||
&:active {
|
||
background-color: #33367a;
|
||
transform: scale(1.1);
|
||
}
|
||
}
|
||
}
|
||
|
||
.action-buttons {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
padding: 30rpx;
|
||
background-color: #fff;
|
||
border-top: 1rpx solid #eee;
|
||
|
||
.share-btn {
|
||
width: 100%;
|
||
height: 80rpx;
|
||
background-color: #ffc107;
|
||
color: #333;
|
||
font-size: 32rpx;
|
||
font-weight: 500;
|
||
border-radius: 8rpx;
|
||
}
|
||
}
|
||
</style> |