From 5691d78004a4b07b2f2b0859fa5006ba6b4c3ed2 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Fri, 28 Nov 2025 16:03:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9C=8B=E5=8F=8B=E5=9C=88=E8=A1=A8=E6=A0=BC?= =?UTF-8?q?=E5=AF=BC=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mobile/mine/wechat-accounts/detail/api.ts | 67 ++++++ .../wechat-accounts/detail/detail.module.scss | 50 +++++ .../mine/wechat-accounts/detail/index.tsx | 208 +++++++++++++++++- .../common/controller/ExportController.php | 184 ++++++++++++++-- Server/application/cunkebao/config/route.php | 1 + .../wechat/GetWechatMomentsV1Controller.php | 159 +++++++++++++ 6 files changed, 654 insertions(+), 15 deletions(-) diff --git a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/api.ts b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/api.ts index 11998fe6..7a9a1ce3 100644 --- a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/api.ts +++ b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/api.ts @@ -1,4 +1,6 @@ import request from "@/api/request"; +import axios from "axios"; +import { useUserStore } from "@/store/module/user"; // 获取微信号详情 export function getWechatAccountDetail(id: string) { @@ -50,3 +52,68 @@ export function transferWechatFriends(params: { }) { return request("/v1/wechats/transfer-friends", params, "POST"); } + +// 导出朋友圈接口(直接下载文件) +export async function exportWechatMoments(params: { + wechatId: string; + keyword?: string; + type?: number; + startTime?: string; + endTime?: string; +}): Promise { + const { token } = useUserStore.getState(); + const baseURL = + (import.meta as any).env?.VITE_API_BASE_URL || "/api"; + + // 构建查询参数 + const queryParams = new URLSearchParams(); + queryParams.append("wechatId", params.wechatId); + if (params.keyword) { + queryParams.append("keyword", params.keyword); + } + if (params.type !== undefined) { + queryParams.append("type", params.type.toString()); + } + if (params.startTime) { + queryParams.append("startTime", params.startTime); + } + if (params.endTime) { + queryParams.append("endTime", params.endTime); + } + + try { + const response = await axios.get( + `${baseURL}/v1/wechats/moments/export?${queryParams.toString()}`, + { + responseType: "blob", + headers: { + Authorization: token ? `Bearer ${token}` : undefined, + }, + } + ); + + // 创建下载链接 + const blob = new Blob([response.data]); + const url = window.URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + + // 从响应头获取文件名,如果没有则使用默认文件名 + const contentDisposition = response.headers["content-disposition"]; + let fileName = "朋友圈导出.xlsx"; + if (contentDisposition) { + const fileNameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/); + if (fileNameMatch && fileNameMatch[1]) { + fileName = decodeURIComponent(fileNameMatch[1].replace(/['"]/g, "")); + } + } + + link.download = fileName; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + window.URL.revokeObjectURL(url); + } catch (error: any) { + throw new Error(error.response?.data?.message || error.message || "导出失败"); + } +} diff --git a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/detail.module.scss b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/detail.module.scss index 08f297a2..354503b6 100644 --- a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/detail.module.scss +++ b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/detail.module.scss @@ -845,6 +845,56 @@ margin-top: 20px; } + .popup-footer { + margin-top: 24px; + padding-top: 16px; + border-top: 1px solid #f0f0f0; + } + + .export-form { + margin-top: 20px; + + .form-item { + margin-bottom: 20px; + + label { + display: block; + font-size: 14px; + font-weight: 500; + color: #333; + margin-bottom: 8px; + } + + .type-selector { + display: flex; + gap: 8px; + flex-wrap: wrap; + + .type-option { + padding: 8px 16px; + border: 1px solid #e0e0e0; + border-radius: 8px; + font-size: 14px; + color: #666; + cursor: pointer; + transition: all 0.2s; + background: white; + + &:hover { + border-color: #1677ff; + color: #1677ff; + } + + &.active { + background: #1677ff; + border-color: #1677ff; + color: white; + } + } + } + } + } + .restrictions-detail { .restriction-detail-item { display: flex; diff --git a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx index 50f2c599..aeb10125 100644 --- a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx +++ b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx @@ -11,6 +11,7 @@ import { Avatar, Tag, Switch, + DatePicker, } from "antd-mobile"; import { Input, Pagination } from "antd"; import NavCommon from "@/components/NavCommon"; @@ -27,6 +28,7 @@ import { transferWechatFriends, getWechatAccountOverview, getWechatMoments, + exportWechatMoments, } from "./api"; import DeviceSelection from "@/components/DeviceSelection"; import { DeviceSelectionItem } from "@/components/DeviceSelection/data"; @@ -64,6 +66,16 @@ const WechatAccountDetail: React.FC = () => { const [momentsError, setMomentsError] = useState(null); const MOMENTS_LIMIT = 10; + // 导出相关状态 + const [showExportPopup, setShowExportPopup] = useState(false); + const [exportKeyword, setExportKeyword] = useState(""); + const [exportType, setExportType] = useState(undefined); + const [exportStartTime, setExportStartTime] = useState(null); + const [exportEndTime, setExportEndTime] = useState(null); + const [showStartTimePicker, setShowStartTimePicker] = useState(false); + const [showEndTimePicker, setShowEndTimePicker] = useState(false); + const [exportLoading, setExportLoading] = useState(false); + // 获取基础信息 const fetchAccountInfo = useCallback(async () => { if (!id) return; @@ -370,6 +382,50 @@ const WechatAccountDetail: React.FC = () => { fetchMomentsList(momentsPage + 1, true); }; + // 处理朋友圈导出 + const handleExportMoments = useCallback(async () => { + if (!id) { + Toast.show({ content: "微信ID不存在", position: "top" }); + return; + } + + setExportLoading(true); + try { + // 格式化时间 + const formatDate = (date: Date | null): string | undefined => { + if (!date) return undefined; + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + return `${year}-${month}-${day}`; + }; + + await exportWechatMoments({ + wechatId: id, + keyword: exportKeyword || undefined, + type: exportType, + startTime: formatDate(exportStartTime), + endTime: formatDate(exportEndTime), + }); + + Toast.show({ content: "导出成功", position: "top" }); + setShowExportPopup(false); + // 重置筛选条件 + setExportKeyword(""); + setExportType(undefined); + setExportStartTime(null); + setExportEndTime(null); + } catch (error: any) { + console.error("导出失败:", error); + Toast.show({ + content: error.message || "导出失败,请重试", + position: "top", + }); + } finally { + setExportLoading(false); + } + }, [id, exportKeyword, exportType, exportStartTime, exportEndTime]); + const formatMomentDateParts = (dateString: string) => { const date = new Date(dateString); if (Number.isNaN(date.getTime())) { @@ -810,7 +866,10 @@ const WechatAccountDetail: React.FC = () => { 视频 -
+
setShowExportPopup(true)} + > 导出
@@ -1023,6 +1082,153 @@ const WechatAccountDetail: React.FC = () => {
+ {/* 朋友圈导出弹窗 */} + setShowExportPopup(false)} + bodyStyle={{ borderRadius: "16px 16px 0 0" }} + > +
+
+

