diff --git a/app/common/controller/UploadController.php b/app/common/controller/UploadController.php index 90921c0..b10cf93 100644 --- a/app/common/controller/UploadController.php +++ b/app/common/controller/UploadController.php @@ -89,12 +89,10 @@ class UploadController $originalName = $file->getUploadName(); $ossservice = new OssService(); - $mimes = $ossservice->getMimeTypes(['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'zip', 'rar', 'MP4', 'MOV', 'M4V', 'WEBM', 'MKV', 'FLV', 'WMV', 'AVI', 'TS']); $result = Storage::adapter('oss') ->path(config('oss.oss_main_dir') . $sceneUrl . date('Ymd')) - ->size(1024*1024*50) - ->extYes($mimes) + ->size(1024*1024*100) ->upload($file); diff --git a/app/common/logic/AuthLogic.php b/app/common/logic/AuthLogic.php index f324b4a..f145f3c 100644 --- a/app/common/logic/AuthLogic.php +++ b/app/common/logic/AuthLogic.php @@ -14,7 +14,6 @@ use app\common\utils\Jwt; use support\Redis; use support\exception\BusinessException; use support\Log; -use function Symfony\Component\String\b; class AuthLogic { diff --git a/app/common/model/UsedCarDetail.php b/app/common/model/UsedCarDetail.php index 3a5ff3c..8688fa6 100644 --- a/app/common/model/UsedCarDetail.php +++ b/app/common/model/UsedCarDetail.php @@ -51,9 +51,9 @@ class UsedCarDetail extends Model * JSON 类型字段自动转换 */ protected $casts = [ - 'images' => 'array', - 'attachment' => 'array', - 'intro_images' => 'array', +// 'images' => 'array', +// 'attachment' => 'array', +// 'intro_images' => 'array', ]; /** diff --git a/app/common/service/UsedCarDetailService.php b/app/common/service/UsedCarDetailService.php index 4b93076..6bdc4fe 100644 --- a/app/common/service/UsedCarDetailService.php +++ b/app/common/service/UsedCarDetailService.php @@ -421,7 +421,70 @@ class UsedCarDetailService ]); // 更新车辆状态 - return UsedCarDetail::where('id', $carId)->update(['status' => $auditStatus]); + $result = UsedCarDetail::where('id', $carId)->update(['status' => $auditStatus]); + + // 审核通过后自动推送到WordPress + $pushResult = ['success' => true, 'message' => '']; + if ($auditStatus == 1) { + $pushResult = $this->pushToWordpressAfterAudit($carId); + + // 推送失败,将车辆状态修改为3 + if (!$pushResult['success']) { + UsedCarDetail::where('id', $carId)->update(['status' => 3]); + } + } + + // 返回审核结果和推送结果 + return [ + 'audit_success' => true, + 'push_success' => $pushResult['success'], + 'push_message' => $pushResult['message'], + 'message' => $pushResult['success'] ? '审核通过并推送成功' : '审核通过,但推送失败:' . $pushResult['message'], + ]; + } + + /** + * 审核通过后推送车辆到WordPress + * @return array ['success' => bool, 'message' => string] + */ + private function pushToWordpressAfterAudit(int $carId): array + { + try { + // 获取启用的WordPress站点 + $site = \app\common\model\WordpressSite::where('status', 1)->first(); + if (!$site) { + $msg = '没有启用的WordPress站点'; + Log::warning('WordPress推送失败:' . $msg, ['car_id' => $carId]); + return ['success' => false, 'message' => $msg]; + } + + // 检查是否已推送过 + $existing = \app\common\model\WordpressUsedCar::where('wp_site_id', $site->id) + ->where('used_car_id', $carId) + ->where('status', 1) + ->first(); + if ($existing) { + $msg = '车辆已推送,跳过'; + Log::info('WordPress推送:' . $msg, ['car_id' => $carId]); + return ['success' => true, 'message' => $msg]; + } + + // 调用推送服务 + $wpService = new \app\common\service\WordpressSiteService(); + $wpService->createProductForNewSite($site, $carId); + + Log::info('WordPress推送成功', ['car_id' => $carId, 'site_id' => $site->id]); + return ['success' => true, 'message' => '']; + + } catch (\Throwable $e) { + $msg = $e->getMessage(); + Log::error('WordPress推送失败', [ + 'car_id' => $carId, + 'error' => $msg, + 'trace' => $e->getTraceAsString(), + ]); + return ['success' => false, 'message' => $msg]; + } } /** diff --git a/app/common/service/WordpressSiteService.php b/app/common/service/WordpressSiteService.php index b091057..1ebac77 100644 --- a/app/common/service/WordpressSiteService.php +++ b/app/common/service/WordpressSiteService.php @@ -767,38 +767,653 @@ class WordpressSiteService */ public function uploadImageToWp(string $domain,$api_user, $api_secret, string $imageUrl = "fdesk_test/usedcar_crawl/20251125/ff847a6a2b3114b4c2c230455b74cd2e.jpg"): array { - $process = 'image/resize,w_900/quality,Q_50/format,webp'; - $imageUrl = (new OssService)->generatePrivateUrlResize($imageUrl,3600,$process); - + $process = 'image/resize,w_800/quality,Q_50/format,webp'; + $processedUrl = (new OssService)->generatePrivateUrlResize($imageUrl,3600,$process); $client = new Client(); - $filename = basename(parse_url($imageUrl, PHP_URL_PATH)); + // 从原始 URL 提取文件扩展名 + $pathInfo = pathinfo(parse_url($imageUrl, PHP_URL_PATH)); + $extension = strtolower($pathInfo['extension'] ?? 'jpg'); + + // 根据处理后的格式确定文件名和 Content-Type + if (strpos($processedUrl, 'webp') !== false || strpos($process, 'webp') !== false) { + $filename = ($pathInfo['filename'] ?? 'image') . '.webp'; + $contentType = 'image/webp'; + } else { + $filename = ($pathInfo['filename'] ?? 'image') . '.' . $extension; + $contentType = 'image/' . ($extension === 'jpg' ? 'jpeg' : $extension); + } + $mediaUrl = $domain . '/wp-json/wp/v2/media'; - try { - // 1. 拉取 OSS 图片内容 - $imageContent = file_get_contents($imageUrl); - if ($imageContent === false) { - throw new \Exception('图片下载失败:' . $imageUrl); - } + // 本地临时文件路径 + $tempDir = runtime_path('temp') . '/wp_images/'; + if (!is_dir($tempDir)) { + mkdir($tempDir, 0755, true); + } + $tempFilePath = $tempDir . md5($processedUrl) . '.' . $extension; - // 2. 上传到 WP 媒体库 - $response = $client->post($mediaUrl, [ - RequestOptions::AUTH => [$api_user, $api_secret], - RequestOptions::HEADERS => [ - 'Content-Disposition' => 'attachment; filename="' . $filename . '"', - 'Content-Type' => 'image/jpeg', // 如有 png/webp 可自行判断 - ], - RequestOptions::BODY => $imageContent, - RequestOptions::VERIFY => false, + try { + Log::info('开始上传图片到WordPress', [ + 'original_url' => $imageUrl, + 'processed_url' => $processedUrl, + 'filename' => $filename, + 'content_type' => $contentType, + 'temp_file' => $tempFilePath, ]); - return json_decode($response->getBody()->getContents(), true); + // 1. 下载图片到本地临时文件 + $imageContent = file_get_contents($processedUrl); + if ($imageContent === false) { + throw new \Exception('图片下载失败:' . $processedUrl); + } + + $imageSize = strlen($imageContent); + Log::info('图片下载成功', ['size' => $imageSize, 'temp_file' => $tempFilePath]); + + // 写入临时文件 + $bytesWritten = file_put_contents($tempFilePath, $imageContent); + if ($bytesWritten === false) { + throw new \Exception('写入临时文件失败:' . $tempFilePath); + } + + Log::info('临时文件创建成功', ['file' => $tempFilePath, 'size' => $bytesWritten]); + + // 2. 使用 GuzzleHttp 上传本地文件到 WP 媒体库 + $fileHandle = fopen($tempFilePath, 'r'); + $response = $client->post($mediaUrl, [ + RequestOptions::AUTH => [$api_user, $api_secret], + RequestOptions::MULTIPART => [ + [ + 'name' => 'file', + 'filename' => $filename, + 'contents' => $fileHandle, + 'headers' => [ + 'Content-Type' => $contentType, + ], + ], + ], + RequestOptions::VERIFY => false, + RequestOptions::TIMEOUT => 30, + ]); + + // 关闭文件句柄 + if (is_resource($fileHandle)) { + fclose($fileHandle); + } + + $result = json_decode($response->getBody()->getContents(), true); + + Log::info('图片上传成功', [ + 'media_id' => $result['id'] ?? 'unknown', + 'source_url' => $result['source_url'] ?? 'not set', + 'url' => $result['url'] ?? 'not set', + 'link' => $result['link'] ?? 'not set', + 'guid' => $result['guid']['rendered'] ?? 'not set', + 'all_keys' => array_keys($result), + ]); + + return $result; } catch (\Exception $e) { - throw new \Exception('上传图片失败:' .$domain . $imageUrl . "?????". $e->getMessage()); + Log::error('上传图片到WordPress失败', [ + 'domain' => $domain, + 'image_url' => $imageUrl, + 'processed_url' => $processedUrl ?? 'N/A', + 'temp_file' => $tempFilePath ?? 'N/A', + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ]); + throw new \Exception('上传图片失败:' . $e->getMessage()); + } finally { + // 3. 删除临时文件 + if (file_exists($tempFilePath)) { + @unlink($tempFilePath); + Log::info('临时文件已删除', ['file' => $tempFilePath]); + } } } + /** + * 创建产品到新站点(使用 Gutenberg Blocks 结构) + * + * @param \app\common\model\WordpressSite $site WordPress站点 + * @param int $carId 车辆ID + * @return array + * @throws \Exception + */ + public function createProductForNewSite($site, int $carId): array + { + // 获取车辆数据 + $car = \app\common\model\UsedCarDetail::find($carId); + if (!$car) { + throw new \Exception('车辆不存在'); + } + + // 检查是否重复推送 + $wpUsed = WordpressUsedCar::where('wp_site_id', $site->id) + ->where('used_car_id', $carId) + ->first(); + if ($wpUsed && ($wpUsed->retry_count > 2 || $wpUsed->status == 1)) { + return ['message' => '重复推送,跳过']; + } + + $domain = $site->site_url; + $client = new Client(); + $url = $domain . '/wp-json/wp/v2/product'; + + // 调试:检查 images 字段的原始值和转换后的值 + $imagesRaw = $car->getAttribute('images'); // 获取原始值,不经过 casts + $imagesCast = $car->images; // 经过 casts 转换后的值 + + Log::info('检查 images 字段', [ + 'car_id' => $carId, + 'images_raw' => $imagesRaw, + 'images_cast' => $imagesCast, + 'images_raw_type' => gettype($imagesRaw), + 'images_cast_type' => gettype($imagesCast), + 'images_cast_is_array' => is_array($imagesCast), + ]); + + // 解析图片列表 - 优先使用已转换的数组,否则尝试解析原始字符串 + $images = []; + if (!empty($imagesCast) && is_array($imagesCast)) { + $images = $imagesCast; + } elseif (!empty($imagesRaw)) { + // 尝试 JSON 解析 + $decoded = json_decode($imagesRaw, true); + if (is_array($decoded)) { + $images = $decoded; + } else { + // 尝试逗号分割 + $images = explode(',', $imagesRaw); + } + } + + Log::info('开始处理车辆图片', [ + 'car_id' => $carId, + 'images_final' => $images, + 'images_count' => count($images), + ]); + + // 上传图片到WordPress媒体库 + $uploadedImages = []; // 存储完整信息 ['id' => xx, 'url' => xx, ...] + $uploadedImageIds = []; // 仅存储ID + + foreach ($images as $index => $imageUrl) { + if (!empty($imageUrl)) { + try { + Log::info('正在上传图片', ['index' => $index, 'url' => $imageUrl]); + $uploadedImg = $this->uploadImageToWp($domain, $site->api_user, $site->api_secret, $imageUrl); + $uploadedImages[] = [ + 'id' => $uploadedImg['id'], + 'url' => $uploadedImg['source_url'] ?? $uploadedImg['url'] ?? '', + ]; + $uploadedImageIds[] = $uploadedImg['id']; + Log::info('图片上传成功', ['index' => $index, 'media_id' => $uploadedImg['id'], 'url' => $uploadedImages[count($uploadedImages)-1]['url']]); + } catch (\Exception $e) { + Log::error('图片上传失败', [ + 'index' => $index, + 'url' => $imageUrl, + 'error' => $e->getMessage(), + ]); + } + } + } + + Log::info('图片上传完成', [ + 'total' => count($images), + 'success' => count($uploadedImages), + 'uploaded_ids' => $uploadedImageIds, + 'uploaded_details' => $uploadedImages, + ]); + + // 获取FAQ数据 + $faqs = \app\common\model\UsedCarFaq::where('car_id', $carId) + ->where('status', 1) + ->orderBy('sort') + ->get(); + + // 翻译标题 + $title = $this->translateIfNeeded($car->product_name, '英文'); + + // 构建Gutenberg Blocks格式content + $content = $this->buildGutenbergContent($car, $uploadedImages, $faqs); + + // 构建产品数据 + $data = [ + 'title' => $title, + 'content' => $content, + 'status' => 'publish', + 'type' => 'simple', + 'regular_price' => number_format($car->price * $site->exchange_rate, 2), + ]; + + // 设置主图和车辆相册(ACF字段) + if (!empty($uploadedImages)) { + $allImageIds = array_map(function($img) { + return $img['id']; + }, $uploadedImages); + + $data['featured_media'] = $uploadedImages[0]['id']; + + // 车辆相册:前5张图片 + $productGalleryImages = array_slice($uploadedImages, 0, 5); + $productGalleryIds = array_map(function($img) { + return $img['id']; + }, $productGalleryImages); + $data['acf']['product_gallery'] = $productGalleryIds; + + Log::info('设置ACF车辆相册', [ + 'featured_media' => $data['featured_media'], + 'total_images' => count($uploadedImages), + 'product_gallery_count' => count($data['acf']['product_gallery']), + 'product_gallery_ids' => $data['acf']['product_gallery'], + ]); + } + + // 设置ACF车辆参数字段(与旧站点保持一致) + $fieldMap = [ + 'plate_time' => '上牌时间', + 'displacement' => '排量', + 'mileage' => '里程', + 'power' => '马力', + 'body_color' => '车身颜色', + 'source_place' => '车源地', + 'price' => '价格', + ]; + + foreach ($fieldMap as $acfKey => $label) { + if (!empty($car->$acfKey)) { + $value = $car->$acfKey; + // 翻译颜色和车源地 + if ($acfKey === 'body_color') { + $value = $this->translateColorToEnglish($value); + } elseif ($acfKey === 'source_place' && $this->containsChinese($value)) { + $value = $this->translateIfNeeded($value, '英文'); + } + $data['acf'][$acfKey] = $value; + } + } + + // 翻译并设置车况介绍和产品简介(ACF字段) + $conditionDesc = $car->condition_desc ?? ''; + if (!empty($conditionDesc)) { + // 产品简介(完整翻译) + $data['acf']['condition_desc'] = $this->translateIfNeeded($conditionDesc, '英文'); + + // 车况介绍(简短翻译,去掉商家信息) + $cleanDesc = preg_replace('/[^\x{4e00}-\x{9fff}\w\s.,!?()-]/u', ' ', $conditionDesc); + $vehicleDesc = $this->translateIfNeeded($cleanDesc, '英文'); + $data['acf']['pro-intro-des'] = $vehicleDesc; + } + + Log::info('设置ACF参数字段', [ + 'acf_fields' => array_keys($data['acf'] ?? []), + 'acf_values' => $data['acf'] ?? [], + ]); + + try { + Log::info('准备推送产品到WordPress', [ + 'car_id' => $carId, + 'featured_media' => $data['featured_media'] ?? 'not set', + 'acf_product_gallery_count' => count($data['acf']['product_gallery'] ?? []), + 'acf_fields_count' => count($data['acf'] ?? []), + 'content_length' => strlen($data['content'] ?? ''), + ]); + + $response = $client->post($url, [ + RequestOptions::AUTH => [$site->api_user, $site->api_secret], + RequestOptions::JSON => $data, + RequestOptions::VERIFY => false, + ]); + + $result = json_decode($response->getBody()->getContents(), true); + + // 记录推送成功 + if ($wpUsed) { + $wpUsed->update([ + 'wp_post_id' => $result['id'], + 'request_payload' => json_encode($data, JSON_UNESCAPED_UNICODE), + 'response_body' => $response->getBody()->getContents(), + 'status' => 1, + 'retry_count' => $wpUsed->retry_count + 1, + 'pushed_at' => time(), + ]); + } else { + WordpressUsedCar::create([ + 'wp_site_id' => $site->id, + 'used_car_id' => $carId, + 'wp_post_id' => $result['id'], + 'request_payload' => json_encode($data, JSON_UNESCAPED_UNICODE), + 'response_body' => $response->getBody()->getContents(), + 'status' => 1, + 'retry_count' => 1, + 'pushed_at' => time(), + ]); + } + + $site->last_uid = $carId; + $site->save(); + + return array_merge($data, ['result' => $result]); + + } catch (\Exception $e) { + // 记录推送失败 + if ($wpUsed) { + $wpUsed->update([ + 'retry_count' => $wpUsed->retry_count + 1, + 'error_message' => $e->getMessage(), + ]); + if ($wpUsed->retry_count >= 3) { + $site->last_uid = $carId; + $site->save(); + } + } else { + WordpressUsedCar::create([ + 'wp_site_id' => $site->id, + 'used_car_id' => $carId, + 'request_payload' => json_encode($data, JSON_UNESCAPED_UNICODE), + 'error_message' => $e->getMessage(), + 'status' => 0, + 'retry_count' => 1, + ]); + } + throw new \Exception('WordPress推送失败:' . $e->getMessage()); + } + } + + /** + * 构建 Gutenberg Blocks 格式 content + */ + private function buildGutenbergContent($car, array $uploadedImages, $faqs): string + { + $content = ''; + + // 1. 车辆相册标题 + $content .= "\n"; + $content .= "

Vehicle Image

\n"; + $content .= "\n\n"; + + // 2. 图库 block(使用第6张开始的图片,不足8张时重复使用前5张) + $content .= "\n"; + $content .= "\n"; + $content .= "\n\n"; + + // 3. Description 标题和段落 + $content .= "\n"; + $content .= "

Description

\n"; + $content .= "\n\n"; + + // 翻译并分段处理 condition_desc + $conditionDesc = $this->translateIfNeeded($car->condition_desc ?? '', '英文'); + $paragraphs = preg_split('/\n\n+/', trim($conditionDesc)); + foreach ($paragraphs as $para) { + $para = trim($para); + if (!empty($para)) { + $content .= "\n"; + $content .= "

" . htmlspecialchars($para) . "

\n"; + $content .= "\n\n"; + } + } + + // 4. Parameters xz-tab block + $tabData = $this->buildXzTabData($car); + $content .= "\n\n"; + + // 5. FAQ + if ($faqs->count() > 0) { + $content .= "\n"; + $content .= "

FAQ

\n"; + $content .= "\n\n"; + + $faqQuestions = []; + foreach ($faqs as $index => $faq) { + $question = $this->translateIfNeeded($faq->question, '英文'); + $answer = $this->translateIfNeeded($faq->answer, '英文'); + + $faqQuestions[] = [ + 'id' => 'faq-question-' . $car->id . '-' . $index, + 'question' => [$question], + 'answer' => [$answer], + 'jsonQuestion' => $question, + 'jsonAnswer' => $answer, + ]; + } + + $faqData = ['questions' => $faqQuestions]; + $content .= "\n"; + $content .= "
"; + foreach ($faqQuestions as $fq) { + $content .= "
"; + $content .= "" . htmlspecialchars($fq['question'][0]) . " "; + $content .= "

" . htmlspecialchars($fq['answer'][0]) . "

"; + $content .= "
"; + } + $content .= "
\n"; + $content .= "\n\n"; + } + + return $content; + } + + /** + * 构建 xz-tab 数据结构 + */ + private function buildXzTabData($car): array + { + // Tab 标题 + $tabTitles = [ + 'Basic Info', + 'Body', + 'Transmission', + 'Chassis & Steering', + 'Active Safety', + ]; + + // Tab 内容数据 + $tabContents = [ + // Tab 1: 基本信息 + $this->buildTabTable([ + ['label' => 'Official Guide Price', 'value' => $car->official_guide_price], + ['label' => 'Manufacturer', 'value' => $car->manufacturer], + ['label' => 'Level', 'value' => $car->level], + ['label' => 'Energy Type', 'value' => $car->energy_type], + ['label' => 'Listing Time', 'value' => $car->listing_time], + ['label' => 'Engine', 'value' => $car->engine], + ['label' => 'Electric Motor', 'value' => $car->electric_motor], + ['label' => 'NEDC Range (Km)', 'value' => $car->nedc_range], + ['label' => 'CLTC Range (Km)', 'value' => $car->cltc_range], + ['label' => 'Fast Charge Platform', 'value' => $car->fast_charge_platform], + ['label' => 'Charging Time (Hours)', 'value' => $car->charging_time], + ['label' => 'Fast Charge Percent (%)', 'value' => $car->fast_charge_percent], + ['label' => 'Max Power (Kw)', 'value' => $car->max_power_kw], + ['label' => 'Max Torque (N·m)', 'value' => $car->max_torque_nm], + ['label' => 'Transmission', 'value' => $car->transmission_desc], + ['label' => 'Dimensions (Mm)', 'value' => $car->dimensions], + ['label' => 'Body Structure', 'value' => $car->body_structure], + ['label' => 'Max Speed (Km/H)', 'value' => $car->max_speed], + ['label' => 'Acceleration 100 (S)', 'value' => $car->acceleration_100], + ['label' => 'NEDC Fuel Consumption', 'value' => $car->nedc_fuel_consumption], + ['label' => 'Power Consumption 100', 'value' => $car->power_consumption_100], + ['label' => 'Equivalent Fuel Consumption', 'value' => $car->equivalent_fuel_consumption], + ]), + // Tab 2: 车身 + $this->buildTabTable([ + ['label' => 'Body Length (Mm)', 'value' => $car->body_length], + ['label' => 'Body Width (Mm)', 'value' => $car->body_width], + ['label' => 'Body Height (Mm)', 'value' => $car->body_height], + ['label' => 'Wheelbase (Mm)', 'value' => $car->wheelbase], + ['label' => 'Body Structure', 'value' => $car->body_structure], + ['label' => 'Door Count', 'value' => $car->door_count], + ['label' => 'Door Open Type', 'value' => $car->door_open_type], + ['label' => 'Seat Count', 'value' => $car->seat_count], + ]), + // Tab 3: 变速箱 + $this->buildTabTable([ + ['label' => 'Transmission Description', 'value' => $car->transmission_desc], + ['label' => 'Gear Count', 'value' => $car->gear_count], + ['label' => 'Transmission Type', 'value' => $car->transmission_type], + ]), + // Tab 4: 底盘/转向 + $this->buildTabTable([ + ['label' => 'Drive Mode', 'value' => $car->drive_mode], + ['label' => 'Front Suspension', 'value' => $car->front_suspension], + ['label' => 'Rear Suspension', 'value' => $car->rear_suspension], + ['label' => 'Steering Type', 'value' => $car->steering_type], + ]), + // Tab 5: 主动安全 + $this->buildTabTable([ + ['label' => 'Has ABS', 'value' => $car->has_abs], + ['label' => 'Has EBD', 'value' => $car->has_ebd], + ['label' => 'Has BRA', 'value' => $car->has_bra], + ['label' => 'Has TCS', 'value' => $car->has_tcs], + ['label' => 'Has ESP', 'value' => $car->has_esp], + ['label' => 'Has Active Brake', 'value' => $car->has_active_brake], + ['label' => 'Has Lane Assist', 'value' => $car->has_lane_assist], + ['label' => 'Has LKA', 'value' => $car->has_lka], + ['label' => 'Has Lane Centering', 'value' => $car->has_lane_centering], + ['label' => 'Has Fatigue Remind', 'value' => $car->has_fatigue_remind], + ]), + ]; + + $tabCount = 5; + $data = [ + 'name' => 'acf/xz-tab', + 'data' => [], + 'mode' => 'edit', + ]; + + for ($i = 0; $i < $tabCount; $i++) { + $data['data']["xz-tabs_{$i}_tab_title"] = $tabTitles[$i] ?? "Tab {$i}"; + $data['data']["_xz-tabs_{$i}_tab_title"] = "field_69ae438a9a253"; + $data['data']["xz-tabs_{$i}_tab_content"] = $tabContents[$i] . ' '; + $data['data']["_xz-tabs_{$i}_tab_content"] = "field_69ae43959a254"; + } + + $data['data']['xz-tabs'] = $tabCount; + $data['data']['_xz-tabs'] = "field_69ae420f515f6"; + + return $data; + } + + /** + * 构建参数表格 HTML + */ + private function buildTabTable(array $rows): string + { + $html = '' . "\n"; + $html .= '' . "\n"; + + $translatedCount = 0; + + foreach ($rows as $row) { + $value = $row['value'] ?? ''; + if (empty($value) && $value !== '0') { + continue; + } + + $originalValue = $value; + // 翻译中文值 + if (is_string($value) && $this->containsChinese($value)) { + $value = $this->translateIfNeeded($value, '英文'); + $translatedCount++; + Log::info('翻译参数表格字段值', [ + 'label' => $row['label'], + 'original' => $originalValue, + 'translated' => $value, + ]); + } + + $html .= '' . "\n"; + $html .= '' . "\n"; + $html .= '' . "\n"; + $html .= '' . "\n"; + } + + $html .= '' . "\n"; + $html .= '
' . htmlspecialchars($row['label']) . '' . htmlspecialchars($value) . '
' . "\n"; + + if ($translatedCount > 0) { + Log::info('参数表格翻译完成', ['translated_count' => $translatedCount]); + } + + return $html; + } + + /** + * 翻译文本(如果包含中文) + */ + private function translateIfNeeded(string $text, string $targetLang = '英文'): string + { + if (empty($text)) { + return ''; + } + + // 检查是否包含中文 + if (!$this->containsChinese($text)) { + return $text; + } + + try { + $spark = new SparkService(); + $prompt = '将以下文本翻译为' . $targetLang . ':' . $text; + return $spark->chat($prompt, null, [], 'wpSend'); + } catch (\Throwable $e) { + Log::warning('翻译失败,使用原文本', ['text' => $text, 'error' => $e->getMessage()]); + return $text; + } + } + + /** + * 检测文本是否包含中文 + */ + private function containsChinese(string $text): bool + { + return preg_match('/[\x{4e00}-\x{9fff}]/u', $text) > 0; + } + } \ No newline at end of file