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 = [];
|
|||
|
|
}
|
|||
|
|
}
|