Node.js+Express剪刀石头布游戏:EJS无法渲染结果求助
剪刀石头布游戏结果无法渲染问题
我基于Node.js的Express后端、frontend.js前端、index.ejs模板及main.css开发了一款剪刀石头布游戏,希望渲染对决结果(获胜、失败、平局请重试),但结果始终无法正常显示。已通过console.log验证路由间数据已传递,且EJS模板中存在该数据,但仍无法渲染,尝试用ChatGPT排查也未解决。
代码文件
backend.js
import express from "express"; import bodyParser from "body-parser"; import pg from "pg"; import ejs from "ejs"; import axios from "axios"; import { dirname } from "path"; import { fileURLToPath } from "url"; const __dirname = dirname(fileURLToPath(import.meta.url)); const app = express(); const port = 3000; let gameResult = []; app.set('view engine', 'ejs'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static("public")); app.get("/", (req, res) => { const results = [...gameResult]; gameResult = []; console.log("Result from session:", results); res.render("index", {results}); }); app.post("/add", (req,res) => { const {targetedId, dataIdList} = req.body; let compSelector = dataIdList[Math.floor(Math.random() * dataIdList.length)]; console.log(`Chosen by client: ${targetedId}`); console.log(`Chosen by computer: ${compSelector}`); function determineResult(targetedId, compSelector) { switch (targetedId) { case "paper": if(compSelector === "scissors") { return "You lost"; } else if(compSelector === "rock") { return "You won!"; } else { return "The same, try again"; } break; case "scissors": if(compSelector === "rock") { return "You lost"; } else if(compSelector === "paper") { return "You won!" } else { return "The same, try again"; } break; case "rock": if(compSelector === "paper") { return "You lost"; } else if(compSelector === "scissors") { return "You won!" } else { return "The same, try again"; } break; default: console.log("Error"); break; } } try { const result = determineResult(targetedId, compSelector); console.log(result); gameResult = [result]; res.redirect("/"); } catch (error) { console.error("Error handling POST /add request:", error); res.status(500).send("Internal Server Error"); } }); app.listen(port, () => { console.log(`Listening on port ${port}`); });
frontend.js
console.log("Frontend.js is loaded"); function myFrontFunction(event) { const selectedImage = event.target; const targetedId = selectedImage.getAttribute("data-id"); const images = document.querySelectorAll(".image"); const dataIdList = []; images.forEach(dataId => { dataIdList.push(dataId.getAttribute("data-id")); }); console.log(`Data list: ${dataIdList}`); console.log(targetedId); fetch("/add", { method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({targetedId, dataIdList}), }) .then(response => { if(response.ok) { console.log("Data sent successfully"); response.text().then(text => console.log(text)); console.log("All images:", images); images.forEach(image => { if(image !== selectedImage){ console.log("Hiding image:", image); image.classList.add('hidden'); // Use class to hide } }); } else { console.error("Failed to send data to the server"); } }) .catch(error => console.error("Error:", error)); }
index.ejs
<%- include('partials/header.ejs') %> <h1>The RSP App!</h1> <h2>Choose Rock, Scissors or Paper:</h2> <img class="image" src="/images/paper.png" alt="A piece of paper" width="400px" height="400px" data-id="paper" onclick="myFrontFunction(event)"> <img class="image" src="/images/rock.png" alt="A rock" width="400px" height="400px" data-id="rock" onclick="myFrontFunction(event)"> <img class="image" src="/images/scissors.png" alt="Scissors" width="400px" height="400px" data-id="scissors" onclick="myFrontFunction(event)"> <div> <% if (results.length > 0) { %> <% results.forEach( result => { %> <p> <%= result %></p> <% }) %> <% } %> </div> <script src="/frontend.js"></script> <%- include('partials/footer.ejs') %>
main.css
.hidden { display: none; }
问题根源与修复方案
核心问题
前端使用fetch发送POST请求后,后端执行了重定向,但浏览器不会自动跟随重定向刷新页面——当前页面仍保持初始状态,后端新生成的results数据无法被渲染。此外,全局变量gameResult在多用户场景下会出现数据冲突,但单用户测试时主要问题是前端未刷新。
修复步骤
方案1:前端跟随重定向刷新页面
修改frontend.js中的fetch回调,收到响应后手动刷新页面,触发后端重新渲染带结果的页面:
fetch("/add", { method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({targetedId, dataIdList}), }) .then(response => { if(response.ok) { // 跟随重定向刷新页面,获取新渲染的结果 window.location.href = response.url; } else { console.error("Failed to send data to the server"); } }) .catch(error => console.error("Error:", error));
方案2:AJAX异步返回结果(无刷新体验)
如果不想刷新页面,可让后端直接返回结果,前端动态插入到页面:
- 修改
backend.js的/add路由,返回JSON结果而非重定向:
try { const result = determineResult(targetedId, compSelector); console.log(result); // 直接返回结果 res.json({result}); } catch (error) { console.error("Error handling POST /add request:", error); res.status(500).send("Internal Server Error"); }
- 修改
frontend.js,接收结果并动态渲染:
fetch("/add", { method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({targetedId, dataIdList}), }) .then(response => response.json()) .then(data => { console.log("Data sent successfully"); // 隐藏其他图片 const images = document.querySelectorAll(".image"); images.forEach(image => { if(image !== event.target){ image.classList.add('hidden'); } }); // 动态添加结果到页面 const resultDiv = document.querySelector('div'); const resultP = document.createElement('p'); resultP.textContent = data.result; resultDiv.appendChild(resultP); }) .catch(error => console.error("Error:", error));
额外优化:解决多用户数据冲突
将全局变量gameResult替换为会话存储(使用express-session),避免多用户数据互相覆盖:
- 安装依赖:
npm install express-session
- 在
backend.js中配置会话:
import session from 'express-session'; app.use(session({ secret: 'your-custom-secret-key', // 替换为自定义密钥 resave: false, saveUninitialized: true, cookie: { secure: false } // 开发环境用false,生产环境建议启用HTTPS并设为true }));
- 修改路由中的结果存储逻辑:
app.get("/", (req, res) => { const results = req.session.gameResult || []; req.session.gameResult = []; // 清空会话中的结果 res.render("index", {results}); }); app.post("/add", (req,res) => { // ... 其他代码不变 const result = determineResult(targetedId, compSelector); req.session.gameResult = [result]; // 结果存入会话 res.redirect("/"); });
内容的提问来源于stack exchange,提问作者Yuval Yanay
相关产品推荐
相关产品推荐

