diff --git a/src/assets/network-bettle.css b/src/assets/network-bettle.css new file mode 100644 index 0000000..4de7e86 --- /dev/null +++ b/src/assets/network-bettle.css @@ -0,0 +1,394 @@ +.network-battle-page { + background: linear-gradient(135deg, #f7f9fc, #e2e7ed); + max-width: 800px; + margin: 2rem auto; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0,0,0,0.1); + text-align: center; + padding: 2rem; + position: relative; +} + +.network-battle-page h1 { + font-size: 2.2rem; + color: #333; + margin-bottom: 1.5rem; +} + +.connection-section { + background-color: rgba(255, 255, 255, 0.7); + border-radius: 10px; + padding: 1.5rem; + margin-bottom: 1.5rem; + box-shadow: 0 2px 6px rgba(0,0,0,0.05); +} + +.connection-type { + display: flex; + justify-content: center; + gap: 1rem; + margin-bottom: 1.5rem; +} + +.connection-type button { + background: rgba(255, 255, 255, 0.8); + color: #555; + border: 2px solid #ddd; + border-radius: 30px; + padding: 0.7rem 1.5rem; + font-size: 1rem; + font-weight: bold; + cursor: pointer; + transition: all 0.3s ease; +} + +.connection-type button.active { + background: linear-gradient(90deg, #84b9ff, #32a8ff, #0faeff); + color: white; + border-color: transparent; + box-shadow: 0 4px 6px rgba(0,0,0,0.1); +} + +.host-section, .join-section { + margin-top: 1rem; +} + +.local-info { + background-color: rgba(255, 255, 255, 0.9); + border-radius: 8px; + padding: 1rem; + margin-bottom: 1rem; + border: 1px solid #ddd; +} + +.peer-id { + font-weight: bold; + color: #0077cc; + background: rgba(0, 119, 204, 0.1); + padding: 0.3rem 0.6rem; + border-radius: 4px; + margin: 0 0.3rem; +} + +.copy-button { + background: rgba(0, 119, 204, 0.1); + color: #0077cc; + border: 1px solid #0077cc; + border-radius: 20px; + padding: 0.3rem 0.8rem; + font-size: 0.9rem; + margin-left: 0.5rem; + cursor: pointer; + transition: all 0.2s ease; +} + +.copy-button:hover { + background: rgba(0, 119, 204, 0.2); +} + +.input-group { + margin-bottom: 1rem; +} + +.input-group label { + display: block; + margin-bottom: 0.5rem; + font-weight: bold; + color: #555; +} + +.input-group input { + padding: 0.8rem; + font-size: 1rem; + border-radius: 20px; + border: 2px solid #ddd; + width: 80%; + max-width: 400px; + text-align: center; + outline: none; + transition: all 0.3s ease; +} + +.input-group input:focus { + border-color: #007ACC; + box-shadow: 0 0 0 2px rgba(0,122,204,0.2); +} + +.game-settings { + margin: 1.5rem 0; +} + +label { + margin-left: 1rem; + font-weight: bold; + color: #555; +} + +select { + padding: 0.6rem 1rem; + font-size: 1rem; + border-radius: 20px; + border: 2px solid #ddd; + background-color: #f8f9fa; + color: #555; + cursor: pointer; + outline: none; + transition: all 0.3s ease; + box-shadow: 0 2px 4px rgba(0,0,0,0.05); +} + +select:focus { + border-color: #007ACC; + box-shadow: 0 0 0 2px rgba(0,122,204,0.2); +} + +button { + background: linear-gradient(90deg, #84b9ff, #32a8ff, #0faeff); + color: white; + border: none; + border-radius: 30px; + padding: 0.7rem 1.5rem; + font-size: 1rem; + font-weight: bold; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 4px 6px rgba(0,0,0,0.1); + margin: 0.5rem; +} + +button:hover { + transform: translateY(-2px); + box-shadow: 0 6px 8px rgba(0,0,0,0.15); +} + +button:disabled { + background: #cccccc; + cursor: not-allowed; + transform: none; + box-shadow: none; +} + +.players-info { + display: flex; + justify-content: space-around; + margin-bottom: 1.5rem; +} + +.player { + background-color: rgba(255, 255, 255, 0.7); + border-radius: 10px; + padding: 1rem; + width: 45%; + transition: all 0.3s ease; + border: 2px solid transparent; +} + +.player-turn { + border-color: #32a8ff; + background-color: rgba(50, 168, 255, 0.1); + box-shadow: 0 0 8px rgba(50, 168, 255, 0.3); +} + +.player h3 { + margin: 0 0 0.5rem 0; + font-size: 1.2rem; + color: #333; +} + +.score { + font-size: 1.4rem; + font-weight: bold; + margin: 0.5rem 0; + color: #42b883; +} + +.question-section p { + font-size: 1.8rem; + font-weight: bold; + margin: 1.5rem 0; + color: #333; +} + +.question-section input { + margin: 1rem 0; + padding: 0.8rem; + font-size: 1.1rem; + border-radius: 20px; + border: 2px solid #ddd; + width: 200px; + text-align: center; + outline: none; + transition: all 0.3s ease; +} + +.question-section input:focus { + border-color: #007ACC; + box-shadow: 0 0 0 2px rgba(0,122,204,0.2); +} + +.question-section input:disabled { + background-color: #f5f5f5; + cursor: not-allowed; +} + +.timer-section p { + font-size: 1.4rem; + font-weight: bold; + color: #e74c3c; +} + +.feedback-container { + height: 30px; + margin: 10px 0; + position: relative; +} + +.feedback { + padding: 6px 12px; + border-radius: 20px; + display: inline-block; + font-weight: bold; + position: absolute; + left: 50%; + transform: translateX(-50%); + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.feedback.correct { + background-color: rgba(66, 184, 131, 0.2); + color: #2a8c5f; + border: 1px solid #42b883; +} + +.feedback.wrong { + background-color: rgba(231, 76, 60, 0.2); + color: #c0392b; + border: 1px solid #e74c3c; +} + +.correct-answer { + animation: pulse-green 0.5s; + border-color: #42b883 !important; +} + +.wrong-answer { + animation: pulse-red 0.5s; + border-color: #e74c3c !important; +} + +.game-messages { + height: 40px; + margin: 1rem 0; + display: flex; + justify-content: center; + align-items: center; +} + +.game-message { + padding: 0.5rem 1rem; + border-radius: 20px; + font-weight: bold; + animation: fade-in 0.5s; +} + +.game-message.success { + background-color: rgba(66, 184, 131, 0.2); + color: #2a8c5f; +} + +.game-message.error { + background-color: rgba(231, 76, 60, 0.2); + color: #c0392b; +} + +.game-message.info { + background-color: rgba(52, 152, 219, 0.2); + color: #2980b9; +} + +.game-result-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.7); + display: flex; + justify-content: center; + align-items: center; + z-index: 10; + animation: fade-in 0.5s; +} + +.game-result { + background-color: white; + border-radius: 12px; + padding: 2rem; + width: 90%; + max-width: 500px; + text-align: center; + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2); +} + +.game-result h2 { + font-size: 2rem; + margin-bottom: 1.5rem; + color: #333; +} + +.final-scores { + display: flex; + justify-content: space-around; + margin-bottom: 1.5rem; +} + +.final-scores p { + font-size: 1.2rem; + font-weight: bold; +} + +.result-message { + font-size: 2rem; + margin: 1.5rem 0; +} + +.result-message.win { + color: #42b883; +} + +.result-message.lose { + color: #e74c3c; +} + +.result-message.draw { + color: #f39c12; +} + +/* 动画效果 */ +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.5s, transform 0.5s; +} + +.fade-enter-from, +.fade-leave-to { + opacity: 0; + transform: translateY(-10px) translateX(-50%); +} + +@keyframes pulse-green { + 0% { box-shadow: 0 0 0 0 rgba(66, 184, 131, 0.7); } + 70% { box-shadow: 0 0 0 10px rgba(66, 184, 131, 0); } + 100% { box-shadow: 0 0 0 0 rgba(66, 184, 131, 0); } +} + +@keyframes pulse-red { + 0% { box-shadow: 0 0 0 0 rgba(231, 76, 60, 0.7); } + 70% { box-shadow: 0 0 0 10px rgba(231, 76, 60, 0); } + 100% { box-shadow: 0 0 0 0 rgba(231, 76, 60, 0); } +} + +@keyframes fade-in { + from { opacity: 0; } + to { opacity: 1; } +} diff --git a/src/assets/practice.css b/src/assets/practice.css new file mode 100644 index 0000000..4f9a043 --- /dev/null +++ b/src/assets/practice.css @@ -0,0 +1,220 @@ +.practice-page { + background: linear-gradient(135deg, #f7f9fc, #e2e7ed); + max-width: 800px; + margin: 2rem auto; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0,0,0,0.1); + text-align: center; + padding: 2rem; +} + +.practice-page h1 { + font-size: 2.2rem; + color: #333; + margin-bottom: 1.5rem; +} + +header label { + margin-left: 1rem; + font-weight: bold; + color: #555; +} + +header select { + padding: 0.6rem 1rem; + font-size: 1rem; + border-radius: 20px; + border: 2px solid #ddd; + background-color: #f8f9fa; + color: #555; + cursor: pointer; + outline: none; + transition: all 0.3s ease; + box-shadow: 0 2px 4px rgba(0,0,0,0.05); +} + +header select:focus { + border-color: #007ACC; + box-shadow: 0 0 0 2px rgba(0,122,204,0.2); +} + +button { + background: linear-gradient(90deg, #84b9ff, #32a8ff, #0faeff); + color: white; + border: none; + border-radius: 30px; + padding: 0.7rem 1.5rem; + font-size: 1rem; + font-weight: bold; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 4px 6px rgba(0,0,0,0.1); + margin: 0.5rem; +} + +button:hover { + transform: translateY(-2px); + box-shadow: 0 6px 8px rgba(0,0,0,0.15); + background: linear-gradient(90deg, #84b9ff, #32a8ff, #0faeff); +} + +header button { + margin-top: 1.5rem; + padding: 0.7rem 1.8rem; +} + +.question-section p { + font-size: 1.8rem; + font-weight: bold; + margin: 1.5rem 0; + color: #333; +} + +.question-section input { + margin: 1rem 0; + padding: 0.8rem; + font-size: 1.1rem; + border-radius: 20px; + border: 2px solid #ddd; + width: 200px; + text-align: center; + outline: none; + transition: all 0.3s ease; +} + +.question-section input:focus { + border-color: #007ACC; + box-shadow: 0 0 0 2px rgba(0,122,204,0.2); +} + +.timer-section p { + font-size: 1.4rem; + font-weight: bold; + color: #e74c3c; +} + +.score-section p { + font-size: 1.4rem; + font-weight: bold; + color: #42b883; +} + +footer { + margin-top: 2rem; + padding-top: 1.5rem; + border-top: 1px solid #ddd; +} + +footer p { + font-size: 1.2rem; + font-weight: bold; + color: #555; + margin-bottom: 1rem; +} + +footer ul { + list-style-type: none; + padding: 0; + margin: 1rem 0; + max-height: 300px; + overflow-y: auto; + /* 移除背景色和内边距,使其更扁平 */ + background-color: transparent; + border-radius: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +footer li { + margin: 0; + padding: 0.8rem 1rem; + background-color: rgba(255, 255, 255, 0.7); + border-radius: 4px; + box-shadow: 0 1px 3px rgba(0,0,0,0.05); + transition: all 0.2s ease; + border-left: 3px solid #32a8ff; +} + +footer li:hover { + background-color: white; + transform: translateX(2px); + box-shadow: 0 2px 5px rgba(0,0,0,0.1); +} + +footer button { + background: linear-gradient(90deg, #ff7675, #d63031); + margin-top: 1.5rem; + padding: 0.6rem 1.4rem; +} + +footer button:hover { + background: linear-gradient(90deg, #ff6b6b, #c0392b); +} + +/* 答题反馈动画 */ +.feedback-container { + height: 30px; + margin: 10px 0; + position: relative; +} + +.feedback { + padding: 6px 12px; + border-radius: 20px; + display: inline-block; + font-weight: bold; + position: absolute; + left: 50%; + transform: translateX(-50%); + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.feedback.correct { + background-color: rgba(66, 184, 131, 0.2); + color: #2a8c5f; + border: 1px solid #42b883; +} + +.feedback.wrong { + background-color: rgba(231, 76, 60, 0.2); + color: #c0392b; + border: 1px solid #e74c3c; +} + +/* 输入框的反馈效果 */ +.correct-answer { + animation: pulse-green 0.5s; + border-color: #42b883 !important; +} + +.wrong-answer { + animation: pulse-red 0.5s; + border-color: #e74c3c !important; +} + +/* 淡入淡出动画 */ +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.5s, transform 0.5s; +} + +.fade-enter-from, +.fade-leave-to { + opacity: 0; + transform: translateY(-10px) translateX(-50%); +} + +/* 脉动动画效果 */ +@keyframes pulse-green { + 0% { box-shadow: 0 0 0 0 rgba(66, 184, 131, 0.7); } + 70% { box-shadow: 0 0 0 10px rgba(66, 184, 131, 0); } + 100% { box-shadow: 0 0 0 0 rgba(66, 184, 131, 0); } +} + +@keyframes pulse-red { + 0% { box-shadow: 0 0 0 0 rgba(231, 76, 60, 0.7); } + 70% { box-shadow: 0 0 0 10px rgba(231, 76, 60, 0); } + 100% { box-shadow: 0 0 0 0 rgba(231, 76, 60, 0); } +} diff --git a/src/views/NetworkBattle.vue b/src/views/NetworkBattle.vue index ed7205e..0d93416 100644 --- a/src/views/NetworkBattle.vue +++ b/src/views/NetworkBattle.vue @@ -7,7 +7,7 @@ - +

你的房间ID: {{ localPeerId }}

@@ -15,7 +15,7 @@

{{ connectionStatus }}

- +
@@ -25,7 +25,7 @@

{{ connectionStatus }}

- +
- +
- +

剩余时间: {{ timeLeft }} 秒

- +
{{ gameMessage }}
- +

游戏结束

@@ -179,16 +179,16 @@ function initPeer() { ] } }); - + peer.value.on('open', (id) => { localPeerId.value = id; connectionStatus.value = '准备就绪,等待连接...'; }); - + peer.value.on('connection', (conn) => { handleConnection(conn); }); - + peer.value.on('error', (err) => { console.error('PeerJS错误:', err); connectionStatus.value = `连接错误: ${err}`; @@ -199,14 +199,14 @@ function initPeer() { // 连接到对方 function connectToPeer() { if (!peer.value || !remotePeerId.value) return; - + connectionStatus.value = '正在连接...'; - + try { const conn = peer.value.connect(remotePeerId.value, { reliable: true }); - + handleConnection(conn); } catch (error) { console.error('连接失败:', error); @@ -217,22 +217,22 @@ function connectToPeer() { // 处理连接 function handleConnection(conn: DataConnection) { connection.value = conn; - + conn.on('open', () => { isConnected.value = true; connectionStatus.value = '连接成功!'; showGameMessage('连接成功!', 'success'); - + // 如果是加入方,等待主机发送游戏设置 if (mode.value === 'join') { localPlayerTurn.value = false; } }); - + conn.on('data', (data: any) => { handleReceivedData(data); }); - + conn.on('close', () => { isConnected.value = false; connectionStatus.value = '连接已关闭'; @@ -241,7 +241,7 @@ function handleConnection(conn: DataConnection) { endGame(); } }); - + conn.on('error', (err) => { console.error('连接错误:', err); connectionStatus.value = `连接错误: ${err}`; @@ -252,13 +252,13 @@ function handleConnection(conn: DataConnection) { // 开始游戏 function startGame() { if (!isConnected.value || !connection.value) return; - + // 发送游戏设置给对方 connection.value.send({ type: 'game-settings', difficulty: selectedDifficulty.value }); - + // 开始倒计时 timeLeft.value = 30; localScore.value = 0; @@ -266,13 +266,13 @@ function startGame() { isGameActive.value = true; gameEnded.value = false; localPlayerTurn.value = mode.value === 'host'; // 主机先手 - + // 生成第一题 generateNewQuestion(); - + // 启动计时器 startTimer(); - + // 通知对方游戏已开始 connection.value.send({ type: 'game-started' @@ -283,7 +283,7 @@ function startGame() { function generateNewQuestion() { try { currentQuestion.value = generateQuestion(parseInt(selectedDifficulty.value)); - + // 如果自己是当前回合玩家,发送题目给对方 if (localPlayerTurn.value && connection.value) { connection.value.send({ @@ -301,17 +301,17 @@ function generateNewQuestion() { // 提交答案 function submitAnswer() { if (!isGameActive.value || !localPlayerTurn.value) return; - + const isCorrect = currentQuestion.value.answer.toString() === userAnswer.value; lastCorrectAnswer.value = currentQuestion.value.answer.toString(); - + if (isCorrect) { localScore.value += 1; answerFeedback.value = 'correct'; } else { answerFeedback.value = 'wrong'; } - + // 发送答题结果给对方 if (connection.value) { connection.value.send({ @@ -320,14 +320,14 @@ function submitAnswer() { score: localScore.value }); } - + // 交换回合 localPlayerTurn.value = false; - + // 生成新题目给对方 generateNewQuestion(); userAnswer.value = ''; - + // 设置短暂的反馈后恢复 setTimeout(() => { answerFeedback.value = 'none'; @@ -337,14 +337,14 @@ function submitAnswer() { // 启动计时器 function startTimer() { clearTimerInterval(); - + timerInterval.value = window.setInterval(() => { timeLeft.value--; - + if (timeLeft.value <= 0) { clearTimerInterval(); endGame(); - + // 通知对方游戏结束 if (connection.value) { connection.value.send({ @@ -384,13 +384,13 @@ function resetGame() { // 处理接收到的数据 function handleReceivedData(data: any) { console.log('收到数据:', data); - + switch (data.type) { case 'game-settings': // 接收到游戏设置 selectedDifficulty.value = data.difficulty; break; - + case 'game-started': // 游戏开始 isGameActive.value = true; @@ -398,7 +398,7 @@ function handleReceivedData(data: any) { timeLeft.value = 30; startTimer(); break; - + case 'new-question': // 接收到新题目 currentQuestion.value = { @@ -408,7 +408,7 @@ function handleReceivedData(data: any) { // 接收到新题目意味着轮到我方回答 localPlayerTurn.value = true; break; - + case 'answer-result': // 对方答题结果 remoteScore.value = data.score; @@ -421,7 +421,7 @@ function handleReceivedData(data: any) { localPlayerTurn.value = true; generateNewQuestion(); break; - + case 'game-ended': // 游戏结束 endGame(); @@ -433,406 +433,11 @@ function handleReceivedData(data: any) { function showGameMessage(message: string, type: 'success' | 'error' | 'info' = 'info') { gameMessage.value = message; gameMessageType.value = type; - + setTimeout(() => { gameMessage.value = ''; }, 3000); } - \ No newline at end of file + diff --git a/src/views/Practice.vue b/src/views/Practice.vue index 41efaf8..33e8315 100644 --- a/src/views/Practice.vue +++ b/src/views/Practice.vue @@ -147,225 +147,4 @@ onUnmounted(() => { }); - +