dcat admin用xlswriter导出数据,自定义列宽行高,首行合并表名

2023-9-19 补充:
文中的功能已经做成扩展包了
一个 dcat 版本,一个是 laravel 版本

博客:laravel 扩展:xlswriter 导出,自定义复杂合并及样式

博客:laravel 的 dcat 扩展:xlswriter 导出,自定义合并数据单元格,自定义样...

——————
这个是根据之前的,把基类抽出来,删除了一些多余的代码
dcat 使用 xlswriter 分块导出数据,自定义列宽行高,首行合并表名
效果图:
dcat admin用xlswriter导出数据,自定义列宽行高,首行合并表名

首先创建基类:

<?php
namespace App\Exports;
use Carbon\Carbon;
use Dcat\Admin\Grid\Exporter;
use Dcat\Admin\Grid\Exporters\AbstractExporter;
use Illuminate\Database\Eloquent\Collection;
use Vtiful\Kernel\Excel;
use Vtiful\Kernel\Format;

abstract class BaseExport extends AbstractExporter {
    public $header = [];
    public $fileName = '文件名';
    public $tableTitle = '表名';
    /**
     * @var Collection
     */
    public $data;

    public function getTmpDir(): string {
        $tmp = ini_get('upload_tmp_dir');

        if ($tmp !== false && file_exists($tmp)) {
            return realpath($tmp);
        }

        return realpath(sys_get_temp_dir());
    }

    public function setFilename($filename) {
        $this->fileName = $filename . Date('YmdHis') . '.xlsx';
        return $this;
    }

    public function getFilename() {
        return $this->fileName;
    }

    public function getHeader() {
        return $this->header;
    }

    public function getTableTitle() {
        return $this->tableTitle;
    }

    public function getData() {
        return $this->data;
    }

    public $index;

    public function setData($data) {
        if (!$data instanceof \Illuminate\Support\Collection) {
            $data = collect($data);
        }
        $this->data = $data;
        $this->index = 1;

        return $this;
    }

    abstract public function map($row);

    public $fontFamily = '微软雅黑';
    public $rowHeight = 40;
    public $headerRowHeight = 40;
    public $filePath;
    public $excel;
    public $headerLen;

    public function __construct() {
        parent::__construct();
        $config = ['path' => $this->getTmpDir() . '/'];
        $this->excel = (new Excel($config))->fileName($this->setFilename($this->fileName)->fileName, 'Sheet1');
    }

    public function store() {
        $fileHandle = $this->excel->getHandle();
        $format1 = new Format($fileHandle);
        $format2 = new Format($fileHandle);
        /** @var Collection $data */
        $data = $this->getData();
        $header = $this->getHeader();
        $this->headerLen = count($header);
        $columnWidths = array_column($header, 'width');
        $columnNames = array_column($header, 'name');
        // header
        $data->prepend($columnNames);
        // title
        $title = array_fill(1, $this->headerLen - 1, '');
        $title[0] = $this->getTableTitle();
        $data->prepend($title);

        // title style
        $titleStyle = $format1->fontSize(16)
                              ->bold()
                              ->font($this->fontFamily)
                              ->align(Format::FORMAT_ALIGN_CENTER, Format::FORMAT_ALIGN_VERTICAL_CENTER)
                              ->wrap()
                              ->toResource();

        // global style
        $globalStyle = $format2->fontSize(10)
                               ->font($this->fontFamily)
                               ->align(Format::FORMAT_ALIGN_CENTER, Format::FORMAT_ALIGN_VERTICAL_CENTER)
                               ->border(Format::BORDER_THIN)
                               ->wrap()
                               ->toResource();

        // 获取最后一列的列名
        $end = $this->getColumn($this->headerLen - 1);

        // 应用样式
        $this->excel = $this->excel/*->defaultFormat($globalStyle)*/// 默认样式
        ->MergeCells("A1:{$end}1", $this->getFilename())                  // 合并title单元格
        ->setRow("A1", 50, $titleStyle)                              // title样式
        ->setRow("A2", $this->headerRowHeight)                       // header样式
        ->freezePanes(2, 0);        // 冻结前两行,列不冻结
        // 设置列宽 以及默认样式

        foreach ($columnWidths as $k => $columnWidth) {
            $column = $this->getColumn($k);
            $this->excel->setColumn($column . ':' . $column, $columnWidth, $globalStyle);
        }

        // 数据填充,导出
        if ($this->isAll) {
            $this->insertData($data);
            $this->filePath = $this->chunk(function(int $times, $perPage) {
                return $this->isAllHandle($times, $perPage);
            })->output();
        } else {
            $this->filePath = $this->insertData($data)->output();
        }

        return $this;
    }

