Vue3 + Laravel实现发票数据按日期及发票号分组展示的问题
Vue3 + Laravel实现发票数据按日期及发票号分组展示的问题
嘿,看来你是想把从数据库查出来的扁平发票数据,转换成按日期、发票号层级分组的结构,然后在Vue3页面里优雅地展示出来对吧?我给你分前后端两部分来梳理解决方案,这样你能一步步搞定:
一、Laravel后端:先把扁平数据转成分组结构
你现在拿到的是join后的扁平数据,每个发票行都会重复发票的基本信息,我们需要先在后端把数据整理成日期→发票号→发票明细的层级结构,这样前端渲染会轻松很多。
示例代码(基于Eloquent或DB查询)
如果是用Eloquent关联查询:
use Carbon\Carbon; // 假设你有Invoice模型和InvoiceItem模型,且已定义关联 $invoices = Invoice::with('invoiceItems') ->get() // 先按日期分组,格式化日期为Y-m-d格式 ->groupBy(function ($invoice) { return Carbon::parse($invoice->datetime)->format('Y-m-d'); }) // 再把每个日期下的发票按发票号分组,整理明细和总金额 ->map(function ($dailyInvoices) { return $dailyInvoices->groupBy('inv_num')->map(function ($invoiceGroup) { $invoice = $invoiceGroup->first(); return [ 'inv_num' => $invoice->inv_num, 'datetime' => $invoice->datetime, 'total_amount' => $invoice->total_amount, 'items' => $invoice->invoiceItems->map(function ($item) { return [ 'line_num' => $item->line_num, 'item_name' => $item->item_name, 'line_amount' => $item->line_amount ]; }) ]; }); }); return response()->json($invoices);
如果是直接用DB查询的扁平数据:
use Carbon\Carbon; $flatData = DB::table('invoices') ->join('invoice_items', 'invoices.id', '=', 'invoice_items.invoice_id') ->select( 'invoices.inv_num', 'invoices.datetime', 'invoices.total_amount', 'invoice_items.line_num', 'invoice_items.item_name', 'invoice_items.line_amount' ) ->get(); // 转换成分组结构 $groupedData = $flatData->groupBy(function ($item) { return Carbon::parse($item->datetime)->format('Y-m-d'); })->map(function ($dailyInvoices) { return $dailyInvoices->groupBy('inv_num')->map(function ($invoiceItems) { $firstItem = $invoiceItems->first(); return [ 'inv_num' => $firstItem->inv_num, 'datetime' => $firstItem->datetime, 'total_amount' => $firstItem->total_amount, 'items' => $invoiceItems->map(function ($item) { return [ 'line_num' => $item->line_num, 'item_name' => $item->item_name, 'line_amount' => $item->line_amount ]; }) ]; }); }); return response()->json($groupedData);
这样后端返回的就是按日期分组的嵌套结构,前端直接用就行。
二、Vue3前端:循环渲染分组数据
前端我们用<script setup>的组合式API来处理,先请求后端数据,然后嵌套循环渲染日期、发票、明细行。
示例代码
<template> <div class="invoice-wrapper"> <!-- 循环日期分组 --> <div v-for="(dailyInvoices, dateStr) in groupedInvoices" :key="dateStr" class="daily-section"> <h2>{{ formatDate(dateStr) }}</h2> <!-- 循环当前日期下的所有发票 --> <div v-for="(invoice, invNum) in dailyInvoices" :key="invNum" class="invoice-card"> <h3>INV#{{ invNum }}</h3> <!-- 循环发票明细行 --> <div class="item-list"> <div v-for="item in invoice.items" :key="item.line_num" class="item-row"> <span>Line {{ item.line_num }}</span> <span>{{ item.item_name }}</span> <span>{{ item.line_amount }}</span> </div> </div> <!-- 发票总金额 --> <div class="invoice-total"> <strong>Total Amount: {{ invoice.total_amount }}</strong> </div> <hr class="divider"> </div> </div> </div> </template> <script setup> import { ref, onMounted } from 'vue' import axios from 'axios' const groupedInvoices = ref({}) // 格式化日期为友好展示格式 const formatDate = (dateStr) => { return new Date(dateStr).toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' }) } onMounted(async () => { try { // 请求后端接口 const res = await axios.get('/api/invoices') groupedInvoices.value = res.data } catch (err) { console.error('请求发票数据失败:', err) } }) </script> <style scoped> .invoice-wrapper { max-width: 800px; margin: 20px auto; padding: 0 20px; } .daily-section { margin-bottom: 30px; } .invoice-card { margin: 15px 0 25px 20px; padding: 15px; border: 1px solid #e5e7eb; border-radius: 6px; } .item-row { display: flex; gap: 30px; margin: 8px 0; padding-left: 10px; } .invoice-total { margin-top: 15px; text-align: right; font-size: 1.1em; } .divider { margin-top: 15px; border: none; border-top: 1px dashed #ccc; } </style>
几个注意点
- 日期格式化:你可以选择在后端用Carbon直接生成友好的日期字符串(比如
$invoice->datetime->toFormattedDateString()),也可以在前端用toLocaleDateString适配不同语言环境。 - 数据准确性:因为扁平数据里发票的总金额会重复,分组时一定要取
first()的总金额,避免重复计算。 - 样式调整:上面的CSS是基础样式,你可以根据自己的需求修改布局、颜色等。
备注:内容来源于stack exchange,提问作者Handsome.Paul
相关产品推荐
相关产品推荐

