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

JavaScript构造含动态嵌套数组JSON及Django交互方案咨询

解决方案

前端JSON结构生成逻辑修复

你现有代码的核心问题是把单据对象初始化、JSON序列化的逻辑都放在了表格行遍历循环内部,导致每遍历一行就生成一个独立的单据对象,items数组自然永远只包含当前行的单个商品。
正确逻辑是先在循环外初始化单据主对象和空的items数组,遍历行时只把当前行的商品数据追加到items数组,所有行处理完成后再做统一序列化。另外注意把“是否是添加按钮行”的判断移到读取行元素之前,避免读到最后一行功能按钮时因找不到对应选择器报错。
修正后的核心代码如下:

// 优先指定tbody选择器,避免读取表头、表尾无关行
const tableTbody = document.querySelector(".invoice-add-table table tbody");
// 循环外初始化主单据对象
const invoice = {
  "doc_type": "capitalize",
  "date": document.querySelector('input[name="invoice-date"]').value,
  "number": 98, // 可替换为实际单据号取值逻辑
  "contragent_id": document.querySelector('select').value,
  "comment": "",
  "items": []
};

for (let i = 0; i < tableTbody.rows.length; i++) {
  const row = tableTbody.rows[i];
  // 碰到添加按钮行直接终止循环
  if (row.classList.contains("add-elements")) break;

  // 读取当前行商品数据
  const productId = row.cells[1].querySelector("span").dataset.id;
  const quantity = row.cells[2].querySelector("input").value;
  const buyPrice = row.cells[3].querySelector("input").value;
  const sellPrice = row.cells[5].querySelector("input").value;

  // 追加到items数组,注意做类型转换匹配目标结构的数字类型要求
  invoice.items.push({
    "product": Number(productId),
    "buy_price": buyPrice,
    "sell_price": sellPrice,
    "quantity": Number(quantity)
  });
}

// 所有行处理完成后,按目标结构包一层数组再序列化
const requestData = [invoice];
const jsonStr = JSON.stringify(requestData);
console.log(jsonStr);
// 发AJAX时直接传jsonStr即可,记得添加请求头 Content-Type: application/json

更稳妥的遍历方式:你动态添加行时已经给业务行加了invoice-row类,可以直接通过类名选中所有商品行遍历,不需要依赖单元格索引,后续调整表格列顺序也不会读错数据:

document.querySelectorAll(".invoice-row").forEach(row => {
  const productId = row.querySelector("td.name span").dataset.id;
  const quantity = row.querySelector('input[name="quantity"]').value;
  const buyPrice = row.querySelector('input[name="last_buy_price"]').value;
  const sellPrice = row.querySelector('input[name="sell_price"]').value;
  invoice.items.push({/* 同上字段结构 */});
})

实现安全性说明

  • 前端逻辑层面:只要做好基础输入校验(比如你已有的数字格式校验),前端拼接JSON本身没有安全风险,但所有前端传入的值都不可信,不要把权限校验、价格计算、金额汇总这类核心逻辑放在前端实现。
  • 传输层面:站点开启HTTPS的前提下,AJAX传输过程不会被窃听篡改,提交时记得携带Django自带的CSRF令牌,避免跨站请求伪造攻击。
  • 后端校验必须覆盖所有字段:不要直接拿前端传的商品价格、商品ID、合作方ID直接落库,必须校验对应数据是否存在、当前用户是否有权限操作、数值是否在合法区间,避免恶意用户篡改请求参数提交非法数据。

Django后端最优实现方案

完全不需要受限于formset的自定义能力,Django原生支持JSON请求体解析,灵活度远高于formset,实现步骤如下:

  • 前端提交时在请求头中携带CSRF令牌,不需要关闭CSRF防护。
  • 后端视图层直接解析JSON请求体处理即可,示例代码:
import json
from django.http import JsonResponse
from django.views.decorators.http import require_POST

@require_POST
def save_invoice(request):
    # 解析前端传入的JSON数据
    req_data = json.loads(request.body)
    invoice_info = req_data[0]

    # 主表字段校验:自行补充日期合法性、合作方ID是否存在等校验逻辑
    # 保存主表记录
    invoice = Invoice.objects.create(
        doc_type=invoice_info.get("doc_type"),
        date=invoice_info.get("date"),
        number=invoice_info.get("number"),
        contragent_id=invoice_info.get("contragent_id"),
        comment=invoice_info.get("comment", "")
    )

    # 循环处理商品明细
    for item in invoice_info.get("items", []):
        # 明细字段校验:自行补充商品ID是否存在、价格/数量是否为合法正数等逻辑
        InvoiceItem.objects.create(
            invoice=invoice,
            product_id=item.get("product"),
            buy_price=item.get("buy_price"),
            sell_price=item.get("sell_price"),
            quantity=item.get("quantity")
        )

    return JsonResponse({"code": 0, "msg": "保存成功"})

如果需要更规范的参数校验逻辑,可以用序列化器做嵌套结构校验,对动态字段、嵌套数组的支持比formset好很多,完全可以满足自定义需求。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.31 04:18:23