You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

493 lines
17 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
/**
* 前端自定义的导出类
*/
namespace App\Exports;
use App\Exceptions\ErrorException;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithStyles;
use Maatwebsite\Excel\Concerns\WithColumnWidths;
use Maatwebsite\Excel\Concerns\WithEvents;
use Maatwebsite\Excel\Events\AfterSheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
class CommonExport implements FromCollection, WithStyles, WithColumnWidths, WithEvents
{
public $fields;
public $data;
public $hasUsersField = false;
public $usersColumnStartIndex = null;
public $totalColumns = 0;
public $totalRows = 0;
public $expandedFields = [];
// 学员信息子列定义
const USERS_SUB_COLUMNS = [
'user_index' => '序号',
'user_no' => '学号',
'user_name' => '姓名',
'user_schoolmate' => '校友',
'user_position' => '职位',
'user_mobile' => '手机',
'user_course' => '报名课程',
'user_sign_date' => '报名时间',
];
public function __construct($data, $exportFields)
{
// 需要导出的字段。格式:['name'=>'名字','user.sex'=>'性别']
$this->fields = $exportFields;
// 数据
$this->data = $data;
// 构建扩展后的字段列表
$this->buildExpandedFields();
}
/**
* 构建扩展后的字段列表将users字段展开成多列
*/
private function buildExpandedFields()
{
if (!is_array($this->fields)) {
return;
}
$index = 1;
foreach ($this->fields as $field => $label) {
if (str_contains($field, 'users')) {
$this->hasUsersField = true;
$this->usersColumnStartIndex = $index;
// 展开学员信息为多列
foreach (self::USERS_SUB_COLUMNS as $subField => $subLabel) {
$this->expandedFields[$subField] = $subLabel;
$index++;
}
} else {
$this->expandedFields[$field] = $label;
$index++;
}
}
$this->totalColumns = count($this->expandedFields);
}
/**
* 获取列字母
*/
private function getColumnLetter($columnNumber)
{
$letter = '';
while ($columnNumber > 0) {
$columnNumber--;
$letter = chr(65 + ($columnNumber % 26)) . $letter;
$columnNumber = intval($columnNumber / 26);
}
return $letter;
}
/**
* 设置列宽
*/
public function columnWidths(): array
{
$widths = [];
$index = 1;
foreach (array_keys($this->expandedFields) as $field) {
$letter = $this->getColumnLetter($index);
if (in_array($field, ['user_course', 'user_name'])) {
$widths[$letter] = 25;
} elseif (in_array($field, ['user_mobile', 'user_sign_date'])) {
$widths[$letter] = 15;
} elseif (in_array($field, ['user_index', 'user_schoolmate'])) {
$widths[$letter] = 8;
} elseif (str_contains($field, 'partners') || str_contains($field, 'project_users')) {
$widths[$letter] = 50;
} elseif (str_contains($field, 'all_course')) {
$widths[$letter] = 40;
} elseif (str_contains($field, 'company_name') || str_contains($field, 'address')) {
$widths[$letter] = 30;
} else {
$widths[$letter] = 15;
}
$index++;
}
return $widths;
}
/**
* 设置样式
*/
public function styles(Worksheet $sheet): array
{
$lastCol = $this->getColumnLetter($this->totalColumns);
$dataStartRow = $this->hasUsersField ? 3 : 2;
$styles = [];
if ($this->hasUsersField) {
// 二级表头样式 - 第一行
$styles[1] = [
'font' => ['bold' => true, 'size' => 12],
'alignment' => [
'horizontal' => Alignment::HORIZONTAL_CENTER,
'vertical' => Alignment::VERTICAL_CENTER,
],
'fill' => [
'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
'startColor' => ['rgb' => 'D0D0D0'],
],
];
// 二级表头样式 - 第二行
$styles[2] = [
'font' => ['bold' => true, 'size' => 11],
'alignment' => [
'horizontal' => Alignment::HORIZONTAL_CENTER,
'vertical' => Alignment::VERTICAL_CENTER,
],
'fill' => [
'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
'startColor' => ['rgb' => 'E8E8E8'],
],
];
} else {
// 单行表头样式
$styles[1] = [
'font' => ['bold' => true, 'size' => 12],
'alignment' => [
'horizontal' => Alignment::HORIZONTAL_CENTER,
'vertical' => Alignment::VERTICAL_CENTER,
],
'fill' => [
'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
'startColor' => ['rgb' => 'E0E0E0'],
],
];
}
// 所有数据区域样式
$styles["A1:{$lastCol}" . ($this->totalRows + $dataStartRow - 1)] = [
'alignment' => [
'vertical' => Alignment::VERTICAL_CENTER,
'wrapText' => true,
],
'borders' => [
'allBorders' => [
'borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN,
'color' => ['rgb' => 'CCCCCC'],
],
],
];
return $styles;
}
/**
* 注册事件
*/
public function registerEvents(): array
{
return [
AfterSheet::class => function (AfterSheet $event) {
$sheet = $event->sheet->getDelegate();
if ($this->hasUsersField) {
// 处理二级表头的单元格合并
$this->mergeHeaderCells($sheet);
// 设置表头行高
$sheet->getRowDimension(1)->setRowHeight(25);
$sheet->getRowDimension(2)->setRowHeight(22);
// 冻结前两行
$sheet->freezePane('A3');
} else {
$sheet->getRowDimension(1)->setRowHeight(25);
$sheet->freezePane('A2');
}
// 设置数据行高
$dataStartRow = $this->hasUsersField ? 3 : 2;
for ($row = $dataStartRow; $row <= $this->totalRows + $dataStartRow - 1; $row++) {
$sheet->getRowDimension($row)->setRowHeight(22);
}
},
];
}
/**
* 合并表头单元格
*/
private function mergeHeaderCells(Worksheet $sheet)
{
$index = 1;
foreach ($this->fields as $field => $label) {
if (str_contains($field, 'users')) {
// 学员信息列:合并第一行的多个单元格
$startCol = $this->getColumnLetter($index);
$endCol = $this->getColumnLetter($index + count(self::USERS_SUB_COLUMNS) - 1);
$sheet->mergeCells("{$startCol}1:{$endCol}1");
$sheet->setCellValue("{$startCol}1", $label);
// 学员信息子表头不需要合并
$index += count(self::USERS_SUB_COLUMNS);
} else {
// 非学员信息列:合并第一行和第二行
$col = $this->getColumnLetter($index);
$sheet->mergeCells("{$col}1:{$col}2");
$sheet->setCellValue("{$col}1", $label);
$index++;
}
}
}
/**
* 数组转集合
* @throws ErrorException
*/
public function collection()
{
$clear = request('clear', 0);
if (empty($this->fields)) {
throw new ErrorException('导出字段不能为空');
}
if (!is_array($this->fields)) {
throw new ErrorException('导出字段必须是数组');
}
$newList = [];
if ($this->hasUsersField) {
// 有学员字段:创建二级表头
// 第一行表头(主表头)- 在 mergeHeaderCells 中处理
$header1 = [];
foreach ($this->fields as $field => $label) {
if (str_contains($field, 'users')) {
// 学员信息占用多列,第一行只需要占位
foreach (self::USERS_SUB_COLUMNS as $subLabel) {
$header1[] = '';
}
} else {
$header1[] = '';
}
}
$newList[] = $header1;
// 第二行表头(子表头)
$header2 = [];
foreach ($this->fields as $field => $label) {
if (str_contains($field, 'users')) {
foreach (self::USERS_SUB_COLUMNS as $subLabel) {
$header2[] = $subLabel;
}
} else {
$header2[] = '';
}
}
$newList[] = $header2;
// 数据行:每个学员的每条课程记录占一行
foreach ($this->data as $info) {
$usersData = $this->getUsersExpanded($info);
if (empty($usersData)) {
// 没有学员数据,输出一行空数据
$row = $this->buildRowWithoutUsers($info, []);
$newList[] = $row;
} else {
// 每个学员记录一行
foreach ($usersData as $userData) {
$row = $this->buildRowWithoutUsers($info, $userData);
$newList[] = $row;
}
}
}
} else {
// 没有学员字段:使用原有逻辑
$header = array_values($this->fields);
$moreFileds = [];
if (empty($clear)) {
if (isset($this->data[0]['data']) && is_array($this->data[0]['data'])) {
$moreHeader = array_column($this->data[0]['data'], 'name');
$header = array_merge($header, $moreHeader);
$moreFileds = array_column($this->data[0]['data'], 'field');
}
}
$newList[] = $header;
foreach ($this->data as $info) {
$temp = [];
foreach (array_keys($this->fields) as $field) {
if (str_contains($field, 'idcard')) {
$temp[$field] = ' ' . $this->getDotValue($info, $field);
} elseif (str_contains($field, 'all_course')) {
$temp[$field] = $this->allCourse($info);
} elseif (str_contains($field, 'partners')) {
$temp[$field] = $this->partners($info);
} elseif (str_contains($field, 'project_users')) {
$temp[$field] = $this->projectManager($info);
} else {
$temp[$field] = $this->getDotValue($info, $field);
}
}
$t2 = [];
if (empty($clear)) {
if (isset($info['data']) && $info['data'] && !empty($moreFileds)) {
$dataCollect = collect($info['data']);
foreach ($moreFileds as $moreFiled) {
$value = ($dataCollect->where('field', $moreFiled)->first()['value']) ?? '';
if (str_contains($moreFiled, 'idcard')) {
$t2[$moreFiled] = ' ' . $value;
} else {
$t2[$moreFiled] = $value;
}
}
}
}
$newList[] = array_values($temp + $t2);
}
}
$this->totalRows = count($newList) - ($this->hasUsersField ? 2 : 1);
return new Collection($newList);
}
/**
* 获取展开的学员数据(每个学员每条课程一行)
*/
private function getUsersExpanded($info)
{
if (empty($info['users'])) {
return [];
}
$result = [];
$userIndex = 1;
foreach ($info['users'] as $user) {
if (!empty($user['course_signs'])) {
foreach ($user['course_signs'] as $signIndex => $sign) {
$result[] = [
'user_index' => $signIndex === 0 ? $userIndex : '',
'user_no' => $signIndex === 0 ? ($user['no'] ?? '-') : '',
'user_name' => $signIndex === 0 ? ($user['username'] ?? '-') : '',
'user_schoolmate' => $signIndex === 0 ? ($user['is_schoolmate_text'] ?? '-') : '',
'user_position' => $signIndex === 0 ? ($user['company_position'] ?? '-') : '',
'user_mobile' => $signIndex === 0 ? ($user['mobile'] ?? '-') : '',
'user_course' => $sign['course']['name'] ?? '-',
'user_sign_date' => isset($sign['created_at']) ? substr($sign['created_at'], 0, 10) : '-',
];
}
} else {
$result[] = [
'user_index' => $userIndex,
'user_no' => $user['no'] ?? '-',
'user_name' => $user['username'] ?? '-',
'user_schoolmate' => $user['is_schoolmate_text'] ?? '-',
'user_position' => $user['company_position'] ?? '-',
'user_mobile' => $user['mobile'] ?? '-',
'user_course' => '-',
'user_sign_date' => '-',
];
}
$userIndex++;
}
return $result;
}
/**
* 构建包含学员数据的行
*/
private function buildRowWithoutUsers($info, $userData)
{
$row = [];
foreach ($this->fields as $field => $label) {
if (str_contains($field, 'users')) {
// 填充学员信息列
foreach (array_keys(self::USERS_SUB_COLUMNS) as $subField) {
$row[] = $userData[$subField] ?? '';
}
} elseif (str_contains($field, 'idcard')) {
$row[] = ' ' . $this->getDotValue($info, $field);
} elseif (str_contains($field, 'all_course')) {
$row[] = $this->allCourse($info);
} elseif (str_contains($field, 'partners')) {
$row[] = $this->partners($info);
} elseif (str_contains($field, 'project_users')) {
$row[] = $this->projectManager($info);
} else {
$row[] = $this->getDotValue($info, $field);
}
}
return $row;
}
/**
* .号转数组层级并返回对应的值
* @param $key
* @param null $default
* @return mixed|null
*/
function getDotValue($config, $key, $default = null)
{
// 如果在第一层,就直接返回
if (isset($config[$key])) {
return $config[$key];
}
// 如果找不到,直接返回默认值
if (false === strpos($key, '.')) {
return $default;
}
// 临时数组
$tempArr = explode('.', $key);
foreach ($tempArr as $segment) {
if (!is_array($config) || !array_key_exists($segment, $config)) {
return $default;
}
$config = $config[$segment];
}
return $config;
}
/**
* 获取所有课程名称
* @param $data
*/
function allCourse($data)
{
$list = [];
foreach ($data['course_signs'] as $item) {
$list[] = $item['course']['name'] ?? '';
}
return implode("\r\n", $list);
}
/**
* 获取所有股东信息
* @param $data
*/
function partners($data)
{
$list = [];
foreach ($data['partners'] as $item) {
$list[] = $item['stockName'] . '-' . $item['stockPercent'] ?? '';
}
return implode("\r\n", $list);
}
/**
* 获取所有项目经理
* @param $data
*/
function projectManager($data)
{
$list = [];
foreach ($data['project_users'] as $item) {
$list[] = $item['groupName'] . '-' . ($item['userName'] ?? '') . '-' . ($item['investDate'] ?? '');
}
return implode("\r\n", $list);
}
}