Files
UserhomeFront/src/views/ThemePreview.vue
T
chenxing 357c0edb52 feat(theme): 设置页「主题」导航组 + iframe 预览页 + 学校默认档对接(主题设置 S2)
- Settings.vue:新增「主题」导航组(组级不设 adminOnly),mytheme「我的主题」所有人可见、schooltheme「本校默认」item 级 adminOnly + 入 ADMIN_PANES 双保险(NavItem 扩展 adminOnly 字段,navGroups 组级+item 级双层过滤);两个 pane 左色块右预览 iframe——候选档经 ?yh-theme=/yh-side=/yh-grad= URL 覆盖参数拼进 #/theme-preview src,切候选即换 src 重载;档位清单本轮硬编码 5 档(主色取通道 tokens);「跟随学校默认」=删用户键、「跟随系统默认」=clearSchTheme 清空学校配置;当前生效档与来源(我的选择/学校默认/系统默认)经 resolveEffectiveThemeState 标注
- views/ThemePreview.vue(新增):纯展示预览页——顶栏 .header-container + 左菜单 .left-menu + 面包屑 + iview Table 写死 4 行,DOM 结构对齐 Notice layout,观感由通道 CSS 按档位类驱动;根容器 pointer-events:none,无搜索/分页/ThemeSwitcher;挂载时动态 append 通道 init(baseHost + /static/js/yh-theme-init.js,判重防双挂)——仅该 document 吃通道 CSS,UserHome 本体 index.html 不加 loader(定案)
- App.vue:/theme-preview 顶层独立分支(不进桌面框架、不依赖登录态与 isRouterAlive——未登录直开不白屏);菜单白名单加 /theme-preview;登录成功后异步拉 schTheme 写 localStorage[yh-theme-sch-default](失败/空不写不报错,推荐值语义)
- router/routes.ts:登记 /theme-preview(实际渲染走 App.vue 分支;若后续引入守卫须列入免登录白名单——注释已写明)
- BaseApi:getSchTheme / saveSchThemeState / clearSchTheme 三封装
- src/utils/theme.ts:YschTheme npm run sync 下发(版本 20260930-1248-4e12e6d,含 SCHOOL_DEFAULT_STORAGE_KEY 与 resolveEffectiveThemeState 新导出)
2026-09-30 12:59:35 +08:00

143 lines
5.3 KiB
Vue
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.
<template>
<!-- 主题预览页(#/theme-preview,YschTheme《主题设置与通道改动-执行方案》§3.4):
只在本路由的 document 内加载通道(组件挂载时动态 append init 脚本),
档位类与通道 CSS 只落在本 document 的 <html> 上——UserHome 本体(父页)永远不吃通道 CSS。
纯展示:根容器 pointer-events:none,不渲染搜索区/分页操作/ThemeSwitcher。
DOM 结构对齐 Notice layout(header-container / left-menu / path-nav / main-container),
观感(配色/侧栏形态)由通道 base.layout.css 按 URL 覆盖参数(?yh-theme= 等)驱动的档位类提供。 -->
<div class="theme-preview-root">
<header class="header-container fix">
<span class="logo-badge fl"><span class="logo-mark">云海</span></span>
<div class="delimiter fl align-middle"></div>
<h4 class="fl align-middle">管理端预览</h4>
<div class="school-bg"></div>
<div class="header-aside fr">
<span class="name fl align-middle text-ellipsis">演示用户</span>
<div class="delimiter fl align-middle"></div>
<a href="javascript:;" class="logout fl align-middle" title="退出登陆"><i>⎋</i></a>
</div>
</header>
<div class="left-menu">
<div class="menu-top-box"></div>
<ul>
<li v-for="(m, i) in menus" :key="m" :class="{ active: i === 1 }">
<a href="javascript:void(0);"><span class="menu-dot"></span>{{ m }}</a>
</li>
</ul>
<div class="clear-box"></div>
</div>
<div class="path-nav">
<span>首页</span><span class="crumb-sep">/</span><span>通知管理</span><span class="crumb-sep">/</span><span>通知列表</span>
</div>
<div class="preview-main">
<div class="main-container">
<h3 class="panel-title">通知列表</h3>
<Table :columns="columns" :data="rows" size="small" :border="true" />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted } from "vue";
import useCurrentInstance from "@/utils/hook/useCurrentInstance";
/**
* 主题预览路由页:档位来源 = URL 覆盖参数(?yh-theme= / ?yh-side= / ?yh-grad=,由设置页 iframe src 拼入,
* 切候选档即换 src 重载),init 启动段按合成优先级挂档——本组件不读写 localStorage、不提供任何交互。
* 直开(不经设置页、未登录)也可渲染:本页由 App.vue 顶层条件分支挂载,不依赖桌面框架与登录态。
*/
const { $conf } = useCurrentInstance();
const menus = ["工作台", "通知管理", "统计分析", "应用中心", "系统设置"];
const columns = [
{ type: "index", title: "序号", width: 70, align: "center" },
{ title: "标题", key: "title" },
{ title: "发布人", key: "author", width: 120, align: "center" },
{ title: "日期", key: "date", width: 160, align: "center" },
];
const rows = [
{ title: "关于国庆节放假安排的通知", author: "校务办", date: "2026-09-28" },
{ title: "期中教学质量检查实施方案", author: "教务处", date: "2026-09-25" },
{ title: "校园安全专项检查通报", author: "保卫处", date: "2026-09-21" },
{ title: "教师信息化能力培训报名", author: "信息中心", date: "2026-09-18" },
];
onMounted(() => {
// 动态引通道 init(仅本 document;UserHome 本体 index.html 不加 loader 是定案)。
// baseHost 即通道域(与 UserBase/Notice 的 loader 同源策略);判重防重复挂载。
if (document.querySelector("script[data-yh-theme-init]")) return;
const s = document.createElement("script");
s.src = `${String($conf.baseHost || "").replace(/\/+$/, "")}/static/js/yh-theme-init.js`;
s.setAttribute("data-yh-theme-init", "1");
document.head.appendChild(s);
});
</script>
<style scoped lang="scss">
/* 本页只补最小布局;配色/圆角/阴影等观感全部由通道 base.layout.css 的档位类驱动 */
.theme-preview-root {
position: fixed;
inset: 0;
overflow: hidden;
pointer-events: none; /* 纯展示:整页不可交互 */
background: #f5f6f8;
font-size: 14px;
}
.header-container {
.logo-mark {
display: inline-flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.22);
color: #fff;
font-size: 12px;
font-weight: 600;
}
h4 {
color: #fff;
font-size: 16px;
font-weight: 600;
margin: 0 12px;
}
}
.menu-dot {
display: inline-block;
width: 7px;
height: 7px;
border-radius: 50%;
background: currentColor;
opacity: 0.45;
margin-right: 9px;
vertical-align: middle;
}
.path-nav .crumb-sep {
margin: 0 8px;
color: #c5c8cd;
}
.preview-main {
position: fixed;
top: calc(var(--topbar-h, 52px) + var(--crumb-h, 44px));
left: var(--side-w, 200px);
right: 0;
bottom: 0;
padding: 14px 24px 24px;
overflow: auto;
.panel-title {
font-size: 15px;
font-weight: 600;
color: #2c3138;
margin: 4px 0 12px;
}
}
</style>