166 lines
4.9 KiB
PHP
166 lines
4.9 KiB
PHP
<?php
|
||
|
||
namespace app\common\controller;
|
||
|
||
use PHPExcel;
|
||
use PHPExcel_IOFactory;
|
||
use PHPExcel_Worksheet_Drawing;
|
||
use think\Controller;
|
||
use think\Exception;
|
||
|
||
/**
|
||
* 通用导出控制器,提供 Excel 导出与图片插入能力
|
||
*/
|
||
class ExportController extends Controller
|
||
{
|
||
/**
|
||
* @var array<string> 需要在请求结束时清理的临时文件
|
||
*/
|
||
protected static $tempFiles = [];
|
||
|
||
/**
|
||
* 导出 Excel(支持指定列插入图片)
|
||
*
|
||
* @param string $fileName 输出文件名(可不带扩展名)
|
||
* @param array $headers 列定义,例如 ['name' => '姓名', 'phone' => '电话']
|
||
* @param array $rows 数据行,需与 $headers 的 key 对应
|
||
* @param array $imageColumns 需要渲染为图片的列 key 列表
|
||
* @param string $sheetName 工作表名称
|
||
*
|
||
* @throws Exception
|
||
*/
|
||
public static function exportExcelWithImages(
|
||
$fileName,
|
||
array $headers,
|
||
array $rows,
|
||
array $imageColumns = [],
|
||
$sheetName = 'Sheet1'
|
||
) {
|
||
if (empty($headers)) {
|
||
throw new Exception('导出列定义不能为空');
|
||
}
|
||
if (empty($rows)) {
|
||
throw new Exception('导出数据不能为空');
|
||
}
|
||
|
||
$excel = new PHPExcel();
|
||
$sheet = $excel->getActiveSheet();
|
||
$sheet->setTitle($sheetName);
|
||
|
||
$columnKeys = array_keys($headers);
|
||
|
||
// 写入表头
|
||
foreach ($columnKeys as $index => $key) {
|
||
$columnLetter = self::columnLetter($index);
|
||
$sheet->setCellValue($columnLetter . '1', $headers[$key]);
|
||
$sheet->getColumnDimension($columnLetter)->setAutoSize(true);
|
||
}
|
||
|
||
// 写入数据与图片
|
||
foreach ($rows as $rowIndex => $rowData) {
|
||
$excelRow = $rowIndex + 2; // 数据从第 2 行开始
|
||
foreach ($columnKeys as $colIndex => $key) {
|
||
$columnLetter = self::columnLetter($colIndex);
|
||
$cell = $columnLetter . $excelRow;
|
||
$value = isset($rowData[$key]) ? $rowData[$key] : '';
|
||
|
||
if (in_array($key, $imageColumns, true) && !empty($value)) {
|
||
$imagePath = self::resolveImagePath($value);
|
||
if ($imagePath) {
|
||
$drawing = new PHPExcel_Worksheet_Drawing();
|
||
$drawing->setPath($imagePath);
|
||
$drawing->setCoordinates($cell);
|
||
$drawing->setOffsetX(5);
|
||
$drawing->setOffsetY(5);
|
||
$drawing->setHeight(60);
|
||
$drawing->setWorksheet($sheet);
|
||
$sheet->getRowDimension($excelRow)->setRowHeight(60);
|
||
} else {
|
||
$sheet->setCellValue($cell, $value);
|
||
}
|
||
} else {
|
||
$sheet->setCellValue($cell, $value);
|
||
}
|
||
}
|
||
}
|
||
|
||
$safeName = preg_replace('/[^\w\-]/', '_', $fileName ?: 'export_' . date('Ymd_His'));
|
||
if (stripos($safeName, '.xlsx') === false) {
|
||
$safeName .= '.xlsx';
|
||
}
|
||
|
||
if (ob_get_length()) {
|
||
ob_end_clean();
|
||
}
|
||
|
||
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||
header('Cache-Control: max-age=0');
|
||
header('Content-Disposition: attachment;filename="' . $safeName . '"');
|
||
|
||
$writer = PHPExcel_IOFactory::createWriter($excel, 'Excel2007');
|
||
$writer->save('php://output');
|
||
|
||
self::cleanupTempFiles();
|
||
exit;
|
||
}
|
||
|
||
/**
|
||
* 根据列序号生成 Excel 列字母
|
||
*
|
||
* @param int $index
|
||
* @return string
|
||
*/
|
||
protected static function columnLetter($index)
|
||
{
|
||
$letters = '';
|
||
do {
|
||
$letters = chr($index % 26 + 65) . $letters;
|
||
$index = intval($index / 26) - 1;
|
||
} while ($index >= 0);
|
||
|
||
return $letters;
|
||
}
|
||
|
||
/**
|
||
* 将远程或本地图片路径转换为可用的本地文件路径
|
||
*
|
||
* @param string $path
|
||
* @return string|null
|
||
*/
|
||
protected static function resolveImagePath($path)
|
||
{
|
||
if (empty($path)) {
|
||
return null;
|
||
}
|
||
|
||
if (preg_match('/^https?:\/\//i', $path)) {
|
||
$tempFile = tempnam(sys_get_temp_dir(), 'export_img_');
|
||
$stream = @file_get_contents($path);
|
||
if ($stream === false) {
|
||
return null;
|
||
}
|
||
file_put_contents($tempFile, $stream);
|
||
self::$tempFiles[] = $tempFile;
|
||
return $tempFile;
|
||
}
|
||
|
||
if (file_exists($path)) {
|
||
return $path;
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 清理所有临时文件
|
||
*/
|
||
protected static function cleanupTempFiles()
|
||
{
|
||
foreach (self::$tempFiles as $file) {
|
||
if (file_exists($file)) {
|
||
@unlink($file);
|
||
}
|
||
}
|
||
self::$tempFiles = [];
|
||
}
|
||
} |