From 41b79566c1ed6e3307ab3ffd152e88116b97eee5 Mon Sep 17 00:00:00 2001 From: chenxing Date: Sat, 3 Oct 2026 13:52:56 +0800 Subject: [PATCH] =?UTF-8?q?feat(theme):=20CMS=20=E7=AB=99=E7=82=B9?= =?UTF-8?q?=E4=B8=BB=E9=A2=98=E5=BC=B1=E8=81=94=E5=8A=A8=20+=20campus-gree?= =?UTF-8?q?n=20=E8=89=B2=E5=80=BC=E5=AF=B9=E9=BD=90=EF=BC=88T3=EF=BC=89?= =?UTF-8?q?=E2=80=94=E2=80=94=E6=96=B0=E5=A2=9E=20utils/cmsTheme=20?= =?UTF-8?q?=E6=98=A0=E5=B0=84=E9=80=82=E9=85=8D=E5=99=A8=EF=BC=88SKIN=5FMA?= =?UTF-8?q?P/PARAM=5FALIASE/colorScheme=20=E9=A2=84=E8=AE=BE=EF=BC=8C?= =?UTF-8?q?=E6=9C=AA=E6=98=A0=E5=B0=84=E4=B8=80=E5=BE=8B=20null=20?= =?UTF-8?q?=E5=9B=9E=E8=90=BD=EF=BC=89=EF=BC=9BApp.vue=20=E7=9A=AE?= =?UTF-8?q?=E8=82=A4=E4=B8=89=E5=B1=82=E4=BC=98=E5=85=88=E7=BA=A7=20stale-?= =?UTF-8?q?while-revalidate=EF=BC=88=E7=BC=93=E5=AD=98=E5=85=88=E7=94=BB?= =?UTF-8?q?=E3=80=81=E5=90=8E=E5=8F=B0=E5=88=B7=E6=96=B0=E3=80=81=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E9=9D=99=E9=BB=98=EF=BC=89=EF=BC=9Bsvc.ts=20=E5=8A=A0?= =?UTF-8?q?=20hallApi.site=EF=BC=9Bcampus-green=20=E5=9B=9B=E8=89=B2?= =?UTF-8?q?=E4=B8=8E=20CMS=20campus-glow=20=E5=90=8C=E6=BA=90=E5=AF=B9?= =?UTF-8?q?=E9=BD=90=E5=B9=B6=E5=8D=87=201.1.0=EF=BC=9Btheme-base.css=20?= =?UTF-8?q?=E5=85=8B=E5=88=B6=E8=A1=A5=E4=B8=81=20view-ui=20=E5=AE=9E?= =?UTF-8?q?=E9=99=85=E4=BD=BF=E7=94=A8=E7=9A=84=20Checkbox/Radio/Page/Moda?= =?UTF-8?q?l.confirm/dyn-file-btn=20=E4=BA=A4=E4=BA=92=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.vue | 46 +++++++++++++++++++++--- src/api/module/svc.ts | 4 +++ src/assets/styles/theme-base.css | 20 +++++++++++ src/themes/campus-green/theme.css | 11 +++--- src/themes/campus-green/theme.json | 12 +++---- src/utils/cmsTheme.ts | 58 ++++++++++++++++++++++++++++++ 6 files changed, 136 insertions(+), 15 deletions(-) create mode 100644 src/utils/cmsTheme.ts diff --git a/src/App.vue b/src/App.vue index d64fb01..04e8130 100644 --- a/src/App.vue +++ b/src/App.vue @@ -5,23 +5,31 @@ * 与后台前端(front/)刻意不同:不渲染平台模块菜单(app_module 是管理端契约), * 只做两个入口——办事大厅、我的办件(详情/办理页从列表进入)。 * - * 皮肤:启动时按运行时配置(env-config 的 theme/themeParams)应用皮肤包; - * 未配置 → default;未注册 → default(整页回退,见 utils/theme.ts)。 + * 皮肤三层优先级(T3 弱联动,2026-10-03): + * CMS 站点推荐(/api/hall/site 的 theme/themeParams,经 utils/cmsTheme 映射; + * stale-while-revalidate——localStorage 缓存先画、后台刷新,失败静默) + * > env-config 部署级皮肤 > default。 + * 推荐值、不保证生效:任一层缺失/非法都回落下一层(utils/theme.ts)。 */ import { onMounted, ref } from "vue"; import { baseApi } from "@/api/module/base"; import config from "@/assets/config/host.config"; import { applyTheme, resolveTheme, type ThemeManifest } from "@/utils/theme"; +import { toHallSkin } from "@/utils/cmsTheme"; import { useSite } from "@/utils/site"; +import { hallApi } from "@/api/module/svc"; const userName = ref(""); const theme = ref(resolveTheme(null)); // CMS 指南页来路(T4 回跳闭环):仅 /#/apply 深链带合法 from 参数时非空 -const { portalFrom } = useSite(); +const { portalFrom, currentSiteCode } = useSite(); onMounted(async () => { - // 皮肤先应用(首屏观感),再拉用户信息——用户信息失败不影响办理页面使用 + // ① 基线:env-config 部署级皮肤(现状语义不变,先应用保证首屏观感) theme.value = applyTheme(config.theme, config.themeParams); + // ② 站点推荐:CMS 站点主题(弱联动)——缓存先画,后台刷新 + applySiteThemeRecommend(); + // 用户信息失败不影响办理页面使用 try { const user: any = await baseApi.getUserInfo().send(); userName.value = user?.name || ""; @@ -32,6 +40,36 @@ onMounted(async () => { } } }); + +/** + * 站点主题推荐应用(stale-while-revalidate): + * 未登录时该请求会 302 跳 CAS(与 items 同行为,页面本来就要登录); + * 任何失败静默保留现皮肤——推荐、不保证生效,绝不阻塞首屏。 + */ +async function applySiteThemeRecommend() { + const code = currentSiteCode.value; // useSite() 模块级 ref,当前视图 onMounted 已 resolve + if (!code) return; + const cacheKey = `svchall.public.siteTheme.${code}`; + // ① stale:同步读缓存立即应用(二次访问无闪色) + try { + const cached = JSON.parse(localStorage.getItem(cacheKey) || "null"); + const stale = toHallSkin(cached); + if (stale) theme.value = applyTheme(stale.name, stale.params); + } catch { + // 坏缓存当没有 + } + // ② revalidate:后台刷新(失败静默保留现皮肤——推荐、不保证生效) + try { + const info: any = await hallApi.site(code).send(); + const pick = toHallSkin({ theme: info?.theme, themeParams: info?.themeParams }); + localStorage.setItem(cacheKey, JSON.stringify({ + theme: info?.theme || null, themeParams: info?.themeParams || null, + })); + if (pick) theme.value = applyTheme(pick.name, pick.params); + } catch (e) { + console.warn("[public-hall] 站点主题获取失败,沿用当前皮肤", e); + } +}