Files
2025-12-16 09:32:55 +08:00

581 lines
14 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>