推送
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 .= "<!-- wp:heading -->\n";
|
||||
$content .= "<h2 class=\"wp-block-heading\" id=\"vehicle-image\">Vehicle Image</h2>\n";
|
||||
$content .= "<!-- /wp:heading -->\n\n";
|
||||
|
||||
// 2. 图库 block(使用第6张开始的图片,不足8张时重复使用前5张)
|
||||
$content .= "<!-- wp:gallery {\"columns\":4,\"linkTo\":\"none\"} -->\n";
|
||||
$content .= "<figure class=\"wp-block-gallery has-nested-images columns-4 is-cropped\">\n";
|
||||
|
||||
$galleryImages = array_slice($uploadedImages, 5); // 第6张开始
|
||||
$firstFiveImages = array_slice($uploadedImages, 0, 5); // 前5张
|
||||
|
||||
Log::info('构建图库block', [
|
||||
'total_images' => count($uploadedImages),
|
||||
'gallery_start_from' => 6,
|
||||
'gallery_before_fill' => count($galleryImages),
|
||||
'first_five_count' => count($firstFiveImages),
|
||||
'gallery_ids_before' => array_column($galleryImages, 'id'),
|
||||
]);
|
||||
|
||||
// 不足8张时,重复使用前5张图片
|
||||
if (count($galleryImages) > 0 && count($galleryImages) < 8) {
|
||||
$repeatSource = $firstFiveImages; // 重复使用前5张
|
||||
while (count($galleryImages) < 8) {
|
||||
$galleryImages = array_merge($galleryImages, $repeatSource);
|
||||
}
|
||||
$galleryImages = array_slice($galleryImages, 0, 8);
|
||||
}
|
||||
|
||||
Log::info('图库图片准备完成', [
|
||||
'final_count' => count($galleryImages),
|
||||
'image_ids' => array_column($galleryImages, 'id'),
|
||||
]);
|
||||
|
||||
foreach ($galleryImages as $img) {
|
||||
$imgId = $img['id'];
|
||||
$imgUrl = $img['url'] ?? '';
|
||||
// 构建 Gutenberg image block,包含完整的 URL
|
||||
$blockAttrs = json_encode([
|
||||
"id" => $imgId,
|
||||
"sizeSlug" => "large",
|
||||
"linkDestination" => "none",
|
||||
"url" => $imgUrl,
|
||||
], JSON_UNESCAPED_SLASHES);
|
||||
$content .= "<!-- wp:image {$blockAttrs} -->\n";
|
||||
$content .= "<figure class=\"wp-block-image size-large\"><img src=\"{$imgUrl}\" alt=\"\" class=\"wp-image-{$imgId}\"/></figure>\n";
|
||||
$content .= "<!-- /wp:image -->\n";
|
||||
}
|
||||
|
||||
$content .= "</figure>\n";
|
||||
$content .= "<!-- /wp:gallery -->\n\n";
|
||||
|
||||
// 3. Description 标题和段落
|
||||
$content .= "<!-- wp:heading -->\n";
|
||||
$content .= "<h2 class=\"wp-block-heading\" id=\"Description\">Description</h2>\n";
|
||||
$content .= "<!-- /wp:heading -->\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 .= "<!-- wp:paragraph -->\n";
|
||||
$content .= "<p>" . htmlspecialchars($para) . "</p>\n";
|
||||
$content .= "<!-- /wp:paragraph -->\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Parameters xz-tab block
|
||||
$tabData = $this->buildXzTabData($car);
|
||||
$content .= "<!-- wp:acf/xz-tab " . json_encode($tabData, JSON_UNESCAPED_UNICODE) . " /-->\n\n";
|
||||
|
||||
// 5. FAQ
|
||||
if ($faqs->count() > 0) {
|
||||
$content .= "<!-- wp:heading -->\n";
|
||||
$content .= "<h2 class=\"wp-block-heading\" id=\"FAQ\">FAQ</h2>\n";
|
||||
$content .= "<!-- /wp:heading -->\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 .= "<!-- wp:yoast/faq-block " . json_encode($faqData, JSON_UNESCAPED_UNICODE) . " -->\n";
|
||||
$content .= "<div class=\"schema-faq wp-block-yoast-faq-block\">";
|
||||
foreach ($faqQuestions as $fq) {
|
||||
$content .= "<div class=\"schema-faq-section\" id=\"" . $fq['id'] . "\">";
|
||||
$content .= "<strong class=\"schema-faq-question\">" . htmlspecialchars($fq['question'][0]) . "</strong> ";
|
||||
$content .= "<p class=\"schema-faq-answer\">" . htmlspecialchars($fq['answer'][0]) . "</p> ";
|
||||
$content .= "</div> ";
|
||||
}
|
||||
$content .= "</div>\n";
|
||||
$content .= "<!-- /wp:yoast/faq-block -->\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 = '<table class="spec-table">' . "\n";
|
||||
$html .= '<tbody>' . "\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 .= '<tr>' . "\n";
|
||||
$html .= '<td class="spec-name">' . htmlspecialchars($row['label']) . '</td>' . "\n";
|
||||
$html .= '<td>' . htmlspecialchars($value) . '</td>' . "\n";
|
||||
$html .= '</tr>' . "\n";
|
||||
}
|
||||
|
||||
$html .= '</tbody>' . "\n";
|
||||
$html .= '</table>' . "\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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user