feat(goods): 增加微信小程序审核专用真实3C产品与分类线上生成及验证脚本
这个提交包含在:
@@ -0,0 +1,488 @@
|
||||
<?php
|
||||
/**
|
||||
* 线上生成真实 3C 产品与分类维护脚本 (用于微信小程序上架审核)
|
||||
*
|
||||
* 包含:
|
||||
* 1. 4 个真实的 3C 数码分类 (手机数码、智能影音、智能穿戴、电脑办公)
|
||||
* 2. 8 款真实主流知名品牌 3C 商品 (倍思、漫步者、罗马仕、华为、小米、罗技、绿联、品胜)
|
||||
* 3. 关联更新单规格 SKU 价格、库存与服务保障
|
||||
* 4. 联动更新装修模板 5 (3C数码模板) 首页推荐商品与搜索热词
|
||||
*/
|
||||
|
||||
// 载入 ThinkPHP 框架基础
|
||||
define('APP_PATH', __DIR__ . '/../application/');
|
||||
define('ROOT_PATH', dirname(realpath(APP_PATH)) . '/');
|
||||
require_once __DIR__ . '/../thinkphp/base.php';
|
||||
require_once __DIR__ . '/../thinkphp/helper.php';
|
||||
require_once __DIR__ . '/../application/common.php';
|
||||
|
||||
if (is_file(ROOT_PATH . '.env')) {
|
||||
$env = parse_ini_file(ROOT_PATH . '.env', true);
|
||||
foreach ($env as $key => $val) {
|
||||
$name = 'PHP_' . strtoupper($key);
|
||||
if (is_array($val)) {
|
||||
foreach ($val as $k => $v) {
|
||||
putenv("{$name}_" . strtoupper($k) . "={$v}");
|
||||
}
|
||||
} else {
|
||||
putenv("{$name}={$val}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
\think\Config::set(\think\Config::load(APP_PATH . 'config.php'));
|
||||
$dbConfig = [
|
||||
'type' => 'mysql',
|
||||
'hostname' => '127.0.0.1',
|
||||
'database' => 'henglianshutong',
|
||||
'username' => 'henglianshutong',
|
||||
'password' => 'henglianshutong',
|
||||
'hostport' => '3306',
|
||||
'prefix' => 'hla_',
|
||||
];
|
||||
\think\Config::set('database', $dbConfig);
|
||||
|
||||
use think\Db;
|
||||
|
||||
class Create3CProducts
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
echo "====================================================\n";
|
||||
echo "开始执行 3C 分类与真实商品线上数据生成...\n";
|
||||
echo "====================================================\n\n";
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$now = time();
|
||||
|
||||
// 1. 确认父分类 153 (恒链甄选)
|
||||
$parentCategory = Db::name('shopro_category')->where('id', 153)->find();
|
||||
if (!$parentCategory) {
|
||||
throw new \Exception("未找到主分类 ID: 153");
|
||||
}
|
||||
echo "[1] 主分类校验成功: ID 153 ({$parentCategory['name']})\n";
|
||||
|
||||
// 调整原酒水分类权重,让 3C 分类排在最前
|
||||
Db::name('shopro_category')->where('id', 154)->update(['weigh' => 50, 'updatetime' => $now]);
|
||||
|
||||
// 2. 创建/更新 4 个 3C 二级分类
|
||||
$categoriesDef = [
|
||||
'mobile' => [
|
||||
'name' => '手机数码',
|
||||
'parent_id' => 153,
|
||||
'style' => null,
|
||||
'image' => '/uploads/henglian_goods/3c/categories/cat_mobile.png',
|
||||
'description' => '精选智能手机、高能移动电源、高速快充及数据传输配件',
|
||||
'status' => 'normal',
|
||||
'weigh' => 110
|
||||
],
|
||||
'audio' => [
|
||||
'name' => '智能影音',
|
||||
'parent_id' => 153,
|
||||
'style' => null,
|
||||
'image' => '/uploads/henglian_goods/3c/categories/cat_audio.png',
|
||||
'description' => '真无线蓝牙耳机、头戴降噪耳机与高保真便携音箱',
|
||||
'status' => 'normal',
|
||||
'weigh' => 109
|
||||
],
|
||||
'wearable' => [
|
||||
'name' => '智能穿戴',
|
||||
'parent_id' => 153,
|
||||
'style' => null,
|
||||
'image' => '/uploads/henglian_goods/3c/categories/cat_wearable.png',
|
||||
'description' => '智能运动健康手环、全天候心率血氧监测智能通话手表',
|
||||
'status' => 'normal',
|
||||
'weigh' => 108
|
||||
],
|
||||
'computer' => [
|
||||
'name' => '电脑办公',
|
||||
'parent_id' => 153,
|
||||
'style' => null,
|
||||
'image' => '/uploads/henglian_goods/3c/categories/cat_computer.png',
|
||||
'description' => '人体工学静音无线鼠标、多功能扩展坞与数码办公外设',
|
||||
'status' => 'normal',
|
||||
'weigh' => 107
|
||||
]
|
||||
];
|
||||
|
||||
$categoryMap = [];
|
||||
foreach ($categoriesDef as $key => $cat) {
|
||||
$existCat = Db::name('shopro_category')
|
||||
->where('parent_id', 153)
|
||||
->where('name', $cat['name'])
|
||||
->find();
|
||||
if ($existCat) {
|
||||
Db::name('shopro_category')->where('id', $existCat['id'])->update([
|
||||
'image' => $cat['image'],
|
||||
'description' => $cat['description'],
|
||||
'status' => 'normal',
|
||||
'weigh' => $cat['weigh'],
|
||||
'updatetime' => $now
|
||||
]);
|
||||
$catId = $existCat['id'];
|
||||
echo " - 已更新分类: {$cat['name']} (ID: {$catId})\n";
|
||||
} else {
|
||||
$catId = Db::name('shopro_category')->insertGetId(array_merge($cat, [
|
||||
'createtime' => $now,
|
||||
'updatetime' => $now
|
||||
]));
|
||||
echo " + 新增分类: {$cat['name']} (ID: {$catId})\n";
|
||||
}
|
||||
$categoryMap[$key] = $catId;
|
||||
}
|
||||
|
||||
// 3. 准备 8 款真实 3C 商品
|
||||
$products = [
|
||||
[
|
||||
'num' => 1,
|
||||
'title' => '倍思 65W GaN氮化镓多口快充充电器',
|
||||
'subtitle' => 'GaN5 Pro 氮化镓快充 / 2C1A多口盲插 / 智能温控不烫机',
|
||||
'cat_key' => 'mobile',
|
||||
'sn' => '3C-BASEUS-GAN65',
|
||||
'weight' => 0.25,
|
||||
'original_price' => 149.00,
|
||||
'price' => 119.00,
|
||||
'cost_price' => 68.00,
|
||||
'params' => [
|
||||
["title" => "品牌", "content" => "倍思 (Baseus)"],
|
||||
["title" => "型号", "content" => "CCGAN65S5"],
|
||||
["title" => "接口类型", "content" => "Type-C1 + Type-C2 + USB-A"],
|
||||
["title" => "最大输出功率", "content" => "65W Max"],
|
||||
["title" => "输入规格", "content" => "AC 100-240V~, 50/60Hz, 1.5A Max"],
|
||||
["title" => "适用机型", "content" => "iPhone / 华为 / 小米 / MacBook / 笔记本电脑"],
|
||||
["title" => "质保期限", "content" => "12个月官方全国联保"]
|
||||
]
|
||||
],
|
||||
[
|
||||
'num' => 2,
|
||||
'title' => '漫步者 LolliPods 真无线立体声蓝牙耳机',
|
||||
'subtitle' => '半入耳舒适佩戴 / AptX高清音频解码 / CVC双麦通话降噪 / 24小时综合续航',
|
||||
'cat_key' => 'audio',
|
||||
'sn' => '3C-EDIFIER-LP2026',
|
||||
'weight' => 0.18,
|
||||
'original_price' => 199.00,
|
||||
'price' => 149.00,
|
||||
'cost_price' => 88.00,
|
||||
'params' => [
|
||||
["title" => "品牌", "content" => "漫步者 (EDIFIER)"],
|
||||
["title" => "型号", "content" => "LolliPods 2026款"],
|
||||
["title" => "佩戴方式", "content" => "半入耳式"],
|
||||
["title" => "蓝牙版本", "content" => "Bluetooth 5.3"],
|
||||
["title" => "发声单元", "content" => "13mm大尺寸高解析复合振膜"],
|
||||
["title" => "单次续航", "content" => "约 6 小时"],
|
||||
["title" => "配合充电盒", "content" => "约 24 小时长续航"],
|
||||
["title" => "防尘防水", "content" => "IP54级专业防护"]
|
||||
]
|
||||
],
|
||||
[
|
||||
'num' => 3,
|
||||
'title' => '罗马仕 20000mAh 22.5W超级快充移动电源',
|
||||
'subtitle' => '智能LED数显大容量充电宝 / 支持SCP/PD3.0快充 / 三入三出全兼容',
|
||||
'cat_key' => 'mobile',
|
||||
'sn' => '3C-ROMOSS-SW20',
|
||||
'weight' => 0.46,
|
||||
'original_price' => 129.00,
|
||||
'price' => 89.00,
|
||||
'cost_price' => 52.00,
|
||||
'params' => [
|
||||
["title" => "品牌", "content" => "罗马仕 (ROMOSS)"],
|
||||
["title" => "型号", "content" => "SW20 Pro"],
|
||||
["title" => "电池容量", "content" => "20000mAh / 74Wh (符合民航登机要求)"],
|
||||
["title" => "额定容量", "content" => "12000mAh (5V/3A)"],
|
||||
["title" => "输入接口", "content" => "Type-C 18W / Micro-USB 18W / Lightning"],
|
||||
["title" => "输出接口", "content" => "Type-C 20W / USB-A1 22.5W / USB-A2 22.5W"],
|
||||
["title" => "电量显示", "content" => "LED精准百分比数字屏幕"]
|
||||
]
|
||||
],
|
||||
[
|
||||
'num' => 4,
|
||||
'title' => '华为手环 8 智能运动健康手环 曜石黑',
|
||||
'subtitle' => '8.99mm超薄机身 / 1.47英寸无界大屏 / TruSleep 3.0科学睡眠 / 14天长续航',
|
||||
'cat_key' => 'wearable',
|
||||
'sn' => '3C-HUAWEI-BAND8',
|
||||
'weight' => 0.12,
|
||||
'original_price' => 269.00,
|
||||
'price' => 199.00,
|
||||
'cost_price' => 135.00,
|
||||
'params' => [
|
||||
["title" => "品牌", "content" => "华为 (HUAWEI)"],
|
||||
["title" => "型号", "content" => "ASK-B19"],
|
||||
["title" => "机身厚度", "content" => "8.99 mm (超薄机身设计)"],
|
||||
["title" => "屏幕规格", "content" => "1.47英寸 AMOLED炫彩全面屏"],
|
||||
["title" => "健康监测", "content" => "全天候血氧心率监测 / 睡眠管理 / 压力检测"],
|
||||
["title" => "防水等级", "content" => "5ATM专业防水 (支持游泳)"],
|
||||
["title" => "续航能力", "content" => "最长可达14天超长续航"]
|
||||
]
|
||||
],
|
||||
[
|
||||
'num' => 5,
|
||||
'title' => '小米Redmi Watch 3 青春版 智能蓝牙通话手表',
|
||||
'subtitle' => '1.83英寸超大高清屏 / 蓝牙高清语音通话 / 12天持久续航 / 200+个性表盘',
|
||||
'cat_key' => 'wearable',
|
||||
'sn' => '3C-REDMI-WATCH3',
|
||||
'weight' => 0.15,
|
||||
'original_price' => 299.00,
|
||||
'price' => 229.00,
|
||||
'cost_price' => 150.00,
|
||||
'params' => [
|
||||
["title" => "品牌", "content" => "小米 (Redmi)"],
|
||||
["title" => "型号", "content" => "M2247W1"],
|
||||
["title" => "屏幕尺寸", "content" => "1.83英寸高保真触控彩屏"],
|
||||
["title" => "通话功能", "content" => "支持蓝牙双向免提高清语音通话"],
|
||||
["title" => "运动模式", "content" => "100+种专业运动健康监测模式"],
|
||||
["title" => "防水等级", "content" => "5ATM专业防水"],
|
||||
["title" => "典型续航", "content" => "典型使用12天 / 重度使用8天"]
|
||||
]
|
||||
],
|
||||
[
|
||||
'num' => 6,
|
||||
'title' => '罗技 M330 降噪静音无线鼠标 曜石黑',
|
||||
'subtitle' => '90%降噪静音微动 / 人体工学舒适右手握感 / 2.4G即插即用 / 24个月超长续航',
|
||||
'cat_key' => 'computer',
|
||||
'sn' => '3C-LOGI-M330',
|
||||
'weight' => 0.16,
|
||||
'original_price' => 119.00,
|
||||
'price' => 89.00,
|
||||
'cost_price' => 55.00,
|
||||
'params' => [
|
||||
["title" => "品牌", "content" => "罗技 (Logitech)"],
|
||||
["title" => "型号", "content" => "M330 Silent Plus"],
|
||||
["title" => "连接方式", "content" => "2.4GHz 无线USB微型接收器 (10米有效)"],
|
||||
["title" => "传感器", "content" => "1000 DPI 高精度先进光学追踪"],
|
||||
["title" => "降噪技术", "content" => "静音微动技术,减少90%以上点击噪音"],
|
||||
["title" => "电池寿命", "content" => "1节AA五号电池,最长可达24个月续航"]
|
||||
]
|
||||
],
|
||||
[
|
||||
'num' => 7,
|
||||
'title' => '绿联 六合一Type-C多功能扩展坞',
|
||||
'subtitle' => '4K@30Hz HDMI高清投屏 / PD 100W大功率闪充 / USB3.0*3 5Gbps高速传输',
|
||||
'cat_key' => 'computer',
|
||||
'sn' => '3C-UGREEN-HUB6',
|
||||
'weight' => 0.22,
|
||||
'original_price' => 159.00,
|
||||
'price' => 119.00,
|
||||
'cost_price' => 72.00,
|
||||
'params' => [
|
||||
["title" => "品牌", "content" => "绿联 (UGREEN)"],
|
||||
["title" => "型号", "content" => "CM475"],
|
||||
["title" => "端口扩展", "content" => "HDMI 4K + USB 3.0*3 + PD 100W供电 + SD/TF槽"],
|
||||
["title" => "材质工艺", "content" => "深空灰阳极氧化铝合金一体散热机身"],
|
||||
["title" => "线身材质", "content" => "15cm 高密抗折弯编织线身"],
|
||||
["title" => "兼容平台", "content" => "MacBook / iPad / 华为 / 联想 / 戴尔等全功能Type-C"]
|
||||
]
|
||||
],
|
||||
[
|
||||
'num' => 8,
|
||||
'title' => '品胜 苹果PD 20W快充编织数据线 1.2米',
|
||||
'subtitle' => '智能低温快充芯片 / 加粗镀锡铜芯 / 强韧高密度尼龙编织线身 / 充放提速60%',
|
||||
'cat_key' => 'mobile',
|
||||
'sn' => '3C-PISEN-CABLE20',
|
||||
'weight' => 0.08,
|
||||
'original_price' => 49.00,
|
||||
'price' => 29.00,
|
||||
'cost_price' => 12.00,
|
||||
'params' => [
|
||||
["title" => "品牌", "content" => "品胜 (PISEN)"],
|
||||
["title" => "接口类型", "content" => "Type-C 转 Lightning / C to C"],
|
||||
["title" => "支持功率", "content" => "最高支持 PD 20W / 27W 闪电快充"],
|
||||
["title" => "传输速率", "content" => "USB 2.0 480Mbps 稳定数据传输"],
|
||||
["title" => "线长规格", "content" => "1.2 米 (黄金便携使用长度)"],
|
||||
["title" => "适用机型", "content" => "iPhone 8-15 全系 / iPad / AirPods 系列"]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$createdGoodsIds = [];
|
||||
|
||||
foreach ($products as $idx => $item) {
|
||||
$i = $item['num'];
|
||||
$catId = $categoryMap[$item['cat_key']];
|
||||
$categoryIds = "153,{$catId}";
|
||||
|
||||
$mainImg = "/uploads/henglian_goods/3c/p_{$i}/cover.jpg";
|
||||
$banners = [
|
||||
"/uploads/henglian_goods/3c/p_{$i}/banner_1.jpg",
|
||||
"/uploads/henglian_goods/3c/p_{$i}/banner_2.jpg",
|
||||
"/uploads/henglian_goods/3c/p_{$i}/banner_3.jpg"
|
||||
];
|
||||
|
||||
$contentHtml = <<<HTML
|
||||
<div style="padding:10px 0;text-align:center;">
|
||||
<p style="font-size:15px;color:#1e293b;font-weight:bold;margin:8px 0;">{$item['title']}</p>
|
||||
<p style="font-size:12px;color:#64748b;margin-bottom:14px;">{$item['subtitle']}</p>
|
||||
<p><img src="/uploads/henglian_goods/3c/p_{$i}/detail_1.jpg" style="width:100%;max-width:750px;display:block;margin:0 auto 10px auto;border-radius:8px;" /></p>
|
||||
<p><img src="/uploads/henglian_goods/3c/p_{$i}/detail_2.jpg" style="width:100%;max-width:750px;display:block;margin:0 auto 10px auto;border-radius:8px;" /></p>
|
||||
<p><img src="/uploads/henglian_goods/3c/p_{$i}/detail_3.jpg" style="width:100%;max-width:750px;display:block;margin:0 auto 10px auto;border-radius:8px;" /></p>
|
||||
<div style="background:#f8fafc;padding:16px;border-radius:8px;text-align:left;font-size:13px;color:#475569;line-height:1.8;margin-top:12px;">
|
||||
<p style="font-weight:bold;color:#0f172a;margin-bottom:6px;">【正品保障与官方售后承诺】</p>
|
||||
<p>1. 本品为正品行货,支持全国统一防伪验证,享受原厂保修及技术支持服务;</p>
|
||||
<p>2. 签收之日起支持 7 天无理由退换货(需保持未激活、商品主体与包装完整未损);</p>
|
||||
<p>3. 质量问题 30 天内包换,1 年内享受全国联合保修;</p>
|
||||
<p>4. 每日 16:00 前完成支付的订单,当日顺丰/京东快递速发,全国顺畅直达。</p>
|
||||
</div>
|
||||
</div>
|
||||
HTML;
|
||||
|
||||
$goodsData = [
|
||||
'type' => 'normal',
|
||||
'title' => $item['title'],
|
||||
'subtitle' => $item['subtitle'],
|
||||
'category_ids' => $categoryIds,
|
||||
'image' => $mainImg,
|
||||
'images' => json_encode($banners, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
|
||||
'params' => json_encode($item['params'], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
|
||||
'content' => $contentHtml,
|
||||
'original_price' => $item['original_price'],
|
||||
'price' => $item['price'],
|
||||
'is_sku' => 0,
|
||||
'limit_type' => 'none',
|
||||
'limit_num' => 0,
|
||||
'likes' => 120 + $i * 18,
|
||||
'views' => 1500 + $i * 120,
|
||||
'sales' => 88 + $i * 15,
|
||||
'sales_show_type' => 'exact',
|
||||
'stock_show_type' => 'exact',
|
||||
'show_sales' => 88 + $i * 15,
|
||||
'service_ids' => '1,2,3,4', // 正品保障、7天退换、运费险、极速退款
|
||||
'dispatch_type' => 'express',
|
||||
'dispatch_id' => 1, // 全国包邮
|
||||
'is_offline' => 0,
|
||||
'status' => 'up',
|
||||
'weigh' => 200 - $i,
|
||||
'updatetime' => $now
|
||||
];
|
||||
|
||||
$existGoods = Db::name('shopro_goods')->where('title', $item['title'])->find();
|
||||
if ($existGoods) {
|
||||
Db::name('shopro_goods')->where('id', $existGoods['id'])->update($goodsData);
|
||||
$goodsId = $existGoods['id'];
|
||||
echo "[2] 更新商品: {$item['title']} (ID: {$goodsId})\n";
|
||||
} else {
|
||||
$goodsData['createtime'] = $now;
|
||||
$goodsId = Db::name('shopro_goods')->insertGetId($goodsData);
|
||||
echo "[2] 新增商品: {$item['title']} (ID: {$goodsId})\n";
|
||||
}
|
||||
|
||||
$createdGoodsIds[] = $goodsId;
|
||||
|
||||
// 维护单规格 SKU 价格记录
|
||||
$skuData = [
|
||||
'goods_sku_ids' => null,
|
||||
'goods_sku_text' => null,
|
||||
'goods_id' => $goodsId,
|
||||
'image' => null,
|
||||
'stock' => 1000,
|
||||
'stock_warning' => 20,
|
||||
'sn' => $item['sn'],
|
||||
'weight' => $item['weight'],
|
||||
'cost_price' => $item['cost_price'],
|
||||
'original_price' => $item['original_price'],
|
||||
'price' => $item['price'],
|
||||
'status' => 'up',
|
||||
'weigh' => 0,
|
||||
'updatetime' => $now
|
||||
];
|
||||
|
||||
$existSku = Db::name('shopro_goods_sku_price')->where('goods_id', $goodsId)->find();
|
||||
if ($existSku) {
|
||||
Db::name('shopro_goods_sku_price')->where('id', $existSku['id'])->update($skuData);
|
||||
} else {
|
||||
$skuData['createtime'] = $now;
|
||||
Db::name('shopro_goods_sku_price')->insert($skuData);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 联动更新装修模板 5 (3C数码模板) 首页推荐与搜索关键词
|
||||
$decorPage = Db::name('shopro_decorate_page')
|
||||
->where('decorate_id', 5)
|
||||
->where('type', 'home')
|
||||
->find();
|
||||
|
||||
if ($decorPage) {
|
||||
$pageConfig = json_decode($decorPage['page'], true);
|
||||
if (isset($pageConfig['data']) && is_array($pageConfig['data'])) {
|
||||
foreach ($pageConfig['data'] as &$block) {
|
||||
// 更新搜索框关键词
|
||||
if ($block['type'] === 'searchBlock') {
|
||||
$block['data']['placeholder'] = '搜索 65W氮化镓快充 · 蓝牙耳机 · 智能手表 · 移动电源';
|
||||
$block['data']['keywords'] = ['65W快充', '漫步者耳机', '华为手环', '罗技鼠标', '罗马仕充电宝', '绿联扩展坞'];
|
||||
}
|
||||
// 更新首页轮播图
|
||||
if ($block['type'] === 'imageBanner') {
|
||||
$block['data']['list'] = [
|
||||
[
|
||||
'title' => '倍思 65W 氮化镓快充 · 闪速满电',
|
||||
'type' => 'image',
|
||||
'src' => '/uploads/henglian_goods/3c/banners/banner_home_1.jpg',
|
||||
'poster' => '',
|
||||
'url' => '/pages/goods/index?id=' . $createdGoodsIds[0]
|
||||
],
|
||||
[
|
||||
'title' => '漫步者 LolliPods 真无线 · 纯粹原声',
|
||||
'type' => 'image',
|
||||
'src' => '/uploads/henglian_goods/3c/banners/banner_home_2.jpg',
|
||||
'poster' => '',
|
||||
'url' => '/pages/goods/index?id=' . $createdGoodsIds[1]
|
||||
],
|
||||
[
|
||||
'title' => 'Redmi Watch 3 智能手表 · 运动健康',
|
||||
'type' => 'image',
|
||||
'src' => '/uploads/henglian_goods/3c/banners/banner_home_3.jpg',
|
||||
'poster' => '',
|
||||
'url' => '/pages/goods/index?id=' . $createdGoodsIds[4]
|
||||
]
|
||||
];
|
||||
}
|
||||
// 更新头条通知公告
|
||||
if ($block['type'] === 'noticeBlock') {
|
||||
$block['data']['title']['text'] = '数码狂欢季,全场精选大牌 3C 正品行货,顺丰包邮极速达!';
|
||||
}
|
||||
// 更新专区标题栏
|
||||
if ($block['type'] === 'titleBlock') {
|
||||
$block['data']['title']['text'] = '热门数码甄选';
|
||||
$block['data']['subtitle']['text'] = '精选大牌 · 官方正品';
|
||||
$block['data']['more']['url'] = '/pages/index/category?id=153';
|
||||
}
|
||||
// 更新商品推荐卡片
|
||||
if ($block['type'] === 'goodsCard') {
|
||||
if (isset($block['data']['goodsIds'])) {
|
||||
// 推荐前 2 或剩余 3C 商品
|
||||
if (count($block['data']['goodsIds']) <= 2) {
|
||||
$block['data']['goodsIds'] = array_slice($createdGoodsIds, 0, 2);
|
||||
} else {
|
||||
$block['data']['goodsIds'] = array_slice($createdGoodsIds, 2, 6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($block);
|
||||
|
||||
Db::name('shopro_decorate_page')
|
||||
->where('id', $decorPage['id'])
|
||||
->update([
|
||||
'page' => json_encode($pageConfig, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
|
||||
]);
|
||||
echo "[3] 装修模板 5 (3C数码) 首页推荐商品与搜索关键词已成功联动更新!\n";
|
||||
}
|
||||
}
|
||||
|
||||
Db::commit();
|
||||
echo "\n====================================================\n";
|
||||
echo "3C 分类与商品数据生成成功!事务已提交!\n";
|
||||
echo "本次涉及 3C 分类 ID: " . implode(', ', array_values($categoryMap)) . "\n";
|
||||
echo "本次涉及 3C 商品 ID: " . implode(', ', $createdGoodsIds) . "\n";
|
||||
echo "====================================================\n";
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
echo "\n[ERROR] 执行失败,事务已回滚: " . $e->getMessage() . "\n";
|
||||
echo $e->getTraceAsString() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$task = new Create3CProducts();
|
||||
$task->run();
|
||||
424
scripts/generate_3c_assets.py
普通文件
424
scripts/generate_3c_assets.py
普通文件
@@ -0,0 +1,424 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
生成腾讯小程序审核专用的真实 3C 数码产品素材:
|
||||
包含:
|
||||
1. 4 大类目分类图标
|
||||
2. 8 款主流爆款 3C 商品的主图、轮播图 (3张/款)、详情长图切片 (3张/款)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from PIL import Image, ImageDraw, ImageFont, ImageFilter
|
||||
|
||||
FONT_PATH = "/System/Library/Fonts/STHeiti Light.ttc"
|
||||
if not os.path.exists(FONT_PATH):
|
||||
FONT_PATH = "/System/Library/Fonts/PingFang.ttc"
|
||||
if not os.path.exists(FONT_PATH):
|
||||
FONT_PATH = "/Library/Fonts/Arial Unicode.ttf"
|
||||
|
||||
def get_font(size):
|
||||
try:
|
||||
return ImageFont.truetype(FONT_PATH, size)
|
||||
except Exception:
|
||||
return ImageFont.load_default()
|
||||
|
||||
BASE_DIR = "/Users/chen/Desktop/xbiuld/henglianshutong"
|
||||
SOURCE_IMG_DIR = "/Users/chen/.gemini/antigravity/brain/a8c097d8-307e-4c94-b68c-7d7aeaa89eee"
|
||||
OUTPUT_DIR = os.path.join(BASE_DIR, "scratch/3c_assets")
|
||||
|
||||
PRODUCTS = [
|
||||
{
|
||||
"id": 1,
|
||||
"key": "gan_charger",
|
||||
"raw_img": "baseus_gan_charger_1790409638528.jpg",
|
||||
"title": "倍思 65W GaN氮化镓多口快充充电器",
|
||||
"brand": "倍思 (Baseus)",
|
||||
"model": "CCGAN65S5",
|
||||
"subtitle": "GaN5 Pro 氮化镓快充 / 2C1A多口盲插 / 智能温控不伤机",
|
||||
"features": ["65W大功率氮化镓", "2C+1A三口盲插", "BPS II充电技术", "低温快充安全不烫"],
|
||||
"specs": [
|
||||
("品名", "倍思 65W GaN5 Pro 多口快充头"),
|
||||
("接口", "Type-C1 + Type-C2 + USB-A"),
|
||||
("输入", "AC 100-240V~, 50/60Hz, 1.5A Max"),
|
||||
("C1/C2输出", "5V/3A, 9V/3A, 12V/3A, 15V/3A, 20V/3.25A (65W Max)"),
|
||||
("USB-A输出", "5V/3A, 9V/3A, 12V/2.5A, 20V/1.5A (30W Max)"),
|
||||
("产品尺寸", "65 x 36 x 32 mm"),
|
||||
("净重", "约 120g"),
|
||||
("质保服务", "1年全国联保")
|
||||
],
|
||||
"package": ["65W氮化镓充电器主机 * 1", "双Type-C 100W快充数据线 * 1", "产品说明书 & 保修卡 * 1", "品牌贴纸 * 1"]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"key": "tws_earbuds",
|
||||
"raw_img": "tws_earbuds_1790409653035.jpg",
|
||||
"title": "漫步者 LolliPods 真无线立体声蓝牙耳机",
|
||||
"brand": "漫步者 (EDIFIER)",
|
||||
"model": "LolliPods 2026款",
|
||||
"subtitle": "半入耳舒适佩戴 / AptX高清音频解码 / CVC双麦通话降噪 / 24小时综合续航",
|
||||
"features": ["13mm大尺寸复合振膜", "高通QCC低延迟芯片", "CVC8.0双麦通话降噪", "24小时复合长续航"],
|
||||
"specs": [
|
||||
("品名", "漫步者 LolliPods 真无线立体声耳机"),
|
||||
("蓝牙版本", "Bluetooth V5.3"),
|
||||
("音频解码", "AptX / SBC"),
|
||||
("有效距离", "10m 无障碍传输"),
|
||||
("耳机续航", "约 6小时 (单次)"),
|
||||
("配合充电仓", "约 24小时"),
|
||||
("充电接口", "Type-C 高速充电"),
|
||||
("防水等级", "IP54级防尘防水")
|
||||
],
|
||||
"package": ["蓝牙耳机本体 * 1对", "便携式无线充电仓 * 1", "Type-C充电线 * 1", "用户手册与保修凭证 * 1"]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"key": "power_bank",
|
||||
"raw_img": "power_bank_1790409670628.jpg",
|
||||
"title": "罗马仕 20000mAh 22.5W超级快充移动电源",
|
||||
"brand": "罗马仕 (ROMOSS)",
|
||||
"model": "SW20 Pro",
|
||||
"subtitle": "智能LED数显大容量充电宝 / 支持SCP/PD3.0快充 / 三入三出全兼容",
|
||||
"features": ["20000mAh大容量", "22.5W超级闪充", "LED百分比数显屏", "三入三出高效充放"],
|
||||
"specs": [
|
||||
("品名", "罗马仕 20000mAh 双向快充移动电源"),
|
||||
("额定容量", "12000mAh (5V/3A)"),
|
||||
("电池能量", "20000mAh / 74Wh (符合民航登机标准)"),
|
||||
("输入接口", "Type-C 18W / Micro-USB 18W / Lightning"),
|
||||
("输出接口", "Type-C 20W / USB-A1 22.5W / USB-A2 22.5W"),
|
||||
("电量显示", "LED数字智能精准显示"),
|
||||
("尺寸重量", "168 x 82 x 23 mm / 约 460g"),
|
||||
("安全保护", "过流/过压/短路/过充等10重安全防护")
|
||||
],
|
||||
"package": ["20000mAh移动电源主机 * 1", "USB转Type-C快充线 * 1", "快速使用指南 * 1", "防伪质保卡 * 1"]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"key": "fitness_band",
|
||||
"raw_img": "fitness_band_1790409752424.jpg",
|
||||
"title": "华为手环 8 智能运动健康手环 曜石黑",
|
||||
"brand": "华为 (HUAWEI)",
|
||||
"model": "ASK-B19",
|
||||
"subtitle": "8.99mm超薄机身 / 1.47英寸无界大屏 / TruSleep 3.0科学睡眠 / 14天长续航",
|
||||
"features": ["8.99mm轻薄无感佩戴", "1.47英寸全面彩屏", "全天候血氧心率监测", "100+种专业运动模式"],
|
||||
"specs": [
|
||||
("品名", "华为手环 8 标准版"),
|
||||
("机身厚度", "8.99 mm (不含传感器凸起)"),
|
||||
("机身重量", "约 14 g (不含表带)"),
|
||||
("屏幕规格", "1.47英寸 AMOLED炫彩触摸屏 (194x368)"),
|
||||
("健康管理", "睡眠监测 / 连续血氧 / 智能心率 / 压力检测"),
|
||||
("防水等级", "5 ATM 防水等级 (支持泳池游泳)"),
|
||||
("典型续航", "最长续航14天 / 常规使用9天"),
|
||||
("连接技术", "BLE 5.0,支持Android 6.0 / iOS 9.0及以上")
|
||||
],
|
||||
"package": ["手环主机含表带 * 1", "专用磁吸充电底座线缆 * 1", "快速入门及安全信息手册 * 1", "三包凭证 * 1"]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"key": "smart_watch",
|
||||
"raw_img": "smart_watch_1790409770345.jpg",
|
||||
"title": "小米Redmi Watch 3 青春版 智能蓝牙通话手表",
|
||||
"brand": "小米 (Redmi)",
|
||||
"model": "M2247W1",
|
||||
"subtitle": "1.83英寸超大高清屏 / 蓝牙高清语音通话 / 12天持久续航 / 200+个性表盘",
|
||||
"features": ["1.83英寸全贴合大屏", "支持蓝牙高清通话", "全天心率血氧监测", "12天长效超强续航"],
|
||||
"specs": [
|
||||
("品名", "Redmi Watch 3 青春版 智能手表"),
|
||||
("屏幕尺寸", "1.83英寸高清触控彩屏 (240x280)"),
|
||||
("表框材质", "高强度高光金属质感中框"),
|
||||
("通话功能", "内置高灵敏降噪麦克风与独立扬声器"),
|
||||
("运动模式", "100+种运动模式,自动识别跑步步行"),
|
||||
("防水等级", "5ATM专业防水"),
|
||||
("电池容量", "289mAh锂聚合物电池"),
|
||||
("续航时间", "典型模式12天 / 重度使用8天")
|
||||
],
|
||||
"package": ["智能手表主机 * 1", "亲肤硅胶表带 * 1", "专用磁吸充电线 * 1", "使用说明书 * 1"]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"key": "wireless_mouse",
|
||||
"raw_img": "wireless_mouse_1790409787831.jpg",
|
||||
"title": "罗技 M330 降噪静音无线鼠标 曜石黑",
|
||||
"brand": "罗技 (Logitech)",
|
||||
"model": "M330 Silent Plus",
|
||||
"subtitle": "90%降噪静音微动 / 人体工学舒适右手握感 / 2.4G即插即用 / 24个月超长续航",
|
||||
"features": ["静音降噪静音点击", "人体工学仿生曲线", "微型USB无线接收器", "24个月超长电池续航"],
|
||||
"specs": [
|
||||
("品名", "罗技 M330 Silent Plus 无线静音鼠标"),
|
||||
("连接方式", "2.4 GHz 无线连接技术 (微型USB Nano接收器)"),
|
||||
("有效距离", "约 10 米"),
|
||||
("传感器类型", "罗技先进光学追踪技术"),
|
||||
("传感器分辨率", "1000 DPI 精准定位"),
|
||||
("按键数量", "3个 (左键、右键、滚轮中键)"),
|
||||
("供电电池", "1节 AA 五号电池 (出厂预装)"),
|
||||
("支持系统", "Windows 10/11, macOS, ChromeOS, Linux")
|
||||
],
|
||||
"package": ["M330 无线鼠标 * 1", "Nano 微型无线接收器 * 1", "AA 电池 (已预装) * 1", "用户文档 * 1"]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"key": "usbc_hub",
|
||||
"raw_img": "usbc_hub_1790409804480.jpg",
|
||||
"title": "绿联 六合一Type-C多功能扩展坞",
|
||||
"brand": "绿联 (UGREEN)",
|
||||
"model": "CM475",
|
||||
"subtitle": "4K@30Hz HDMI高清投屏 / PD 100W大功率闪充 / USB3.0*3 5Gbps高速传输",
|
||||
"features": ["4K高清HDMI输出", "PD 100W边充边玩", "3口USB3.0 5Gbps", "铝合金一体高效散热"],
|
||||
"specs": [
|
||||
("品名", "绿联 Type-C 六合一多功能扩展坞"),
|
||||
("输入端口", "Type-C (兼容雷电3/4, USB4)"),
|
||||
("视频端口", "HDMI 支持 4K*2K@30Hz / 1080P@60Hz"),
|
||||
("数据端口", "USB 3.0 * 3 (传输速率最高 5Gbps)"),
|
||||
("供电端口", "Type-C PD 3.0 (最高支持 100W 快速充电)"),
|
||||
("外壳材质", "深空灰阳极氧化铝合金"),
|
||||
("线身工艺", "高密度尼龙精编,抗弯折寿命10000+次"),
|
||||
("兼容平台", "MacBook / iPad / 华为 / 联想 / 戴尔等全功能Type-C设备")
|
||||
],
|
||||
"package": ["六合一多功能扩展坞 * 1", "产品快速使用手册 * 1", "产品保修凭证 * 1"]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"key": "fast_cable",
|
||||
"raw_img": "fast_cable_1790409818654.jpg",
|
||||
"title": "品胜 苹果PD 20W快充编织数据线 1.2米",
|
||||
"brand": "品胜 (PISEN)",
|
||||
"model": "AL10-1200",
|
||||
"subtitle": "智能低温快充芯片 / 加粗镀锡铜芯 / 强韧高密度尼龙编织线身 / 充放提速60%",
|
||||
"features": ["PD 20W急速快充", "高密度尼龙防断编织", "加粗五股纯铜线芯", "充电传输二合一"],
|
||||
"specs": [
|
||||
("品名", "品胜 Type-C 转 Lightning / C to C 快充线"),
|
||||
("接口规格", "Type-C to Lightning / Type-C (兼容PD快充)"),
|
||||
("最大输出", "20W / 27W Max (向下兼容标准充电)"),
|
||||
("数据传输", "USB 2.0 480Mbps 稳定互传"),
|
||||
("线身长度", "1.2 米 (黄金日用长度)"),
|
||||
("线身材质", "精工高密尼龙双色编织 + 铝合金端子外壳"),
|
||||
("弯折测试", "经过 20000+ 次SR摇摆弯折抗疲劳测试"),
|
||||
("适用机型", "iPhone 8-15全系 / iPad / AirPods 系列设备")
|
||||
],
|
||||
"package": ["快充编织数据线 1.2米 * 1", "硅胶收纳绑带 * 1", "产品合格证 & 质保卡 * 1"]
|
||||
}
|
||||
]
|
||||
|
||||
def create_banner_2(prod, raw_img_path, out_path):
|
||||
"""生成轮播图2:性能特性/场景解析"""
|
||||
canvas = Image.new("RGB", (800, 800), (248, 250, 252))
|
||||
raw = Image.open(raw_img_path).convert("RGBA").resize((560, 560), Image.Resampling.LANCZOS)
|
||||
canvas.paste(raw, (120, 180), raw)
|
||||
|
||||
draw = ImageDraw.Draw(canvas)
|
||||
# 顶部标签
|
||||
font_tag = get_font(26)
|
||||
font_sub = get_font(20)
|
||||
draw.rounded_rectangle([(40, 40), (220, 85)], radius=8, fill=(30, 41, 59))
|
||||
draw.text((60, 48), "核心性能解析", fill=(255, 255, 255), font=font_tag)
|
||||
draw.text((240, 52), prod["brand"] + " · 原厂正品", fill=(100, 116, 139), font=font_sub)
|
||||
|
||||
# 底部核心特性芯片卡片
|
||||
features = prod["features"]
|
||||
card_w = 345
|
||||
card_h = 44
|
||||
coords = [
|
||||
(40, 680), (415, 680),
|
||||
(40, 735), (415, 735)
|
||||
]
|
||||
font_f = get_font(20)
|
||||
for i, f in enumerate(features[:4]):
|
||||
x, y = coords[i]
|
||||
draw.rounded_rectangle([(x, y), (x + card_w, y + card_h)], radius=6, fill=(241, 245, 249), outline=(226, 232, 240))
|
||||
draw.ellipse([(x + 14, y + 16), (x + 24, y + 26)], fill=(37, 99, 235))
|
||||
draw.text((x + 36, y + 10), f, fill=(15, 23, 42), font=font_f)
|
||||
|
||||
canvas.save(out_path, "JPEG", quality=92)
|
||||
|
||||
def create_banner_3(prod, raw_img_path, out_path):
|
||||
"""生成轮播图3:做工细节与设计质感"""
|
||||
canvas = Image.new("RGB", (800, 800), (255, 255, 255))
|
||||
raw = Image.open(raw_img_path).convert("RGBA").resize((600, 600), Image.Resampling.LANCZOS)
|
||||
canvas.paste(raw, (100, 80), raw)
|
||||
|
||||
draw = ImageDraw.Draw(canvas)
|
||||
# 底部质感横幅
|
||||
draw.rectangle([(0, 680), (800, 800)], fill=(15, 23, 42))
|
||||
font_title = get_font(28)
|
||||
font_desc = get_font(20)
|
||||
draw.text((50, 705), "极简精工美学 · 严苛品质出厂质检", fill=(255, 255, 255), font=font_title)
|
||||
draw.text((50, 748), prod["subtitle"][:32], fill=(148, 163, 184), font=font_desc)
|
||||
|
||||
canvas.save(out_path, "JPEG", quality=92)
|
||||
|
||||
def create_detail_1(prod, raw_img_path, out_path):
|
||||
"""生成详情切片1:产品核心卖点海报长图 (750 x 1000)"""
|
||||
w, h = 750, 1000
|
||||
canvas = Image.new("RGB", (w, h), (248, 250, 252))
|
||||
draw = ImageDraw.Draw(canvas)
|
||||
|
||||
# 顶部品牌条
|
||||
draw.rectangle([(0, 0), (w, 140)], fill=(15, 23, 42))
|
||||
font_brand = get_font(22)
|
||||
font_title = get_font(32)
|
||||
draw.text((40, 30), prod["brand"].upper(), fill=(148, 163, 184), font=font_brand)
|
||||
draw.text((40, 70), prod["title"][:16], fill=(255, 255, 255), font=font_title)
|
||||
|
||||
# 主体产品图片
|
||||
raw = Image.open(raw_img_path).convert("RGBA").resize((480, 480), Image.Resampling.LANCZOS)
|
||||
canvas.paste(raw, (135, 170), raw)
|
||||
|
||||
# 4大卖点模块
|
||||
draw.rectangle([(30, 680), (720, 960)], fill=(255, 255, 255), outline=(226, 232, 240))
|
||||
font_block_t = get_font(24)
|
||||
font_block_c = get_font(20)
|
||||
draw.text((55, 705), "产品核心亮点", fill=(15, 23, 42), font=font_block_t)
|
||||
draw.line([(55, 745), (695, 745)], fill=(241, 245, 249), width=2)
|
||||
|
||||
feats = prod["features"]
|
||||
for i, ft in enumerate(feats):
|
||||
fy = 765 + i * 44
|
||||
draw.ellipse([(60, fy + 8), (72, fy + 20)], fill=(37, 99, 235))
|
||||
draw.text((85, fy + 4), ft, fill=(51, 65, 85), font=font_block_c)
|
||||
|
||||
canvas.save(out_path, "JPEG", quality=92)
|
||||
|
||||
def create_detail_2(prod, out_path):
|
||||
"""生成详情切片2:标准技术参数表格长图 (750 x 900)"""
|
||||
w, h = 750, 900
|
||||
canvas = Image.new("RGB", (w, h), (255, 255, 255))
|
||||
draw = ImageDraw.Draw(canvas)
|
||||
|
||||
# 顶部标题栏
|
||||
draw.rectangle([(0, 0), (w, 90)], fill=(241, 245, 249))
|
||||
font_t = get_font(30)
|
||||
draw.text((40, 28), "详细规格参数与技术指标", fill=(15, 23, 42), font=font_t)
|
||||
|
||||
# 表格
|
||||
specs = prod["specs"]
|
||||
table_top = 120
|
||||
row_h = 75
|
||||
font_k = get_font(22)
|
||||
font_v = get_font(20)
|
||||
|
||||
for i, (k, v) in enumerate(specs):
|
||||
curr_y = table_top + i * row_h
|
||||
bg_col = (248, 250, 252) if i % 2 == 0 else (255, 255, 255)
|
||||
draw.rectangle([(30, curr_y), (720, curr_y + row_h)], fill=bg_col, outline=(226, 232, 240))
|
||||
# 键列
|
||||
draw.text((50, curr_y + 25), k, fill=(100, 116, 139), font=font_k)
|
||||
# 值列 (换行处理)
|
||||
if len(v) > 28:
|
||||
draw.text((220, curr_y + 12), v[:28], fill=(15, 23, 42), font=font_v)
|
||||
draw.text((220, curr_y + 40), v[28:], fill=(15, 23, 42), font=font_v)
|
||||
else:
|
||||
draw.text((220, curr_y + 26), v, fill=(15, 23, 42), font=font_v)
|
||||
|
||||
canvas.save(out_path, "JPEG", quality=92)
|
||||
|
||||
def create_detail_3(prod, out_path):
|
||||
"""生成详情切片3:包装清单与正品保障售后承诺长图 (750 x 850)"""
|
||||
w, h = 750, 850
|
||||
canvas = Image.new("RGB", (w, h), (248, 250, 252))
|
||||
draw = ImageDraw.Draw(canvas)
|
||||
|
||||
# 包装清单部分
|
||||
draw.rectangle([(30, 40), (720, 360)], fill=(255, 255, 255), outline=(226, 232, 240))
|
||||
font_sec = get_font(26)
|
||||
font_item = get_font(21)
|
||||
draw.text((55, 65), "包装清单 (Standard Package)", fill=(15, 23, 42), font=font_sec)
|
||||
draw.line([(55, 110), (695, 110)], fill=(241, 245, 249), width=2)
|
||||
|
||||
for i, item in enumerate(prod["package"]):
|
||||
iy = 135 + i * 48
|
||||
draw.ellipse([(60, iy + 8), (70, iy + 18)], fill=(16, 185, 129))
|
||||
draw.text((85, iy + 4), item, fill=(51, 65, 85), font=font_item)
|
||||
|
||||
# 服务保障部分
|
||||
draw.rectangle([(30, 400), (720, 800)], fill=(255, 255, 255), outline=(226, 232, 240))
|
||||
draw.text((55, 425), "正品承诺与售后保障说明", fill=(15, 23, 42), font=font_sec)
|
||||
draw.line([(55, 470), (695, 470)], fill=(241, 245, 249), width=2)
|
||||
|
||||
guarantees = [
|
||||
("官方正品", "100% 正品行货,支持全国防伪验证查询,假一赔十"),
|
||||
("七天无理由", "支持 7 天无理由退换货(保持商品包装完好未激活)"),
|
||||
("一年全国联保", "自购买之日起享受 365 天官方全国联合保修服务"),
|
||||
("极速闪电发货", "每日16点前订单顺丰/京东快递当日发出,全场包邮")
|
||||
]
|
||||
font_gt = get_font(22)
|
||||
font_gc = get_font(18)
|
||||
for j, (gt, gc) in enumerate(guarantees):
|
||||
gy = 495 + j * 72
|
||||
draw.rounded_rectangle([(55, gy), (180, gy + 36)], radius=6, fill=(238, 242, 255))
|
||||
draw.text((65, gy + 6), gt, fill=(79, 70, 229), font=font_gt)
|
||||
draw.text((200, gy + 8), gc, fill=(71, 85, 105), font=font_gc)
|
||||
|
||||
canvas.save(out_path, "JPEG", quality=92)
|
||||
|
||||
def create_category_icon(title, subtitle, icon_color, out_path, sample_img_path):
|
||||
"""生成 200x200 分类图标"""
|
||||
canvas = Image.new("RGBA", (200, 200), (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(canvas)
|
||||
|
||||
# 渐变或圆角卡片底色
|
||||
draw.rounded_rectangle([(10, 10), (190, 190)], radius=36, fill=(245, 247, 250), outline=(226, 232, 240))
|
||||
|
||||
# 贴上该类目产品的居中缩略图
|
||||
sample = Image.open(sample_img_path).convert("RGBA").resize((130, 130), Image.Resampling.LANCZOS)
|
||||
canvas.paste(sample, (35, 35), sample)
|
||||
|
||||
canvas.save(out_path, "PNG")
|
||||
|
||||
def main():
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
os.makedirs(os.path.join(OUTPUT_DIR, "categories"), exist_ok=True)
|
||||
|
||||
print("开始生成 3C 分类与商品图片素材...")
|
||||
|
||||
# 1. 生成商品素材
|
||||
for p in PRODUCTS:
|
||||
p_dir = os.path.join(OUTPUT_DIR, f"p_{p['id']}")
|
||||
os.makedirs(p_dir, exist_ok=True)
|
||||
raw_path = os.path.join(SOURCE_IMG_DIR, p["raw_img"])
|
||||
|
||||
# main/cover (800x800)
|
||||
img_main = Image.open(raw_path).convert("RGB").resize((800, 800), Image.Resampling.LANCZOS)
|
||||
main_path = os.path.join(p_dir, "cover.jpg")
|
||||
img_main.save(main_path, "JPEG", quality=94)
|
||||
|
||||
# banner 1 (800x800)
|
||||
banner1_path = os.path.join(p_dir, "banner_1.jpg")
|
||||
img_main.save(banner1_path, "JPEG", quality=94)
|
||||
|
||||
# banner 2
|
||||
banner2_path = os.path.join(p_dir, "banner_2.jpg")
|
||||
create_banner_2(p, raw_path, banner2_path)
|
||||
|
||||
# banner 3
|
||||
banner3_path = os.path.join(p_dir, "banner_3.jpg")
|
||||
create_banner_3(p, raw_path, banner3_path)
|
||||
|
||||
# detail 1
|
||||
detail1_path = os.path.join(p_dir, "detail_1.jpg")
|
||||
create_detail_1(p, raw_path, detail1_path)
|
||||
|
||||
# detail 2
|
||||
detail2_path = os.path.join(p_dir, "detail_2.jpg")
|
||||
create_detail_2(p, detail2_path)
|
||||
|
||||
# detail 3
|
||||
detail3_path = os.path.join(p_dir, "detail_3.jpg")
|
||||
create_detail_3(p, detail3_path)
|
||||
|
||||
print(f"商品 {p['id']} [{p['title']}] 素材生成完成。")
|
||||
|
||||
# 2. 生成 4 个分类图标
|
||||
cat_items = [
|
||||
("手机数码", "充电/配件/外设", (37, 99, 235), "cat_mobile.png", os.path.join(SOURCE_IMG_DIR, PRODUCTS[0]["raw_img"])),
|
||||
("智能影音", "耳机/音箱/降噪", (124, 58, 237), "cat_audio.png", os.path.join(SOURCE_IMG_DIR, PRODUCTS[1]["raw_img"])),
|
||||
("智能穿戴", "手环/手表/健康", (225, 29, 72), "cat_wearable.png", os.path.join(SOURCE_IMG_DIR, PRODUCTS[3]["raw_img"])),
|
||||
("电脑办公", "鼠标/键鼠/扩展坞", (13, 148, 136), "cat_computer.png", os.path.join(SOURCE_IMG_DIR, PRODUCTS[5]["raw_img"]))
|
||||
]
|
||||
for c_title, c_sub, c_col, c_name, sample_path in cat_items:
|
||||
c_path = os.path.join(OUTPUT_DIR, "categories", c_name)
|
||||
create_category_icon(c_title, c_sub, c_col, c_path, sample_path)
|
||||
print(f"分类图标 [{c_title}] 生成完成: {c_name}")
|
||||
|
||||
print("全部素材生成完毕!输出目录:", OUTPUT_DIR)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
199
tests/verify_3c_online_data.php
普通文件
199
tests/verify_3c_online_data.php
普通文件
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
/**
|
||||
* 线上 3C 商品与分类完整性校验测试脚本
|
||||
*
|
||||
* 覆盖:
|
||||
* 1. 分类层级与权重测试
|
||||
* 2. 8 款 3C 商品与 SKU 价格/库存数据完整性测试
|
||||
* 3. 前端接口模拟测试 (正常场景、业务失败场景、参数错误场景)
|
||||
* 4. 装修模板 5 首页推荐联动验证
|
||||
*/
|
||||
|
||||
namespace tests\goods;
|
||||
|
||||
define('APP_PATH', __DIR__ . '/../application/');
|
||||
define('ROOT_PATH', dirname(realpath(APP_PATH)) . '/');
|
||||
require_once __DIR__ . '/../thinkphp/base.php';
|
||||
require_once __DIR__ . '/../thinkphp/helper.php';
|
||||
require_once __DIR__ . '/../application/common.php';
|
||||
|
||||
if (is_file(ROOT_PATH . '.env')) {
|
||||
$env = parse_ini_file(ROOT_PATH . '.env', true);
|
||||
foreach ($env as $key => $val) {
|
||||
$name = 'PHP_' . strtoupper($key);
|
||||
if (is_array($val)) {
|
||||
foreach ($val as $k => $v) {
|
||||
putenv("{$name}_" . strtoupper($k) . "={$v}");
|
||||
}
|
||||
} else {
|
||||
putenv("{$name}={$val}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
\think\Config::set(\think\Config::load(APP_PATH . 'config.php'));
|
||||
$dbConfig = [
|
||||
'type' => 'mysql',
|
||||
'hostname' => '127.0.0.1',
|
||||
'database' => 'henglianshutong',
|
||||
'username' => 'henglianshutong',
|
||||
'password' => 'henglianshutong',
|
||||
'hostport' => '3306',
|
||||
'prefix' => 'hla_',
|
||||
];
|
||||
\think\Config::set('database', $dbConfig);
|
||||
|
||||
use think\Db;
|
||||
|
||||
class Verify3COnlineData
|
||||
{
|
||||
private $passed = 0;
|
||||
private $failed = 0;
|
||||
|
||||
private function assert($condition, $message)
|
||||
{
|
||||
if ($condition) {
|
||||
echo " [PASS] {$message}\n";
|
||||
$this->passed++;
|
||||
} else {
|
||||
echo " [FAIL] {$message}\n";
|
||||
$this->failed++;
|
||||
}
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
echo "====================================================\n";
|
||||
echo "开始验证 3C 商品与分类线上数据及 API 接口...\n";
|
||||
echo "====================================================\n\n";
|
||||
|
||||
// 1. 分类检验
|
||||
echo "--- 场景 1: 分类层级与权重检验 ---\n";
|
||||
$categories = Db::name('shopro_category')
|
||||
->where('parent_id', 153)
|
||||
->where('status', 'normal')
|
||||
->order('weigh', 'desc')
|
||||
->select();
|
||||
|
||||
$catNames = array_column($categories, 'name');
|
||||
$this->assert(in_array('手机数码', $catNames), "分类列表中包含 '手机数码'");
|
||||
$this->assert(in_array('智能影音', $catNames), "分类列表中包含 '智能影音'");
|
||||
$this->assert(in_array('智能穿戴', $catNames), "分类列表中包含 '智能穿戴'");
|
||||
$this->assert(in_array('电脑办公', $catNames), "分类列表中包含 '电脑办公'");
|
||||
|
||||
// 校验权重排在酒水之前
|
||||
$top4Names = array_slice($catNames, 0, 4);
|
||||
$this->assert(
|
||||
in_array('手机数码', $top4Names) && in_array('智能影音', $top4Names),
|
||||
"前 4 大展示分类全部为 3C 核心品类"
|
||||
);
|
||||
|
||||
// 2. 商品检验
|
||||
echo "\n--- 场景 2: 8 款主流 3C 商品及 SKU 检验 ---\n";
|
||||
$titles = [
|
||||
'倍思 65W GaN氮化镓多口快充充电器',
|
||||
'漫步者 LolliPods 真无线立体声蓝牙耳机',
|
||||
'罗马仕 20000mAh 22.5W超级快充移动电源',
|
||||
'华为手环 8 智能运动健康手环 曜石黑',
|
||||
'小米Redmi Watch 3 青春版 智能蓝牙通话手表',
|
||||
'罗技 M330 降噪静音无线鼠标 曜石黑',
|
||||
'绿联 六合一Type-C多功能扩展坞',
|
||||
'品胜 苹果PD 20W快充编织数据线 1.2米'
|
||||
];
|
||||
|
||||
$goodsList = Db::name('shopro_goods')
|
||||
->whereIn('title', $titles)
|
||||
->whereNull('deletetime')
|
||||
->select();
|
||||
|
||||
$this->assert(count($goodsList) === 8, "8 款 3C 商品均已完整入库并正常启用 (实际: " . count($goodsList) . ")");
|
||||
|
||||
$goodsIds = [];
|
||||
foreach ($goodsList as $goods) {
|
||||
$goodsIds[] = $goods['id'];
|
||||
$this->assert($goods['status'] === 'up', "商品 [{$goods['title']}] 状态为正常上架 (up)");
|
||||
$this->assert(!empty($goods['image']), "商品 [{$goods['title']}] 配备高清封面图");
|
||||
|
||||
$images = json_decode($goods['images'], true);
|
||||
$this->assert(is_array($images) && count($images) >= 3, "商品 [{$goods['title']}] 包含 3 张高清轮播图");
|
||||
|
||||
$params = json_decode($goods['params'], true);
|
||||
$this->assert(is_array($params) && count($params) >= 5, "商品 [{$goods['title']}] 包含完整规格参数指标 (≥5项)");
|
||||
|
||||
$this->assert(strpos($goods['content'], '正品保障') !== false, "商品 [{$goods['title']}] 详情包含正品与官方售后承诺");
|
||||
|
||||
// SKU 价格与库存
|
||||
$sku = Db::name('shopro_goods_sku_price')->where('goods_id', $goods['id'])->find();
|
||||
$this->assert(!empty($sku), "商品 [{$goods['title']}] 存在有效 SKU 价格记录");
|
||||
$this->assert($sku['stock'] >= 1000, "商品 [{$goods['title']}] 库存充足 (当前库存: {$sku['stock']})");
|
||||
$this->assert($sku['price'] > 0 && $sku['price'] == $goods['price'], "商品 [{$goods['title']}] 售价核验一致 ({$sku['price']}元)");
|
||||
}
|
||||
|
||||
// 3. API 模拟测试
|
||||
echo "\n--- 场景 3: API 模拟测试 (覆盖正常/业务失败/参数异常) ---\n";
|
||||
|
||||
// 3.1 正常场景: 查询分类下的商品
|
||||
$mobileCat = Db::name('shopro_category')->where('parent_id', 153)->where('name', '手机数码')->find();
|
||||
$mobileGoods = Db::name('shopro_goods')
|
||||
->whereRaw("find_in_set({$mobileCat['id']}, category_ids)")
|
||||
->where('status', 'up')
|
||||
->whereNull('deletetime')
|
||||
->select();
|
||||
$this->assert(count($mobileGoods) >= 3, "API 模拟: 查询 [手机数码] 分类成功返回对应商品 (返回: " . count($mobileGoods) . "款)");
|
||||
|
||||
// 3.2 正常场景: 商品详情查询
|
||||
$firstGoodsId = $goodsIds[0];
|
||||
$detail = Db::name('shopro_goods')->where('id', $firstGoodsId)->where('status', 'up')->find();
|
||||
$this->assert(!empty($detail) && $detail['id'] == $firstGoodsId, "API 模拟: 获取商品 ID [{$firstGoodsId}] 详情成功");
|
||||
|
||||
// 3.3 业务失败场景: 查询下架或不存在的商品
|
||||
$nonExistDetail = Db::name('shopro_goods')->where('id', 999999)->where('status', 'up')->find();
|
||||
$this->assert(empty($nonExistDetail), "API 模拟: 查询不存在商品 ID [999999] 正确返回空 (业务预期失败)");
|
||||
|
||||
// 3.4 参数异常场景: 空参数或非法分类 ID
|
||||
$invalidCatGoods = Db::name('shopro_goods')
|
||||
->whereRaw("find_in_set(-1, category_ids)")
|
||||
->select();
|
||||
$this->assert(empty($invalidCatGoods), "API 模拟: 传入非法分类 ID [-1] 正确返回空列表");
|
||||
|
||||
// 4. 装修模板 5 首页联动校验
|
||||
echo "\n--- 场景 4: 装修模板 5 (3C数码) 首页配置联动检验 ---\n";
|
||||
$decorPage = Db::name('shopro_decorate_page')
|
||||
->where('decorate_id', 5)
|
||||
->where('type', 'home')
|
||||
->find();
|
||||
$this->assert(!empty($decorPage), "装修模板 5 (3C数码) home 页面记录存在");
|
||||
|
||||
if ($decorPage) {
|
||||
$pageConfig = json_decode($decorPage['page'], true);
|
||||
$found3CSearch = false;
|
||||
$found3CGoods = false;
|
||||
|
||||
foreach ($pageConfig['data'] as $block) {
|
||||
if ($block['type'] === 'searchBlock') {
|
||||
if (strpos($block['data']['placeholder'], '65W') !== false || in_array('65W快充', $block['data']['keywords'])) {
|
||||
$found3CSearch = true;
|
||||
}
|
||||
}
|
||||
if ($block['type'] === 'goodsCard') {
|
||||
if (isset($block['data']['goodsIds']) && !empty(array_intersect($block['data']['goodsIds'], $goodsIds))) {
|
||||
$found3CGoods = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->assert($found3CSearch, "首页搜索框关键词已更新为 3C 数码热词");
|
||||
$this->assert($found3CGoods, "首页推荐商品卡片已关联新生成的 3C 数码爆品");
|
||||
}
|
||||
|
||||
echo "\n====================================================\n";
|
||||
echo "测试执行完成: 通过 {$this->passed} 项, 失败 {$this->failed} 项\n";
|
||||
echo "====================================================\n";
|
||||
|
||||
if ($this->failed > 0) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$verifier = new Verify3COnlineData();
|
||||
$verifier->run();
|
||||
在新工单中引用
屏蔽一个用户