build / build (push) Successful in 5m35s
- BaseApi:schAvatarStyle/saveSchAvatarStyle/clearSchAvatarStyle 三端点(后端 SchSettingsController 配套,本仓 service 仓随轮提交) - App.vue:登录后 loadSchAvatarDefault()(与 loadSchThemeDefault 同构:失败/404/空→不写键不报错不阻塞),写 localStorage[yh-avatar-sch-default] - Settings.vue:新增 avatar pane(我的头像样式人人可见 + 本校默认管理员可配,watch 进 pane 拉取) - AppHeader/UserInfoModal:头像展示位换 UserAvatar 三态组件 - ThemePreview:预览页头像形态联动 运行时联调与部署验证待人工(后端端点须随 service 发版)
245 lines
8.2 KiB
Vue
245 lines
8.2 KiB
Vue
<script setup lang="ts">
|
||
import { ref, onMounted, reactive, provide, computed, nextTick } from "vue";
|
||
import Basic from "@/api/BaseApi";
|
||
|
||
import { RouteRecordRaw, useRoute, useRouter } from "vue-router";
|
||
import ContextMenu from "@/components/ContextMenu.vue";
|
||
import { useDefaultStore } from "@/store/index.js";
|
||
import { Message } from "view-ui-plus";
|
||
import LeftMenu from "@/components/LeftMenu.vue";
|
||
import MyHeader from "@/components/Header.vue";
|
||
import BschIframe from "@/components/BschIframe.vue";
|
||
import SearchPanel from "@/components/SearchPanel.vue";
|
||
import UserInfoModal from "@/components/UserInfoModal.vue";
|
||
import LightPageModal from "@/components/LightPageModal.vue";
|
||
import ThemePreview from "@/views/ThemePreview.vue";
|
||
|
||
import BaseApi from "@/api/BaseApi";
|
||
import { BschIframeType, IframePageParmas, RoleInfo, StateEnum } from "./types";
|
||
import { storeToRefs } from "pinia";
|
||
import UserSettingApi from "./api/UserSettingApi";
|
||
import { useCompRef } from "./utils/hook/useCompRef";
|
||
import mittBus from "@/utils/mitt";
|
||
import MenuBarApi from "./api/MenuBarApi";
|
||
import { isTickeRoterByName } from "./utils/bschLinkPage";
|
||
import Utils from "./utils";
|
||
|
||
const store = useDefaultStore();
|
||
const route = useRoute();
|
||
const router = useRouter();
|
||
|
||
const isRouterAlive = ref(false),
|
||
StateType = ref<StateEnum>(StateEnum.Loading);
|
||
|
||
const refBschIframe = useCompRef(BschIframe);
|
||
|
||
const isBaseMask = computed(() => route.name !== "index");
|
||
const isHeader = computed(() => !(route.meta.isHeader === false));
|
||
|
||
mittBus.on("reload", () => {
|
||
isRouterAlive.value = false;
|
||
setTimeout(() => {
|
||
isRouterAlive.value = true;
|
||
}, 100);
|
||
});
|
||
|
||
mittBus.on("updateState", (state: StateEnum) => (StateType.value = state));
|
||
mittBus.on("switchRole", switchRole);
|
||
mittBus.on("reloadUserSettings", loadUserSettings);
|
||
|
||
mittBus.on("openBschIframePage", async (params: IframePageParmas) => {
|
||
StateType.value = StateEnum.Iframe;
|
||
await nextTick();
|
||
refBschIframe.value?.openPage(params);
|
||
});
|
||
|
||
(async () => {
|
||
console.time("init");
|
||
const userinfo = await BaseApi.getUserInfo();
|
||
const p1 = BaseApi.getUserRole();
|
||
const p2 = BaseApi.findUserRole();
|
||
try {
|
||
const [r1, r2] = await Promise.all([p1, p2]);
|
||
store.$state.userInfo = userinfo;
|
||
loadSchThemeDefault();
|
||
loadSchAvatarDefault();
|
||
if (r1.length == 0) {
|
||
store.$state.userRoleList = [
|
||
{
|
||
roleId: 0,
|
||
roleName: "",
|
||
schId: 0,
|
||
schName: "",
|
||
childId: 0,
|
||
childName: "",
|
||
},
|
||
];
|
||
} else store.$state.userRoleList = r1.filter(x => [1, 2, 3, 4, 5, 7].includes(x.roleId));
|
||
store.setCurRoleInfo(r2 as RoleInfo);
|
||
// 已移除 bsch SSO ticket 获取(登录改走 CAS)
|
||
await switchRole();
|
||
} catch (error) {
|
||
Message.error("内部服务错误");
|
||
console.error(error);
|
||
return false;
|
||
}
|
||
|
||
isRouterAlive.value = true;
|
||
StateType.value = StateEnum.Desktop;
|
||
console.timeEnd("init");
|
||
})();
|
||
|
||
/** 拉学校默认主题档写键(异步尽力而为:失败/404/空 → 不写、不报错、不阻塞登录流程) */
|
||
function loadSchThemeDefault() {
|
||
BaseApi.getSchTheme()
|
||
.then((res: any) => {
|
||
if (res && res.themeState) {
|
||
try {
|
||
localStorage.setItem("yh-theme-sch-default", res.themeState);
|
||
} catch (e) {
|
||
/* 隐私模式写失败静默 */
|
||
}
|
||
}
|
||
})
|
||
.catch(() => undefined);
|
||
}
|
||
|
||
/** 拉学校默认头像样式写键(默认头像轮 2026-09-30;与 loadSchThemeDefault 同构:失败/404/空 → 不写、不报错、不阻塞登录流程) */
|
||
function loadSchAvatarDefault() {
|
||
BaseApi.getSchAvatarStyle()
|
||
.then((res: any) => {
|
||
if (res && res.avatarStyle) {
|
||
try {
|
||
localStorage.setItem("yh-avatar-sch-default", res.avatarStyle);
|
||
} catch (e) {
|
||
/* 隐私模式写失败静默 */
|
||
}
|
||
}
|
||
})
|
||
.catch(() => undefined);
|
||
}
|
||
|
||
/**切换角色 */
|
||
async function switchRole() {
|
||
await loadUserMenuBarList();
|
||
await loadUserSettings();
|
||
store.curRoleUid = Utils.uuid();
|
||
// const p1 = BaseApi.getUsedApp(store.getCurRoleId, store.getCurSchId),
|
||
// p2 = BaseApi.getUserApp(store.getCurUserId, store.getCurSchId);
|
||
// const [r1, r2] = await Promise.all([p1, p2]);
|
||
|
||
// store.$state.curUsedAppList = r1;
|
||
// store.$state.curUserAppList = r2.filter(x => x.app.length > 0);
|
||
}
|
||
async function loadUserSettings() {
|
||
const userSettings = await UserSettingApi.getUserSettings();
|
||
store.setCurUserSettingsInfo(userSettings);
|
||
}
|
||
|
||
async function loadUserMenuBarList() {
|
||
const menuList = await MenuBarApi.getMenuBarUserList();
|
||
store.setCurUserMenuList(menuList);
|
||
let findIndex = -1;
|
||
if (isTickeRoterByName(route.name as string)) {
|
||
let menuId = route.params.menuId as string;
|
||
findIndex = menuList.findIndex(x => x.id == menuId);
|
||
} else {
|
||
findIndex = menuList.findIndex(x => x.url === route.path);
|
||
}
|
||
// /msg、/settings 由后台「边栏设置」配置后即进入菜单表;未配置时也允许直达/刷新,不被弹回桌面。
|
||
// /theme-preview 同理(预览页按 URL 参数挂档,不依赖菜单表与登录态)
|
||
if (findIndex === -1 && !["/msg", "/settings", "/theme-preview"].includes(route.path)) router.push({ name: "index" });
|
||
}
|
||
|
||
const getRouterKey: (route: any) => string = route => {
|
||
let str = route?.name;
|
||
// if (str === "clocklist") str += "-" + route.params.id;
|
||
return str;
|
||
};
|
||
const { menuVisible } = storeToRefs(store);
|
||
|
||
const mainStyle = computed(() => `background-image: url(${store.getCurWallPaperUrl})`);
|
||
</script>
|
||
|
||
<template>
|
||
<!-- 主题预览页(主题设置 2026-09-30):顶层独立分支——不进桌面框架(无左栏/Header/壁纸)、
|
||
不依赖登录态与 isRouterAlive 门控(未登录直开也不白屏);仅在该 document 内动态引通道 init -->
|
||
<ThemePreview v-if="route.path === '/theme-preview'" />
|
||
<div
|
||
v-else
|
||
:class="['app-desktop app-desktop__animation', !menuVisible ? 'app-desktop__full' : '']"
|
||
:style="mainStyle"
|
||
v-show="StateType > StateEnum.Loading"
|
||
>
|
||
<LeftMenu />
|
||
<div :class="{ 'app-main-wrap': true, 'base-mask': isBaseMask }" v-if="StateType === StateEnum.Desktop">
|
||
<MyHeader v-if="isHeader" />
|
||
<router-view v-if="!!isRouterAlive" v-slot="{ Component, route }">
|
||
<template v-if="isBaseMask">
|
||
<component :is="Component" :key="route?.name" />
|
||
</template>
|
||
<transition v-else name="fade" mode="out-in">
|
||
<div class="app-main-wrap">
|
||
<component :is="Component" :key="route?.name" />
|
||
</div>
|
||
</transition>
|
||
</router-view>
|
||
</div>
|
||
<template v-if="StateType === StateEnum.Iframe">
|
||
<BschIframe ref="refBschIframe" />
|
||
</template>
|
||
</div>
|
||
<div class="app-loading" v-if="StateType == StateEnum.Loading">
|
||
<div class="loadEffect">
|
||
<span></span>
|
||
<span></span>
|
||
<span></span>
|
||
<span></span>
|
||
<span></span>
|
||
<span></span>
|
||
<span></span>
|
||
<span></span>
|
||
</div>
|
||
</div>
|
||
<!-- <SearchPanel class="app-search" v-show="StateType == StateEnum.Search" /> -->
|
||
<UserInfoModal />
|
||
<LightPageModal />
|
||
</template>
|
||
|
||
<style lang="scss">
|
||
/* 开始过渡阶段,动画出去阶段 */
|
||
.fade-enter-active,
|
||
.fade-leave-active {
|
||
transition: all 1ms cubic-bezier(0, 0.99, 1, 0.03);
|
||
}
|
||
/* 进入开始 */
|
||
.fade-enter {
|
||
// transform: translateY(10px);
|
||
opacity: 0;
|
||
}
|
||
/* 出去终点 */
|
||
.fade-leave-active {
|
||
// transform: translateY(-10px);
|
||
// opacity: 0;
|
||
}
|
||
|
||
.back-enter-active,
|
||
.back-leave-active {
|
||
transition: all 1ms cubic-bezier(0, 0.99, 1, 0.03);
|
||
}
|
||
/* 进入开始 */
|
||
.back-enter {
|
||
display: none;
|
||
}
|
||
/* 出去终点 */
|
||
.back-leave-active {
|
||
display: none;
|
||
}
|
||
#app {
|
||
height: 100%;
|
||
}
|
||
.base-wrap-main .main-wrapper {
|
||
height: 100%;
|
||
}
|
||
</style>
|