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

Express+Mongoose全栈应用中Chain组件增减交互的实现问题

问题解决:定位点击的Chain并更新streak字段

核心问题分析

  1. 表单结构缺陷:所有+/-按钮和secret隐藏字段都在同一个大表单中,点击按钮时会提交整个表单,多个同名的secret字段会被覆盖,最终只有最后一个字段的值被传递到后端,导致req.body.secret为空或错误。
  2. 后端逻辑错误:Chain是User Schema的子文档,并非独立模型,使用Chain.findOne()无法查询到数据,必须直接操作User文档中的chains数组。

分步解决方案

1. 修复前端表单结构

将每个Chain的+/-按钮和对应隐藏字段放入独立的小表单中,确保点击按钮时仅提交当前Chain的标识:

<!-- 新增Chain的独立表单 -->
<form action="/chain" method="POST">
    <div class="row chain-main">
      <div class="col-lg-6">
        <input
          name="newChain"
          type="text"
          class="form-control"
          placeholder="Chain Name"
        />
      </div>
      <div class="col-lg-6">
        <button class="btn btn-md btn-dark" type="submit" name="submit" value="add">Add New Chain</button>
      </div>
    </div>
</form>

<!-- 已存在Chain的操作区域 -->
<div class="chains-wrapper">
  <div class="my-chains">
    <%userChains.forEach((chain, index) => { %>
    <h5 class="chain-header">
      <!-- 减号按钮的独立表单 -->
      <form action="/chain" method="POST" style="display:inline;">
        <input name="secret" type="hidden" value="<%=chain.chainName%>">
        <button type="submit" name="submit" value="decrease"><i class="fa-solid fa-minus"></i></button>
      </form>
      <!-- 加号按钮的独立表单 -->
      <form action="/chain" method="POST" style="display:inline;">
        <input name="secret" type="hidden" value="<%=chain.chainName%>">
        <button type="submit" name="submit" value="increase"><i class="fa-solid fa-plus"></i></button>
      </form>
      
      <span><%=chain.chainName%></span>
      <span>
        <%for(i=0; i<chain.streak; i++){ %>
          <i class="fa-solid fa-trophy"></i>
        <%}%>
      </span>
    </h5>
    <%});%>
  </div>
</div>

2. 修复后端路由逻辑

使用Mongoose的数组更新操作符,直接定位到用户下的目标Chain子文档并修改streak:

app.post("/chain", function (req, res) {
  const newChainName = req.body.newChain;
  const action = req.body.submit;
  const clickedChainName = req.body.secret;

  if (action === "add") {
    User.findById(req.user._id, function (err, foundUser) {
      if (err) {
        console.log(err);
        return res.redirect("/chain");
      }
      if (foundUser) {
        // 避免添加同名Chain
        const isDuplicate = foundUser.chains.some(chain => chain.chainName === newChainName);
        if (!isDuplicate) {
          foundUser.chains.push({
            chainName: newChainName,
            streak: 1,
          });
          foundUser.save(function () {
            res.redirect("/chain");
          });
        } else {
          res.redirect("/chain");
        }
      }
    });
  } else if (action === "increase" || action === "decrease") {
    const increment = action === "increase" ? 1 : -1;

    // 定位用户及目标Chain,更新streak(确保不小于0)
    User.findOneAndUpdate(
      { 
        _id: req.user._id, 
        "chains.chainName": clickedChainName 
      },
      { 
        $inc: { "chains.$.streak": increment },
        $max: { "chains.$.streak": 0 } // 防止streak变为负数
      },
      { new: true },
      function (err) {
        if (err) console.log(err);
        res.redirect("/chain");
      }
    );
  }
});

额外优化建议

  • 用子文档ID替代名称定位:Mongoose默认会给数组中的子文档生成_id,用chain._id作为标识比chainName更可靠(避免同名冲突),只需将前端隐藏字段的值改为<%=chain._id%>,后端查询条件改为"chains._id": clickedChainId。
  • 改用async/await简化代码:替代回调函数,提升代码可读性:
app.post("/chain", async function (req, res) {
  try {
    const newChainName = req.body.newChain;
    const action = req.body.submit;
    const clickedChainName = req.body.secret;

    if (action === "add") {
      const foundUser = await User.findById(req.user._id);
      if (!foundUser) return res.redirect("/chain");
      const isDuplicate = foundUser.chains.some(chain => chain.chainName === newChainName);
      if (!isDuplicate) {
        foundUser.chains.push({ chainName: newChainName, streak: 1 });
        await foundUser.save();
      }
      res.redirect("/chain");
    } else if (action === "increase" || action === "decrease") {
      const increment = action === "increase" ? 1 : -1;
      await User.findOneAndUpdate(
        { _id: req.user._id, "chains.chainName": clickedChainName },
        { $inc: { "chains.$.streak": increment }, $max: { "chains.$.streak": 0 } },
        { new: true }
      );
      res.redirect("/chain");
    }
  } catch (err) {
    console.log(err);
    res.redirect("/chain");
  }
});

内容的提问来源于stack exchange,提问作者UmutE

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 20:05:23