webman后台导出

wz_8849

layui 有前端导出,这是接口导出csv.

一般情况大家可能也不需要。有需要时来复制。
1,2 部分 直接复制粘贴, 3,4部分 复制粘贴后 改着用。

1,一个类 CsvExporter.php 放在 plugin/admin/app/common/ 下。

<?php 
namespace plugin\admin\app\common;
/**
 * 
 * @author Administrator
 *
 */
class CsvExporter
{
    protected $config = [
        'max_direct_export' => 2000,  
        'page_size' => 2000, // 分页界面每页显示条数
        'temp_dir' => 'runtime/export',
    ];

    protected $callbacks = [
        'getTotal' => null,
        'getData' => null,
        'processField' => null,
    ];

    protected $fields = [];

    public function __construct(array $config = [])
    {
        $this->config = array_merge($this->config, $config);

        if (!is_dir($this->config['temp_dir'])) {
            mkdir($this->config['temp_dir'], 0755, true);
        }
    }

    public function setCallback(string $type, callable $callback)
    {
        if (array_key_exists($type, $this->callbacks)) {
            $this->callbacks[$type] = $callback;
        }
        return $this;
    }

    public function setFields(array $fields)
    {
        $this->fields = $fields;
        return $this;
    }
    public function export(string $filename)
    {
        if (!is_callable($this->callbacks['getTotal'])) {
            throw new \RuntimeException('必须设置getTotal回调');
        }

        // 检查是否是分页导出请求

        $page = (int) request()->get('page', 0);
        $limit = (int) request()->get('limit', 0);

        if ($page > 0 && $limit > 0) {
            return $this->exportPage($filename, $page, $limit);
        }

        // 普通导出模式
        $total = call_user_func($this->callbacks['getTotal']);

        if ($total <= $this->config['max_direct_export']) {
            return $this->directExport($filename, $total);
        } else {
            return $this->showPaginatedExport($filename, $total);
        }
    }
    protected function exportPage(string $filename, int $page, int $limit)
    {
        if (!is_callable($this->callbacks['getData'])) {
            throw new \RuntimeException('必须设置getData回调');
        }

        $data = call_user_func($this->callbacks['getData'], $page, $limit);
        $processedData = $this->processData($data);

        $start = ($page - 1) * $limit + 1;
        $end = $start + count($data) - 1;

        return $this->generateCsvResponse(
            $filename . '_' . $start . '_' . $end,
            array_values($this->fields),
            $processedData
            );
    } 

    protected function directExport(string $filename, int $total)
    {
        if (!is_callable($this->callbacks['getData'])) {
            throw new \RuntimeException('必须设置getData回调');
        }

        $data = call_user_func($this->callbacks['getData'], 1, $total);
        $processedData = $this->processData($data);

        return $this->generateCsvResponse(
            $filename,
            array_values($this->fields),
            $processedData
            );
    }

    // 在CsvExporter类中修改showPaginatedExport方法
    protected function showPaginatedExport(string $filename, int $total)
    {
        $totalPages = ceil($total / $this->config['page_size']);

        return view('common/paginated_export', [
            'filename' => $filename,
            'headers' => array_values($this->fields),
            'totalPages' => $totalPages,
            'pageSize' => $this->config['page_size'],
            'total' => $total,
            'exporter' => $this,
            'gets'=>request()->get()
        ]);
    }

    protected function processData(array $data): array
    {
        $result = [];
        foreach ($data as $item) {
            $row = [];
            foreach ($this->fields as $field => $title) {
                $value = $item[$field] ?? '';

                if (is_callable($this->callbacks['processField'])) {
                    $value = call_user_func($this->callbacks['processField'], $field, $value, $item);
                }

                $row[] = $value;
            }
            $result[] = $row;
        }
        return $result;
    }

    protected function generateCsvResponse(string $filename, array $headers, array $data)
    {
        $filePath = $this->config['temp_dir'] . '/' . uniqid() . '.csv';
        $this->writeCsv($filePath, $headers, $data);

        $response = response()->file($filePath, [
            'Content-Disposition' => 'attachment; filename="' . $filename . '.csv"'
        ]);
        // $response->deleteFileAfterSend(true);
        return $response; 
    }

    protected function writeCsv(string $filePath, array $headers, array $data)
    {
        $file = fopen($filePath, 'w');

        if ($file === false) {
            throw new \RuntimeException("无法打开文件: {$filePath}");
        }

        fwrite($file, "\xEF\xBB\xBF"); // BOM头

        fputcsv($file, $headers);

        foreach ($data as $row) {
            fputcsv($file, $row);
        }

        fclose($file);
    }
}

