数据导出常规方式(经验分享贴)
导出

一、说明

  • 使用常规的导出方式,不走计划任务,数据量大的小伙伴慎用。

  • 依赖phpoffice/phpspreadsheet库。

  • 优点:可以直接使用BuildAdmin原有的数据筛选功能,列表所查即导出,避免重复造轮子,方便数据处理,自定义筛选等操作。

  • 缺点:比较占内存,大数据量的建议限制导出数量。

  • 直接上图

二、前端代码

在页面中添加导出按钮

找到页面vue代码中<TableHeader>组件,使用自定义插槽添加按钮。

<!-- 表格顶部菜单 -->
<!-- 自定义按钮请使用插槽,甚至公共搜索也可以使用具名插槽渲染,参见文档 -->
<TableHeader
    :buttons="['refresh', 'add', 'edit', 'delete', 'comSearch', 'quickSearch', 'columnDisplay']"
    :quick-search-placeholder="t('Quick search placeholder', { fields: t('article.quick Search Fields') })"
>

    <!-- 自定义按钮 -->
    <template #default>
        <el-button color="#40485b" class="table-header-operate" type="info" style="margin-left: 12px;" @click="exportHandle">
            <Icon color="#ffffff" name="el-icon-RefreshRight" />
            <span class="table-header-operate-text">导出</span>
        </el-button>
    </template>

</TableHeader>

script中定义点击事件,放在const baTable = new baTableClass后面

//这段可以移到上面去
import { ElNotification } from 'element-plus'
import { ElLoading } from 'element-plus'

const exportHandle = ()=>{

    let params = {}
    //带上页面筛选数据
    Object.assign(params, baTable.table.filter)

    //加载中转圈圈
    const loadIndex = ElLoading.service()

    //请求url,请求的是当前业务控制器的export方法
    baTable.api.postData('export', params).then(res=>{

        //关闭加载中
        loadIndex.close()
        if(res.code == 1 && res.data.full_url){
            //下载excel文件
            window.open(res.data.full_url)
        }else{
            ElNotification({
                message: '请求错误',
                type: 'error',
            })
        }
    })
}

三、后端代码

引入依赖

composer require phpoffice/phpspreadsheet

在业务控制器中添加导出方法

public function export()
  {
      try {
          list($where, $alias, $limit, $order) = $this->queryBuilder();
          $res = $this->model
              ->field($this->indexField)
              ->withJoin($this->withJoinTable, $this->withJoinType)
              ->alias($alias)
              ->where($where)
              ->order($order)
              ->select()
              ->toArray();
          //以上部分直接复制的index控制器,然后将paginate()方法改成select()->toArray()
          

          //创建导出类实例
          $export = new \alei\Export('文章');

          //设置行参数
          $export->setColumn([
              ['field' => 'id', 'title' => '编号'],
              ['field' => 'title', 'title' => '标题'],
              ['field' => 'create_time', 'title' => '创建时间', 'formater' => 'time'], //格式化时间,时间戳格式化为时间日期格式,默认格式: “Y-m-d H:i:s”
              ['field' => 'update_time', 'title' => '更新时间', 'formater' => 'time', 'format_time'=>'Y/m/d H:i'], // format_time自定义时间格式
              ['field' => 'weigh', 'title' => '排序'],
              ['field' => 'content', 'title' => '详情', 'formater' => function ($value, $row, $index) { //自定义过滤函数
                  return strip_tags($value);
              }]
          ]);
  
          //设置数据
          $export->setData($res);
          
          //生成表格,返回url
          $url = $export->build(); //'export/文章.xlsx'

          //完整域名
          $full_url = 'http://localhost:8000' . $url;

      } catch (\Throwable $th) {
          $this->error($th->getMessage());
      }
      $this->success('', compact('url', 'full_url'));
  }

引入封装的导出类

在根目录extend目录中,新建目录alei,目录名可自定义,如果自定义的话记得修改类中的命名空间。在新建的目录中,新建Export.php,将下面的代码粘贴进去。

<?php
namespace alei;

use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;

/**
 * 导出类
 */
