站点:纯静态 HTML + CSS + 原生 JS,零框架零构建
页面:首页 / 合作品牌 / 荣誉资质 / 客户案例 / 关于我们
内容:
- site/ 站点本体(含已压缩 webp 图片,约 4.8 MB)
- deploy/ Docker Compose / Caddy / Apache 部署配置
- _tools/ 开发自检脚本与部署脚本
- Dockerfile 自建 nginx 镜像方案
- README.md 项目文档
设计:品牌黄 #ffc107 + 白,深蓝仅用于页脚;菜单长期置顶;
交互动效只加在关键词与区块级元素上。
素材说明:原始素材(背景图/老网站/方案/荣誉资质/合作品牌,约 290 MB)
未纳入版本控制,站点使用的是 site/assets 内已处理压缩的图片。
354 lines
14 KiB
Python
354 lines
14 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
炫恒网络官网 - 素材处理脚本
|
|
把原始素材转成站点可直接使用的 web 资源:
|
|
* 复制 + 压缩(webp / jpg / png),控制体积
|
|
* 生成 logo 多倍图与 favicon
|
|
* 生成 LQIP(超小模糊占位图)用于渐进式加载
|
|
运行: python tools/build_assets.py
|
|
"""
|
|
import base64
|
|
import io
|
|
import os
|
|
import shutil
|
|
import sys
|
|
|
|
from PIL import Image, ImageFilter
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
SITE = os.path.join(ROOT, "site")
|
|
ASSETS = os.path.join(SITE, "assets")
|
|
IMG = os.path.join(ASSETS, "img")
|
|
|
|
SRC_BRAND = os.path.join(ROOT, "合作品牌")
|
|
SRC_HONOR = os.path.join(ROOT, "荣誉资质")
|
|
SRC_BG = os.path.join(ROOT, "背景图")
|
|
SRC_BG_LB = os.path.join(SRC_BG, "轮播")
|
|
SRC_PLAN = os.path.join(ROOT, "方案")
|
|
SRC_CASE = os.path.join(ROOT, "老网站", "10.0.0.131", "10.0.0.131", "assets", "img",
|
|
"solution-details")
|
|
|
|
OUT_DIRS = [
|
|
IMG,
|
|
os.path.join(IMG, "brands"),
|
|
os.path.join(IMG, "honors"),
|
|
os.path.join(IMG, "hero"),
|
|
os.path.join(IMG, "cases"),
|
|
os.path.join(IMG, "solutions"),
|
|
os.path.join(IMG, "misc"),
|
|
]
|
|
|
|
LQIP = {}
|
|
|
|
|
|
def ensure_dirs():
|
|
for d in OUT_DIRS:
|
|
os.makedirs(d, exist_ok=True)
|
|
|
|
|
|
def save_webp(im, path, quality=80, method=6):
|
|
im.save(path, "WEBP", quality=quality, method=method)
|
|
|
|
|
|
def save_jpg(im, path, quality=82, progressive=True):
|
|
im.convert("RGB").save(path, "JPEG", quality=quality,
|
|
optimize=True, progressive=progressive)
|
|
|
|
|
|
def resize_to_width(im, width):
|
|
if im.width <= width:
|
|
return im.copy()
|
|
h = round(im.height * width / im.width)
|
|
return im.resize((width, h), Image.LANCZOS)
|
|
|
|
|
|
def cover_crop(im, tw, th):
|
|
"""按目标宽高比裁剪(居中),再缩放"""
|
|
im = im.convert("RGB")
|
|
tr = tw / th
|
|
r = im.width / im.height
|
|
if r > tr:
|
|
nw = round(im.height * tr)
|
|
left = (im.width - nw) // 2
|
|
im = im.crop((left, 0, left + nw, im.height))
|
|
else:
|
|
nh = round(im.width / tr)
|
|
top = max(0, (im.height - nh) // 2)
|
|
im = im.crop((0, top, im.width, top + nh))
|
|
return im.resize((tw, th), Image.LANCZOS)
|
|
|
|
|
|
def make_lqip(im, key, width=28):
|
|
small = resize_to_width(im.convert("RGB"), width)
|
|
small = small.filter(ImageFilter.GaussianBlur(1.2))
|
|
buf = io.BytesIO()
|
|
small.save(buf, "JPEG", quality=45)
|
|
LQIP[key] = "data:image/jpeg;base64," + base64.b64encode(buf.getvalue()).decode()
|
|
|
|
|
|
def log(msg):
|
|
print(msg, flush=True)
|
|
|
|
|
|
# ---------------------------------------------------------------- 品牌 logo
|
|
def build_brand_logos():
|
|
names = {
|
|
"锐捷.png": "ruijie",
|
|
"思科.png": "cisco",
|
|
"华三.png": "h3c",
|
|
"Microsoft.png": "microsoft",
|
|
"vmware.png": "vmware",
|
|
"radware.png": "radware",
|
|
"juniper.png": "juniper",
|
|
"citrix.png": "citrix",
|
|
}
|
|
for src, slug in names.items():
|
|
p = os.path.join(SRC_BRAND, src)
|
|
if not os.path.exists(p):
|
|
log(" ! 缺少 " + src)
|
|
continue
|
|
im = Image.open(p).convert("RGBA")
|
|
# 统一裁剪到内容边界并留白,让视觉尺寸更一致
|
|
bbox = im.split()[3].getbbox()
|
|
if bbox:
|
|
im = im.crop(bbox)
|
|
im = im.resize((320, round(320 * im.height / im.width)), Image.LANCZOS)
|
|
canvas = Image.new("RGBA", (360, 360), (0, 0, 0, 0))
|
|
canvas.paste(im, ((360 - im.width) // 2, (360 - im.height) // 2), im)
|
|
save_webp(canvas, os.path.join(IMG, "brands", slug + ".webp"), 88)
|
|
log(" brand " + slug)
|
|
|
|
|
|
# ---------------------------------------------------------------- 荣誉证书
|
|
def build_honors():
|
|
items = [
|
|
("荣誉资质/锐捷行业白金证书2026.png", "ruijie-industry", "锐捷行业白金证书"),
|
|
("荣誉资质/锐捷商业白金证书.png", "ruijie-business", "锐捷商业白金证书"),
|
|
("荣誉资质/锐捷服务商证书2026.png", "ruijie-service", "锐捷服务商证书"),
|
|
("荣誉资质/锐捷行业白金证书.png", "ruijie-industry-old", "锐捷行业白金证书(往期)"),
|
|
("荣誉资质/思科代理商证书.jpg", "cisco-agent", "思科代理商证书"),
|
|
("荣誉资质/H3C代理商证书.jpg", "h3c-agent", "H3C 代理商证书"),
|
|
("荣誉资质/juniper代理商证书.JPG", "juniper-agent", "Juniper 代理商证书"),
|
|
("荣誉资质/radware代理商证书.jpg", "radware-agent", "Radware 代理商证书"),
|
|
("荣誉资质/电信业务经营许可证.png", "telecom-license", "电信业务经营许可证"),
|
|
]
|
|
out = []
|
|
for rel, slug, title in items:
|
|
p = os.path.join(ROOT, rel)
|
|
if not os.path.exists(p):
|
|
log(" ! 缺少 " + rel)
|
|
continue
|
|
im = Image.open(p).convert("RGB")
|
|
big = resize_to_width(im, 1100)
|
|
save_webp(big, os.path.join(IMG, "honors", slug + ".webp"), 80)
|
|
thumb = resize_to_width(im, 560)
|
|
save_webp(thumb, os.path.join(IMG, "honors", slug + "-thumb.webp"), 78)
|
|
make_lqip(im, "honors/" + slug)
|
|
out.append(slug)
|
|
log(" honor %s %dx%d" % (slug, im.width, im.height))
|
|
return out
|
|
|
|
|
|
# ---------------------------------------------------------------- 个人认证证书
|
|
PERSON_GROUP_SLUG = {
|
|
"交换路由": "route",
|
|
"云桌面": "cloud",
|
|
"思科": "cisco",
|
|
"RCTE": "rcte",
|
|
"项目经理": "pm",
|
|
}
|
|
|
|
|
|
def person_slug(group, fn, i):
|
|
g = PERSON_GROUP_SLUG.get(group)
|
|
if g is None:
|
|
g = "g%02d" % (abs(hash(group)) % 90 + 10)
|
|
return "%s-%02d" % (g, i)
|
|
|
|
|
|
def build_person_certs():
|
|
"""老网站里的个人技术认证证书(另有目录保留原图,此处只做压缩副本)"""
|
|
base = os.path.join(ROOT, "老网站", "10.0.0.131", "10.0.0.131", "assets", "img", "about", "2")
|
|
if not os.path.isdir(base):
|
|
return
|
|
out = os.path.join(IMG, "honors", "person")
|
|
os.makedirs(out, exist_ok=True)
|
|
manifest = []
|
|
for group in sorted(os.listdir(base)):
|
|
gdir = os.path.join(base, group)
|
|
if not os.path.isdir(gdir):
|
|
continue
|
|
files = sorted(fn for fn in os.listdir(gdir)
|
|
if os.path.splitext(fn)[1].lower() in (".jpg", ".jpeg", ".png"))
|
|
for i, fn in enumerate(files, 1):
|
|
try:
|
|
im = Image.open(os.path.join(gdir, fn)).convert("RGB")
|
|
except Exception:
|
|
continue
|
|
slug = person_slug(group, fn, i)
|
|
save_webp(resize_to_width(im, 760), os.path.join(out, slug + ".webp"), 78)
|
|
make_lqip(im, "honors/person/" + slug)
|
|
manifest.append((group, fn, slug))
|
|
log(" person cert %s / %s -> %s" % (group, fn, slug))
|
|
with open(os.path.join(out, "manifest.tsv"), "w", encoding="utf-8") as f:
|
|
f.write("分组\t原始文件\t站点文件名\n")
|
|
for g, fn, slug in manifest:
|
|
f.write("%s\t%s\t%s.webp\n" % (g, fn, slug))
|
|
|
|
|
|
# ---------------------------------------------------------------- 头图 / 背景
|
|
def build_hero():
|
|
slides = [
|
|
("背景图/轮播/lunbo01.png", "hero-1", "构建高效互联的数字未来"),
|
|
("背景图/轮播/lunbo02.png", "hero-2", "稳定连接 · 高效网络"),
|
|
("背景图/轮播/lunbo03.png", "hero-3", "专业建设 · 简单连接"),
|
|
]
|
|
for rel, slug, _ in slides:
|
|
p = os.path.join(ROOT, rel)
|
|
if not os.path.exists(p):
|
|
log(" ! 缺少 " + rel)
|
|
continue
|
|
im = Image.open(p)
|
|
big = resize_to_width(im, 1920)
|
|
save_webp(big, os.path.join(IMG, "hero", slug + ".webp"), 80)
|
|
mid = resize_to_width(im, 1200)
|
|
save_webp(mid, os.path.join(IMG, "hero", slug + "-md.webp"), 76)
|
|
make_lqip(im, "hero/" + slug)
|
|
log(" hero %s" % slug)
|
|
|
|
# 深色科技背景(做 CTA / 页脚)
|
|
for rel, slug in [("背景图/2.png", "tech-dark"),
|
|
("背景图/ChatGPT Image 2026年6月8日 15_51_29(1).png", "tech-dark-2")]:
|
|
p = os.path.join(ROOT, rel)
|
|
if not os.path.exists(p):
|
|
continue
|
|
im = Image.open(p)
|
|
big = resize_to_width(im, 1600)
|
|
save_webp(big, os.path.join(IMG, "hero", slug + ".webp"), 78)
|
|
make_lqip(im, "hero/" + slug)
|
|
log(" bg " + slug)
|
|
|
|
|
|
# ---------------------------------------------------------------- 方案图
|
|
def build_solutions():
|
|
items = [
|
|
("方案/地产.png", "realestate", "地产商业综合体"),
|
|
("方案/工业.png", "industry", "工厂制造业"),
|
|
("方案/金融.png", "finance", "金融行业"),
|
|
("方案/三甲医院.png", "medical", "医疗行业"),
|
|
]
|
|
for rel, slug, _ in items:
|
|
p = os.path.join(ROOT, rel)
|
|
if not os.path.exists(p):
|
|
log(" ! 缺少 " + rel)
|
|
continue
|
|
im = Image.open(p)
|
|
save_webp(cover_crop(im, 1000, 700), os.path.join(IMG, "solutions", slug + ".webp"), 78)
|
|
make_lqip(im, "solutions/" + slug)
|
|
log(" solution " + slug)
|
|
|
|
|
|
# ---------------------------------------------------------------- 案例图
|
|
def build_cases():
|
|
pairs = [
|
|
(os.path.join(SRC_CASE, "地产商业综合体案例.jpg"), "realestate", 1400),
|
|
(os.path.join(SRC_CASE, "工厂制造业案例", "药企工厂案例.jpg"), "industry", 1400),
|
|
(os.path.join(SRC_CASE, "工厂制造业案例", "图片1.png"), "industry-2", 1200),
|
|
(os.path.join(SRC_CASE, "金融公司案例", "1.png"), "finance", 1200),
|
|
(os.path.join(SRC_CASE, "金融公司案例", "c26fc86b1715c1963021096104b0673.png"), "finance-2", 1200),
|
|
(os.path.join(SRC_CASE, "金融公司案例", "图片2.png"), "finance-3", 1200),
|
|
(os.path.join(SRC_CASE, "商业连锁门店案例", "药业连锁门店案例.jpg"), "retail", 1400),
|
|
(os.path.join(SRC_CASE, "商业连锁门店案例",
|
|
"主要中标项目2 XX知名快消品牌全国门店网络系统建设及运维项目.jpg"), "retail-2", 1400),
|
|
(os.path.join(SRC_CASE, "三甲医院案例", "三甲医院案例1.jpg"), "medical", 1400),
|
|
(os.path.join(SRC_CASE, "三甲医院案例2", "三甲医院案例2.jpg"), "medical-2", 1400),
|
|
(os.path.join(SRC_CASE, "三甲医院案例2", "1.png"), "medical-3", 1200),
|
|
(os.path.join(ROOT, "成功案例-吾悦架构图.png"), "wuyue-arch", 1100),
|
|
(os.path.join(ROOT, "三甲医院-封面.jpeg"), "medical-cover", 1000),
|
|
]
|
|
for path, slug, maxw in pairs:
|
|
if not os.path.exists(path):
|
|
log(" ! 缺少 " + os.path.basename(path))
|
|
continue
|
|
im = Image.open(path).convert("RGB")
|
|
big = resize_to_width(im, maxw)
|
|
save_webp(big, os.path.join(IMG, "cases", slug + ".webp"), 80)
|
|
thumb = cover_crop(im, 900, 620) if slug != "wuyue-arch" else resize_to_width(im, 900)
|
|
save_webp(thumb, os.path.join(IMG, "cases", slug + "-thumb.webp"), 76)
|
|
make_lqip(im, "cases/" + slug)
|
|
log(" case %s %dx%d" % (slug, im.width, im.height))
|
|
|
|
|
|
# ---------------------------------------------------------------- LOGO & favicon
|
|
def build_logo():
|
|
src = os.path.join(ROOT, "XH-logo.png")
|
|
im = Image.open(src).convert("RGBA")
|
|
# 裁掉多余透明边缘,再按倍率放大(几何图形放大后依然干净)
|
|
bbox = im.split()[3].getbbox()
|
|
if bbox:
|
|
im = im.crop(bbox)
|
|
for scale, name in ((1, "logo-128.png"), (2, "logo-256.png"), (3, "logo-384.png")):
|
|
size = 128 * scale
|
|
h = round(size * im.height / im.width)
|
|
im.resize((size, h), Image.LANCZOS).save(os.path.join(IMG, "misc", name))
|
|
# 白色版(用于深色页脚):把非透明像素统一为白色
|
|
white = Image.new("RGBA", im.size, (255, 255, 255, 0))
|
|
alpha = im.split()[3]
|
|
solid = Image.new("RGBA", im.size, (255, 255, 255, 255))
|
|
white = Image.composite(solid, white, alpha)
|
|
white.resize((384, round(384 * white.height / white.width)), Image.LANCZOS).save(
|
|
os.path.join(IMG, "misc", "logo-white-384.png"))
|
|
|
|
# favicon
|
|
fav = os.path.join(SITE, "favicon.ico")
|
|
base = im.copy()
|
|
base.thumbnail((256, 256), Image.LANCZOS)
|
|
base.save(fav, sizes=[(16, 16), (32, 32), (48, 48), (64, 64)])
|
|
for s in (32, 180, 192, 512):
|
|
ic = im.resize((s, round(s * im.height / im.width)), Image.LANCZOS)
|
|
ic.save(os.path.join(IMG, "misc", "favicon-%dx%d.png" % (s, s)))
|
|
log(" logo + favicon")
|
|
|
|
|
|
# ---------------------------------------------------------------- LQIP 输出
|
|
def write_lqip():
|
|
out = os.path.join(ASSETS, "lqip.css")
|
|
with open(out, "w", encoding="utf-8") as f:
|
|
f.write("/* 自动生成:图片模糊占位(渐进加载) */\n")
|
|
for k, v in sorted(LQIP.items()):
|
|
f.write('[data-lqip="%s"]{background-image:url(%s)}\n' % (k, v))
|
|
log(" lqip.css (%d 条)" % len(LQIP))
|
|
|
|
# 同时输出一份 JS map,供 lightbox 使用
|
|
out2 = os.path.join(ASSETS, "lqip.js")
|
|
with open(out2, "w", encoding="utf-8") as f:
|
|
f.write("window.XH_LQIP = {\n")
|
|
for k, v in sorted(LQIP.items()):
|
|
f.write(' "%s": "%s",\n' % (k, v))
|
|
f.write("};\n")
|
|
|
|
|
|
def main():
|
|
ensure_dirs()
|
|
log("== 处理品牌 logo ==")
|
|
build_brand_logos()
|
|
log("== 处理荣誉证书 ==")
|
|
build_honors()
|
|
log("== 处理个人认证 ==")
|
|
build_person_certs()
|
|
log("== 处理头图 ==")
|
|
build_hero()
|
|
log("== 处理方案图 ==")
|
|
build_solutions()
|
|
log("== 处理案例图 ==")
|
|
build_cases()
|
|
log("== 处理 LOGO / favicon ==")
|
|
build_logo()
|
|
log("== 生成模糊占位 ==")
|
|
write_lqip()
|
|
log("完成。")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|