2: 一个html模板文件 paginatd_export.html 放在 plugin/admin/app/view/common/下

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="utf-8">
    <title>数据导出</title>
    <link href="/app/admin/component/pear/css/pear.css" rel="stylesheet" />
    <link href="/app/admin/admin/css/pages/error.css" rel="stylesheet" />
    <style>
        .export-buttons {
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
            margin: 20px 0;
        }
        .export-btn {
            min-width: 120px;
        }
        .export-summary {
            margin-bottom: 20px;
            font-size: 16px;
        }
        .content {
            position: absolute;
            top: 20%;
            padding: 0 50px;
            width: 100%;
            text-align: left; 

        }
    </style>
</head>
<body>
    <div class="content">
        <div class="export-dialog">
            <h3>数据导出</h3>

            <div style="margin: 10px 0;">
                <button onclick="history.back()" class="pear-btn pear-btn-warming">
                    ← 返回
                </button>
            </div>
            <div class="export-summary">
                总记录数: <?=$total?> 条,共 <?=$totalPages?> 页
            </div>

            <div class="export-buttons">
                <?php for($i = 1; $i <= $totalPages; $i++): ?>
                    <?php 
                    $start = ($i - 1) * $pageSize + 1; 
                    $end = min($i * $pageSize, $total);
                    ?>
                    <button class="pear-btn pear-btn-primary export-btn" 
                            data-i="<?=$i?>">
                        导出 <?=$start?>-<?=$end?> 条
                    </button>
                <?php endfor; ?>
            </div>
        </div>
    </div>

    <script src="/app/admin/component/layui/layui.js?v=2.8.12"></script>
    <script src="/app/admin/component/pear/pear.js"></script>
    <script>
    layui.use(['jquery'], function(){
        var $ = layui.$;

        // 导出按钮点击事件
        $('.export-btn').click(function() {
            // 获取当前URL对象
            var url = new URL(window.location.href);

            // 更新page和limit参数

            <?php foreach($gets as $f=>$v): ?>
            url.searchParams.set('<?=$f?>', '<?=$v?>');
            <?php endforeach;?>
            url.searchParams.set('page', $(this).data('i'));
            url.searchParams.set('limit', '<?=$pageSize?>');

            // 跳转到新URL
            window.location.href = url.toString();
        });
    });
    </script>
</body>
</html>

3,某个后台控制器的方法:

public function export(Request $request)
{
        $exporter = new CsvExporter();

        $exporter->setFields([
            'order_no' => '订单编号',
            'user_id' => '用户ID',
            'amount' => '金额',
            'status' => '状态',
            'created_at' => '创建时间'
        ]);
        $status = $request->get('status','');

        $exporter->setCallback('getTotal', function()use($status) {
            return \app\model\Order::count();
            //return \app\model\Order::where('status',$status)->count();
        });

        $exporter->setCallback('getData', function($page, $limit)use($status) {
            return \app\model\Order::with('user')
                ->orderBy('id', 'desc')
                ->offset(($page - 1) * $limit)
                ->limit($limit)
                //->where('status',$status)
                ->get()
                ->toArray();
        });

        $exporter->setCallback('processField', function($field, $value, $item) {
            if ($field === 'status') {
                $statusMap = [
                    0 => '待支付',
                    1 => '已支付',
                    2 => '已发货',
                    3 => '已完成',
                    4 => '已取消'
                ];
                return $statusMap[$value] ?? '未知状态';
            }
            if ($field === 'user_id') {
                return $item['user']['username'] ?? $value;
            }
            return $value;
        });

        return $exporter->export('订单列表_' . date('Ymd'));
 }

4:后台前端部分4:后台前端部分

<a href="/app/admin/order/export" class="btn btn-primary">导出用户数据</a>

或者下面的 带参数的:

<!-- 表格顶部工具栏 -->
     <script type="text/html" id="table-toolbar">
          <button class="pear-btn pear-btn-md" lay-event="export" permission="app.admin.order.export">
                <i class="layui-icon layui-icon-export"></i>导出
           </button>
    </script>
// 表格顶部工具栏事件
   table.on("toolbar(data-table)", function(obj) {
       if (obj.event === "refresh") {
            refreshTable();
       } else if (obj.event === "batchRemove") {
            batchRemove(obj);
       } else if(obj.event=='export'){
            //class="layui-form" lay-filter="searchForm"
            var status = form.val("searchForm").status*1;
            location.href='/app/admin/order/export?status='+status; 
       }
   });
82 0 1
0个评论

wz_8849

890
积分
0
获赞数
0
粉丝数
2021-12-24 加入
🔝