class Export
{
    //导出选项
    private $option = [
        //导出文件路径
        'file_path'                         => '',
        //导出文件名
        'file_name'                         => '导出文件',
        //首行是否加粗
        'header_bold'                       => true,
        //垂直居中
        'header_vertical_center'            => true,
        //水平居中
        'header_horizontal_center'          => true,
        //首列高度
        'header_row_height'                 => 30
    ];

    private $column;

    private $data;

    /**
     * @params Array|String $option_or_file_name 导出选项或者文件名
     * @params String $file_path 文件保存路径
     */
    public function __construct($option_or_file_name = null, $file_path = null)
    {        
        if(!is_null($option_or_file_name)){
            if(is_array($option_or_file_name)) {
                //设置导出选项
                $this->option = array_merge($this->option, $option_or_file_name);
            }else if(is_string($option_or_file_name) && strlen($option_or_file_name) > 0){
                //设置保存的文件名
                $this->option['file_name'] = $option_or_file_name;
            }
        }

        //设置保存的文件路径
        if (!is_null($file_path) && is_string($file_path) && strlen($file_path) > 0) {
            $this->option['file_path'] = $file_path;
        }
    }


    /**
     * 设置列参数
     * @param String field 字段名
     * @param String title 标题
     * @param Int width 列宽
     * @param Function|String formater [格式化器,参数1:字段值,参数2:行数据,参数3:行号] 或 [预定义方法:'time':格式化时间]
     * @param String type 类型 default 'text'
     * @param Boole vertical_center 是否垂直居中,default false 
     * @param Boole horizontal_center 是否水平居中,default false
     */
    public function setColumn($column)
    {
        $this->column = $column;
    }

    /**
     * 格式化行参数
     */
    private function formatRowData($data)
    {
        $tmp = [];
        foreach ($this->column as $key => $column) {
            if (isset($data[$column['field']])) {
                $tmp[$column['field']] = $data[$column['field']];
            } else {
                $tmp[$column['field']] = '';
            }
            if (isset($column['formater'])) {
                if (gettype($column['formater']) === 'object' && is_callable($column['formater'])) {
                    $tmp[$column['field']] = $column['formater']($tmp[$column['field']], $data, $key);
                } else if (is_string($column['formater'])) {
                    //格式化
                    switch($column['formater']){
                        case 'time': //时间戳转时间
                            $format_time = $column['format_time'] ?? 'Y-m-d H:i:s';
                            if (empty($tmp[$column['field']])) {
                                $tmp[$column['field']] = '';
                            } else {
                                $tmp[$column['field']] = date($format_time, $tmp[$column['field']]);
                            }
                            break;
                        default :
                    }
                }
            }
        }
        return $tmp;
    }


    /**
     * 设置数据
     */
    public function setData($list)
    {
        if (empty($this->column)) throw new \think\Exception('Please set the column parameters first!');
        $data = [];
        foreach ($list as $key => $val) {
            $data[] = $this->formatRowData($val);
            unset($list[$key]);
        }

        $this->data = $data;
    }


