如何在页面刷新后保留搜索结果并导出为 CSV

本文介绍如何通过 laravel session 持久化搜索条件,确保「下载 csv」操作仅导出当前筛选后的数据,而非全量数据库记录。核心方案是:在搜索提交时存入会话,在下载方法中复用相同查询逻辑。

在 Laravel 中实现「按搜索结果导出 CSV」的关键在于状态同步:前端展示的筛选结果(如日期范围)必须与后端下载逻辑保持一致。由于 HTTP 是无状态协议,页面刷新或跳转后原始请求参数($request->startDate 等)将丢失。直接在 download() 方法中重复构建查询却未传入筛选条件,自然导致导出全部数据。

✅ 正确做法:用 Session 暂存搜索参数

修改 index() 方法,在执行查询前将用户输入的筛选条件存入 Session:

public function index(Request $request)
{
    // 保存搜索条件到 Session(仅当有输入时)
    if ($request->filled('startDate') || $request->filled('dateEnd')) {
        session([
            'downloads_filter_start_date' => $request->startDate,
            'downloads_filter_end_date'   => $request->dateEnd,
        ]);
    }

    // 构建可复用的查询构造器(提取为独立方法更佳)
    $userQuery = DB::table('downloads')
        ->select('user_id as downloader_id', DB::raw('COUNT(id) as count'))
        ->groupBy('user_id');

    // 应用日期过滤(复用逻辑)
    $startDate = session('downloads_filter_start_date');
    $endDate   = session('downloads_filter_end_date');

    if ($startDate && $endDate) {
        $userQuery->whereBetween(DB::raw("DATE_FORMAT(downloads.created_at, '%Y-%m-%d')"), [$startDate, $endDate]);
    } elseif ($startDate) {
        $userQuery->whereDate('downloads.created_at', $startDate);
    }

    $works = DB::table('users')
        ->joinSub($userQuery, 'downloads_byuser', function ($join) {
            $join->on('id', '=', 'downloads_byuser.downloader_id');
        })
        ->select('users.id as user_id', 'users.name as name', 'users.email as 

email', 'count') ->get(); return view('admin.downloadsuser', compact('works')); }
? 注意:使用 whereDate() 替代原代码中的 where('created_at', $startDate) 更语义清晰且兼容性更好;同时避免 isset() 判断,改用 $request->filled() 防止空字符串干扰。

? 在 download() 方法中复用相同查询逻辑

download() 不应重新查询全表,而应重建与 index() 完全一致的查询,并导出结果:

use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\DB;

public function download(Request $request)
{
    // 从 Session 获取筛选条件(无则为 null,表示不加过滤)
    $startDate = session('downloads_filter_start_date');
    $endDate   = session('downloads_filter_end_date');

    // 完全复刻 index() 中的查询逻辑(不含 ->get())
    $userQuery = DB::table('downloads')
        ->select('user_id as downloader_id', DB::raw('COUNT(id) as count'))
        ->groupBy('user_id');

    if ($startDate && $endDate) {
        $userQuery->whereBetween(DB::raw("DATE_FORMAT(downloads.created_at, '%Y-%m-%d')"), [$startDate, $endDate]);
    } elseif ($startDate) {
        $userQuery->whereDate('downloads.created_at', $startDate);
    }

    $results = DB::table('users')
        ->joinSub($userQuery, 'downloads_byuser', function ($join) {
            $join->on('id', '=', 'downloads_byuser.downloader_id');
        })
        ->select('users.email', 'downloads_byuser.count as downloads')
        ->get(); // ← 此处才执行查询获取数据

    // 生成 CSV 文件
    $filename = 'Downloads.csv';
    $headers = [
        'Content-Type' => 'text/csv',
        'Content-Disposition' => 'attachment; filename="' . $filename . '"',
    ];

    $callback = function () use ($results) {
        $fp = fopen('php://output', 'w');
        // 写入表头
        fputcsv($fp, ['Email', 'Downloads']);
        // 写入每行数据
        foreach ($results as $row) {
            fputcsv($fp, [$row->email, $row->downloads]);
        }
        fclose($fp);
    };

    return Response::stream($callback, 200, $headers);
}

优势说明

  • 一致性保障:index() 和 download() 共享同一套查询构建逻辑,杜绝结果偏差;
  • 无文件写入风险:使用 Response::stream() 直接输出流,避免临时文件残留与权限问题;
  • Session 安全清理:如需在下载后清空筛选条件,可在 download() 末尾添加 session()->forget(['downloads_filter_start_date', 'downloads_filter_end_date']);。

? 补充建议:提升健壮性

  • 验证日期格式:在 index() 中增加 $request->validate(['startDate' => 'date', 'dateEnd' => 'date|after_or_equal:startDate']);
  • 空结果处理:若 $results->isEmpty(),可提前返回提示或空 CSV;
  • 前端体验优化:为「下载」按钮添加 loading 状态,或禁用重复点击。

通过 Session 桥接前后端状态,即可优雅解决「搜索后下载」场景的核心痛点——让导出真正反映用户所见。