Commit a48fbab0 authored by 梁俊杰's avatar 梁俊杰
Browse files

提供导出器功能

parent 762f61c1
Loading
Loading
Loading
Loading

Exporter.MD

0 → 100644
+48 −0
Original line number Diff line number Diff line
# 导出Excel csv工具使用说明
1、导出Excel,不使用模板的情况
```$xslt
    $exporter = new Exporter(Exporter::EXPORTER_TYPE_XLSX,'导出产品信息');#不用模板的情况
    
    //填充表头
    $title = ['产品','颜色','尺码','sku']
    $exporter->append($title);
    
    //填充数据
    $list = Product::with(["color","size"])->get()->toArray();
    $exporter->append($list,['name','color.name','size.name|未设定尺码','sku']);
    
    //设置保护
    $exporter->setUnprotectRange("A1:B".$exporter->getCurrentRowIndex());

    $exporter->download(Exporter::DOWNLOAD_TYPE_STREAM,"产品数据");
```

2、导出Excel,使用模板的情况
```$xslt
    //根据模板导出器
    $exporter = new Exporter(Exporter::EXPORTER_TYPE_XLSX,'导出产品信息','template/xxx.xlsx');#使用模板来生成
    
    
    
    //新增sheet并使用它
    $exporter->addSheet('颜色参考表', true);
    //填充表头
    $title = ['颜色id','中文颜色名','英文颜色名']
    $exporter->append($title);
    $exporter->append(Color::get(["id", "cn_name", "en_name"]));
    //设置保护
    $exporter->setUnprotectRange();//全表保护
    
    //使用别的sheet表
    $exporter->setSheetByIndex(0);
    $exporter->setBeginRowIndex(2);//从第二行开始填充数据
    $exporter->setBeginColumnChar("C");//从第三列开始填充数据
    //填充数据
    $list = Product::with(["color","size"])->get()->toArray();
    $exporter->append($list,['name','color.name','size.name|未设定尺码','sku']);
    
    $file= $exporter->download(Exporter::DOWNLOAD_TYPE_RETURN_FILE_PATH,"产品数据");
    
    echo $file;
        
```
+2 −1
Original line number Diff line number Diff line
@@ -22,7 +22,8 @@
    "hyperf/config": "~1.1.0",
    "hyperf/logger": "~1.1.0",
    "hyperf/service-governance": "~1.1.0",
    "fzaninotto/faker": "^1.9"
    "fzaninotto/faker": "^1.9",
    "phpoffice/phpspreadsheet": "^1.8"
  },
  "minimum-stability": "dev",
  "autoload": {

src/Tools/Exporter.php

0 → 100644
+360 −0
Original line number Diff line number Diff line
<?php
/**
 * Created by PhpStorm.
 * User: 梁俊杰
 * Date: 2020/5/12
 * Time: 11:37 AM
 * Description:
 */

namespace Meibuyu\Micro\Tools;

use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Protection;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;


class Exporter
{
    /**
     * 以字节流的形式下载
     */
    CONST DOWNLOAD_TYPE_STREAM = 1;

    /**
     * 保存文件,并返回文件的路径
     */
    CONST DOWNLOAD_TYPE_RETURN_FILE_PATH = 2;

    /**
     * 保存文件,直接输出下载
     */
    CONST DOWNLOAD_TYPE_SAVE_AND_DOWNLOAD = 3;

    CONST EXPORTER_TYPE_CSV = 1;
    CONST EXPORTER_TYPE_XLS = 2;
    CONST EXPORTER_TYPE_XLSX = 3;

    /**
     * @var string 设置非保护区间 例如"A1:B1";从A列B列第一行到数据结束行不需要保护
     */
    private $unprotectRange = "";
    private $beginRowIndex = 0;
    private $fileType = 'Xlsx';
    private $isFromTemplate = false;
    private $sheet = null;
    private $reader = null;
    private $sheetIndex = 0;
    private $name = "";
    private $beginColumnChar = "A";


    /**
     * Exporter constructor.
     * @param $export_type 导出者类型 支持 Exporter::EXPORTER_TYPE_CSV Exporter::EXPORTER_TYPE_XLS Exporter::EXPORTER_TYPE_XLSX
     * @param int $sheetIndex sheet索引
     * @param string $name $sheet表
     * @param string $fromTemplateFile 从模板中创建 导出者
     * @throws Exception
     */
    public function __construct($export_type, $sheetIndex = 0, $name = "export_data", $fromTemplateFile = "")
    {
        $reader = null;
        switch ($export_type) {
            case self::EXPORTER_TYPE_CSV:
                $this->fileType = "Csv";
                break;
            case self::EXPORTER_TYPE_XLS:
                $this->fileType = "Xls";
                break;
            case self::EXPORTER_TYPE_XLSX:
                $this->fileType = "Xlsx";
                break;
            default:
                throw new Exception("类型不支持。");
                break;
        }
        if ($fromTemplateFile && !file_exists($fromTemplateFile)) {
            throw new Exception("模板文件不存在。");
            $this->fileType = ucfirst(strtolower(pathinfo($fromTemplateFile, PATHINFO_EXTENSION)));
            $reader = IOFactory::createReaderForFile($fromTemplateFile);
            $reader = $reader->load($fromTemplateFile);
            $this->isFromTemplate = true;
        }
        if (!$this->isFromTemplate && empty($reader)) {
            $reader = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
        }
        $this->name = $name;
        $sheet = $reader->getSheet($sheetIndex);
        $sheet->setTitle($name);
        $this->reader = $reader;
        $this->sheet = $sheet;
        $this->sheetIndex = $sheetIndex;
    }

    /**
     * 设置非保护区间,调用此方法全表将自动保护
     * @param string $unprotectRange 例如"A1:B1";从A列B列第一行到数据结束行不需要保护,
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     */
    public function setUnprotectRange($unprotectRange = "")
    {
        $this->unprotectRange = $this->unprotectRange;
        $this->sheet->getProtection()->setSheet(true);
        if ($unprotectRange) {
            $this->sheet->getStyle($unprotectRange)
                ->getProtection()
                ->setLocked(Protection::PROTECTION_UNPROTECTED);
        }
    }

    /**
     * 往Excel里面新增一张sheet表
     * @param string $name sheet的名字
     * @param bool $activeSheet 是否激货并切换到当前sheet 默认否
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     */
    public function addSheet($name, $activeSheet = false)
    {
        $workSheet = new Worksheet(null, $name);
        $this->reader->addSheet($workSheet);
        if ($activeSheet) {
            $this->setSheetByName($name);
        }
    }

    /** 根据名字sheet表名激化切换到 该表
     * @param $name
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     */
    public function setSheetByName($name)
    {
        $this->sheet = $this->reader->setActiveSheetIndexByName($name);
        $this->sheetIndex = $this->reader->getActiveSheetIndex();
        $this->resetRowIndex();
    }

    /**
     * 重置排序
     */
    private function resetRowIndex()
    {
        $this->beginRowIndex = 1;
    }

    /**
     * 获取当前数据填充到哪一行了 或者最高行
     * @return int
     */
    public function getCurrentRowIndex()
    {
        return $this->sheet->getHighestRow();
    }

    /**设置从哪一列开始填充数据
     * @param string $char 默认 A列
     */
    public function setBeginColumnChar($char = "A")
    {
        $this->beginColumnChar = $char;
    }

    /**设置从哪一行开始填充数据
     * @param int $index 默认第一行
     */
    public function setBeginRowIndex($index = 1)
    {
        $this->beginRowIndex = $index;
    }

    /**
     * 根据名字sheet索引激化切换到 该表
     * @param int $sheetIndex
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     */
    public function setSheetByIndex(int $sheetIndex)
    {
        $this->sheet = $this->reader->setActiveSheetIndex($sheetIndex);
        $this->sheetIndex = $sheetIndex;
        $this->resetRowIndex();
    }

    /**
     * 往表格里填充数据
     * @param array $data 支持一维数组和二位数组,数据顺序与表头顺序相同
     * @param array $keys 如果$data是原生数组支持直接根据$keys
     * 提取数据,可多层提取例如 ['product_name','color.name','creator.user_status.name']
     * 支持默认值,为空时显示默认值 例如['product_name|默认值','color.name|无颜色']
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     */
    public function append(array $data, $keys = [])
    {

        //一维数组转二维
        if (!(isset($data[0]) && is_array($data[0]))) {
            $data = [$data];
        }
        if ($keys) {
            foreach ($data as $k => $v) {
                $data[$k] = $this->getKeyValue($v, $keys);
            }
        }

        $this->sheet->fromArray($data, null, $this->beginColumnChar . $this->beginRowIndex);
        //美化样式
        $this->applyStyle($this->beginColumnChar . $this->beginRowIndex . ":" . $this->sheet->getHighestColumn() . ($this->beginRowIndex + count($data)));
        $this->beginRowIndex += count($data);
    }

    /**根据key提取数据
     * @param array $data
     * @param array $keys
     * @return array
     */
    private function getKeyValue(array $data, array $keys)
    {
        $result = [];
        foreach ($keys as $k => $v) {
            if (strpos($v, ".") === false) {
                if (isset($data[$v])) {
                    $result[] = $data[$v];
                } else {
                    new \Exception("key为{$v}的数据不存在");
                }
            } else {
                $separate = explode("|", $v);
                $nullValue = isset($separate[1]) ? $separate[1] : "";
                $list = explode(".", $separate[0]);
                $t = count($list) - 1;
                $b = $data;
                foreach ($list as $lk => $lv) {
                    if (isset($b[$lv])) {
                        $b = $b[$lv];
                    } else {
                        $b = "";
                    }
                    //提取到最后一个
                    if ($t === $lk) {
                        $result[] = (($b === "") ? $nullValue : $b);
                    }
                }
            }
        }
        return $result;
    }

    /**
     * 给表格加样式
     * @param $pRange
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     */
    private function applyStyle($pRange)
    {
        $styleArray = [
            'borders' => [
                'allBorders' => [
                    'borderStyle' => Border::BORDER_THIN,
                    'color' => ['argb' => '666'],
                ],
            ],
            'alignment' => [
                'horizontal' => Alignment::HORIZONTAL_CENTER,
                'vertical' => Alignment::VERTICAL_CENTER,
                'wrapText' => true,
            ],
            'quotePrefix' => true,
        ];

        $this->sheet->getStyle($pRange)->applyFromArray($styleArray);
    }

    /**
     *下载导出的文件
     * @param int $downloadType 下载形式 支持 Exporter::DOWNLOAD_TYPE_STREAM Exporter::DOWNLOAD_TYPE_RETURN_FILE_PATH Exporter::DOWNLOAD_TYPE_SAVE_AND_DOWNLOAD
     * @param string $filename 下载的文件名
     * @return string
     * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
     */
    public function download(int $downloadType, $filename = "")
    {
        set_time_limit(0);
        if (!$filename) {
            $filename = $this->name ? $this->name : rand(1, 9999999) . time() . rand(1, 9999999);
        }
        $filename .= "." . strtolower($this->fileType);
        $objWriter = IOFactory::createWriter($this->reader, $this->fileType);
        switch ($downloadType) {
            case self::DOWNLOAD_TYPE_STREAM:
            case self::DOWNLOAD_TYPE_SAVE_AND_DOWNLOAD:

                if ($this->fileType == 'Xlsx') {
                    header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
                } elseif ($this->fileType == 'Xls') {
                    header('Content-Type: application/vnd.ms-excel');
                } else {
                    header("Content-type:text/csv;");
                }
                header("Content-Disposition: attachment;filename="
                    . $filename);
                header('Cache-Control: max-age=0');
                ob_clean();

                if ($downloadType == self::DOWNLOAD_TYPE_STREAM) {
                    $objWriter->save('php://output');
                } else {
                    $f = $_SERVER['DOCUMENT_ROOT'] . "/tmp/data";
                    if (!file_exists($f)) {
                        mkdir($f, 0777, true);
                    }
                    $f .= "/" . $filename;
                    $objWriter->save($f);
                    header('Content-length:' . filesize($f));
                    $fp = fopen($f, 'r');
                    fseek($fp, 0);
                    ob_start();
                    while (!feof($fp) && connection_status() == 0) {
                        $chunk_size = 1024 * 1024 * 2; // 2MB
                        echo fread($fp, $chunk_size);
                        ob_flush();
                        flush();
                    }
                    ob_end_clean();
                    fclose($fp);
                    @unlink($f);
                }
                break;
            case self::DOWNLOAD_TYPE_RETURN_FILE_PATH:
                $f = $_SERVER['DOCUMENT_ROOT'] . "/tmp/data";
                if (!file_exists($f)) {
                    mkdir($f, 0777, true);
                }
                $f .= "/" . $filename;
                $objWriter->save($f);
                return $f;
                break;
            default:
                throw new \Exception('不支持此种下载类型');
                break;
        }
    }

    /**
     * 返回当前激化的sheet表
     * @return null|Worksheet
     */
    public function getSheet()
    {
        return $this->sheet;
    }

    /**
     * 返回原生的PhpOffice Reader 手动处理数据
     * @return null|\PhpOffice\PhpSpreadsheet\Spreadsheet
     */
    public function getReader()
    {
        return $this->reader;
    }
}