From 3231ae30a4cf24dd284ae4cc6e3875c692ef23f1 Mon Sep 17 00:00:00 2001 From: wizardchen Date: Thu, 23 Apr 2026 18:20:24 +0800 Subject: [PATCH] fix(frontend): bundle tdesign-icons sprite locally for offline deploys The component from tdesign-vue-next lazily loads its SVG sprite from https://tdesign.gtimg.com/icon//fonts/index.js at runtime. In air-gapped or intranet deployments this request fails and the whole UI loses its icons (agent cards, knowledge-base breadcrumbs/cards, upload buttons, etc.). Ship the sprite (tdesign-icons-vue-next@0.4.1) as a static asset under frontend/public/tdesign-icons/0.4.1/fonts/index.js and preload it from index.html so every resolves against local symbols. An offline guard injects stub + +
+ * 在本地提前注册,因此 仍可正常渲染。 + */ + +const SVG_SCRIPT_CLASS = "t-svg-js-stylesheet--unique-class"; +const ICONFONT_LINK_CLASS = "t-iconfont-stylesheet--unique-class"; + +// 对齐 tdesign-icons-vue-next 0.4.x 内部硬编码的地址;多版本号并存以兼容潜在升级。 +const BLOCKED_SCRIPT_URLS = [ + "https://tdesign.gtimg.com/icon/0.4.0/fonts/index.js", + "https://tdesign.gtimg.com/icon/0.4.1/fonts/index.js", +]; + +const BLOCKED_LINK_URLS = [ + "https://tdesign.gtimg.com/icon/0.4.0/fonts/index.css", + "https://tdesign.gtimg.com/icon/0.4.1/fonts/index.css", +]; + +let installed = false; + +export function installTDesignIconOfflineGuard(): void { + if (installed || typeof document === "undefined") return; + installed = true; + + const body = document.body; + if (!body) { + document.addEventListener( + "DOMContentLoaded", + () => installTDesignIconOfflineGuard(), + { once: true }, + ); + installed = false; + return; + } + + BLOCKED_SCRIPT_URLS.forEach((src) => { + const exists = document.querySelector( + `script.${SVG_SCRIPT_CLASS}[src="${src}"]`, + ); + if (exists) return; + const stub = document.createElement("script"); + stub.setAttribute("class", SVG_SCRIPT_CLASS); + stub.setAttribute("src", src); + // 非标准 MIME 类型会让浏览器跳过脚本的 fetch/执行阶段 + stub.setAttribute("type", "text/no-load"); + stub.setAttribute("data-weknora-blocked-cdn", "tdesign-icons"); + body.appendChild(stub); + }); + + BLOCKED_LINK_URLS.forEach((href) => { + const exists = document.querySelector( + `link.${ICONFONT_LINK_CLASS}[href="${href}"]`, + ); + if (exists) return; + const stub = document.createElement("link"); + stub.setAttribute("class", ICONFONT_LINK_CLASS); + stub.setAttribute("href", href); + // 不声明 rel="stylesheet",浏览器不会发起样式表请求 + stub.setAttribute("rel", "preload-blocked"); + stub.setAttribute("data-weknora-blocked-cdn", "tdesign-icons"); + document.head.appendChild(stub); + }); +}