Laravel使用excel3.1导出时,防止长数字变科学计数法的方法
2019-10-08 admin laravel 1539
因项目中使用了最新的excel3.1插件来导出为excel表格,所以相关的资料非常缺乏,网上大多是老版本的,无法使用。经反复测试,最为简单的方法是,将所有导出的内容设置为文本。
首先,修改默认绑定器,打开config/excel.php文件,修改 'value_binder' 这一段,修改为:
'default' => PhpOffice\PhpSpreadsheet\Cell\StringValueBinder::class,//单元格输出为字符串,防止科学计数法
然后,在导出模型中,也要use这个绑定器:
namespace App\Admin\Controllers; use Encore\Admin\Grid\Exporters\ExcelExporter; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\DataType; use PhpOffice\PhpSpreadsheet\Cell\StringValueBinder;//防止科学计数法 class PostsExporter extends ExcelExporter { protected $fileName; //导出的文件名 protected $headings; //导出所有字段 public function __construct($bm,$zwbm) { $zdsz = Schema::getColumnListing($bm);//获取所有字段 //$szdx = json_encode($zdsz,JSON_UNESCAPED_UNICODE);//转为对象 $zwbm = $zwbm.'.xls'; $this->fileName = $zwbm; //将控制器传来的导出表名赋值给fileName $this->headings = $zdsz; //将所有字段赋值给headings } }
这样,导出的默认就是文本了。相关知识可以了解一下,绑定器的目录是:
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/
里面有许多绑定器,其中默认的是DefaultValueBinder.php,它会根据规则进行格式转换:
class DefaultValueBinder implements IValueBinder { /** * Bind value to a cell. * * @param Cell $cell Cell to bind value to * @param mixed $value Value to bind in cell * * @throws \PhpOffice\PhpSpreadsheet\Exception * * @return bool */ public function bindValue(Cell $cell, $value) { // sanitize UTF-8 strings if (is_string($value)) { $value = StringHelper::sanitizeUTF8($value); } elseif (is_object($value)) { // Handle any objects that might be injected if ($value instanceof DateTimeInterface) { $value = $value->format('Y-m-d H:i:s'); } elseif (!($value instanceof RichText)) { $value = (string) $value; } } // Set value explicit $cell->setValueExplicit($value, static::dataTypeForValue($value)); // Done! return true; } /** * DataType for value. * * @param mixed $pValue * * @return string */ public static function dataTypeForValue($pValue) { // Match the value against a few data types if ($pValue === null) { return DataType::TYPE_NULL; } elseif ($pValue === '') { return DataType::TYPE_STRING; } elseif ($pValue instanceof RichText) { return DataType::TYPE_INLINE; } elseif ($pValue[0] === '=' && strlen($pValue) > 1) { return DataType::TYPE_FORMULA; } elseif (is_bool($pValue)) { return DataType::TYPE_BOOL; } elseif (is_float($pValue) || is_int($pValue)) { return DataType::TYPE_NUMERIC; } elseif (preg_match('/^[\+\-]?(\d+\\.?\d*|\d*\\.?\d+)([Ee][\-\+]?[0-2]?\d{1,3})?$/', $pValue)) { $tValue = ltrim($pValue, '+-'); if (is_string($pValue) && $tValue[0] === '0' && strlen($tValue) > 1 && $tValue[1] !== '.') { return DataType::TYPE_STRING; } elseif ((strpos($pValue, '.') === false) && ($pValue > PHP_INT_MAX)) { return DataType::TYPE_STRING; } return DataType::TYPE_NUMERIC; } elseif (is_string($pValue)) { $errorCodes = DataType::getErrorCodes(); if (isset($errorCodes[$pValue])) { return DataType::TYPE_ERROR; } } return DataType::TYPE_STRING; } }
我们改后的绑定器为StringValueBinder.php,它把全部单元格作为文本处理,内容如下:
class StringValueBinder implements IValueBinder { /** * Bind value to a cell. * * @param Cell $cell Cell to bind value to * @param mixed $value Value to bind in cell * * @throws \PhpOffice\PhpSpreadsheet\Exception * * @return bool */ public function bindValue(Cell $cell, $value) { // sanitize UTF-8 strings if (is_string($value)) { $value = StringHelper::sanitizeUTF8($value); } $cell->setValueExplicit((string) $value, DataType::TYPE_STRING); // Done! return true; } }
其实,官方文档有描述:如果要禁用值的智能格式,则可以使用扩展导出类 \PhpOffice\PhpSpreadsheet\Cell\StringValueBinder。在这种情况下,所有值都作为字符串传递。可用的数据类型
PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_FORMULA PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_NUMERIC PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_BOOL PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_NULL PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_INLINE PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_ERROR
只是没有中文文档,所以理解有些差错,加上百度等搜索上的文章大多错误,所以浪费我一晚上时间找方法。
以上方法发现有个遗留问题,如果导出的是json数组类型的单元格数据,将报错,不过这个功能暂时用不上,需要使用时,只需要在config/excel.php中,把配置改回默认的即可:
还有一个更好的方法是:
打开默认配置文件vendor/maatwebsite/excel/src/DefaultValueBinder.php,添加
use PhpOffice\PhpSpreadsheet\Cell\DataType;
函数内部添加:
//超过10位的数字转文本格式,防止科学计数法 if (strlen($value) > 10) { $cell->setValueExplicit($value, DataType::TYPE_STRING); return true; }
文件代码最终如下:
namespace Maatwebsite\Excel; use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\DefaultValueBinder as PhpSpreadsheetDefaultValueBinder; use PhpOffice\PhpSpreadsheet\Cell\DataType; class DefaultValueBinder extends PhpSpreadsheetDefaultValueBinder { /** * @param Cell $cell Cell to bind value to * @param mixed $value Value to bind in cell * * @return bool */ public function bindValue(Cell $cell, $value) { if (is_array($value)) { $value = \json_encode($value); } //超过10位的数字转文本格式,防止科学计数法 if (strlen($value) > 10) { $cell->setValueExplicit($value, DataType::TYPE_STRING); return true; } return parent::bindValue($cell, $value); } }
这个方法可以导出json字段,而且不影响其它类型的数据。最后附上导出类文件代码Encore\Admin\Grid\Exporters\ExcelExporter:
namespace Encore\Admin\Grid\Exporters; use Illuminate\Support\Str; use Maatwebsite\Excel\Concerns\Exportable; use Maatwebsite\Excel\Concerns\FromQuery; use Maatwebsite\Excel\Concerns\WithHeadings; abstract class ExcelExporter extends AbstractExporter implements FromQuery, WithHeadings { use Exportable; /** * @var string */ protected $fileName; /** * @var array */ protected $headings = []; /** * @var array */ protected $columns = []; /** * @return array */ public function headings(): array { if (!empty($this->columns)) { return array_values($this->columns); } return $this->headings; } /** * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model */ public function query() { if (!empty($this->columns)) { $columns = array_keys($this->columns); $eagerLoads = array_keys($this->getQuery()->getEagerLoads()); $columns = collect($columns)->reject(function ($column) use ($eagerLoads) { return Str::contains($column, '.') || in_array($column, $eagerLoads); }); return $this->getQuery()->select($columns->toArray()); } return $this->getQuery(); } /** * {@inheritdoc} */ public function export() { $this->download($this->fileName)->prepare(request())->send(); exit; } }
可参数这个自定义修改。