导出朋友圈

+ +
+ +
+ {/* 关键词搜索 */} +
+ + setExportKeyword(e.target.value)} + allowClear + /> +
+ + {/* 类型筛选 */} +
+ +
+
setExportType(undefined)} + > + 全部 +
+
setExportType(4)} + > + 文本 +
+
setExportType(1)} + > + 图片 +
+
setExportType(3)} + > + 视频 +
+
+
+ + {/* 开始时间 */} +
+ + setShowStartTimePicker(true)} + /> + setShowStartTimePicker(false)} + onConfirm={val => { + setExportStartTime(val); + setShowStartTimePicker(false); + }} + /> +
+ + {/* 结束时间 */} +
+ + setShowEndTimePicker(true)} + /> + setShowEndTimePicker(false)} + onConfirm={val => { + setExportEndTime(val); + setShowEndTimePicker(false); + }} + /> +
+
+ +
+ + +
+
+
+ {/* 好友详情弹窗 */} {/* Removed */} diff --git a/Server/application/common/controller/ExportController.php b/Server/application/common/controller/ExportController.php index aea58f15..8d2a8079 100644 --- a/Server/application/common/controller/ExportController.php +++ b/Server/application/common/controller/ExportController.php @@ -26,6 +26,11 @@ class ExportController extends Controller * @param array $rows 数据行,需与 $headers 的 key 对应 * @param array $imageColumns 需要渲染为图片的列 key 列表 * @param string $sheetName 工作表名称 + * @param array $options 额外选项: + * - imageWidth(图片宽度,默认100) + * - imageHeight(图片高度,默认100) + * - imageColumnWidth(图片列宽,默认15) + * - titleRow(标题行内容,支持多行文本数组) * * @throws Exception */ @@ -34,7 +39,8 @@ class ExportController extends Controller array $headers, array $rows, array $imageColumns = [], - $sheetName = 'Sheet1' + $sheetName = 'Sheet1', + array $options = [] ) { if (empty($headers)) { throw new Exception('导出列定义不能为空'); @@ -43,22 +49,114 @@ class ExportController extends Controller throw new Exception('导出数据不能为空'); } + // 默认选项 + $imageWidth = isset($options['imageWidth']) ? (int)$options['imageWidth'] : 100; + $imageHeight = isset($options['imageHeight']) ? (int)$options['imageHeight'] : 100; + $imageColumnWidth = isset($options['imageColumnWidth']) ? (float)$options['imageColumnWidth'] : 15; + $rowHeight = isset($options['rowHeight']) ? (int)$options['rowHeight'] : ($imageHeight + 10); + $excel = new PHPExcel(); $sheet = $excel->getActiveSheet(); $sheet->setTitle($sheetName); $columnKeys = array_keys($headers); + $totalColumns = count($columnKeys); + $lastColumnLetter = self::columnLetter($totalColumns - 1); - // 写入表头 + // 定义特定列的固定宽度(如果未指定则使用默认值) + $columnWidths = isset($options['columnWidths']) ? $options['columnWidths'] : []; + + // 检查是否有标题行 + $titleRow = isset($options['titleRow']) ? $options['titleRow'] : null; + $dataStartRow = 1; // 数据开始行(表头行) + + // 如果有标题行,先写入标题行 + if ($titleRow && is_array($titleRow) && !empty($titleRow)) { + $dataStartRow = 2; // 数据从第2行开始(第1行是标题,第2行是表头) + + // 合并标题行单元格(从第一列到最后一列) + $titleRange = 'A1:' . $lastColumnLetter . '1'; + $sheet->mergeCells($titleRange); + + // 构建标题内容(支持多行) + $titleContent = ''; + if (is_array($titleRow)) { + $titleContent = implode("\n", $titleRow); + } else { + $titleContent = (string)$titleRow; + } + + // 写入标题 + $sheet->setCellValue('A1', $titleContent); + + // 设置标题行样式 + $sheet->getStyle('A1')->applyFromArray([ + 'font' => ['bold' => true, 'size' => 16], + 'alignment' => [ + 'horizontal' => \PHPExcel_Style_Alignment::HORIZONTAL_CENTER, + 'vertical' => \PHPExcel_Style_Alignment::VERTICAL_CENTER, + 'wrap' => true + ], + 'fill' => [ + 'type' => \PHPExcel_Style_Fill::FILL_SOLID, + 'color' => ['rgb' => 'FFF8DC'] // 浅黄色背景 + ], + 'borders' => [ + 'allborders' => [ + 'style' => \PHPExcel_Style_Border::BORDER_THIN, + 'color' => ['rgb' => '000000'] + ] + ] + ]); + $sheet->getRowDimension(1)->setRowHeight(80); // 标题行高度 + } + + // 写入表头并设置列宽 + $headerRow = $dataStartRow; foreach ($columnKeys as $index => $key) { $columnLetter = self::columnLetter($index); - $sheet->setCellValue($columnLetter . '1', $headers[$key]); - $sheet->getColumnDimension($columnLetter)->setAutoSize(true); + $sheet->setCellValue($columnLetter . $headerRow, $headers[$key]); + + // 如果是图片列,设置固定列宽 + if (in_array($key, $imageColumns, true)) { + $sheet->getColumnDimension($columnLetter)->setWidth($imageColumnWidth); + } elseif (isset($columnWidths[$key])) { + // 如果指定了该列的宽度,使用指定宽度 + $sheet->getColumnDimension($columnLetter)->setWidth($columnWidths[$key]); + } else { + // 否则自动调整 + $sheet->getColumnDimension($columnLetter)->setAutoSize(true); + } } + // 设置表头样式 + $headerRange = 'A' . $headerRow . ':' . $lastColumnLetter . $headerRow; + $sheet->getStyle($headerRange)->applyFromArray([ + 'font' => ['bold' => true, 'size' => 11], + 'alignment' => [ + 'horizontal' => \PHPExcel_Style_Alignment::HORIZONTAL_CENTER, + 'vertical' => \PHPExcel_Style_Alignment::VERTICAL_CENTER, + 'wrap' => true + ], + 'fill' => [ + 'type' => \PHPExcel_Style_Fill::FILL_SOLID, + 'color' => ['rgb' => 'FFF8DC'] + ], + 'borders' => [ + 'allborders' => [ + 'style' => \PHPExcel_Style_Border::BORDER_THIN, + 'color' => ['rgb' => '000000'] + ] + ] + ]); + $sheet->getRowDimension($headerRow)->setRowHeight(30); // 增加表头行高以确保文本完整显示 + // 写入数据与图片 + $dataRowStart = $dataStartRow + 1; // 数据从表头行下一行开始 foreach ($rows as $rowIndex => $rowData) { - $excelRow = $rowIndex + 2; // 数据从第 2 行开始 + $excelRow = $dataRowStart + $rowIndex; // 数据行 + $maxRowHeight = $rowHeight; // 记录当前行的最大高度 + foreach ($columnKeys as $colIndex => $key) { $columnLetter = self::columnLetter($colIndex); $cell = $columnLetter . $excelRow; @@ -67,21 +165,79 @@ class ExportController extends Controller 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); + // 获取图片实际尺寸并等比例缩放 + $imageSize = @getimagesize($imagePath); + if ($imageSize) { + $originalWidth = $imageSize[0]; + $originalHeight = $imageSize[1]; + + // 计算等比例缩放后的尺寸 + $ratio = min($imageWidth / $originalWidth, $imageHeight / $originalHeight); + $scaledWidth = $originalWidth * $ratio; + $scaledHeight = $originalHeight * $ratio; + + // 确保不超过最大尺寸 + if ($scaledWidth > $imageWidth) { + $scaledWidth = $imageWidth; + $scaledHeight = $originalHeight * ($imageWidth / $originalWidth); + } + if ($scaledHeight > $imageHeight) { + $scaledHeight = $imageHeight; + $scaledWidth = $originalWidth * ($imageHeight / $originalHeight); + } + + $drawing = new PHPExcel_Worksheet_Drawing(); + $drawing->setPath($imagePath); + $drawing->setCoordinates($cell); + + // 居中显示图片(Excel列宽1单位≈7像素,行高1单位≈0.75像素) + $cellWidthPx = $imageColumnWidth * 7; + $cellHeightPx = $maxRowHeight * 0.75; + $offsetX = max(2, ($cellWidthPx - $scaledWidth) / 2); + $offsetY = max(2, ($cellHeightPx - $scaledHeight) / 2); + + $drawing->setOffsetX((int)$offsetX); + $drawing->setOffsetY((int)$offsetY); + $drawing->setWidth((int)$scaledWidth); + $drawing->setHeight((int)$scaledHeight); + $drawing->setWorksheet($sheet); + + // 更新行高以适应图片(留出一些边距) + $neededHeight = (int)($scaledHeight / 0.75) + 10; + if ($neededHeight > $maxRowHeight) { + $maxRowHeight = $neededHeight; + } + } else { + // 如果无法获取图片尺寸,使用默认尺寸 + $drawing = new PHPExcel_Worksheet_Drawing(); + $drawing->setPath($imagePath); + $drawing->setCoordinates($cell); + $drawing->setOffsetX(5); + $drawing->setOffsetY(5); + $drawing->setWidth($imageWidth); + $drawing->setHeight($imageHeight); + $drawing->setWorksheet($sheet); + } } else { - $sheet->setCellValue($cell, $value); + $sheet->setCellValue($cell, ''); } } else { $sheet->setCellValue($cell, $value); + // 设置文本对齐和换行 + $style = $sheet->getStyle($cell); + $style->getAlignment()->setVertical(\PHPExcel_Style_Alignment::VERTICAL_CENTER); + $style->getAlignment()->setWrapText(true); + // 根据列类型设置水平对齐 + if (in_array($key, ['date', 'postTime'])) { + $style->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_CENTER); + } else { + $style->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_LEFT); + } } } + + // 设置行高 + $sheet->getRowDimension($excelRow)->setRowHeight($maxRowHeight); } $safeName = preg_replace('/[^\w\-]/', '_', $fileName ?: 'export_' . date('Ymd_His')); diff --git a/Server/application/cunkebao/config/route.php b/Server/application/cunkebao/config/route.php index c34fcd07..056667da 100644 --- a/Server/application/cunkebao/config/route.php +++ b/Server/application/cunkebao/config/route.php @@ -38,6 +38,7 @@ Route::group('v1/', function () { Route::get('getWechatInfo', 'app\cunkebao\controller\wechat\GetWechatController@getWechatInfo'); Route::get('overview', 'app\cunkebao\controller\wechat\GetWechatOverviewV1Controller@index'); // 获取微信账号概览数据 Route::get('moments', 'app\cunkebao\controller\wechat\GetWechatMomentsV1Controller@index'); // 获取微信朋友圈 + Route::get('moments/export', 'app\cunkebao\controller\wechat\GetWechatMomentsV1Controller@export'); // 导出微信朋友圈 Route::get('count', 'app\cunkebao\controller\DeviceWechat@count'); Route::get('device-count', 'app\cunkebao\controller\DeviceWechat@deviceCount'); // 获取有登录微信的设备数量 Route::put('refresh', 'app\cunkebao\controller\DeviceWechat@refresh'); // 刷新设备微信状态 diff --git a/Server/application/cunkebao/controller/wechat/GetWechatMomentsV1Controller.php b/Server/application/cunkebao/controller/wechat/GetWechatMomentsV1Controller.php index b34ef7ff..2057a44d 100644 --- a/Server/application/cunkebao/controller/wechat/GetWechatMomentsV1Controller.php +++ b/Server/application/cunkebao/controller/wechat/GetWechatMomentsV1Controller.php @@ -2,6 +2,7 @@ namespace app\cunkebao\controller\wechat; +use app\common\controller\ExportController; use app\common\model\Device as DeviceModel; use app\common\model\DeviceUser as DeviceUserModel; use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel; @@ -145,6 +146,164 @@ class GetWechatMomentsV1Controller extends BaseController } } + /** + * 导出朋友圈数据到Excel + * + * @return void + */ + public function export() + { + try { + $wechatId = $this->request->param('wechatId/s', ''); + if (empty($wechatId)) { + return ResponseHelper::error('wechatId不能为空'); + } + + // 权限校验:只能查看当前账号可访问的微信 + $accessibleWechatIds = $this->getAccessibleWechatIds(); + if (!in_array($wechatId, $accessibleWechatIds, true)) { + return ResponseHelper::error('无权查看该微信的朋友圈', 403); + } + + // 获取对应的微信账号ID + $accountId = Db::table('s2_wechat_account') + ->where('wechatId', $wechatId) + ->value('id'); + + if (empty($accountId)) { + return ResponseHelper::error('微信账号不存在或尚未同步', 404); + } + + $query = Db::table('s2_wechat_moments') + ->where('wechatAccountId', $accountId); + + // 关键词搜索 + if ($keyword = trim((string)$this->request->param('keyword', ''))) { + $query->whereLike('content', '%' . $keyword . '%'); + } + + // 类型筛选 + $type = $this->request->param('type', ''); + if ($type !== '' && $type !== null) { + $query->where('type', (int)$type); + } + + // 时间筛选 + $startTime = $this->request->param('startTime', ''); + $endTime = $this->request->param('endTime', ''); + if ($startTime || $endTime) { + $start = $startTime ? strtotime($startTime) : 0; + $end = $endTime ? strtotime($endTime) : time(); + if ($start && $end && $end < $start) { + return ResponseHelper::error('结束时间不能早于开始时间'); + } + $query->whereBetween('createTime', [$start ?: 0, $end ?: time()]); + } + + // 获取所有数据(不分页) + $moments = $query->order('createTime', 'desc')->select(); + + if (empty($moments)) { + return ResponseHelper::error('暂无数据可导出'); + } + + // 定义表头 + $headers = [ + 'date' => '日期', + 'postTime' => '投放时间', + 'functionCategory' => '作用分类', + 'content' => '朋友圈文案', + 'selfReply' => '自回评内容', + 'displayForm' => '朋友圈展示形式', + 'image1' => '配图1', + 'image2' => '配图2', + 'image3' => '配图3', + 'image4' => '配图4', + 'image5' => '配图5', + 'image6' => '配图6', + 'image7' => '配图7', + 'image8' => '配图8', + 'image9' => '配图9', + ]; + + // 格式化数据 + $rows = []; + foreach ($moments as $moment) { + $resUrls = $this->decodeJson($moment['resUrls'] ?? null); + $imageUrls = is_array($resUrls) ? $resUrls : []; + + // 格式化日期和时间 + $createTime = !empty($moment['createTime']) + ? (is_numeric($moment['createTime']) ? $moment['createTime'] : strtotime($moment['createTime'])) + : 0; + $date = $createTime ? date('Y年m月d日', $createTime) : ''; + $postTime = $createTime ? date('H:i', $createTime) : ''; + + // 判断展示形式 + $displayForm = ''; + if (!empty($moment['content']) && !empty($imageUrls)) { + $displayForm = '文字+图片'; + } elseif (!empty($moment['content'])) { + $displayForm = '文字'; + } elseif (!empty($imageUrls)) { + $displayForm = '图片'; + } + + $row = [ + 'date' => $date, + 'postTime' => $postTime, + 'functionCategory' => '', // 暂时放空 + 'content' => $moment['content'] ?? '', + 'selfReply' => '', // 暂时放空 + 'displayForm' => $displayForm, + ]; + + // 分配图片到配图1-9列 + for ($i = 1; $i <= 9; $i++) { + $imageKey = 'image' . $i; + $row[$imageKey] = isset($imageUrls[$i - 1]) ? $imageUrls[$i - 1] : ''; + } + + $rows[] = $row; + } + + // 定义图片列(配图1-9) + $imageColumns = ['image1', 'image2', 'image3', 'image4', 'image5', 'image6', 'image7', 'image8', 'image9']; + + // 生成文件名 + $fileName = '朋友圈投放_' . date('Ymd_His'); + + // 调用导出方法,优化图片显示效果 + ExportController::exportExcelWithImages( + $fileName, + $headers, + $rows, + $imageColumns, + '朋友圈投放', + [ + 'imageWidth' => 120, // 图片宽度(像素) + 'imageHeight' => 120, // 图片高度(像素) + 'imageColumnWidth' => 18, // 图片列宽(Excel单位) + 'rowHeight' => 130, // 行高(像素) + 'columnWidths' => [ // 特定列的固定宽度 + 'date' => 15, // 日期列宽 + 'postTime' => 12, // 投放时间列宽 + 'functionCategory' => 15, // 作用分类列宽 + 'content' => 40, // 朋友圈文案列宽(自动调整可能不够) + 'selfReply' => 30, // 自回评内容列宽 + 'displayForm' => 18, // 朋友圈展示形式列宽 + ], + 'titleRow' => [ // 标题行内容(第一行) + '朋友圈投放', + '我能提供什么价值? (40%) 有谁正在和我合作 (20%) 如何和我合作? (20%) 你找我合作需要付多少钱? (20%)' + ] + ] + ); + } catch (\Exception $e) { + return ResponseHelper::error('导出失败:' . $e->getMessage(), 500); + } + } + /** * 格式化朋友圈数据 *