master
cody 2 weeks ago
parent 1c9880394e
commit 1a72542463

@ -20,7 +20,22 @@ class CommonExport implements FromCollection, WithStyles, WithColumnWidths, With
public $fields;
public $data;
public $hasUsersField = false;
public $usersColumnIndex = null;
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)
{
@ -29,17 +44,35 @@ class CommonExport implements FromCollection, WithStyles, WithColumnWidths, With
// 数据
$this->data = $data;
// 检查是否有 users 字段
if (is_array($exportFields)) {
$index = 1;
foreach (array_keys($exportFields) as $field) {
if (str_contains($field, 'users')) {
$this->hasUsersField = true;
$this->usersColumnIndex = $this->getColumnLetter($index);
// 构建扩展后的字段列表
$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);
}
/**
@ -63,22 +96,21 @@ class CommonExport implements FromCollection, WithStyles, WithColumnWidths, With
{
$widths = [];
$index = 1;
foreach (array_keys($this->fields) as $field) {
foreach (array_keys($this->expandedFields) as $field) {
$letter = $this->getColumnLetter($index);
if (str_contains($field, 'users')) {
// 学员信息列设置较宽
$widths[$letter] = 80;
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++;
@ -91,13 +123,14 @@ class CommonExport implements FromCollection, WithStyles, WithColumnWidths, With
*/
public function styles(Worksheet $sheet): array
{
$rowCount = count($this->data) + 1; // 数据行数 + 表头
$colCount = count($this->fields);
$lastCol = $this->getColumnLetter($colCount);
$lastCol = $this->getColumnLetter($this->totalColumns);
$dataStartRow = $this->hasUsersField ? 3 : 2;
return [
// 表头样式
1 => [
$styles = [];
if ($this->hasUsersField) {
// 二级表头样式 - 第一行
$styles[1] = [
'font' => ['bold' => true, 'size' => 12],
'alignment' => [
'horizontal' => Alignment::HORIZONTAL_CENTER,
@ -105,23 +138,51 @@ class CommonExport implements FromCollection, WithStyles, WithColumnWidths, With
],
'fill' => [
'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
'startColor' => ['rgb' => 'E0E0E0'],
'startColor' => ['rgb' => 'D0D0D0'],
],
],
// 所有数据区域样式
"A1:{$lastCol}{$rowCount}" => [
];
// 二级表头样式 - 第二行
$styles[2] = [
'font' => ['bold' => true, 'size' => 11],
'alignment' => [
'vertical' => Alignment::VERTICAL_TOP,
'wrapText' => true, // 自动换行
'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'],
],
'borders' => [
'allBorders' => [
'borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN,
'color' => ['rgb' => 'CCCCCC'],
],
];
}
// 所有数据区域样式
$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;
}
/**
@ -132,34 +193,57 @@ class CommonExport implements FromCollection, WithStyles, WithColumnWidths, With
return [
AfterSheet::class => function (AfterSheet $event) {
$sheet = $event->sheet->getDelegate();
$rowCount = count($this->data) + 1;
// 设置表头行高
$sheet->getRowDimension(1)->setRowHeight(25);
// 遍历数据行,根据内容设置行高
for ($row = 2; $row <= $rowCount; $row++) {
// 获取该行的最大内容行数
$maxLines = 1;
foreach (array_keys($this->fields) as $index => $field) {
$col = $this->getColumnLetter($index + 1);
$cellValue = $sheet->getCell($col . $row)->getValue();
if ($cellValue) {
$lines = substr_count($cellValue, "\n") + 1;
$maxLines = max($maxLines, $lines);
}
}
// 设置行高每行内容约15像素最小20最大400
$rowHeight = min(400, max(20, $maxLines * 15));
$sheet->getRowDimension($row)->setRowHeight($rowHeight);
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');
}
// 冻结首行
$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
@ -173,65 +257,172 @@ class CommonExport implements FromCollection, WithStyles, WithColumnWidths, With
if (!is_array($this->fields)) {
throw new ErrorException('导出字段必须是数组');
}
// 获取表头
$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');
}
}
// 获取字段指向
$fields = array_keys($this->fields);
$newList = [];
foreach ($this->data as $info) {
$temp = [];
foreach ($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);
} elseif (str_contains($field, 'users')) {
// 学员信息
$temp[$field] = $this->getUsers($info);
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 {
$temp[$field] = $this->getDotValue($info, $field);
$header1[] = '';
}
}
// 如果有自定义数据,全部附件上去
$t2 = [];
$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($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;
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);
}
$newList[] = $temp + $t2;
}
array_unshift($newList, $header); //插入表头
$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
@ -298,77 +489,4 @@ class CommonExport implements FromCollection, WithStyles, WithColumnWidths, With
return implode("、\r\n", $list);
}
/**
* 获取学员信息(表格格式显示)
* @param $data
*/
function getUsers($data)
{
if (empty($data['users'])) {
return '';
}
$result = [];
// 表头
$header = "序号\t学号\t姓名\t校友\t职位\t手机\t报名课程\t报名时间";
$result[] = $header;
$result[] = str_repeat("─", 80); // 分隔线
// 数据行
$index = 1;
foreach ($data['users'] as $item) {
// 如果有多个课程报名,需要展开多行
if (!empty($item['course_signs'])) {
foreach ($item['course_signs'] as $signIndex => $sign) {
$courseName = $sign['course']['name'] ?? '-';
$signDate = isset($sign['created_at']) ? substr($sign['created_at'], 0, 10) : '-';
if ($signIndex === 0) {
// 第一行显示完整信息
$row = implode("\t", [
$index,
$item['no'] ?? '-',
$item['username'] ?? '-',
$item['is_schoolmate_text'] ?? '-',
$item['company_position'] ?? '-',
$item['mobile'] ?? '-',
$courseName,
$signDate
]);
} else {
// 后续行只显示课程信息,其他列留空
$row = implode("\t", [
'',
'',
'',
'',
'',
'',
$courseName,
$signDate
]);
}
$result[] = $row;
}
} else {
// 没有课程报名
$row = implode("\t", [
$index,
$item['no'] ?? '-',
$item['username'] ?? '-',
$item['is_schoolmate_text'] ?? '-',
$item['company_position'] ?? '-',
$item['mobile'] ?? '-',
'-',
'-'
]);
$result[] = $row;
}
$index++;
}
return implode("\n", $result);
}
}

Loading…
Cancel
Save