mr_web_api/app/common/arw/adjfut/src/Excel.php
2023-06-28 18:45:02 +08:00

520 lines
14 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\common\arw\adjfut\src;
use app\common\arw\adjfut\src\Exception\ErrorMsg;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use think\helper\Arr;
use think\helper\Str;
use think\Validate;
use app\common\arw\adjfut\src\Traits\Event;
use think\facade\Env;
class Excel
{
use Event;
/**
* Spreadsheet
*
* @var Spreadsheet
*/
private $spreadsheet = null;
/**
* 工作表样式
*
* @var array
*/
private $worksheetStyle = [];
/**
* 文件路径
*
* @var string
* @date 2022-04-15
* @example
* @author arw
* @since 1.0.0
*/
private $path = '';
/**
* 初始化
*
* @param string|UploadFile $path
* @date 2022-04-15
* @example
* @author arw
* @since 1.0.0
*/
public function __construct($path = '')
{
if ($path) {
if ($path instanceof UploadFile) {
$path = $path->getRealPath();
}
$this->spreadsheet = self::loadExcel($path);
$this->path = $path;
} else {
$this->spreadsheet = new Spreadsheet();
}
}
/**
* 设置工作表样式
*
* @param array $options
* @param boolean $options[autoBorder] 是否自动添加边框
* @param boolean $options[freezeLine] 是否自动冻结首行
* @return self
*/
public function setWorksheetStyle(array $options): self
{
$this->worksheetStyle = $options;
return $this;
}
/**
* 获取当前Spreadsheet
*
* @return Spreadsheet
* @date 2020-11-23
* @example
* @author arw
* @since 1.0.0
*/
public function getSpreadsheet(): Spreadsheet
{
return $this->spreadsheet;
}
/**
* 读取excel
*
* @param string $path excel路径
* @return Spreadsheet
*/
public static function loadExcel(string $path): Spreadsheet
{
if (!is_file($path)) {
throw new ErrorMsg("文件不存在 $path", 1);
}
$ext = pathinfo($path, PATHINFO_EXTENSION);
return IOFactory::createReader(ucfirst($ext))->load($path);
}
/**
* 获取工作表
* 默认获取当前激活的
*
* @param string $sheet
* @return Worksheet
*/
public function getWorksheet(string $sheet = 'active'): Worksheet
{
$Spreadsheet = $this->getSpreadsheet();
if ($sheet === 'active') {
$Worksheet = $Spreadsheet->getActiveSheet();
} else {
$Worksheet = $Spreadsheet->getSheet($sheet);
}
return $Worksheet;
}
/**
* 获取excel数据
*
* @param string $sheet
* @return array
*/
public function getExcelData(string $sheet = 'active'): array
{
return $this->getWorksheet($sheet)->toArray();
}
/**
* 解析excel
*
* @param array $rules
* @param string $rules[][title] 表头
* @param string $rules[][validate] 验证器
* @param string $rules[][field] 字段名
* @param string $rules[][type] 数据类型 date:日期格式(Y-m-d H:i:s)
* @param array $options
* @param array $options[titleLine] 表头行
* @param boolean $options[autoRemove] 解析后移除文件
* @param boolean $options[ignoreNullRow] 忽略空行
* @return array
* @date 2022-04-11
* @example
* @author arw
* @since 1.0.0
*/
public function parseExcel(array $rules, array $options = []): array
{
$titleLine = Arr::get($options, 'titleLine', [1]);
$autoRemove = Arr::get($options, 'autoRemove', true);
$ignoreNullRow = Arr::get($options, 'ignoreNullRow', true);
if ($autoRemove) {
if (is_file($this->path)) {
unlink($this->path);
}
}
$title = [];
$_title = [];
$_field = [];
$_type = [];
$_validate = [];
foreach ($rules as $rule) {
$_title[] = $rule['title'];
if (isset($rule['field'])) {
$_field[] = $rule['field'];
if (isset($rule['validate'])) {
$_validate[join('|', [
$rule['field'],
$rule['title']
])] = $rule['validate'];
}
} else {
$_field[] = '';
}
if (isset($rule['type'])) {
$_type[] = $rule['type'];
} else {
$_type[] = '';
}
}
$Worksheet = $this->getWorksheet();
$data = $Worksheet->toArray();
// 获取表头 合并复杂表头
foreach ($titleLine as $line) {
$values = $data[$line - 1];
foreach ($values as $key => $value) {
if ($value) {
$title[$key] = $value;
}
}
}
// 验证表头
if ($title != $_title) {
throw new ErrorMsg("表头不一致 请确认excel是否正确", 1);
}
// 去除非数据字段
$i = max($titleLine) - 1;
do {
unset($data[$i]);
$i--;
} while ($i >= 0);
$validate = new Validate;
$validate->rule($_validate)->batch(true)->failException(true);
$result = [];
$error = [];
foreach ($data as $row => $value) {
$index = $row + 1;
$line = "${index}";
$_value = [];
if ($ignoreNullRow && self::isNullRow($value)) {
continue;
}
foreach ($_field as $column => $field) {
if ($field) {
if ($_type[$column]) {
$type = $_type[$column];
if (Str::startsWith($type, 'date') && $value[$column]) {
$format = 'Y-m-d H:i:s';
$types = explode(':', $type);
if (isset($types[1])) {
$format = $types[1];
}
$value[$column] = Tool::conversionDateTime($value[$column], $format);
}
}
$_value[$field] = $value[$column];
}
}
// 验证数据
if ($_validate) {
try {
$validate->check($_value);
} catch (\Throwable $th) {
$error[] = $line . '' . $th->getMessage();
}
}
$result[$line] = $_value;
}
if ($error) {
throw new ErrorMsg(join(PHP_EOL, $error), 1);
}
return $result;
}
/**
* 导出excel
*
* @param array $data 数据
* @param array $options
* @param boolean $options[autoBorder] 是否自动添加边框
* @param string $options[freezeLine] 是否自动冻结首行
* @date 2022-04-11
* @example
* @author arw
* @since 1.0.0
*/
public static function exportSheet(array $data, array $options = [])
{
$excel = new self;
$excel->setWorksheetStyle($options);
$spreadsheet = $excel->getSpreadsheet();
$worksheet = $spreadsheet->getActiveSheet();
$worksheet->fromArray($data);
return $excel;
}
/**
* 导出处理图片字段
*/
public static function ExportImgFiled($value)
{
if(strpos($value,"http") !== false){
$base_url = "";
}else{
$base_url = Env::get('APP.DEFAULT_IMG_URL');
}
return $base_url . $value;
}
/**
* 导出excel 多sheet
*
* @param array $data 数据
* @param array $options
* @param boolean $options[autoBorder] 是否自动添加边框
* @param string $options[freezeLine] 是否自动冻结首行
* @date 2022-04-11
* @example
* @author arw
* @since 1.0.0
*/
public static function exportMoreSheet(array $data, array $options = [])
{
$excel = new self;
$excel->setWorksheetStyle($options);
$spreadsheet = $excel->getSpreadsheet();
$spreadsheet->removeSheetByIndex(0);
foreach ($data as $key => $value) {
$worksheet = new Worksheet($spreadsheet, $key);
$worksheet->fromArray($value);
$spreadsheet->addSheet($worksheet);
}
$spreadsheet->setActiveSheetIndex(0);
return $excel;
}
/**
* 保存excel
*
* @param string $name 文件名
* @param string $save 保存路径
* @date 2022-04-11
* @example
* @author arw
* @since 1.0.0
*/
public function save(string $name, string $save = 'php://output')
{
$spreadsheet = $this->getSpreadsheet();
$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
$_name = $name;
if (!preg_match("/\./", $name)) {
$_name .= '.Xlsx';
}
$worksheets = $spreadsheet->getAllSheets();
foreach ($worksheets as $worksheet) {
$this->_setWorksheetStyle($worksheet);
}
// 保存前
$this->trigger('beforeSave', [$spreadsheet]);
// 浏览器下载
if ($save === 'php://output') {
Tool::obEndClean();
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Cache-Control: max-age=0');
header('Content-Disposition:inline;filename="' . $_name . '"');
$writer->save($save);
} else {
Tool::mkdir($save);
// 保存到本地目录
$writer->save(join(DIRECTORY_SEPARATOR, [
$save, $_name,
]));
}
$spreadsheet->disconnectWorksheets();
unset($spreadsheet);
// 保存后
$this->trigger('AfterSave');
}
/**
* 保存前
*
* @param callable $cb
* @date 2022-04-11
* @example
* @author arw
* @since 1.0.0
*/
public function onBeforeSave(callable $cb): self
{
return $this->event('beforeSave', $cb);
}
/**
* 保存后
*
* @param callable $cb
* @date 2022-04-11
* @example
* @author arw
* @since 1.0.0
*/
public function onAfterSave(callable $cb): self
{
return $this->event('afterSave', $cb);
}
/**
* 判断是否空行
*
* @param array $rows
* @return boolean
* @date 2021-11-27
* @example
* @author arw
* @since 1.0.0
*/
public static function isNullRow(array $rows)
{
$rows = array_unique($rows);
$rows = array_filter($rows, function ($v) {
return !is_null($v);
});
return count($rows) == 0;
}
/**
* 快捷设置excel样式
* 首行冻结 自动添加边框
*
* @param Worksheet $worksheet
* @return Worksheet $worksheet
* @date 2021-01-25
* @author arw
* @since 1.0.0
*/
private function _setWorksheetStyle(Worksheet $worksheet): Worksheet
{
$options = $this->worksheetStyle;
$autoBorder = Arr::get($options, 'autoBorder', true);
$freezeLine = Arr::get($options, 'freezeLine', 'A2');
$autoBackground = Arr::get($options, 'autoBackground', true);
if ($freezeLine) {
$worksheet->freezePane($freezeLine);
}
if ($autoBorder) {
$worksheet->getStyle(join(':', [
'A1',
$worksheet->getHighestDataColumn() . $worksheet->getHighestDataRow()
]))->applyFromArray([
'borders' => [
'allBorders' => [
'borderStyle' => Border::BORDER_THIN, //细边框
],
],
]);
}
if($autoBackground){
$style = join(':', [
'A1',
$worksheet->getHighestDataColumn() . "1"
]);
// 列宽
$worksheet -> getDefaultColumnDimension() -> setWidth(25);
// $worksheet -> getColumnDimension('A') -> setWidth(30); //设置A列宽度为30
//行高
// $worksheet -> getRowDimension(1) -> setRowHeight(25);
$worksheet -> getDefaultRowDimension() -> setRowHeight(25);
//样式设置 - 字体
$worksheet -> getStyle($style) -> getFont()
// -> setBold(true)
->setName('微软雅黑')
-> setSize(13);
//样式设置 - 字体颜色
// $worksheet -> getStyle('A1') -> getFont()
// -> getColor() -> setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_RED); //设置单元格A1的字体颜色
// $font = $worksheet -> getCell('B1') -> getColor() -> setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_RED);
// if(substr($font,0,1) == "*"){
// }
// $worksheet -> setCellValue('A2', substr($font,0,1) );
//样式设置 - 水平、垂直居中
$styleArray = [
'alignment' => [
'horizontal' => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER,
'vertical' => \PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER
],
];
$worksheet -> getStyle($style) -> applyFromArray($styleArray);
//样式设置 - 单元格背景颜色
$worksheet->getStyle($style)->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)->getStartColor()->setARGB('FFFFFF00');
}
return $worksheet;
}
/**
* 数字转字母
* 1 => A
*
* @param integer $index
* @return string
* @date 2021-01-23
* @author arw
* @since 1.0.0
*/
public static function stringToIndex(int $index)
{
return Coordinate::stringFromColumnIndex($index);
}
/**
* 字母转数字
* A => 1
*
* @param string $string
* @return int
* @date 2021-01-23
* @author arw
* @since 1.0.0
*/
public static function indexToString(string $string)
{
return Coordinate::columnIndexFromString($string);
}
}