feat: 初始化画个标仓库 - AI驱动设计系统(BYOK模式) - 224源/371504图标+25海报源+130技能
This commit is contained in:
+199
@@ -0,0 +1,199 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/svg+xml" href="logo/favicon.svg">
|
||||
<title>画个标 · 智能海报设计</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: "PingFang SC", "Microsoft YaHei", "Segoe UI", sans-serif; font-size: 14px; color: #1f2937; background: #f3f4f6; min-height: 100vh; }
|
||||
a { color: #4f46e5; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
.topbar { display: flex; align-items: center; justify-content: space-between; padding: 0 20px; height: 54px; background: linear-gradient(90deg, #1e1b4b, #312e81); color: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.15); position: sticky; top: 0; z-index: 20; }
|
||||
.topbar .brand { display: flex; align-items: center; gap: 10px; }
|
||||
.topbar .brand .icon { font-size: 20px; }
|
||||
.topbar .brand .name { font-size: 17px; font-weight: 700; letter-spacing: 1px; }
|
||||
.topbar .brand .tag { font-size: 11px; color: #c7d2fe; background: rgba(255,255,255,.12); padding: 2px 8px; border-radius: 20px; }
|
||||
.topbar .nav { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
|
||||
.topbar .nav a { color: #e0e7ff; font-size: 13px; padding: 6px 12px; border: 1px solid rgba(255,255,255,.35); border-radius: 6px; background: rgba(255,255,255,.1); transition: background .15s; }
|
||||
.topbar .nav a:hover { background: rgba(255,255,255,.22); text-decoration: none; }
|
||||
.topbar .nav a.active { background: #fff; color: #312e81; font-weight: 700; }
|
||||
.workspace { display: grid; grid-template-columns: 320px 1fr; gap: 16px; max-width: 1200px; margin: 22px auto 60px; padding: 0 16px; }
|
||||
@media (max-width: 820px) { .workspace { grid-template-columns: 1fr; } }
|
||||
.config { background: #fff; border-radius: 12px; padding: 16px; box-shadow: 0 1px 3px rgba(0,0,0,.08); height: fit-content; }
|
||||
.config .label { font-size: 13px; color: #6b7280; margin: 12px 0 6px; }
|
||||
.config .label:first-child { margin-top: 0; }
|
||||
.spec-tabs { display: flex; flex-wrap: wrap; gap: 6px; }
|
||||
.spec-tab { font-size: 12px; padding: 5px 10px; border: 1px solid #e5e7eb; border-radius: 20px; cursor: pointer; background: #fff; color: #4b5563; }
|
||||
.spec-tab.active { background: #4f46e5; color: #fff; border-color: #4f46e5; }
|
||||
.config input, .config textarea { width: 100%; padding: 8px 10px; border: 1px solid #e5e7eb; border-radius: 8px; font-size: 13px; font-family: inherit; }
|
||||
.config textarea { resize: vertical; min-height: 60px; }
|
||||
.btn-primary { display: block; width: 100%; margin-top: 16px; padding: 10px; background: #4f46e5; color: #fff; border: none; border-radius: 8px; font-size: 14px; cursor: pointer; }
|
||||
.btn-primary:disabled { opacity: .6; cursor: not-allowed; }
|
||||
.plans { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 14px; align-content: start; }
|
||||
.plan-card { background: #fff; border-radius: 12px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,.08); }
|
||||
.plan-card .svg-wrap { display: flex; justify-content: center; background: #f8fafc; padding: 10px; }
|
||||
.plan-card .svg-wrap svg { max-height: 260px; width: auto; height: auto; }
|
||||
.plan-card .ops { display: flex; gap: 6px; padding: 10px; border-top: 1px solid #f1f5f9; }
|
||||
.plan-card .ops button { flex: 1; padding: 6px; border: 1px solid #e5e7eb; border-radius: 6px; background: #fff; cursor: pointer; font-size: 12px; }
|
||||
.plan-card .concept { padding: 0 10px 10px; font-size: 12px; color: #6b7280; line-height: 1.5; }
|
||||
.empty { grid-column: 1/-1; text-align: center; color: #6b7280; padding: 60px 20px; background: #fff; border-radius: 12px; border: 1px dashed #c7d2fe; }
|
||||
.toast { position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%); background: #111827; color: #fff; padding: 10px 18px; border-radius: 8px; font-size: 13px; z-index: 50; }
|
||||
.hidden { display: none !important; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- ===== 顶部工具栏 ===== -->
|
||||
<header class="topbar">
|
||||
<div class="brand">
|
||||
<img class="icon" src="logo/logo-mark.svg" alt="画个标" width="20" height="20">
|
||||
<span class="name">画个标</span>
|
||||
<span class="tag">✨ 海报设计</span>
|
||||
</div>
|
||||
<nav class="nav">
|
||||
<a href="index.html" title="LOGO 设计工作台">🎨 LOGO</a>
|
||||
<a href="avatar.html" title="头像设计工作台">🖼 头像</a>
|
||||
<a class="active" href="poster.html" title="海报设计工作台">📰 海报</a>
|
||||
<a href="emoji.html" title="动态表情设计工作台">😀 表情</a>
|
||||
<a href="skills.html" title="共享技能中心">🧩 技能中心</a>
|
||||
<a href="poster-skills.html" title="海报技能中心">🧩 海报技能</a>
|
||||
<a href="sources.html" title="图标素材中心">📚 素材中心</a>
|
||||
<a href="poster-sources.html" title="海报素材中心">📚 海报素材</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div class="workspace">
|
||||
<!-- 左:配置 -->
|
||||
<aside class="config">
|
||||
<div class="label">📐 画布规格</div>
|
||||
<div class="spec-tabs" id="spec-tabs"></div>
|
||||
|
||||
<div class="label">主标题</div>
|
||||
<input type="text" id="p-title" maxlength="40" placeholder="如:夏季狂欢节">
|
||||
|
||||
<div class="label">副标题(可选)</div>
|
||||
<input type="text" id="p-sub" maxlength="60" placeholder="如:全场 5 折起">
|
||||
|
||||
<div class="label">主题描述(AI 设计依据)</div>
|
||||
<textarea id="p-desc" maxlength="200" placeholder="如:面向年轻人的夏季促销活动,热闹活泼,暖色系…"></textarea>
|
||||
|
||||
<button class="btn-primary" id="btn-gen">🤖 AI 生成海报</button>
|
||||
</aside>
|
||||
|
||||
<!-- 右:方案库 -->
|
||||
<div class="plans" id="plans">
|
||||
<div class="empty">🎯 填写主题信息,点击「AI 生成海报」</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="toast" class="toast hidden"></div>
|
||||
|
||||
<script src="js/data/poster-sources.js"></script>
|
||||
<script src="js/data/poster-builtin.js?v=20260823e"></script>
|
||||
<script src="js/core/posterlib.js?v=20260823e"></script>
|
||||
<script src="js/core/ai.js?v=20260927a"></script>
|
||||
<script src="js/core/poster-store.js"></script>
|
||||
<script src="js/core/poster-generator.js"></script>
|
||||
<script src="js/core/poster-renderer.js"></script>
|
||||
<script src="js/core/poster-exporter.js"></script>
|
||||
<script src="js/core/poster-ai.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
'use strict';
|
||||
function toast(msg) {
|
||||
const el = document.getElementById('toast');
|
||||
el.textContent = msg; el.classList.remove('hidden');
|
||||
setTimeout(() => el.classList.add('hidden'), 2200);
|
||||
}
|
||||
|
||||
function renderSpecs() {
|
||||
const wrap = document.getElementById('spec-tabs');
|
||||
wrap.innerHTML = HGB_PosterStore.SPECS.map(s =>
|
||||
`<button class="spec-tab ${s.id === HGB_PosterStore.get().specId ? 'active' : ''}" data-spec="${s.id}">${s.name}</button>`).join('');
|
||||
wrap.querySelectorAll('.spec-tab').forEach(b => b.addEventListener('click', () => {
|
||||
HGB_PosterStore.set({ specId: b.dataset.spec });
|
||||
renderSpecs();
|
||||
// 已有方案重新按新画布渲染
|
||||
HGB_PosterStore.set({ plans: HGB_PosterStore.get().plans.map(p => Object.assign({}, p, {
|
||||
elements: HGB_PosterGenerator.generate(HGB_PosterStore.spec(), HGB_PosterGenerator.defaultDecision(
|
||||
(HGB_PosterStore.get().title || ''), (HGB_PosterStore.get().sub || '')))
|
||||
})) });
|
||||
renderPlans();
|
||||
}));
|
||||
}
|
||||
|
||||
function inlineSvg(plan) {
|
||||
return HGB_PosterExporter.buildSvg(HGB_PosterStore.SPECS.find(s => s.id === plan.specId) || HGB_PosterStore.spec(), plan.elements);
|
||||
}
|
||||
|
||||
function renderPlans() {
|
||||
const wrap = document.getElementById('plans');
|
||||
const plans = HGB_PosterStore.get().plans;
|
||||
if (!plans.length) {
|
||||
wrap.innerHTML = '<div class="empty">🎯 填写主题信息,点击「AI 生成海报」</div>';
|
||||
return;
|
||||
}
|
||||
wrap.innerHTML = plans.map((p, i) => `
|
||||
<div class="plan-card">
|
||||
<div class="svg-wrap">${inlineSvg(p)}</div>
|
||||
<div class="concept">${p.name}${p.concept ? ' · ' + p.concept.replace(/[<>&"]/g, '') : ''}</div>
|
||||
<div class="ops">
|
||||
<button data-act="svg" data-i="${i}">导出 SVG</button>
|
||||
<button data-act="png" data-i="${i}">导出 PNG</button>
|
||||
</div>
|
||||
</div>`).join('');
|
||||
wrap.querySelectorAll('button[data-act]').forEach(b => b.addEventListener('click', async () => {
|
||||
const p = plans[parseInt(b.dataset.i, 10)];
|
||||
const spec = HGB_PosterStore.SPECS.find(s => s.id === p.specId) || HGB_PosterStore.spec();
|
||||
const base = (p.name || '海报');
|
||||
if (b.dataset.act === 'svg') { HGB_PosterExporter.exportSvg(spec, p.elements, base); toast('✅ 已导出 SVG'); }
|
||||
else { await HGB_PosterExporter.exportPng(spec, p.elements, base, 1); toast('✅ 已导出 PNG'); }
|
||||
}));
|
||||
}
|
||||
|
||||
function generateLocal(count) {
|
||||
const title = (document.getElementById('p-title').value || '我的海报').trim();
|
||||
const sub = document.getElementById('p-sub').value.trim();
|
||||
const palettes = HGB_PosterGenerator.PALETTES;
|
||||
const layouts = HGB_PosterGenerator.LAYOUTS;
|
||||
const spec = HGB_PosterStore.spec();
|
||||
const plans = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
const d = HGB_PosterGenerator.defaultDecision(title, sub);
|
||||
d.paletteId = palettes[i % palettes.length].id;
|
||||
d.layoutId = layouts[i % layouts.length].id;
|
||||
plans.push({ id: 'p_' + i, name: layouts[i % layouts.length].name + ' · ' + palettes[i % palettes.length].name, concept: '内置引擎兜底', elements: HGB_PosterGenerator.generate(spec, d), specId: spec.id });
|
||||
}
|
||||
return plans;
|
||||
}
|
||||
|
||||
async function generate() {
|
||||
const btn = document.getElementById('btn-gen');
|
||||
btn.disabled = true; btn.textContent = '生成中…';
|
||||
const opt = {
|
||||
industry: '通用',
|
||||
title: document.getElementById('p-title').value.trim(),
|
||||
sub: document.getElementById('p-sub').value.trim(),
|
||||
desc: document.getElementById('p-desc').value.trim(),
|
||||
count: 4
|
||||
};
|
||||
try {
|
||||
const r = await HGB_PosterAI.generatePostersAI(opt);
|
||||
HGB_PosterStore.set({ plans: r.plans, title: opt.title, sub: opt.sub });
|
||||
} catch (e) {
|
||||
// AI 失败 → 降级内置引擎(保证页面不空白)
|
||||
HGB_PosterStore.set({ plans: generateLocal(4), title: opt.title, sub: opt.sub });
|
||||
toast('⚠️ AI 失败,已降级为内置引擎');
|
||||
}
|
||||
renderPlans();
|
||||
btn.disabled = false; btn.textContent = '🤖 AI 生成海报';
|
||||
}
|
||||
|
||||
document.getElementById('btn-gen').addEventListener('click', generate);
|
||||
renderSpecs();
|
||||
renderPlans();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user