/* 图标库:统一 24×24 线性 SVG,描边走 currentColor,重点色由外层 CSS 决定。 用法:Icon.svg("wallet") → 裸图标 Icon.badge("wallet") → 图标 + 重点色底板 Icon.status("in_progress") → 执行状态图标 Icon.stage("signed") → 定开阶段图标 Icon.pipe("entry") → 交付环节图标 静态标记里写 ,加载时自动替换成 SVG。 */ (function () { "use strict"; var P = { /* 导航 */ board: '', "list-checks": '', package: '', target: '', users: '', pencil: '', sliders: '', /* 通用动作 */ plus: '', x: '', "chevron-left": '', "chevron-right": '', "chevron-down": '', "chevron-up": '', "log-out": '', "arrow-right": '', search: '', download: '', check: '', "check-circle": '', circle: '', clock: '', alert: '', "alert-circle": '', info: '', ban: '', pause: '', refresh: '', star: '', filter: '', sparkles: '', trash: '', archive: '', flag: '', /* 业务语义 */ calendar: '', "calendar-check": '', user: '', layers: '', activity: '', pie: '', "bar-chart": '', wallet: '', receipt: '', "trending-up": '', inbox: '', "shield-check": '', gauge: '', hourglass: '', note: '', database: '', bell: '', code: '', link: '', }; var STATUS_ICON = { not_started: "circle", in_progress: "refresh", done: "check-circle", at_risk: "alert", blocked: "ban", paused: "pause", }; var STAGE_ICON = { lead: "target", signed: "pencil", developing: "code", delivered: "package", accepted: "shield-check", revenue: "wallet", paused: "pause", closed: "archive", }; var PIPE_ICON = { entry: "inbox", signed: "pencil", delivered: "package", accepted: "shield-check", revenue: "wallet" }; function svg(name, cls, size) { var body = P[name]; if (!body) return ""; var attrs = 'class="ic' + (cls ? " " + cls : "") + '" viewBox="0 0 24 24" fill="none" stroke="currentColor"' + ' stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"'; if (size) attrs += ' width="' + size + '" height="' + size + '"'; return "" + body + ""; } /** 图标 + 重点色小底板:重点色来自外层 --c */ function badge(name, cls) { return '' + svg(name) + ""; } function status(code) { return svg(STATUS_ICON[code] || "circle"); } function nameOfStatus(code) { return STATUS_ICON[code] || "circle"; } function nameOfStage(code) { return STAGE_ICON[code] || "circle"; } function statusBadge(code) { return badge(nameOfStatus(code)); } function stage(code) { return svg(STAGE_ICON[code] || "circle"); } function pipe(key) { return svg(PIPE_ICON[key] || "circle"); } function hydrate(root) { var nodes = (root || document).querySelectorAll("[data-icon]"); for (var i = 0; i < nodes.length; i++) { var el = nodes[i]; el.innerHTML = svg(el.getAttribute("data-icon"), el.getAttribute("data-icon-class") || ""); el.removeAttribute("data-icon"); } } /* ---------- 按标题关键词自动配图标 ---------- 标题类文案(卡片标题 / 区块标题 / 看板列头 / 左栏分组头)由渲染函数拼装, 逐个手写图标会散落几十处;这里按关键词统一命中一次,命中不到就不加图标。 */ var TITLE_ICON = [ ["工作重点", "list-checks"], ["重点工作", "list-checks"], ["工作重点", "list-checks"], ["执行记录", "activity"], ["交付记录", "activity"], ["更新活跃度", "activity"], ["活跃度", "activity"], ["未更新", "alert-circle"], ["卡点", "alert-circle"], ["预警", "alert-circle"], ["风险", "alert"], ["状态分布", "gauge"], ["进度", "gauge"], ["趋势", "trending-up"], ["走势", "trending-up"], ["分类分布", "pie"], ["金额分布", "pie"], ["金额", "wallet"], ["财务", "wallet"], ["收入", "wallet"], ["计收", "wallet"], ["合同", "wallet"], ["开票", "receipt"], ["回款", "receipt"], ["负责人", "users"], ["人员", "users"], ["人力", "users"], ["花名册", "users"], ["在册", "users"], ["区域", "layers"], ["归属", "layers"], ["分类", "pie"], ["明细", "list-checks"], ["待办", "list-checks"], ["本周新增", "inbox"], ["有变化", "refresh"], ["在管项目", "layers"], ["定开项目", "package"], ["交付环节", "layers"], ["交付漏斗", "filter"], ["交付", "package"], ["验收", "shield-check"], ["入库", "inbox"], ["签单", "pencil"], ["阶段流转", "refresh"], ["阶段", "layers"], ["线索", "target"], ["开发", "code"], ["重点项目", "star"], ["重点", "star"], ["周期反馈", "calendar-check"], ["周期管理", "calendar"], ["周期", "calendar"], ["更新录入", "pencil"], ["最近更新", "clock"], ["整理结果", "sparkles"], ["配置", "sliders"], ["数据库", "database"], ["统计概览", "bar-chart"], ["概览", "bar-chart"], ["基础信息", "info"], ["基本信息", "info"], ["说明", "info"], ["反馈", "note"], ["记录", "note"], ["归档", "archive"], ["项目", "layers"], ]; var EMPTY_ICON = [["加载", "refresh"], ["没有", "inbox"], ["暂无", "inbox"], ["未填写", "note"], ["找不到", "search"]]; function pickIcon(text, rules) { for (var i = 0; i < rules.length; i++) if (text.indexOf(rules[i][0]) >= 0) return rules[i][1]; return ""; } function leadingIcon(el, name, cls) { if (!el || el.getAttribute("data-ico")) return; el.setAttribute("data-ico", "1"); if (name) el.insertAdjacentHTML("afterbegin", svg(name, cls)); } function text(el) { return (el.textContent || "").replace(/\s+/g, ""); } /** 给一处渲染结果补图标:静态标记里的 data-icon + 标题/空态/提示条的关键词图标 */ function auto(root) { if (!root) return; if (root.nodeType === 1) fill(root); if (!root.querySelectorAll) return; var nodes = root.querySelectorAll("[data-icon], .card-head h4, .sec-title, .kcol-title, .wk-group-head, .empty, .notice"); fillAll(nodes); } function fillAll(nodes) { for (var i = 0; i < nodes.length; i++) fill(nodes[i]); } function fill(el) { if (el.getAttribute("data-icon")) { el.innerHTML = svg(el.getAttribute("data-icon"), el.getAttribute("data-icon-class") || ""); el.removeAttribute("data-icon"); return; } if (el.matches(".card-head h4, .sec-title, .kcol-title, .wk-group-head")) { // 模板里已经自带前导图标的标题(如阶段看板的阶段图标)不再自动补第二个 if (el.querySelector(":scope > .ic")) { el.setAttribute("data-ico", "1"); return; } leadingIcon(el, pickIcon(text(el), TITLE_ICON), "ti"); } else if (el.matches(".notice")) { leadingIcon(el, el.classList.contains("err") ? "alert-circle" : el.classList.contains("warn") ? "alert-circle" : "info", "ti"); } else if (el.matches(".empty")) { leadingIcon(el, pickIcon(text(el), EMPTY_ICON) || "inbox", "ic-lg"); } } function observe() { if (!window.MutationObserver) return; new MutationObserver(function (ms) { for (var i = 0; i < ms.length; i++) { var added = ms[i].addedNodes; for (var j = 0; j < added.length; j++) auto(added[j]); } }).observe(document.body, { childList: true, subtree: true }); } window.Icon = { svg: svg, badge: badge, status: status, stage: stage, pipe: pipe, nameOfStatus: nameOfStatus, nameOfStage: nameOfStage, statusBadge: statusBadge, hydrate: hydrate, auto: auto, names: P }; function boot() { hydrate(document); auto(document); observe(); } if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", boot); else boot(); })();