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.

622 lines
22 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 $hasProjectUsersField = false;
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' => '报名时间',
];
// 项目经理信息子列定义
const PROJECT_USERS_SUB_COLUMNS = [
'pm_platform' => '管理平台',
'pm_name' => '项目经理',
'pm_invest_date' => '首次出资时间',
'pm_amount' => '投资金额',
];
public function __construct($data, $exportFields)
{
// 需要导出的字段。格式:['name'=>'名字','user.sex'=>'性别']
$this->fields = $exportFields;
// 数据
$this->data = $data;
// 构建扩展后的字段列表
$this->buildExpandedFields();
}
/**
* 构建扩展后的字段列表将users和project_users字段展开成多列
*/
private function buildExpandedFields()
{
if (!is_array($this->fields)) {
return;
}
$index = 1;
foreach ($this->fields as $field => $label) {
if (str_contains($field, 'users') && !str_contains($field, 'project_users')) {
$this->hasUsersField = true;
// 展开学员信息为多列
foreach (self::USERS_SUB_COLUMNS as $subField => $subLabel) {
$this->expandedFields[$subField] = $subLabel;
$index++;
}
} elseif (str_contains($field, 'project_users')) {
$this->hasProjectUsersField = true;
// 展开项目经理信息为多列
foreach (self::PROJECT_USERS_SUB_COLUMNS as $subField => $subLabel) {
$this->expandedFields[$subField] = $subLabel;
$index++;
}
} else {
$this->expandedFields[$field] = $label;
$index++;
}
}
$this->totalColumns = count($this->expandedFields);
}
/**
* 是否需要二级表头
*/
private function needsDoubleHeader()
{
return $this->hasUsersField || $this->hasProjectUsersField;
}
/**
* 获取列字母
*/
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', 'pm_invest_date', 'pm_amount'])) {
$widths[$letter] = 15;
} elseif (in_array($field, ['user_index', 'user_schoolmate'])) {
$widths[$letter] = 8;
} elseif (in_array($field, ['pm_platform', 'pm_name'])) {
$widths[$letter] = 18;
} elseif (str_contains($field, 'partners')) {
$widths[$letter] = 50;
} elseif (str_contains($field, 'history_courses')) {
// 历史课程信息通常较长,适当放宽列宽
$widths[$letter] = 40;
} 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->needsDoubleHeader() ? 3 : 2;
$styles = [];
if ($this->needsDoubleHeader()) {
// 二级表头样式 - 第一行
$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->needsDoubleHeader()) {
// 处理二级表头的单元格合并
$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->needsDoubleHeader() ? 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') && !str_contains($field, 'project_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);
} elseif (str_contains($field, 'project_users')) {
// 项目经理信息列:合并第一行的多个单元格
$startCol = $this->getColumnLetter($index);
$endCol = $this->getColumnLetter($index + count(self::PROJECT_USERS_SUB_COLUMNS) - 1);
$sheet->mergeCells("{$startCol}1:{$endCol}1");
$sheet->setCellValue("{$startCol}1", $label);
$index += count(self::PROJECT_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->needsDoubleHeader()) {
// 有需要展开的字段:创建二级表头
// 第一行表头(主表头)- 在 mergeHeaderCells 中处理
$header1 = [];
foreach ($this->fields as $field => $label) {
if (str_contains($field, 'users') && !str_contains($field, 'project_users')) {
foreach (self::USERS_SUB_COLUMNS as $subLabel) {
$header1[] = '';
}
} elseif (str_contains($field, 'project_users')) {
foreach (self::PROJECT_USERS_SUB_COLUMNS as $subLabel) {
$header1[] = '';
}
} else {
$header1[] = '';
}
}
$newList[] = $header1;
// 第二行表头(子表头)
$header2 = [];
foreach ($this->fields as $field => $label) {
if (str_contains($field, 'users') && !str_contains($field, 'project_users')) {
foreach (self::USERS_SUB_COLUMNS as $subLabel) {
$header2[] = $subLabel;
}
} elseif (str_contains($field, 'project_users')) {
foreach (self::PROJECT_USERS_SUB_COLUMNS as $subLabel) {
$header2[] = $subLabel;
}
} else {
$header2[] = '';
}
}
$newList[] = $header2;
// 数据行:每个展开记录占一行
foreach ($this->data as $info) {
$expandedRows = $this->getExpandedRows($info);
if (empty($expandedRows)) {
// 没有展开数据,输出一行空数据
$row = $this->buildExpandedRow($info, [], []);
$newList[] = $row;
} else {
foreach ($expandedRows as $expandedRow) {
$row = $this->buildExpandedRow($info, $expandedRow['users'] ?? [], $expandedRow['project_users'] ?? []);
$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, 'history_courses')) {
// 历史课程信息字段,格式化为多行文本
$temp[$field] = $this->historyCourses($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->needsDoubleHeader() ? 2 : 1);
return new Collection($newList);
}
/**
* 获取展开后的所有行数据
*/
private function getExpandedRows($info)
{
$usersData = $this->hasUsersField ? $this->getUsersExpanded($info) : [];
$projectUsersData = $this->hasProjectUsersField ? $this->getProjectUsersExpanded($info) : [];
// 计算最大行数
$maxRows = max(count($usersData), count($projectUsersData), 1);
$result = [];
for ($i = 0; $i < $maxRows; $i++) {
$result[] = [
'users' => $usersData[$i] ?? [],
'project_users' => $projectUsersData[$i] ?? [],
];
}
return $result;
}
/**
* 获取展开的项目经理数据(每条记录一行)
*/
private function getProjectUsersExpanded($info)
{
if (empty($info['project_users'])) {
return [];
}
$result = [];
foreach ($info['project_users'] as $item) {
$result[] = [
'pm_platform' => $item['groupName'] ?? '-',
'pm_name' => $item['userName'] ?? '-',
'pm_invest_date' => $item['investDate'] ?? '-',
'pm_amount' => $item['amount'] ?? '-',
];
}
return $result;
}
/**
* 获取展开的学员数据(每个学员每条课程一行)
*/
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 buildExpandedRow($info, $userData, $projectUserData)
{
$row = [];
foreach ($this->fields as $field => $label) {
if (str_contains($field, 'users') && !str_contains($field, 'project_users')) {
// 填充学员信息列
foreach (array_keys(self::USERS_SUB_COLUMNS) as $subField) {
$row[] = $userData[$subField] ?? '';
}
} elseif (str_contains($field, 'project_users')) {
// 填充项目经理信息列
foreach (array_keys(self::PROJECT_USERS_SUB_COLUMNS) as $subField) {
$row[] = $projectUserData[$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, 'history_courses')) {
// 历史课程信息字段,格式化为多行文本
$row[] = $this->historyCourses($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
* @return string
*/
function historyCourses($data)
{
if (empty($data['history_courses']) || !is_array($data['history_courses'])) {
return '';
}
$list = [];
foreach ($data['history_courses'] as $item) {
$courseName = $item['course_name'] ?? '';
$type = $item['type'] ?? '';
$pass = $item['course_type_signs_pass'] ?? 0;
$passUnique = $item['course_type_signs_pass_unique'] ?? 0;
$signPass = $item['course_signs_pass'] ?? 0;
$start = $item['start_time'] ?? '';
$end = $item['end_time'] ?? '';
// 构造单行描述:课程名称[体系ID](开始~结束) 培养人数/去重/课程人数
$parts = [];
if ($courseName !== '') {
$parts[] = $courseName;
}
if ($type !== '') {
$parts[] = "[类型:{$type}]";
}
if ($start !== '' || $end !== '') {
$parts[] = "({$start}~{$end})";
}
$parts[] = "培养:{$pass}/去重:{$passUnique}/课程:{$signPass}";
$list[] = implode(' ', $parts);
}
// 每门历史课程占一行
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'] ?? '') . '-' . ($item['amount'] ?? '');
}
return implode("\r\n", $list);
}
}