    public function isAllHandle($times, $perPage) {
        return $this->buildData($times, $perPage);
    }

    public $shouldDelete = false;
    public $startDataRow = 2; // 第三行开始数据行(0是第一行)
    public $currentLine = 0;  // 当前数据插入行

    public function insertData($data) {
        foreach ($data as $row => $rowData) {
            // 对数据行处理
            if ($this->currentLine >= $this->startDataRow) {
                $this->excel->setRow($this->currentLine + 1, $this->rowHeight);          // 设置行高,这里的行又是从1开始的,+1
            }
            if ($rowData instanceof Model) {
                $rowData = $this->map($rowData);
                $this->index++;
            }
            foreach ($rowData as $column => $columnData) {
                // dump($this->currentLine.'-'.$column.'-'. $columnData);
                $this->excel->insertText($this->currentLine, $column, $columnData);
            }

            $this->currentLine++;
        }
        return $this;
    }

    public function output() {
        return $this->excel->output();
    }

    public function getColumn(int $columnIndex) {
        $columnIndex++;
        $first = 64 + (int)($columnIndex / 26);
        $second = 64 + $columnIndex % 26; // 26个字母
        if ($second === 64) { 
            $first--;
            $second = 'Z';
        } else {
            $second = chr($second);
        }
        if ($first > 90) {          //64 + 26
            throw new \Exception('超出最大列数');
        } else if ($first === 64) {
            $first = '';
        } else {
            $first = chr($first);
        }
        return $first . $second;
    }

    public function shouldDelete($v = true) {
        $this->shouldDelete = $v;
        return $this;
    }

    public function download($filePath = null) {
        if ($filePath) {
            $this->filePath = $filePath;
        }
        if ($key = request('key')) {
            $this->filePath = base64_decode($key);
        }
        response()->download($this->filePath)->deleteFileAfterSend($this->shouldDelete)->send();
        exit();
    }
    public $isAll;
    public function export() {
        $this->isAll = $this->scope === Exporter::SCOPE_ALL;
        if ($this->isAll) {
            set_time_limit(0);
            $this->setData([])->store();
        } else {
            $this->setData($this->buildData())->store();
        }
        $this->shouldDelete()->download();
    }

    public function chunk($callback = null) {
        $times = 1;
        $chunkSize = 5000;   // 分块处理 5000查一次数据库
        $max = 100000;       // 限制最大一次导出10万行
        $completed = 0;
        do {
            /** @var Collection $result */
            $result = $callback($times, $chunkSize);

            $count = count($result);
            $completed += $count;

            $this->insertData($result);
            unset($result);

            $times++;
        } while ($count === $chunkSize && $completed < $max);

        return $this;
    }
}

如果要新增一个导出,就新建一个导出类,继承基类,然后定义一下 header、map、tableTitle 、fileName 就行了。

<?php
namespace App\Exports;

use App\Models\RiskArea;
use Illuminate\Database\Eloquent\Model;

class RiskAreaExport extends BaseExport {
    public $header = [
        ['column' => 'a', 'width' => 8, 'name' => '序号'],
        ['column' => 'b', 'width' => 20, 'name' => '省'],
        ['column' => 'c', 'width' => 20, 'name' => '市'],
        ['column' => 'd', 'width' => 20, 'name' => '区县'],
        ['column' => 'e', 'width' => 20, 'name' => '乡镇街道'],
        ['column' => 'f', 'width' => 20, 'name' => '管控规则'],
        ['column' => 'g', 'width' => 20, 'name' => '检测检测'],
    ];
    public $fileName = '风险地区';
    public $tableTitle = '风险地区';
    public $rowHeight = 50;

    public function map($row) {
        /** @var RiskArea $row */
        $rowData = [
            $this->index,
            $row->province->name,
            $row->city->name??null,
            $row->region->name??null,
            $row->street->name??null,
            implode('+',$row->isolatedRulesId),
            implode(',',$row->testRulesId),
        ];
        // dd($rowData);
        return $rowData;
    }
}

控制器调用:

...
$grid->export(new \App\Exports\RiskAreaExport());
...
本作品采用《CC 协议》,转载必须注明作者和本文链接
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
讨论数量: 3

感谢

1年前 评论
DogLoML (楼主) 1年前
一叶菩提 1年前