feat(decorate): 归档恒链数通旗舰商城装修模板迁移脚本与视觉海报构建工具
这个提交包含在:
@@ -0,0 +1,25 @@
|
||||
-- ==============================================================================
|
||||
-- 恒链数通商城 - 旗舰主营产品装修模板与分类校准迁移脚本
|
||||
-- 归档时间: 2026-09-17
|
||||
-- 适用平台: WechatOfficialAccount, WechatMiniProgram, H5, App
|
||||
-- ==============================================================================
|
||||
|
||||
-- 1. 商品分类绑定关系校准(白酒:154,155,158;美护:163,164)
|
||||
UPDATE `hla_shopro_goods` SET `category_ids` = '153,154,155,158' WHERE `id` IN (34, 35);
|
||||
UPDATE `hla_shopro_goods` SET `category_ids` = '153,163,164' WHERE `id` IN (32, 33);
|
||||
|
||||
-- 2. 分类横幅与封面更新
|
||||
UPDATE `hla_shopro_category` SET `image` = '/uploads/henglian_goods/20260917/ff1b5f21/cc03fa9120352080.jpg' WHERE `id` = 154;
|
||||
UPDATE `hla_shopro_category` SET `image` = '/uploads/henglian_goods/20260917/c76a38b6/0a6fed04b792a271.png' WHERE `id` = 163;
|
||||
UPDATE `hla_shopro_category` SET `image` = '/uploads/henglian_goods/20260917/3a0470ce/e335dc712cf6d84a.jpg' WHERE `id` = 167;
|
||||
|
||||
-- 3. 启用模板2(恒链数通 · 旗舰臻选),清除软删除标记,全平台发布
|
||||
UPDATE `hla_shopro_decorate`
|
||||
SET `name` = '恒链数通 · 旗舰臻选',
|
||||
`status` = 'enable',
|
||||
`platform` = 'WechatOfficialAccount,WechatMiniProgram,H5,App',
|
||||
`deletetime` = NULL
|
||||
WHERE `id` = 2;
|
||||
|
||||
-- 4. 禁用其他冲突模板
|
||||
UPDATE `hla_shopro_decorate` SET `status` = 'disabled' WHERE `id` != 2;
|
||||
315
scripts/generate_banners.py
普通文件
315
scripts/generate_banners.py
普通文件
@@ -0,0 +1,315 @@
|
||||
import math
|
||||
from PIL import Image, ImageDraw, ImageFont, ImageFilter
|
||||
|
||||
W, H = 1420, 640
|
||||
font_title = "/System/Library/Fonts/Supplemental/Songti.ttc"
|
||||
font_text = "/System/Library/Fonts/STHeiti Medium.ttc"
|
||||
|
||||
def create_gradient(w, h, c1, c2, horizontal=True):
|
||||
base = Image.new('RGBA', (w, h), c1)
|
||||
top = Image.new('RGBA', (w, h), c2)
|
||||
mask = Image.new('L', (w, h))
|
||||
for y in range(h):
|
||||
for x in range(w):
|
||||
ratio = x / w if horizontal else y / h
|
||||
mask.putpixel((x, y), int(255 * ratio))
|
||||
base.paste(top, (0, 0), mask)
|
||||
return base
|
||||
|
||||
def draw_pill(draw, xy, fill, outline=None, width=1):
|
||||
draw.rounded_rectangle(xy, radius=xy[3]-xy[1], fill=fill, outline=outline, width=width)
|
||||
|
||||
# ==================== BANNER 1: WINE ====================
|
||||
def build_wine_banner():
|
||||
# Base background: dark luxury burgundy
|
||||
bg = create_gradient(W, H, (35, 12, 14, 255), (15, 6, 8, 255), horizontal=True)
|
||||
draw = ImageDraw.Draw(bg)
|
||||
|
||||
# Ambient gold glow circle
|
||||
glow = Image.new('RGBA', (W, H), (0, 0, 0, 0))
|
||||
g_draw = ImageDraw.Draw(glow)
|
||||
g_draw.ellipse([900, -100, 1600, 700], fill=(212, 160, 89, 40))
|
||||
glow = glow.filter(ImageFilter.GaussianBlur(80))
|
||||
bg = Image.alpha_composite(bg, glow)
|
||||
draw = ImageDraw.Draw(bg)
|
||||
|
||||
# Border & frame accents
|
||||
draw.rounded_rectangle([16, 16, W-16, H-16], radius=24, outline=(212, 175, 110, 80), width=2)
|
||||
|
||||
# Tag
|
||||
font_tag = ImageFont.truetype(font_text, 28)
|
||||
tag_text = "✦ 国酿琼浆 · 东方名酒专区 ✦"
|
||||
draw.rounded_rectangle([80, 80, 520, 130], radius=25, fill=(60, 20, 24, 200), outline=(212, 175, 110, 200), width=2)
|
||||
draw.text((105, 92), tag_text, font=font_tag, fill=(245, 215, 160))
|
||||
|
||||
# Title
|
||||
font_t = ImageFont.truetype(font_title, 68)
|
||||
draw.text((80, 165), "贵州飞天盛世 · 龙抬头", font=font_t, fill=(255, 235, 195))
|
||||
|
||||
# Subtitle
|
||||
font_s = ImageFont.truetype(font_text, 34)
|
||||
draw.text((80, 265), "古法大曲坤沙足年窖藏 | 传世名酿 · 宴饮礼遇", font=font_s, fill=(210, 185, 170))
|
||||
|
||||
font_s2 = ImageFont.truetype(font_text, 28)
|
||||
draw.text((80, 325), "陶坛封坛 · 酱香幽雅 · 回味悠长 · 祥瑞尊享", font=font_s2, fill=(160, 140, 135))
|
||||
|
||||
# CTA Button
|
||||
draw.rounded_rectangle([80, 420, 460, 500], radius=40, fill=(197, 160, 89, 255))
|
||||
font_btn = ImageFont.truetype(font_text, 32)
|
||||
draw.text((120, 442), "官方尊享价 ¥1398 立即品鉴 →", font=font_btn, fill=(25, 10, 12))
|
||||
|
||||
# Trust chips
|
||||
font_chip = ImageFont.truetype(font_text, 24)
|
||||
draw.text((80, 540), "✔ 官方正品保真 ✔ 原厂陶坛直采 ✔ 顺丰保价尊享配", font=font_chip, fill=(180, 155, 130))
|
||||
|
||||
# Right products: wine_blue & wine_red
|
||||
img_red = Image.open('/tmp/henglian_assets/wine_red.jpg').convert('RGBA')
|
||||
img_blue = Image.open('/tmp/henglian_assets/wine_blue.jpg').convert('RGBA')
|
||||
|
||||
# Resize & crop
|
||||
red_w, red_h = 460, int(460 * (img_red.height / img_red.width))
|
||||
img_red_r = img_red.resize((red_w, red_h), Image.Resampling.LANCZOS)
|
||||
|
||||
blue_w, blue_h = 420, int(420 * (img_blue.height / img_blue.width))
|
||||
img_blue_r = img_blue.resize((blue_w, blue_h), Image.Resampling.LANCZOS)
|
||||
|
||||
# Crop to neat card style on right
|
||||
card_right = Image.new('RGBA', (W, H), (0, 0, 0, 0))
|
||||
|
||||
# Make rounded masks for product cards
|
||||
def rounded_card(img, size, radius=20):
|
||||
c = img.resize(size, Image.Resampling.LANCZOS)
|
||||
mask = Image.new('L', size, 0)
|
||||
m_draw = ImageDraw.Draw(mask)
|
||||
m_draw.rounded_rectangle([0, 0, size[0], size[1]], radius=radius, fill=255)
|
||||
res = Image.new('RGBA', size, (0, 0, 0, 0))
|
||||
res.paste(c, (0, 0), mask)
|
||||
return res
|
||||
|
||||
p1 = rounded_card(img_red, (380, 460), radius=24)
|
||||
p2 = rounded_card(img_blue, (360, 420), radius=24)
|
||||
|
||||
# Border for product cards
|
||||
def add_border(img, radius=24):
|
||||
border = Image.new('RGBA', img.size, (0, 0, 0, 0))
|
||||
b_draw = ImageDraw.Draw(border)
|
||||
b_draw.rounded_rectangle([0, 0, img.size[0]-1, img.size[1]-1], radius=radius, outline=(212, 175, 110, 180), width=3)
|
||||
return Image.alpha_composite(img, border)
|
||||
|
||||
p1 = add_border(p1)
|
||||
p2 = add_border(p2)
|
||||
|
||||
# Shadow for p1, p2
|
||||
shadow = Image.new('RGBA', (W, H), (0, 0, 0, 0))
|
||||
s_draw = ImageDraw.Draw(shadow)
|
||||
s_draw.rounded_rectangle([650, 110, 650+360, 110+420], radius=24, fill=(0, 0, 0, 120))
|
||||
s_draw.rounded_rectangle([970, 80, 970+380, 80+460], radius=24, fill=(0, 0, 0, 150))
|
||||
shadow = shadow.filter(ImageFilter.GaussianBlur(30))
|
||||
bg = Image.alpha_composite(bg, shadow)
|
||||
|
||||
bg.paste(p2, (660, 110), p2)
|
||||
bg.paste(p1, (980, 80), p1)
|
||||
|
||||
bg.convert('RGB').save('/tmp/henglian_assets/banner_wine.jpg', quality=95)
|
||||
print("banner_wine.jpg generated")
|
||||
|
||||
# ==================== BANNER 2: SKINCARE ====================
|
||||
def build_skincare_banner():
|
||||
# Base background: luxury emerald / jade green
|
||||
bg = create_gradient(W, H, (14, 38, 32, 255), (6, 18, 15, 255), horizontal=True)
|
||||
draw = ImageDraw.Draw(bg)
|
||||
|
||||
# Ambient mint/teal glow
|
||||
glow = Image.new('RGBA', (W, H), (0, 0, 0, 0))
|
||||
g_draw = ImageDraw.Draw(glow)
|
||||
g_draw.ellipse([900, -100, 1600, 700], fill=(64, 180, 140, 45))
|
||||
glow = glow.filter(ImageFilter.GaussianBlur(80))
|
||||
bg = Image.alpha_composite(bg, glow)
|
||||
draw = ImageDraw.Draw(bg)
|
||||
|
||||
# Frame
|
||||
draw.rounded_rectangle([16, 16, W-16, H-16], radius=24, outline=(100, 200, 165, 80), width=2)
|
||||
|
||||
# Tag
|
||||
font_tag = ImageFont.truetype(font_text, 28)
|
||||
tag_text = "✦ 极光凝时 · 医研美护专区 ✦"
|
||||
draw.rounded_rectangle([80, 80, 520, 130], radius=25, fill=(20, 55, 45, 200), outline=(100, 200, 165, 200), width=2)
|
||||
draw.text((105, 92), tag_text, font=font_tag, fill=(180, 240, 220))
|
||||
|
||||
# Title
|
||||
font_t = ImageFont.truetype(font_title, 68)
|
||||
draw.text((80, 165), "润泽优选 · 轻盈赋活系列", font=font_t, fill=(235, 255, 248))
|
||||
|
||||
# Subtitle
|
||||
font_s = ImageFont.truetype(font_text, 34)
|
||||
draw.text((80, 265), "医研级西林瓶锁鲜 | 一秒赋活 · 紧致淡纹抗皱", font=font_s, fill=(185, 225, 215))
|
||||
|
||||
font_s2 = ImageFont.truetype(font_text, 28)
|
||||
draw.text((80, 325), "高纯活性精粹 · 密集修护赋能 · 水润焕亮无负担", font=font_s2, fill=(130, 180, 168))
|
||||
|
||||
# CTA Button
|
||||
draw.rounded_rectangle([80, 420, 460, 500], radius=40, fill=(50, 150, 120, 255))
|
||||
font_btn = ImageFont.truetype(font_text, 32)
|
||||
draw.text((120, 442), "官方尊享价 ¥1398 焕活美肌 →", font=font_btn, fill=(255, 255, 255))
|
||||
|
||||
# Trust chips
|
||||
font_chip = ImageFont.truetype(font_text, 24)
|
||||
draw.text((80, 540), "✔ 医研级鲜活锁鲜 ✔ 多维抗皱淡纹 ✔ 院线赋能滋养", font=font_chip, fill=(160, 215, 200))
|
||||
|
||||
# Right products: skincare_runze & skincare_qingying
|
||||
img_runze = Image.open('/tmp/henglian_assets/skincare_runze.png').convert('RGBA')
|
||||
img_qing = Image.open('/tmp/henglian_assets/skincare_qingying.png').convert('RGBA')
|
||||
|
||||
def rounded_card(img, size, radius=20):
|
||||
c = img.resize(size, Image.Resampling.LANCZOS)
|
||||
mask = Image.new('L', size, 0)
|
||||
m_draw = ImageDraw.Draw(mask)
|
||||
m_draw.rounded_rectangle([0, 0, size[0], size[1]], radius=radius, fill=255)
|
||||
res = Image.new('RGBA', size, (0, 0, 0, 0))
|
||||
res.paste(c, (0, 0), mask)
|
||||
return res
|
||||
|
||||
def add_border(img, radius=24):
|
||||
border = Image.new('RGBA', img.size, (0, 0, 0, 0))
|
||||
b_draw = ImageDraw.Draw(border)
|
||||
b_draw.rounded_rectangle([0, 0, img.size[0]-1, img.size[1]-1], radius=radius, outline=(100, 200, 165, 180), width=3)
|
||||
return Image.alpha_composite(img, border)
|
||||
|
||||
p1 = add_border(rounded_card(img_runze, (380, 440), radius=24))
|
||||
p2 = add_border(rounded_card(img_qing, (360, 420), radius=24))
|
||||
|
||||
shadow = Image.new('RGBA', (W, H), (0, 0, 0, 0))
|
||||
s_draw = ImageDraw.Draw(shadow)
|
||||
s_draw.rounded_rectangle([660, 110, 660+360, 110+420], radius=24, fill=(0, 0, 0, 100))
|
||||
s_draw.rounded_rectangle([980, 90, 980+380, 90+440], radius=24, fill=(0, 0, 0, 120))
|
||||
shadow = shadow.filter(ImageFilter.GaussianBlur(30))
|
||||
bg = Image.alpha_composite(bg, shadow)
|
||||
|
||||
bg.paste(p2, (660, 110), p2)
|
||||
bg.paste(p1, (980, 90), p1)
|
||||
|
||||
bg.convert('RGB').save('/tmp/henglian_assets/banner_skincare.jpg', quality=95)
|
||||
print("banner_skincare.jpg generated")
|
||||
|
||||
# ==================== BANNER 3: PARTNER ====================
|
||||
def build_partner_banner():
|
||||
# Base background: prestige navy & gold
|
||||
bg = create_gradient(W, H, (12, 24, 45, 255), (5, 10, 20, 255), horizontal=True)
|
||||
draw = ImageDraw.Draw(bg)
|
||||
|
||||
# Warm gold aura
|
||||
glow = Image.new('RGBA', (W, H), (0, 0, 0, 0))
|
||||
g_draw = ImageDraw.Draw(glow)
|
||||
g_draw.ellipse([800, -50, 1500, 650], fill=(212, 175, 110, 40))
|
||||
glow = glow.filter(ImageFilter.GaussianBlur(80))
|
||||
bg = Image.alpha_composite(bg, glow)
|
||||
draw = ImageDraw.Draw(bg)
|
||||
|
||||
draw.rounded_rectangle([16, 16, W-16, H-16], radius=24, outline=(212, 175, 110, 80), width=2)
|
||||
|
||||
# Tag
|
||||
font_tag = ImageFont.truetype(font_text, 28)
|
||||
tag_text = "✦ 恒链合伙人 · 共享新零售红利 ✦"
|
||||
draw.rounded_rectangle([80, 80, 560, 130], radius=25, fill=(25, 40, 70, 200), outline=(212, 175, 110, 200), width=2)
|
||||
draw.text((105, 92), tag_text, font=font_tag, fill=(245, 220, 165))
|
||||
|
||||
# Title
|
||||
font_t = ImageFont.truetype(font_title, 68)
|
||||
draw.text((80, 165), "聚势共赢 · 开启数字化财富新篇", font=font_t, fill=(255, 240, 205))
|
||||
|
||||
# Subtitle
|
||||
font_s = ImageFont.truetype(font_text, 34)
|
||||
draw.text((80, 265), "零门槛赋能体系 | 高额佣金 · 裂变倍增 · 团队管理", font=font_s, fill=(195, 210, 235))
|
||||
|
||||
font_s2 = ImageFont.truetype(font_text, 28)
|
||||
draw.text((80, 325), "全链路数字分销 · 私域专属顾问 · 收益实时明细透明", font=font_s2, fill=(140, 165, 195))
|
||||
|
||||
# CTA Button
|
||||
draw.rounded_rectangle([80, 420, 460, 500], radius=40, fill=(212, 175, 110, 255))
|
||||
font_btn = ImageFont.truetype(font_text, 32)
|
||||
draw.text((120, 442), "立即进入团队中心 · 查阅权益 →", font=font_btn, fill=(15, 25, 45))
|
||||
|
||||
# Trust chips
|
||||
font_chip = ImageFont.truetype(font_text, 24)
|
||||
draw.text((80, 540), "✔ 专属推广海报 ✔ 团队收益实时结算 ✔ 尊崇合伙人礼遇", font=font_chip, fill=(185, 200, 225))
|
||||
|
||||
# Right graphic: Golden geometric emblem / shield
|
||||
emblem = Image.new('RGBA', (480, 480), (0, 0, 0, 0))
|
||||
e_draw = ImageDraw.Draw(emblem)
|
||||
|
||||
# Concentric rings & diamond
|
||||
e_draw.ellipse([40, 40, 440, 440], outline=(212, 175, 110, 160), width=4)
|
||||
e_draw.ellipse([80, 80, 400, 400], outline=(212, 175, 110, 100), width=2)
|
||||
e_draw.ellipse([120, 120, 360, 360], fill=(30, 50, 85, 180), outline=(212, 175, 110, 220), width=4)
|
||||
|
||||
font_emb = ImageFont.truetype(font_title, 48)
|
||||
font_emb_sub = ImageFont.truetype(font_text, 26)
|
||||
|
||||
e_draw.text((150, 180), "恒链合伙", font=font_emb, fill=(255, 235, 185))
|
||||
e_draw.text((170, 250), "VIP PARTNER", font=font_emb_sub, fill=(212, 175, 110))
|
||||
e_draw.text((160, 290), "共创 · 共享 · 共赢", font=font_emb_sub, fill=(180, 200, 230))
|
||||
|
||||
bg.paste(emblem, (850, 80), emblem)
|
||||
|
||||
bg.convert('RGB').save('/tmp/henglian_assets/banner_partner.jpg', quality=95)
|
||||
print("banner_partner.jpg generated")
|
||||
|
||||
if __name__ == '__main__':
|
||||
build_wine_banner()
|
||||
build_skincare_banner()
|
||||
build_partner_banner()
|
||||
|
||||
def build_menu_icons():
|
||||
font_menu = ImageFont.truetype(font_title, 42)
|
||||
font_sub = ImageFont.truetype(font_text, 22)
|
||||
|
||||
menus = [
|
||||
("menu_wine.png", "白酒", "典藏", (85, 25, 25), (212, 175, 110)),
|
||||
("menu_skincare.png", "美护", "赋活", (25, 65, 50), (130, 220, 185)),
|
||||
("menu_partner.png", "合伙", "中心", (25, 45, 80), (230, 200, 130)),
|
||||
("menu_gift.png", "尊享", "礼盒", (95, 45, 20), (245, 210, 150)),
|
||||
("menu_coupon.png", "领券", "福利", (90, 30, 60), (250, 180, 200)),
|
||||
]
|
||||
|
||||
for filename, t1, t2, bg_c, fg_c in menus:
|
||||
size = (160, 160)
|
||||
img = Image.new('RGBA', size, (0, 0, 0, 0))
|
||||
d = ImageDraw.Draw(img)
|
||||
|
||||
# Soft background circle with border
|
||||
d.ellipse([6, 6, 154, 154], fill=(*bg_c, 240), outline=(*fg_c, 200), width=3)
|
||||
d.ellipse([14, 14, 146, 146], outline=(*fg_c, 80), width=1)
|
||||
|
||||
# Text
|
||||
d.text((40, 42), t1, font=font_menu, fill=(*fg_c, 255))
|
||||
d.text((58, 96), t2, font=font_sub, fill=(240, 240, 240, 220))
|
||||
|
||||
img.save('/tmp/henglian_assets/' + filename)
|
||||
print(filename, "generated")
|
||||
|
||||
build_menu_icons()
|
||||
|
||||
def build_trust_bar():
|
||||
size = (1420, 110)
|
||||
img = Image.new('RGBA', size, (0, 0, 0, 0))
|
||||
d = ImageDraw.Draw(img)
|
||||
|
||||
# Rounded card background
|
||||
d.rounded_rectangle([4, 4, 1416, 106], radius=20, fill=(255, 255, 255, 255), outline=(230, 215, 190, 200), width=2)
|
||||
|
||||
font_bar = ImageFont.truetype(font_text, 28)
|
||||
|
||||
items = [
|
||||
("✦ 100% 官方正品", 120, (197, 160, 89)),
|
||||
("✦ 产地源头直采", 480, (197, 160, 89)),
|
||||
("✦ 顺丰冷链速达", 830, (197, 160, 89)),
|
||||
("✦ 尊崇私享服务", 1160, (197, 160, 89)),
|
||||
]
|
||||
|
||||
for text, x, col in items:
|
||||
d.text((x, 38), text, font=font_bar, fill=(50, 40, 35))
|
||||
|
||||
img.save('/tmp/henglian_assets/trust_bar.png')
|
||||
print("trust_bar.png generated")
|
||||
|
||||
build_trust_bar()
|
||||
在新工单中引用
屏蔽一个用户