Files
dsh f81d41c5ca 初始化:炫恒网络官网(五个页面)
站点:纯静态 HTML + CSS + 原生 JS,零框架零构建
页面:首页 / 合作品牌 / 荣誉资质 / 客户案例 / 关于我们

内容:
- site/          站点本体(含已压缩 webp 图片,约 4.8 MB)
- deploy/        Docker Compose / Caddy / Apache 部署配置
- _tools/        开发自检脚本与部署脚本
- Dockerfile     自建 nginx 镜像方案
- README.md      项目文档

设计:品牌黄 #ffc107 + 白,深蓝仅用于页脚;菜单长期置顶;
      交互动效只加在关键词与区块级元素上。

素材说明:原始素材(背景图/老网站/方案/荣誉资质/合作品牌,约 290 MB)
         未纳入版本控制,站点使用的是 site/assets 内已处理压缩的图片。
2026-10-13 16:12:41 +08:00

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()