Files
-----/scoring/pages/index/singleplay/singleplay.vue
2025-11-11 17:07:13 +08:00

751 lines
18 KiB
Vue
Raw 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="singleplay">
<!-- 顶部功能区 -->
<view class="top-functions">
<view class="function-btn" @click="addPlayer">
<image src="/static/add.png" class="btn-icon" mode="aspectFill"></image>
<text>添加玩家</text>
</view>
<view class="function-btn" @click="transferScorer">
<image src="/static/transfer.png" class="btn-icon" mode="aspectFill"></image>
<text>转让计分员</text>
</view>
<view class="switch-group">
<text>语音播报</text>
<switch :checked="voiceBroadcast" @change="voiceBroadcast = $event.detail.value" color="#007aff" />
</view>
<view class="switch-group">
<text>台板</text>
<switch :checked="tableMode" @change="tableMode = $event.detail.value" color="#007aff" />
</view>
</view>
<!-- 对局记录区域 -->
<view class="game-record">
<view class="record-title">
对局记录
<text class="hint">点击对局分数进行修改</text>
</view>
<!-- 用户信息区域 -->
<view class="user-section">
<text class="user-hint">点击自己头像编辑用户头像和昵称~</text>
<view class="user-info" @click="editUserInfo">
<image class="user-avatar" :src="currentUser.avatar" mode="aspectFill"></image>
<view class="user-details">
<view class="user-name">
{{ currentUser.name }}
<text class="self-tag">自己</text>
</view>
</view>
</view>
</view>
<!-- 玩家列表 -->
<view class="players-section">
<view class="section-header">
<text class="header-title">玩家</text>
<text class="player-count">({{ players.length }})</text>
</view>
<!-- 玩家表格 -->
<view class="players-table">
<view class="table-header">
<view class="player-column">玩家</view>
<view class="score-column">总分</view>
</view>
<view class="table-body">
<view v-for="(player, index) in displayPlayers" :key="player.id" class="player-row">
<view class="player-info">
<image :src="player.avatar" mode="aspectFill"></image>
<text>{{ player.name }}</text>
</view>
<view class="score-display" @click="editScore(index)">
{{ player.score }}
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 底部操作按钮 -->
<view class="bottom-buttons">
<button class="start-btn" type="primary" @click="startScoring">开局计分</button>
<button class="settle-btn" type="default" @click="settleRoom">结算房间</button>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
// 状态管理
const voiceBroadcast = ref(false)
const tableMode = ref(false)
const roomId = ref('')
const currentUser = ref({
id: 'self',
name: '玩家50950',
avatar: 'https://t14.baidu.com/it/u=3165460156,649373630&fm=224&app=112&f=JPEG?w=500&h=500'
})
// 玩家列表(初始包含自己)
const players = ref([
{
id: 'self',
name: '玩家50950',
avatar: 'https://t14.baidu.com/it/u=3165460156,649373630&fm=224&app=112&f=JPEG?w=500&h=500',
score: 0
}
])
// 台板玩家对象
const tablePlayer = {
id: 'table',
name: '台板',
avatar: '/static/robot.png',
score: 0
}
// 计算属性:动态返回包含或不包含台板玩家的列表
const displayPlayers = computed(() => {
if (tableMode.value) {
// 检查台板玩家是否已存在,避免重复添加
const hasTablePlayer = players.value.some(player => player.id === 'table')
if (hasTablePlayer) {
return players.value
}
// 添加台板玩家
return [...players.value, tablePlayer]
} else {
// 移除台板玩家
return players.value.filter(player => player.id !== 'table')
}
})
// 添加玩家
const addPlayer = () => {
console.log('添加玩家')
// 生成默认玩家名
const defaultPlayerName = '请输入玩家名称'
// 弹出对话框让用户输入玩家名称
uni.showModal({
title: '添加玩家',
editable: true,
placeholderText: defaultPlayerName,
// #ifdef MP-WEIXIN
confirmText: '添加',
cancelText: '取消',
autofocus: true, // 微信小程序下自动聚焦输入框
// #endif
success: (res) => {
if (res.confirm) {
// 获取用户输入的名称,如果为空则使用默认名称
const playerName = res.content && res.content.trim() !== '' ? res.content.trim() : defaultPlayerName
// 创建新玩家
const newPlayer = {
id: `player_${Date.now()}`,
name: playerName,
avatar: '/static/people.png',
score: 0
}
// 添加到玩家列表
players.value.push(newPlayer)
// 语音提示或普通提示(如果开启)
if (voiceBroadcast.value) {
uni.showToast({
title: `已添加玩家 ${newPlayer.name}`,
icon: 'none'
})
// 这里可以添加语音播报逻辑
} else {
uni.showToast({
title: `已添加玩家 ${newPlayer.name}`,
icon: 'none'
})
}
}
},
fail: (err) => {
console.error('添加玩家失败:', err)
}
})
}
// 转让计分员
const transferScorer = () => {
console.log('转让计分员')
// 显示提示对话框
uni.showModal({
title: '提示',
content: '房间内暂无扫码或分享加入房间的玩家,无法转让计分员。',
showCancel: false,
// #ifdef MP-WEIXIN
confirmText: '确定',
// #endif
success: (res) => {
if (res.confirm) {
console.log('用户确认提示')
}
},
fail: (err) => {
console.error('显示提示失败:', err)
}
})
}
// 编辑用户信息 - 跳转到change页面
const editUserInfo = () => {
console.log('跳转到用户信息编辑页面')
// 跳转到change页面并传递当前用户信息
uni.navigateTo({
url: `/pages/index/singleplay/change?userData=${encodeURIComponent(JSON.stringify(currentUser.value))}`
})
}
// 更新用户数据 - 从change页面返回时调用
const updateUserData = (updatedUser) => {
console.log('更新用户数据', updatedUser)
// 更新当前用户信息
currentUser.value = { ...updatedUser }
// 更新玩家列表中的用户信息
const selfPlayerIndex = players.value.findIndex(p => p.id === 'self')
if (selfPlayerIndex !== -1) {
players.value[selfPlayerIndex] = {
...players.value[selfPlayerIndex],
name: updatedUser.name,
avatar: updatedUser.avatar
}
}
// 当启用台板模式时,确保台板玩家信息正确
if (tableMode.value) {
const tablePlayerIndex = players.value.findIndex(p => p.id === 'table')
if (tablePlayerIndex === -1) {
// 如果需要,重新添加台板玩家(这通常不会发生,因为计算属性会处理)
players.value.push(tablePlayer)
}
}
}
// 头像和昵称编辑功能已迁移到change页面
// 编辑分数
const editScore = (index) => {
console.log('编辑分数', index)
const player = displayPlayers.value[index]
// 检查是否是台板玩家
if (player.id === 'table') {
// 台板玩家的分数编辑逻辑
uni.showModal({
title: '修改台板分数',
content: `当前分数: ${player.score}`,
editable: true,
placeholderText: '请输入新分数',
success: (res) => {
if (res.confirm && res.content !== null) {
const newScore = parseInt(res.content)
if (!isNaN(newScore)) {
// 找到台板玩家在原始数组中的位置
const tableIndex = players.value.findIndex(p => p.id === 'table')
if (tableIndex !== -1) {
players.value[tableIndex].score = newScore
} else {
// 如果台板玩家不在原始数组中,创建一个副本并更新
tablePlayer.score = newScore
}
// 语音播报分数(如果开启)
if (voiceBroadcast.value) {
console.log(`语音播报: 台板 分数更新为 ${newScore}`)
}
} else {
uni.showToast({
title: '请输入有效的数字',
icon: 'none'
})
}
}
}
})
return
}
// 普通玩家的分数编辑逻辑
uni.showModal({
title: '修改分数',
content: `${player.score}`,
editable: true,
placeholderText: '请输入新分数',
success: (res) => {
if (res.confirm && res.content !== null) {
const newScore = parseInt(res.content)
if (!isNaN(newScore)) {
// 找到对应的普通玩家
const playerIndex = players.value.findIndex(p => p.id === player.id)
if (playerIndex !== -1) {
players.value[playerIndex].score = newScore
}
// 语音播报分数(如果开启)
if (voiceBroadcast.value) {
console.log(`语音播报: ${player.name} 分数更新为 ${newScore}`)
}
} else {
uni.showToast({
title: '请输入有效的数字',
icon: 'none'
})
}
}
}
})
}
// 开局计分
const startScoring = () => {
console.log('开局计分')
// 检查是否有足够的玩家至少需要2个玩家才能开始计分
if (displayPlayers.value.length < 2) {
uni.showToast({
title: '玩家数不超过两个人时无法点击进行开始计分',
icon: 'none'
})
return
}
// 重置所有玩家分数
players.value.forEach(player => {
player.score = 0
})
// 重置台板玩家分数(即使不在原始数组中)
if (tableMode.value) {
tablePlayer.score = 0
}
// 准备要传递的玩家数据
const playersToPass = [...displayPlayers.value]
// 保存到临时存储,用于页面间数据传递
uni.setStorageSync('currentPlayers', JSON.stringify(playersToPass))
// 跳转到计分页面
uni.navigateTo({
url: '/pages/index/singleplay/scoring'
})
}
// 结算房间
const settleRoom = () => {
console.log('结算房间')
// 显示结算结果使用displayPlayers确保包含台板玩家
let result = '结算结果:\n'
displayPlayers.value.forEach(player => {
result += `${player.name}: ${player.score}\n`
})
uni.showModal({
title: '结算',
content: result,
confirmText: '保存记录',
cancelText: '关闭',
success: (res) => {
if (res.confirm) {
// 这里可以保存记录到数据库
uni.showToast({
title: '记录已保存',
icon: 'success'
})
}
}
})
}
// 生命周期
onMounted(() => {
console.log('单人模式页面加载完成')
// 可以在这里初始化数据或加载用户信息
// 生成随机房间号4位数字
roomId.value = Math.floor(1000 + Math.random() * 9000).toString()
// 动态设置导航栏标题
uni.setNavigationBarTitle({
title: `单人 - ${roomId.value}号房间`
})
// 监听页面返回事件,用于接收从计分页面传回的更新后的分数
const updateListener = () => {
// 尝试获取更新后的玩家数据
const updatedPlayers = uni.getStorageSync('updatedPlayers')
if (updatedPlayers) {
try {
const parsedPlayers = JSON.parse(updatedPlayers)
console.log('收到更新的玩家数据:', parsedPlayers)
// 更新玩家列表中的分数
parsedPlayers.forEach(updatedPlayer => {
// 查找对应的玩家
const playerIndex = players.value.findIndex(p => p.id === updatedPlayer.id)
if (playerIndex !== -1) {
// 更新普通玩家的分数
players.value[playerIndex].score = updatedPlayer.score
} else if (updatedPlayer.id === 'table') {
// 更新台板玩家的分数
tablePlayer.score = updatedPlayer.score
}
})
// 清除临时存储的数据
uni.removeStorageSync('updatedPlayers')
} catch (error) {
console.error('解析更新的玩家数据失败:', error)
}
}
}
// 监听页面显示事件
uni.$on('updatePlayers', updateListener)
// 监听页面显示生命周期
uni.onShow(() => {
updateListener()
})
// 组件卸载时移除监听
onUnmounted(() => {
uni.$off('updatePlayers', updateListener)
})
})
</script>
<style lang="less" scoped>
.singleplay {
width: 750rpx;
margin: 0;
padding: 20rpx;
box-sizing: border-box;
background-color: #f8f8f8;
min-height: 100vh;
display: flex;
flex-direction: column;
/* #ifdef MP-WEIXIN */
user-select: none;
/* #endif */
}
/* 顶部功能区 */
.top-functions {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 100%;
//padding: 10rpx 15rpx;
margin-bottom: 20rpx;
box-sizing: border-box;
overflow-x: auto;
.function-btn {
display: flex;
flex-direction: row;
align-items: center;
gap: 5rpx;
padding: 8rpx 8rpx;
background-color: #fff;
border-radius: 10rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
margin: 0 8rpx;
transition: all 0.3s ease;
white-space: nowrap;
flex-shrink: 0;
// 图标样式
.btn-icon {
width: 30rpx;
height: 30rpx;
}
// 文本样式
text {
font-size: 22rpx;
color: #333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
// 微信小程序特有点击反馈
/* #ifdef MP-WEIXIN */
&:active {
opacity: 0.8;
transform: scale(0.95);
}
/* #endif */
}
.switch-group {
display: flex;
flex-direction: row;
align-items: center;
gap: 6rpx;
margin: 0 8rpx;
white-space: nowrap;
flex-shrink: 0;
text {
font-size: 22rpx;
color: #333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
switch {
transform: scale(0.75);
}
}
}
/* 对局记录区域 */
.game-record {
background-color: #fff;
border-radius: 20rpx;
padding: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
margin-bottom: 30rpx;
flex: 1;
overflow-y: auto;
padding-bottom: 20rpx;
}
.record-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
.hint {
font-size: 24rpx;
font-weight: normal;
color: #666;
margin-left: 10rpx;
}
}
/* 用户信息区域 */
.user-section {
margin-bottom: 20rpx;
.user-hint {
font-size: 24rpx;
color: #fa5d5d;
margin-bottom: 15rpx;
display: block;
}
.user-info {
display: flex;
flex-direction: row;
align-items: center;
gap: 15rpx;
padding: 10rpx;
border-radius: 10rpx;
background-color: #f0f8ff;
.user-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
}
.user-details {
.user-name {
font-size: 28rpx;
color: #333;
.self-tag {
background-color: #007aff;
color: #fff;
font-size: 20rpx;
padding: 2rpx 10rpx;
border-radius: 10rpx;
margin-left: 8rpx;
}
}
}
}
}
/* 玩家列表区域 */
.players-section {
.section-header {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 15rpx;
.header-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.player-count {
font-size: 24rpx;
color: #666;
margin-left: 10rpx;
}
}
}
/* 玩家表格 */
.players-table {
border: 1rpx solid #e0e0e0;
border-radius: 10rpx;
overflow: hidden;
.table-header {
display: flex;
flex-direction: row;
background-color: #f5f5f5;
border-bottom: 1rpx solid #e0e0e0;
.player-column {
flex: 2;
padding: 15rpx;
font-size: 26rpx;
font-weight: bold;
color: #333;
border-right: 1rpx solid #e0e0e0;
}
.score-column {
flex: 1;
padding: 15rpx;
font-size: 26rpx;
font-weight: bold;
color: #333;
text-align: center;
}
}
.table-body {
.player-row {
display: flex;
flex-direction: row;
border-bottom: 1rpx solid #e0e0e0;
&:last-child {
border-bottom: none;
}
.player-info {
flex: 2;
display: flex;
flex-direction: row;
align-items: center;
gap: 15rpx;
padding: 15rpx;
border-right: 1rpx solid #e0e0e0;
image {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
}
text {
font-size: 26rpx;
color: #333;
}
}
.score-display {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
padding: 15rpx;
font-size: 28rpx;
font-weight: bold;
color: #007aff;
&:active {
background-color: #f0f8ff;
}
}
}
}
}
/* 底部操作按钮 */
.bottom-buttons {
display: flex;
flex-direction: row;
gap: 20rpx;
position: sticky;
bottom: 0;
left: 0;
right: 0;
padding: 20rpx;
background-color: #fff;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
/* #ifdef MP-WEIXIN */
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
/* #endif */
.start-btn,
.settle-btn {
flex: 1;
height: 90rpx;
font-size: 32rpx;
line-height: 90rpx;
margin: 0;
}
.start-btn {
background-color: #007aff;
}
.settle-btn {
border: 1rpx solid #007aff;
color: #007aff;
}
}
/* 适配不同屏幕尺寸 */
@media screen and (max-width: 375px) {
.singleplay {
padding: 15rpx;
}
.top-functions {
gap: 8rpx;
.function-btn {
padding: 1rpx 1rpx;
text {
font-size: 22rpx;
}
}
}
}
</style>