文件
nahaichuan/tests/generate_article_covers.py

227 行
12 KiB
Python

# -*- coding: utf-8 -*-
"""
生成文章资讯专属现代中式极简矢量封面图 (Concise Article Covers Generator)
彻底杜绝文字介绍类图片,纯粹以现代中式浅色专业风空间几何、流光、细线窗棂与单字印章呈现。
尺寸 600x380,浅色专业底色 #FFFFFF / #F8FAFC,主色 #2563EB,辅色 #06B6D4 / #7C3AED。
"""
import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TARGET_DIR = os.path.join(BASE_DIR, 'public', 'assets', 'addons', 'official', 'images', 'articles')
os.makedirs(TARGET_DIR, exist_ok=True)
COMMON_DEFS = '''
<linearGradient id="artBgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#FFFFFF"/>
<stop offset="100%" stop-color="#F8FAFC"/>
</linearGradient>
<linearGradient id="artBlue" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#2563EB"/>
<stop offset="100%" stop-color="#1D4ED8"/>
</linearGradient>
<linearGradient id="artCyan" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#06B6D4"/>
<stop offset="100%" stop-color="#0EA5E9"/>
</linearGradient>
<linearGradient id="artPurple" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#7C3AED"/>
<stop offset="100%" stop-color="#6366F1"/>
</linearGradient>
<linearGradient id="artGlass" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#FFFFFF" stop-opacity="0.85"/>
<stop offset="100%" stop-color="#F1F5F9" stop-opacity="0.6"/>
</linearGradient>
<pattern id="artLattice" width="24" height="24" patternUnits="userSpaceOnUse">
<path d="M 24 0 L 0 0 0 24" fill="none" stroke="#E2E8F0" stroke-width="0.75" stroke-dasharray="3,3"/>
</pattern>
<filter id="artShadow" x="-10%" y="-10%" width="120%" height="120%">
<feDropShadow dx="0" dy="6" stdDeviation="10" flood-color="#0F172A" flood-opacity="0.06"/>
</filter>
<filter id="artGlow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="10" result="blur"/>
<feComposite in="SourceGraphic" in2="blur" operator="over"/>
</filter>
'''
def make_art_seal(x, y, char):
return f'''
<g transform="translate({x}, {y})">
<rect width="24" height="24" rx="4" fill="#FEF2F2" stroke="#DC2626" stroke-width="1.2"/>
<rect x="2" y="2" width="20" height="20" rx="2" fill="none" stroke="#DC2626" stroke-width="0.5" stroke-opacity="0.5"/>
<text x="12" y="17" fill="#DC2626" font-family="'Noto Serif SC', 'SimSun', serif" font-size="12" font-weight="700" text-anchor="middle">{char}</text>
</g>
'''
def create_base_svg(seal_char, visual_elements):
return f'''<svg width="600" height="380" viewBox="0 0 600 380" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
{COMMON_DEFS}
</defs>
<rect width="600" height="380" rx="8" fill="url(#artBgGrad)"/>
<rect width="600" height="380" rx="8" fill="url(#artLattice)" opacity="0.6"/>
<rect x="1" y="1" width="598" height="378" rx="7" fill="none" stroke="#E2E8F0" stroke-width="1.5"/>
{make_art_seal(554, 20, seal_char)}
<g transform="translate(300, 190)">
{visual_elements}
</g>
</svg>'''
articles = {
# 1. 微服务分布式事务
'article_microservice.svg': ('枢', '''
<ellipse cx="0" cy="50" rx="190" ry="75" fill="none" stroke="#E2E8F0" stroke-width="1.2" stroke-dasharray="5,4"/>
<g transform="translate(-70, 0)" filter="url(#artShadow)">
<polygon points="0,-28 35,-10 0,10 -35,-10" fill="url(#artBlue)"/>
<polygon points="-35,-10 0,10 0,34 -35,14" fill="#1D4ED8"/>
<polygon points="0,10 35,-10 35,14 0,34" fill="#1E40AF"/>
</g>
<g transform="translate(70, 0)" filter="url(#artShadow)">
<polygon points="0,-28 35,-10 0,10 -35,-10" fill="url(#artCyan)"/>
<polygon points="-35,-10 0,10 0,34 -35,14" fill="#0891B2"/>
<polygon points="0,10 35,-10 35,14 0,34" fill="#0E7490"/>
</g>
<g transform="translate(0, -50)" filter="url(#artShadow)">
<polygon points="0,-24 28,-8 0,8 -28,-8" fill="url(#artGlass)" stroke="#2563EB" stroke-width="1.2"/>
<circle cx="0" cy="0" r="4" fill="#2563EB"/>
</g>
<path d="M -70 0 Q 0 -40 70 0" fill="none" stroke="url(#artBlue)" stroke-width="2" stroke-linecap="round"/>
<path d="M 70 15 Q 0 55 -70 15" fill="none" stroke="url(#artCyan)" stroke-width="1.8" stroke-dasharray="4,3"/>
'''),
# 2. 跨端APP与小程序
'article_cross_app.svg': ('端', '''
<ellipse cx="0" cy="60" rx="180" ry="65" fill="none" stroke="#E2E8F0" stroke-width="1.2" stroke-dasharray="4,4"/>
<g transform="translate(-65, -10)" filter="url(#artShadow)">
<rect x="-45" y="-65" width="90" height="130" rx="6" fill="url(#artGlass)" stroke="#CBD5E1" stroke-width="1.2"/>
<rect x="-35" y="-55" width="70" height="95" rx="3" fill="#FFFFFF" stroke="#E2E8F0"/>
<circle cx="0" cy="52" r="3" fill="#94A3B8"/>
</g>
<g transform="translate(65, 10)" filter="url(#artShadow)">
<rect x="-32" y="-70" width="64" height="140" rx="10" fill="#FFFFFF" stroke="#2563EB" stroke-width="1.5"/>
<rect x="-26" y="-55" width="52" height="110" rx="5" fill="url(#artBgGrad)"/>
<circle cx="0" cy="-20" r="14" fill="url(#artPurple)"/>
</g>
<path d="M -20 -15 C 10 -40 30 -30 65 -15" fill="none" stroke="url(#artCyan)" stroke-width="2" stroke-linecap="round"/>
<circle cx="20" cy="-25" r="4" fill="#06B6D4" filter="url(#artGlow)"/>
'''),
# 3. 私域AI智能体落地
'article_ai_agent.svg': ('智', '''
<circle cx="0" cy="0" r="100" fill="#FAF5FF" opacity="0.7"/>
<ellipse cx="0" cy="0" rx="150" ry="60" fill="none" stroke="#E2E8F0" stroke-width="1.2" stroke-dasharray="5,4" transform="rotate(-30)"/>
<ellipse cx="0" cy="0" rx="150" ry="60" fill="none" stroke="#E2E8F0" stroke-width="1.2" stroke-dasharray="5,4" transform="rotate(30)"/>
<polygon points="0,-50 45,-20 45,25 0,55 -45,25 -45,-20" fill="none" stroke="#93C5FD" stroke-width="1.2"/>
<circle cx="0" cy="0" r="24" fill="url(#artPurple)" filter="url(#artShadow)"/>
<circle cx="0" cy="0" r="15" fill="url(#artBlue)"/>
<circle cx="0" cy="0" r="6" fill="#FFFFFF"/>
<circle cx="-130" cy="0" r="5" fill="#7C3AED" transform="rotate(-30)"/>
<circle cx="130" cy="0" r="5" fill="#06B6D4" transform="rotate(30)"/>
'''),
# 4. 断网离线收银
'article_pos_offline.svg': ('笃', '''
<circle cx="0" cy="0" r="120" fill="none" stroke="#E2E8F0" stroke-width="1" stroke-dasharray="4,4"/>
<circle cx="0" cy="0" r="90" fill="none" stroke="#CBD5E1" stroke-width="1.2"/>
<path d="M 0 -70 L 45 -45 V 0 C 45 35 0 70 0 70 C 0 70 -45 35 -45 0 V -45 Z" fill="url(#artGlass)" stroke="#CBD5E1" stroke-width="1.5" filter="url(#artShadow)"/>
<circle cx="0" cy="0" r="22" fill="url(#artBlue)" filter="url(#artGlow)"/>
<circle cx="0" cy="0" r="10" fill="#FFFFFF"/>
<line x1="-90" y1="0" x2="-22" y2="0" stroke="url(#artCyan)" stroke-width="2" stroke-linecap="round"/>
<line x1="22" y1="0" x2="90" y2="0" stroke="url(#artBlue)" stroke-width="2" stroke-linecap="round"/>
<circle cx="-90" cy="0" r="4.5" fill="#06B6D4"/>
<circle cx="90" cy="0" r="4.5" fill="#2563EB"/>
'''),
# 5. 进销存BOM物料匹配
'article_erp_bom.svg': ('准', '''
<ellipse cx="0" cy="55" rx="170" ry="60" fill="none" stroke="#E2E8F0" stroke-width="1.2" stroke-dasharray="4,4"/>
<g transform="translate(-65, 15)" filter="url(#artShadow)">
<polygon points="0,-25 30,-8 0,8 -30,-8" fill="url(#artGlass)" stroke="#CBD5E1" stroke-width="1.2"/>
<polygon points="-30,-8 0,8 0,28 -30,12" fill="#E2E8F0"/>
<polygon points="0,8 30,-8 30,12 0,28" fill="#F1F5F9"/>
</g>
<g transform="translate(65, 15)" filter="url(#artShadow)">
<polygon points="0,-25 30,-8 0,8 -30,-8" fill="url(#artGlass)" stroke="#CBD5E1" stroke-width="1.2"/>
<polygon points="-30,-8 0,8 0,28 -30,12" fill="#E2E8F0"/>
<polygon points="0,8 30,-8 30,12 0,28" fill="#F1F5F9"/>
</g>
<g transform="translate(0, -30)" filter="url(#artShadow)">
<circle cx="0" cy="0" r="32" fill="none" stroke="#2563EB" stroke-width="1.2" stroke-dasharray="4,4"/>
<circle cx="0" cy="0" r="22" fill="url(#artBlue)"/>
<circle cx="0" cy="0" r="8" fill="#FFFFFF"/>
</g>
<path d="M -65 5 Q 0 -15 65 5" fill="none" stroke="url(#artCyan)" stroke-width="2" stroke-linecap="round"/>
'''),
# 6. 安全合规白皮书
'article_security_whitepaper.svg': ('安', '''
<polygon points="0,-95 82,-47 82,47 0,95 -82,47 -82,-47" fill="none" stroke="#E2E8F0" stroke-width="1.2" stroke-dasharray="4,4"/>
<polygon points="0,-75 65,-37 65,37 0,75 -65,37 -65,-37" fill="url(#artGlass)" stroke="#CBD5E1" stroke-width="1.5" filter="url(#artShadow)"/>
<circle cx="0" cy="0" r="26" fill="url(#artPurple)"/>
<circle cx="0" cy="0" r="14" fill="url(#artBlue)"/>
<circle cx="0" cy="0" r="6" fill="#FFFFFF"/>
<circle cx="0" cy="-75" r="4" fill="#2563EB"/>
<circle cx="65" cy="-37" r="4" fill="#7C3AED"/>
<circle cx="65" cy="37" r="4" fill="#06B6D4"/>
<circle cx="0" cy="75" r="4" fill="#2563EB"/>
<circle cx="-65" cy="37" r="4" fill="#7C3AED"/>
<circle cx="-65" cy="-37" r="4" fill="#06B6D4"/>
'''),
# 7. 数字化转型路径
'article_southwest_digital.svg': ('融', '''
<ellipse cx="0" cy="20" rx="180" ry="80" fill="none" stroke="#E2E8F0" stroke-width="1.2" stroke-dasharray="5,4"/>
<g filter="url(#artShadow)">
<circle cx="0" cy="0" r="28" fill="url(#artBlue)"/>
<circle cx="0" cy="0" r="12" fill="#FFFFFF"/>
</g>
<g transform="translate(-110, -25)">
<circle cx="0" cy="0" r="16" fill="url(#artGlass)" stroke="#2563EB" stroke-width="1.2"/>
<circle cx="0" cy="0" r="5" fill="#2563EB"/>
<line x1="0" y1="0" x2="85" y2="20" stroke="#93C5FD" stroke-width="1.5" stroke-dasharray="3,3"/>
</g>
<g transform="translate(110, -25)">
<circle cx="0" cy="0" r="16" fill="url(#artGlass)" stroke="#06B6D4" stroke-width="1.2"/>
<circle cx="0" cy="0" r="5" fill="#06B6D4"/>
<line x1="0" y1="0" x2="-85" y2="20" stroke="#93C5FD" stroke-width="1.5" stroke-dasharray="3,3"/>
</g>
<g transform="translate(0, 70)">
<circle cx="0" cy="0" r="14" fill="url(#artGlass)" stroke="#7C3AED" stroke-width="1"/>
<circle cx="0" cy="0" r="4.5" fill="#7C3AED"/>
<line x1="0" y1="0" x2="0" y2="-42" stroke="#CBD5E1" stroke-width="1.2"/>
</g>
'''),
# 8. TDD 工程规范
'article_tdd_standards.svg': ('律', '''
<circle cx="0" cy="0" r="110" fill="none" stroke="#E2E8F0" stroke-width="1" stroke-dasharray="5,5"/>
<g transform="translate(-60, 0)" filter="url(#artShadow)">
<circle cx="0" cy="0" r="30" fill="url(#artBlue)"/>
<circle cx="0" cy="0" r="12" fill="#FFFFFF"/>
<circle cx="0" cy="0" r="5" fill="#1D4ED8"/>
</g>
<g transform="translate(60, 0)" filter="url(#artShadow)">
<circle cx="0" cy="0" r="30" fill="url(#artCyan)"/>
<circle cx="0" cy="0" r="12" fill="#FFFFFF"/>
<circle cx="0" cy="0" r="5" fill="#0891B2"/>
</g>
<path d="M -30 -15 C 0 -35 0 -35 30 -15" fill="none" stroke="url(#artBlue)" stroke-width="2.2" stroke-linecap="round"/>
<path d="M 30 15 C 0 35 0 35 -30 15" fill="none" stroke="url(#artCyan)" stroke-width="2.2" stroke-linecap="round"/>
<circle cx="0" cy="-26" r="4" fill="#2563EB" filter="url(#artGlow)"/>
<circle cx="0" cy="26" r="4" fill="#06B6D4" filter="url(#artGlow)"/>
''')
}
def generate_all_articles():
for fname, (seal, vis) in articles.items():
content = create_base_svg(seal, vis)
path = os.path.join(TARGET_DIR, fname)
with open(path, 'w', encoding='utf-8') as f:
f.write(content.strip())
print(f"Generated concise article cover: {fname} ({len(content)} bytes)")
if __name__ == '__main__':
generate_all_articles()