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

805 lines
19 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="scoring-page">
<!-- 顶部导航栏 -->
<view class="header">
<view class="back-btn" @click="goBack">
<uni-icons type="arrow-left" size="24" color="#fff"></uni-icons>
</view>
<view class="title">{{ roundCount }}</view>
<view class="header-right">
<view class="more-btn" @click="showMoreOptions">...</view>
<view class="separator"></view>
<view class="sound-toggle" @click="toggleSound">
<text class="sound-icon">{{ soundEnabled ? '🔊' : '🔇' }}</text>
</view>
</view>
</view>
<!-- 玩家列表区域 -->
<view class="players-list">
<!-- 表头 -->
<view class="list-header">
<view class="col-player">玩家</view>
<view class="col-result">胜负</view>
<view class="col-score">得分</view>
</view>
<!-- 玩家行 -->
<view
v-for="(player, index) in players"
:key="player.id"
class="player-row"
:class="{ 'selected': selectedPlayerIndex === index }"
@click="selectPlayer(index)"
>
<view class="col-player">
<image :src="player.avatar" mode="aspectFill" class="player-avatar"></image>
<text class="player-name">{{ player.name }}</text>
</view>
<view class="col-result">
<view class="result-buttons">
<button
class="result-btn win"
:class="{ 'active': player.result === 'win' }"
@click.stop="setPlayerResult(index, 'win')"
></button>
<button
class="result-btn lose"
:class="{ 'active': player.result === 'lose' }"
@click.stop="setPlayerResult(index, 'lose')"
></button>
</view>
</view>
<view class="col-score">
<view class="score-input-container">
<input
type="number"
class="score-input"
:value="selectedPlayerIndex === index && showKeyboard ? currentInput : player.score"
@focus.prevent
@click.stop="selectPlayer(index)"
readonly
disabled
/>
<!-- 合分按钮 -->
<button
class="combine-btn"
@click.stop="combineScore(index)"
:disabled="!selectedPlayerIndex === index"
>合分</button>
</view>
</view>
</view>
</view>
<!-- 底部数字键盘 -->
<view class="number-keyboard" v-if="showKeyboard">
<view class="keyboard-row">
<view class="key" @click="inputNumber(1)">1</view>
<view class="key" @click="inputNumber(2)">2</view>
<view class="key" @click="inputNumber(3)">3</view>
<view class="key operation" @click="inputOperation('+')">+</view>
<view class="key delete" @click="deleteLastChar">
<text></text>
</view>
</view>
<view class="keyboard-row">
<view class="key" @click="inputNumber(4)">4</view>
<view class="key" @click="inputNumber(5)">5</view>
<view class="key" @click="inputNumber(6)">6</view>
<view class="key operation" @click="inputOperation('-')">-</view>
<view class="key empty"></view>
</view>
<view class="keyboard-row">
<view class="key" @click="inputNumber(7)">7</view>
<view class="key" @click="inputNumber(8)">8</view>
<view class="key" @click="inputNumber(9)">9</view>
<view class="key operation" @click="clearInput">C</view>
<view class="key confirm" @click="confirmInput">提交</view>
</view>
<view class="keyboard-row">
<view class="key zero" @click="inputNumber(0)">0</view>
</view>
</view>
<!-- 底部操作栏 -->
<view class="bottom-actions" v-if="!showKeyboard">
<button class="action-btn" type="default" @click="nextRound">下一局</button>
<button class="action-btn primary" type="primary" @click="endGame">结束对局</button>
</view>
</view>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
// 从上个页面接收的玩家数据
const players = ref([])
const roundCount = ref(1)
const history = ref([])
const selectedPlayerIndex = ref(-1)
const showKeyboard = ref(false)
const currentInput = ref('')
const soundEnabled = ref(true)
// 初始化玩家数据
const loadPlayersData = () => {
try {
const playersData = uni.getStorageSync('currentPlayers')
if (playersData) {
// 解析并添加result字段
const parsedPlayers = JSON.parse(playersData).map(player => ({
...player,
result: '', // 初始胜负状态为空
score: player.score || 0
}))
players.value = parsedPlayers
saveHistoryState()
}
} catch (error) {
console.error('加载玩家数据失败:', error)
uni.showToast({
title: '加载数据失败',
icon: 'none'
})
}
}
// 保存历史状态
const saveHistoryState = () => {
const stateCopy = JSON.parse(JSON.stringify(players.value))
history.value.push(stateCopy)
if (history.value.length > 50) {
history.value.shift()
}
}
// 返回上一页
const goBack = () => {
uni.showModal({
title: '提示',
content: '当前分数未保存,确定要返回吗?',
success: (res) => {
if (res.confirm) {
uni.navigateBack()
}
}
})
}
// 选择玩家
const selectPlayer = (index) => {
selectedPlayerIndex.value = index
showKeyboard.value = true
// 显示当前分数
currentInput.value = players.value[index].score.toString()
}
// 设置玩家胜负
const setPlayerResult = (index, result) => {
// 如果已经是当前状态,则取消选择
if (players.value[index].result === result) {
players.value[index].result = ''
// 取消状态时,保持当前分数符号不变
} else {
players.value[index].result = result
// 根据胜负状态调整分数正负号,并考虑当前输入框的内容
let currentScore = players.value[index].score
let currentInputValue = currentInput.value
if (result === 'win') {
// 胜利时确保分数为正数
if (currentScore !== 0) {
// 根据输入框当前内容或分数值判断是否需要调整符号
if (currentInputValue && currentInputValue !== '-' && parseInt(currentInputValue) !== 0) {
// 使用输入框的值并确保为正数
currentScore = Math.abs(parseInt(currentInputValue))
} else {
// 使用现有分数值并确保为正数
currentScore = Math.abs(currentScore)
}
}
} else if (result === 'lose') {
// 失败时确保分数为负数
if (currentScore !== 0) {
// 根据输入框当前内容或分数值判断是否需要调整符号
if (currentInputValue && currentInputValue !== '-' && parseInt(currentInputValue) !== 0) {
// 使用输入框的值并确保为负数
currentScore = -Math.abs(parseInt(currentInputValue))
} else {
// 使用现有分数值并确保为负数
currentScore = -Math.abs(currentScore)
}
}
}
// 更新分数
players.value[index].score = currentScore
}
// 如果当前正在编辑该玩家同步更新currentInput
if (selectedPlayerIndex.value === index && showKeyboard.value) {
currentInput.value = players.value[index].score.toString()
}
saveHistoryState()
}
// 数字键盘输入
const inputNumber = (num) => {
// 处理特殊情况:如果当前是负号,直接添加数字
if (currentInput.value === '-') {
currentInput.value += num.toString()
}
// 避免以0开头的数字除了单独的0和负数中的0
else if (currentInput.value === '0' && num !== 0) {
currentInput.value = num.toString()
}
// 避免输入过多的数字
else if (currentInput.value.length < 10) {
currentInput.value += num.toString()
}
// 实时保存输入内容到玩家分数(不等待提交)
if (selectedPlayerIndex.value !== -1) {
let score = 0
if (currentInput.value && currentInput.value !== '-') {
score = parseInt(currentInput.value)
if (isNaN(score)) {
score = 0
}
}
players.value[selectedPlayerIndex.value].score = score
saveHistoryState()
}
}
// 操作符输入
const inputOperation = (op) => {
if (op === '-') {
// 支持负数输入
if (currentInput.value === '') {
// 当输入框为空时,按减号开始负数输入
currentInput.value = '-'
} else if (currentInput.value.startsWith('-')) {
// 如果已经是负数,移除负号变为正数
currentInput.value = currentInput.value.substring(1)
} else {
// 如果已有数字,添加负号变为负数
currentInput.value = '-' + currentInput.value
}
} else if (op === '+') {
// 按加号确保是正数(移除负号)
if (currentInput.value.startsWith('-')) {
currentInput.value = currentInput.value.substring(1)
}
}
// 实时保存操作符结果到玩家分数
if (selectedPlayerIndex.value !== -1) {
let score = 0
if (currentInput.value && currentInput.value !== '-') {
score = parseInt(currentInput.value)
if (isNaN(score)) {
score = 0
}
}
players.value[selectedPlayerIndex.value].score = score
saveHistoryState()
}
}
// 删除最后一个字符
const deleteLastChar = () => {
if (currentInput.value.length > 0) {
currentInput.value = currentInput.value.slice(0, -1)
// 实时保存删除后的内容到玩家分数
if (selectedPlayerIndex.value !== -1) {
let score = 0
if (currentInput.value && currentInput.value !== '-') {
score = parseInt(currentInput.value)
if (isNaN(score)) {
score = 0
}
}
players.value[selectedPlayerIndex.value].score = score
saveHistoryState()
}
}
}
// 清空输入
const clearInput = () => {
currentInput.value = ''
// 实时保存清空操作到玩家分数
if (selectedPlayerIndex.value !== -1) {
players.value[selectedPlayerIndex.value].score = 0
saveHistoryState()
}
}
// 确认输入
const confirmInput = () => {
if (selectedPlayerIndex.value !== -1) {
// 播放音效
if (soundEnabled.value) {
playSound()
}
// 隐藏键盘
showKeyboard.value = false
}
}
// 合分功能
const combineScore = (index) => {
// 计算其他玩家分数的总和
const otherPlayersTotal = players.value.reduce((sum, player, i) => {
if (i !== index) {
return sum + player.score
}
return sum
}, 0)
// 设置被点击玩家的分数为其他玩家分数总和的相反数,确保所有分数相加为零
players.value[index].score = -otherPlayersTotal
// 如果当前正在编辑该玩家同步更新currentInput
if (selectedPlayerIndex.value === index && showKeyboard.value) {
currentInput.value = players.value[index].score.toString()
}
saveHistoryState()
uni.showToast({
title: '合分成功',
icon: 'success'
})
}
// 下一局
const nextRound = () => {
uni.showModal({
title: '确认',
content: '确定要开始下一局吗?',
success: (res) => {
if (res.confirm) {
// 保存当前状态
saveHistoryState()
// 重置分数和胜负状态
players.value.forEach(player => {
player.score = 0
player.result = ''
})
roundCount.value++
uni.showToast({
title: `${roundCount.value}`,
icon: 'none'
})
}
}
})
}
// 播放音效
const playSound = () => {
// #ifdef MP-WEIXIN
const innerAudioContext = uni.createInnerAudioContext()
innerAudioContext.src = '/static/sound/click.mp3' // 需要准备音效文件
innerAudioContext.play()
innerAudioContext.onEnded(() => {
innerAudioContext.destroy()
})
// #endif
}
// 切换音效
const toggleSound = () => {
soundEnabled.value = !soundEnabled.value
uni.setStorageSync('soundEnabled', soundEnabled.value)
}
// 显示更多选项
const showMoreOptions = () => {
uni.showActionSheet({
itemList: ['历史记录', '设置', '帮助'],
success: (res) => {
switch (res.tapIndex) {
case 0:
uni.navigateTo({ url: '/pages/history-game/index' })
break
case 1:
uni.showToast({ title: '设置功能开发中', icon: 'none' })
break
case 2:
uni.showToast({ title: '帮助文档开发中', icon: 'none' })
break
}
}
})
}
// 结束对局
const endGame = () => {
uni.showModal({
title: '确认',
content: '确定要结束当前对局吗?',
success: (res) => {
if (res.confirm) {
try {
// 保存最终分数
uni.setStorageSync('updatedPlayers', JSON.stringify(players.value))
// 触发全局事件通知其他页面
uni.$emit('updatePlayers')
uni.showToast({
title: '对局已结束',
icon: 'success',
duration: 1500,
success: () => {
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
})
} catch (error) {
console.error('结束对局失败:', error)
uni.showToast({
title: '操作失败',
icon: 'none'
})
}
}
}
})
}
// 生命周期
onMounted(() => {
console.log('计分页面加载完成')
loadPlayersData()
// 加载音效设置
const savedSound = uni.getStorageSync('soundEnabled')
if (savedSound !== '') {
soundEnabled.value = savedSound
}
})
onUnmounted(() => {
// 清理工作
})
</script>
<style lang="less" scoped>
.scoring-page {
width: 750rpx;
margin: 0;
padding: 0;
box-sizing: border-box;
background-color: #f5f5f5;
min-height: 100vh;
display: flex;
flex-direction: column;
/* #ifdef MP-WEIXIN */
user-select: none;
/* #endif */
}
/* 顶部导航栏 */
.header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
height: 90rpx;
background-color: #3a68b9;
color: white;
padding: 0 30rpx;
box-sizing: border-box;
}
.back-btn {
font-size: 36rpx;
padding: 10rpx;
}
.back-btn:active {
opacity: 0.8;
}
.title {
font-size: 36rpx;
font-weight: bold;
}
.header-right {
display: flex;
flex-direction: row;
align-items: center;
}
.more-btn,
.sound-toggle {
font-size: 36rpx;
padding: 10rpx;
}
.separator {
width: 2rpx;
height: 40rpx;
background-color: rgba(255, 255, 255, 0.3);
margin: 0 10rpx;
}
/* 玩家列表区域 */
.players-list {
flex: 1;
background-color: #fff;
margin: 20rpx;
border-radius: 10rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
overflow: hidden;
}
/* 列表表头 */
.list-header {
display: flex;
flex-direction: row;
height: 80rpx;
background-color: #f0f0f0;
border-bottom: 1rpx solid #e0e0e0;
}
.col-player,
.col-result,
.col-score {
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
font-weight: bold;
color: #666;
}
.col-player {
flex: 2;
justify-content: flex-start;
padding-left: 30rpx;
}
.col-result {
flex: 1;
}
.col-score {
flex: 1;
}
/* 玩家行 */
.player-row {
display: flex;
flex-direction: row;
height: 100rpx;
border-bottom: 1rpx solid #f0f0f0;
transition: background-color 0.2s;
}
.player-row:last-child {
border-bottom: none;
}
.player-row.selected {
background-color: #e6f0ff;
}
.player-row .col-player {
display: flex;
flex-direction: row;
align-items: center;
}
.player-avatar {
width: 60rpx;
height: 60rpx;
border-radius: 30rpx;
margin-right: 20rpx;
}
.player-name {
font-size: 28rpx;
color: #333;
}
/* 胜负按钮 */
.result-buttons {
display: flex;
flex-direction: row;
gap: 10rpx;
}
.result-btn {
width: 60rpx;
height: 40rpx;
line-height: 40rpx;
padding: 0;
margin: 0;
font-size: 24rpx;
border-radius: 20rpx;
border: 1rpx solid #ddd;
}
.result-btn.win.active {
background-color: #4cd964;
color: white;
border-color: #4cd964;
}
.result-btn.lose.active {
background-color: #ff3b30;
color: white;
border-color: #ff3b30;
}
/* 分数输入区域 */
.score-input-container {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
}
.score-input {
width: 100rpx;
height: 50rpx;
text-align: center;
font-size: 32rpx;
font-weight: bold;
border: 1rpx solid #ddd;
border-radius: 8rpx;
padding: 0;
margin: 0;
}
.combine-btn {
position: absolute;
bottom: -40rpx;
width: 80rpx;
height: 30rpx;
line-height: 30rpx;
font-size: 20rpx;
padding: 0;
margin: 0;
background-color: #34aadc;
color: white;
border: none;
border-radius: 15rpx;
}
/* 数字键盘 */
.number-keyboard {
width: 100%;
background-color: #f0f0f0;
padding: 20rpx;
box-sizing: border-box;
}
.keyboard-row {
display: flex;
flex-direction: row;
justify-content: space-between;
margin-bottom: 20rpx;
}
.keyboard-row:last-child {
margin-bottom: 0;
}
.key {
flex: 1;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: white;
border-radius: 10rpx;
margin: 0 10rpx;
font-size: 36rpx;
font-weight: bold;
color: #333;
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.1);
}
.key:active {
background-color: #e0e0e0;
}
.key.zero {
flex: 3;
}
.key.operation {
background-color: #ff9500;
color: white;
}
.key.delete {
background-color: #ff3b30;
color: white;
position: relative;
display: flex;
align-items: center;
justify-content: center;
min-width: 80rpx; /* 确保按钮有足够宽度 */
}
.key.delete img {
width: 52rpx;
height: 52rpx;
object-fit: contain;
transition: transform 0.1s ease, opacity 0.1s ease;
/* #ifdef MP-WEIXIN */
pointer-events: none; /* 防止图片在微信小程序中捕获点击事件 */
/* #endif */
}
.key.delete:active {
background-color: #e03024; /* 点击时背景色变深 */
transform: scale(0.98);
}
.key.delete:active img {
transform: scale(0.9);
opacity: 0.85;
}
.key.delete-icon {
font-size: 36rpx;
transform: rotate(180deg);
}
.key.confirm {
background-color: #34c759;
color: white;
}
.key.empty {
background-color: transparent;
box-shadow: none;
}
/* 底部操作栏 */
.bottom-actions {
padding: 20rpx;
background-color: white;
border-top: 1rpx solid #e0e0e0;
}
.action-btn {
width: 100%;
height: 80rpx;
line-height: 80rpx;
font-size: 32rpx;
margin-bottom: 15rpx;
border-radius: 40rpx;
}
.action-btn:last-child {
margin-bottom: 0;
}
.action-btn.primary {
background-color: #007aff;
color: white;
border: none;
}
</style>