chore: 前置快照-assetUrl前缀与学校切换收敛会话遗留(执行方案B1~B5前既有未提交变更)

This commit is contained in:
chenxing
2026-09-08 15:14:07 +08:00
parent 67a7164897
commit c77be8b5d8
6 changed files with 232 additions and 137 deletions
+29 -97
View File
@@ -1,7 +1,8 @@
<script setup lang="ts">
/* 学校专属管理壳(docs/09 §3.3,由 layout.vue 改造,二级壳 /sch/:schCode):
* header 左侧「←学校列表」+ 校名徽标/快速切换下拉;面包屑首段=校名;
* 面包屑栏右侧「站点切换(校内主站+院系子站)+ 预览门户」承接院系子站维度。
* header 左侧仅超管可见「↩返回学校列表」图标(校级账号不显示);
* 面包屑首段=校名(落点为院系子站时后跟当前站点名),超管在校名前多一段可点「学校列表」(与返回图标同效)。
* 学校/站点切换一律离壳完成:学校在 /workbench,站点在站点管理行「进入管理」。
* 菜单/高亮/子页面面包屑逻辑与原 layout 一致,布局数据来自 store.session。 */
import { ref, computed, watch, onBeforeMount } from "vue";
import { useRoute, useRouter } from "vue-router";
@@ -9,7 +10,6 @@ import { Message } from "view-ui-plus";
import { resolveMenuPage } from "@/utils/menuRoute";
import { useCmsStore } from "@/store";
import config from "@/assets/config/host.config";
import { portalBase } from "@/api/module/cms";
const route = useRoute();
const router = useRouter();
@@ -31,13 +31,18 @@ const modList = computed(() => store.session.modList || []);
const pathNavStyle = computed(() => (isAlone.value ? { left: "0", width: "100%" } : null));
/** 当前学校 + 校内站点(主站在前,切换下拉用) */
/** 当前学校上下文(面包屑首段) */
const school = computed(() => store.currentSchool);
const schoolSites = computed(() => store.schoolSites);
/** 快速切换学校候选 = 可管理顶层站点(主站) */
const schoolOptions = computed(() =>
((store.user?.sites || []) as any[]).filter((s: any) => !s.parentSiteId)
);
const isSuper = computed(() => store.isSuperAdmin);
/** 面包屑校名后缀:操作落点非主站时跟随展示当前站点名(院系子站管理可见性,
* 站点切换入口在站点管理行「进入管理」,壳上不再放切换器) */
const currentSiteSuffix = computed(() => {
const s = store.currentSite;
const sc = school.value;
if (!s || !sc || s.id === sc.mainSiteId) return "";
return s.name || "";
});
const flaten = (tree: any[], pid = 0): any[] => {
const arr: any[] = [];
@@ -187,29 +192,6 @@ function goWorkbench() {
router.push("/workbench");
}
/** 快速切换学校:守卫负责恢复上下文,进入后落在文章列表 */
function onSchoolChange(code: string) {
if (!code || code === store.schoolCode) return;
router.push(`/sch/${code}/article`);
}
function onSiteChange(siteId: number) {
const site = schoolSites.value.find((s: any) => s.id === siteId);
if (site) {
store.setCurrentSite({ id: site.id, code: site.code, name: site.name });
}
}
function openPortal() {
if (!store.currentSite) return;
if (config.isMock) {
// mock 模式:SPA 内部门户预览(免依赖后端 Thymeleaf 门户)
router.push(`/sch/${store.schoolCode}/portal-preview`);
} else {
window.open(`${portalBase(store.currentSite.code)}/`, "_blank");
}
}
/** 启动引导期守卫放行的深链:user 数据就绪后兜底校验/恢复学校上下文 */
watch(
() => store.user,
@@ -263,28 +245,18 @@ watch(
</div>
</div>
<div class="delimiter fl align-middle"></div>
<a class="fl align-middle back-workbench" href="javascript:void(0);" title="返回学校列表" @click="goWorkbench">
<Icon type="ios-arrow-back" />
学校列表
<!-- 返回学校列表:仅超管显示(校级账号固定本校,无需返回入口) -->
<a
v-if="isSuper"
class="fl align-middle back-workbench"
href="javascript:void(0);"
title="返回学校列表"
@click="goWorkbench"
>
<Icon type="ios-undo" :size="18" />
</a>
<div class="delimiter fl align-middle"></div>
<div v-if="isSuper" class="delimiter fl align-middle"></div>
<h4 class="fl align-middle">{{ appName }}</h4>
<div class="delimiter fl align-middle"></div>
<!-- 校名徽标 + 快速切换(当前学校常驻 header) -->
<div class="school-badge fl align-middle" v-if="school">
<Icon type="ios-school" :size="16" />
<Select
size="small"
:model-value="school.mainSiteCode"
class="school-select"
transfer
@on-change="onSchoolChange"
>
<Option v-for="s in schoolOptions" :key="s.id" :value="s.code">
{{ s.schName || s.name }}
</Option>
</Select>
</div>
<div class="header-aside fr">
<img :src="userInfo.avatar" v-if="userInfo.avatar" class="fl align-middle" @click="linkUserInfo" />
<a class="name fl align-middle text-ellipsis" href="javascript:void(0);" @click="linkUserInfo">
@@ -335,10 +307,12 @@ watch(
<div class="path-nav" :style="pathNavStyle">
<Breadcrumb separator=">">
<!-- 面包屑首段 = 学校上下文(常驻) -->
<!-- 超管:校名前多一段可点「学校列表」,与 header 返回图标同效(回工作台切换学校) -->
<BreadcrumbItem v-if="isSuper" to="/workbench">学校列表</BreadcrumbItem>
<!-- 面包屑首段 = 学校上下文(常驻);落点为院系子站时跟出当前站点名 -->
<BreadcrumbItem v-if="school">
<Icon type="ios-school" />
{{ school.schName || school.mainSiteName }}
{{ school.schName || school.mainSiteName }}<template v-if="currentSiteSuffix"> · {{ currentSiteSuffix }}</template>
</BreadcrumbItem>
<template v-for="(item, index) in navPath" :key="index">
<BreadcrumbItem v-if="index != navPath.length - 1" :to="item.page">
@@ -347,26 +321,6 @@ watch(
<BreadcrumbItem v-else>{{ item.name }}</BreadcrumbItem>
</template>
</Breadcrumb>
<!-- 站点上下文(校内:主站 + 院系子站;切换后各页面即时跟随) -->
<div class="cms-site-bar fr">
<span class="site-label">站点:</span>
<Select
size="small"
:model-value="store.currentSite?.id"
:placeholder="schoolSites.length ? '选择站点' : '暂无可管理站点'"
style="width: 200px"
transfer
@on-change="onSiteChange"
>
<Option v-for="opt in schoolSites" :key="opt.id" :value="opt.id">
{{ opt.name }}{{ opt.parentSiteId ? "(院系)" : "" }}
</Option>
</Select>
<Button size="small" type="primary" ghost style="margin-left: 10px" @click="openPortal">
<Icon type="ios-eye" />
预览门户
</Button>
</div>
</div>
<transition name="fade_apps">
@@ -401,37 +355,15 @@ watch(
</template>
<style scoped lang="scss">
/* 返回学校列表 + 校名徽标:header 常驻学校上下文(docs/09 §3.3) */
/* 超管返回学校列表图标:header 常驻入口(校级账号不渲染) */
.back-workbench {
color: #fff;
font-size: 13px;
display: inline-flex;
align-items: center;
&:hover {
opacity: 0.85;
}
}
.school-badge {
display: inline-flex;
align-items: center;
color: #fff;
.school-select {
width: 180px;
margin-left: 6px;
}
}
/* 站点切换条:挂在面包屑条右侧,沿用 path-nav 白底视觉 */
.cms-site-bar {
display: flex;
align-items: center;
height: 56px;
margin-right: 24px;
.site-label {
color: #777;
font-size: 13px;
}
}
.no-site-tip {
height: 60vh;
display: flex;