一般情况大家可能也不需要。有需要时来复制。
1,2 部分 直接复制粘贴, 3,4部分 复制粘贴后 改着用。
<?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);
}
}
<!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>
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'));
}
<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;
}
});