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

Laravel 5.6发票表单仅保存首行商品数据问题求助

解决Laravel多商品发票表单仅首行数据保存的问题

嘿,我帮你梳理下这个问题:从你的请求数据能看出来,只有第一行商品数据被提交到后端了,这说明动态新增的表单行没有被正确识别和提交,结合你的代码来看,主要问题出在前端的jQuery新增逻辑里,还有控制器里的一个小错误,咱们一步步搞定:

1. 修复动态新增行的核心问题(前端)

你写的addRow函数里直接用了Blade的@foreach语法,这可是后端渲染的代码啊!页面加载完成后,前端的jQuery根本认不出@foreach,所以你动态生成的<select>里其实根本没有实际的产品选项,甚至可能因为语法错误,浏览器都不把这些新增行的表单元素当成表单的一部分,自然提交的时候就只会带第一行的数据。

正确的做法是先把后端的产品列表转成前端能识别的JS变量,再用JS动态生成选项:

第一步:在Blade模板里定义产品列表的JS变量

在页面底部(比如</body>标签前)加这段代码,把后端的$product_lists转成JS对象:

<script>
// 用@json把Laravel数组转成合法的JS对象
const productLists = @json($product_lists);
</script>

第二步:修改addRow函数,用JS生成选项

把原来的addRow改成这样,用JS循环生成select的选项:

function addRow(){
    // 先构建select的选项内容
    let productOptions = '<option value="0" selected="true" disabled="true">Select Product</option>';
    // 循环遍历产品列表,生成每个选项
    for(const [productId, productName] of Object.entries(productLists)){
        productOptions += `<option value="${productId}">${productName}</option>`;
    }
    // 组装完整的表格行HTML
    const newRow = `
        <tr>
            <td>
                <select class="form-control name" name="productname[]">${productOptions}</select>
            </td>
            <td><input type="text" class="form-control form-control-line qty" name="qty[]"></td>
            <td><input type="text" class="form-control form-control-line price" name="price[]"></td>
            <td><input type="text" class="form-control form-control-line total" name="total[]"></td>
            <td><a href="#" class="remove">-<i class="glyphicon glyphicon-remove"></i></a></td>
        </tr>
    `;
    $('tbody').append(newRow);
};

这样生成的新行里,select才有真实的产品选项,而且所有表单元素的name属性(比如productname[])都能被浏览器正确识别,提交时就会把所有行的数据都打包成数组传给后端了。

2. 修复控制器里的产品ID赋值错误

你的控制器代码里有个明显的错误:'product_id'=>$request->$v,这里的$v已经是productname数组里的产品ID值(比如1),你不需要再用$request->$v去取值,直接用$v就可以了!另外还要记得给商品关联对应的发票ID,不然商品数据不知道属于哪张发票:

public function store(Request $request) {
    // 验证发票号
    $validated = $this->validate(request(), [
        'invoice_no' => 'required'
    ]);
    // 创建发票记录,保存后拿到发票ID
    $invoice = SalesInvoice::create($validated);
    
    // 先判断是否有商品数据,避免空数组报错
    if($request->has('productname') && is_array($request->productname)){
        foreach ($request->productname as $key => $productId) {
            // 跳过未选择产品的空行
            if($productId == 0) continue;
            // 组装商品数据
            $itemData = [
                'sales_invoice_id' => $invoice->id, // 必须关联发票ID
                'product_id' => $productId, // 这里直接用productId即可
                'qty' => $request->qty[$key],
                'price' => $request->price[$key],
                'total' => $request->total[$key]
            ];
            // 推荐用create而不是insert,能触发模型的事件和验证
            SalesInvoiceItem::create($itemData);
        }
    }
    
    // 这里可以加个重定向或者返回响应
    return redirect()->back()->with('success', '发票创建成功!');
}

3. 额外的实用优化建议

  • 给删除按钮绑定事件,确保能正确删除行:
    // 用事件委托绑定删除按钮,因为动态生成的元素直接绑定事件无效
    $(document).on('click', '.remove', function(e){
        e.preventDefault(); // 阻止默认跳转行为
        $(this).closest('tr').remove(); // 删除当前行
    });
    
  • 自动计算总价,用户输入数量和价格后自动算出total,不用手动输入:
    // 监听数量和价格的输入事件,自动计算总价
    $(document).on('input', '.qty, .price', function(){
        const currentRow = $(this).closest('tr');
        const qty = parseFloat(currentRow.find('.qty').val()) || 0;
        const price = parseFloat(currentRow.find('.price').val()) || 0;
        currentRow.find('.total').val(qty * price);
    });
    
  • 可以加前端验证,比如确保每行都选择了产品、数量和价格不为空,避免提交无效数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:32:01