    /**
     * 渲染表格并返回
     * @params $column 列参数
     * @params $data 导出数据
     */
    public function build()
    {
        if (empty($this->column)) {
            throw new \think\Exception('Please set the column parameters first!');
        }

        $spreadsheet = new Spreadsheet();
        // 1获取活动工作薄
        $sheet = $spreadsheet->getActiveSheet();

        //最后一列列号
        $end_row = count($this->column);

        // 首行选择器
        $first_cell = [1, 1, $end_row, 1];

        //设置背景色
        $sheet->getStyle($first_cell)->getFill()->setFillType(Fill::FILL_SOLID);
        $sheet->getStyle($first_cell)->getFill()->getStartColor()->setARGB('FFeeeeee');

        //首行加边框
        $sheet->getStyle($first_cell)->getBorders()->applyFromArray([
            'allBorders'   => [
                'borderStyle'   =>  Border::BORDER_HAIR,
                'color' =>   ['argb' => '666666']
            ]
        ]);

        //加粗
        if ($this->option['header_bold']) {
            $sheet->getStyle($first_cell)->getFont()->setBold(true);
        }

        //垂直居中
        if ($this->option['header_vertical_center']) {
            $sheet->getStyle($first_cell)->getAlignment()->setVertical(Alignment::VERTICAL_CENTER);
        }

        //水平居中
        if ($this->option['header_horizontal_center']) {
            $sheet->getStyle($first_cell)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
        }

        //首行高度
        $sheet->getRowDimension(1)->setRowHeight($this->option['header_row_height']);


        foreach ($this->column as $key => $column) {
            $sheet->setCellValue([$key + 1, 1], $column['title']);
            $sheet->getColumnDimensionByColumn($key + 1)->setAutoSize(true);
        }


        foreach ($this->data as $key => $row) {
            foreach ($this->column as $k => $column) {
                $value = $row[$column['field']];
                $cell = [$k + 1, $key + 2];

                //换行
                if (mb_strpos($value, PHP_EOL) !== false) {
                    $sheet->getStyle($cell)->getAlignment()->setWrapText(true);
                }

                //垂直居中
                if (!empty($column['vertical_center'])) {
                    $sheet->getStyle($cell)->getAlignment()->setVertical(Alignment::VERTICAL_CENTER);
                }

                //水平居中
                if (!empty($column['horizontal_center'])) {
                    $sheet->getStyle($cell)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
                }

                //设置文字
                $sheet->setCellValueExplicit($cell, $value, DataType::TYPE_STRING);
            }
            unset($this->data[$key]);
        }


        //设置工作表标题
        $sheet->setTitle($this->option['file_name']);

        $this->option['file_path'] = $this->option['file_path'] == '' ? '' : ($this->option['file_path'] . '/' );

        $url = '/export/' . $this->option['file_path'] . $this->option['file_name'] . '.xlsx';
        $filePath = public_path() . str_replace('/', DIRECTORY_SEPARATOR, $url);

        if( is_file&#40; $filePath &#41; ){
            //如果文件已经导出过则删除旧文件
            unlink&#40; $filePath &#41;;
        }else if (!is_dir(dirname($filePath))) {
            mkdir(dirname($filePath), 0700, true);
        }

        
        // 保存电子表格
        $writer = new Xlsx($spreadsheet);
        $writer->save($filePath);

        return $url;
    }

}

设置行参数$export->setColumn()参数说明

//设置行参数
$export->setColumn([
    ['field' => 'id', 'title' => '编号'],
    ['field' => 'title', 'title' => '标题'],
    ['field' => 'create_time', 'title' => '创建时间', 'formater' => 'time'], //格式化时间,时间戳格式化为时间日期格式,默认格式: “Y-m-d H:i:s”
    ['field' => 'update_time', 'title' => '更新时间', 'formater' => 'time', 'format_time'=>'Y/m/d H:i'], // format_time自定义时间格式
    ['field' => 'weigh', 'title' => '排序'],
    ['field' => 'content', 'title' => '详情', 'formater' => function ($value, $row, $index) { //自定义过滤函数
        return strip_tags($value);
    }]
]);
  • field string 字段名,对应数据库字段名。
  • title string 标题,作为导出的excel表头。
  • formater string | function 格式化参数
    • string类型: 传time,可将时间戳格式化为日期,默认格式Y-m-d H:i:s,可配合format_time参数自定义格式。
    • function类型:自定义格式化函数。返回参数1: $value, 参数2: $row, 参数3: $value。
  • format_time string 若formater字段参数为time, 则此字段可以自定义时间格式。
  • vertical_center bool 是否垂直居中。
  • horizontal_center bool 是否水平居中。
5个回答默认排序 投票数排序
YANG001
YANG001
这家伙很懒,什么也没写~
3月前

感谢分享~

leigevip
leigevip
这家伙很懒,什么也没写~
3月前

感谢分享

souvenir
souvenir
这家伙很懒,什么也没写~
3月前

感谢😋

火风风
火风风
这家伙很懒,什么也没写~
2月前

每个表的前后端都得改么?

fenglei
fenglei回复火风风
这家伙很懒,什么也没写~
1月前

使用场景比较多的话,可以封装一下,后端代码放到基础控制器,前端代码封装成组件

error
error
这家伙很懒,什么也没写~
2周前

感谢分享

请先登录
3
2
4
6