You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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异步返回结果(无刷新体验)

如果不想刷新页面,可让后端直接返回结果,前端动态插入到页面:

  1. 修改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");
}
  1. 修改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),避免多用户数据互相覆盖:

  1. 安装依赖:
npm install express-session
  1. 在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
}));
  1. 修改路由中的结果存储逻辑:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 12:35:07