Files
drawlogo/js/skills/core.js
T

145 lines
7.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 画个标 · 技能资源库(核心:注册表 + 通用工具)
* 每个技能采用标准化标识:id / name / category / emoji / scene / summary / params / source / run / test
* 分类:layout(构图) color(配色) typography(字体) icon(图标) ai(智能工作流) presentation(展示) export(导出)
*
* 技能按分类拆分文件(js/skills/{category}.js),各自调用 HGB_Skills.register([...]) 注册:
* layout.js color.js typography.js icon.js ai.js presentation.js export.js
*/
const HGB_Skills = (function () {
'use strict';
/* ---------- 通用工具 ---------- */
/** 颜色亮度(0-1) */
function lum(hex) {
const c = String(hex || '#FFFFFF').replace('#', '');
if (c.length !== 6) return 0.9;
const r = parseInt(c.slice(0, 2), 16) / 255;
const g = parseInt(c.slice(2, 4), 16) / 255;
const b = parseInt(c.slice(4, 6), 16) / 255;
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
/** WCAG 对比度 */
function contrast(a, b) {
const la = lum(a), lb = lum(b);
const hi = Math.max(la, lb), lo = Math.min(la, lb);
return Math.round(((hi + 0.05) / (lo + 0.05)) * 100) / 100;
}
/** 从内置图标库搜索图标(懒加载适配:优先搜已加载源,其次索引;返回带库前缀 id 的图标) */
function searchIcon(keyword) {
const kw = String(keyword || '').toLowerCase();
if (!kw) return [];
const out = [];
const seen = new Set();
const push = (id, source) => {
if (!seen.has(id)) { seen.add(id); out.push({ id, source }); }
};
// 已加载源(含 svg,能力最全)
for (const hit of HGB_IconLib.searchLoaded(kw, 8)) push(hit.id, hit.source);
// 索引兜底(轻量全量 id/name,无需 svg)
if (out.length < 8) {
for (const hit of HGB_IconLib.searchIndex(kw, 8)) push(hit.id, hit.source);
}
return out.slice(0, 8);
}
/** 估算文字元素的实际渲染宽度(中文全角,西文 0.55,计入字距) */
function estTextW(el) {
const t = String(el.text || '');
const cn = (t.match(/[\u4e00-\u9fa5]/g) || []).length;
const fs = el.fontSize || 24;
const sp = el.letterSpacing || 0;
return cn * fs + (t.length - cn) * fs * 0.55 + Math.max(0, t.length - 1) * sp;
}
/** 元素实际包围盒(文字按估算宽度,水平居中) */
function elBox(el) {
if (el.type === 'text') {
const w = estTextW(el);
return { x: el.x - w / 2, y: el.y - (el.h || 40) / 2, w, h: el.h || 40 };
}
return { x: el.x, y: el.y, w: el.w, h: el.h };
}
/* ---------- 设计流程步骤(2026-08-15:技能按「设计流水线」组织) ---------- */
const DESIGN_STEPS = [
{ step: 1, id: 'insight', name: '品牌洞察', emoji: '🔍', desc: '把品牌描述提炼成关键词与方向,指导后续生成' },
{ step: 2, id: 'create', name: '方案生成', emoji: '🎨', desc: '产出色板/字体/布局/图标等设计建议,生成多套候选方案' },
{ step: 3, id: 'review', name: '方案评审', emoji: '🔬', desc: '对每套方案统一体检(构图/配色/字体/图标),输出检查报告' },
{ step: 4, id: 'iterate', name: '迭代优化', emoji: '🔄', desc: '按评审结果自动迭代,生成改进方案' },
{ step: 5, id: 'deliver', name: '交付导出', emoji: '📦', desc: '按用途导出多格式文件,输出交付清单' }
];
/** 技能 → 设计流程步骤(1-5)映射(按 id 显式指定) */
const SKILL_STEPS = {
// ① 品牌洞察
'ai-workflow': 1, 'brand-brief': 1, 'slogan-gen': 1, 'prompt-craft': 1, 'concept-words': 1,
// ② 方案生成
'layout-choice': 2, 'palette-build': 2, 'color-harmony': 2, 'gradient-suggest': 2, 'color-psychology': 2,
'font-pairing': 2, 'font-weight-choose': 2, 'icon-match': 2, 'icon-search-cn': 2, 'showcase-bg': 2,
'neutral-palette': 2,
// ③ 方案评审(检查类)
'composition': 3, 'align-check': 3, 'negative-space': 3, 'spacing-check': 3, 'proportion-check': 3,
'visual-balance': 3, 'focal-point': 3, 'rule-of-thirds': 3, 'circle-crop-safety': 3,
'contrast-check': 3, 'wcag-contrast': 3, 'color-blind-check': 3, 'monochrome-check': 3,
'font-hierarchy': 3, 'text-fit': 3, 'tiny-readable': 3, 'letter-spacing-check': 3, 'line-break': 3,
'cjk-mix-check': 3,
'icon-consistency': 3, 'icon-tone': 3, 'icon-count': 3, 'icon-text-ratio': 3, 'icon-padding': 3,
'stroke-consistency': 3,
'print-safety': 3,
// ④ 迭代优化
'ai-iterate': 4,
// ⑤ 交付导出
'format-choose': 5, 'export-sizing': 5, 'export-multi': 5, 'export-avatar': 5, 'favicon-kit': 5,
'transparent-check': 5, 'mockup-scene': 5, 'vi-checklist': 5, 'launch-kit': 5,
'file-naming': 5, 'brand-story': 5
};
/** 技能性质:gen 生成建议 / check 方案检查 / deliver 交付导出 */
function kind(id) {
const st = SKILL_STEPS[id];
if (st === 3) return 'check';
if (st === 5) return (id === 'transparent-check' || id === 'print-safety') ? 'check' : 'deliver';
return 'gen';
}
/* ---------- 注册表 API ---------- */
const skills = [];
function stepOf(id) { return SKILL_STEPS[id] || 2; } // 技能所属流程步骤(未映射默认方案生成)
/** 技能适用工作区:logo(LOGO) / avatar(头像) / poster(海报);未显式标注按分类默认 */
function workspacesOf(id) {
const s = get(id);
if (!s) return ['logo', 'avatar', 'poster'];
if (s.workspaces) return s.workspaces;
if (s.category === 'typography' || id === 'slogan-gen') return ['logo', 'poster']; // 头像纯图形无文字(slogan 为文字类)
return ['logo', 'avatar', 'poster'];
}
/** 某工作区可用的全部技能 */
function forWorkspace(ws) {
return skills.filter(s => workspacesOf(s.id).includes(ws));
}
function byStep(step, ws) { return (ws ? forWorkspace(ws) : skills).filter(s => SKILL_STEPS[s.id] === step); } // 某步骤的全部技能
function steps() { return DESIGN_STEPS; } // 设计流程步骤元数据
function register(defs) {
for (const d of (defs || [])) skills.push(d);
return skills.length;
}
function all() { return skills; }
function categories() {
const map = {};
for (const s of skills) map[s.category] = (map[s.category] || 0) + 1;
return Object.entries(map).map(([id, n]) => ({ id, count: n }));
}
function get(id) { return skills.find(s => s.id === id) || null; }
function run(id, args) {
const s = get(id);
if (!s) return { error: '技能不存在: ' + id };
try { return s.run(args || {}); }
catch (e) { return { error: '技能执行失败: ' + e.message }; }
}
function test(id) {
const s = get(id);
if (!s) return { error: '技能不存在: ' + id };
try { return s.test ? s.test() : { note: '该技能未提供自测' }; }
catch (e) { return { error: '技能自测失败: ' + e.message }; }
}
return { register, all, categories, get, run, test, steps, stepOf, byStep, workspacesOf, forWorkspace, kind, utils: { lum, contrast, searchIcon, estTextW, elBox } };
})();