feat: 接入 UserHome 前端业务代码(含真实 src/依赖,保留 projman 标准 CI)
This commit is contained in:
+137
-157
@@ -1,184 +1,170 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, reactive, provide, computed } from "vue";
|
||||
import layout from "./components/layout.vue";
|
||||
import { ref, onMounted, reactive, provide, computed, nextTick } from "vue";
|
||||
import Basic from "@/api/BaseApi";
|
||||
|
||||
import { CurrentRole, LayoutData } from "./types/Basics";
|
||||
import { useRouter } from "vue-router";
|
||||
import config from "@/assets/config/host.config";
|
||||
import { RouteRecordRaw, useRoute, useRouter } from "vue-router";
|
||||
import ContextMenu from "@/components/ContextMenu.vue";
|
||||
import { useDefaultStore } from "@/store/index.js";
|
||||
import Apis from '@/api/index'
|
||||
import { alovaInstance } from "@/utils/alova/alova-iview";
|
||||
import useCurrentInstance from "@/utils/hook/useCurrentInstance";
|
||||
import { UserRoleEnum } from "./types";
|
||||
import { isRouteAllowed } from "@/router";
|
||||
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 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 { $bus } = useCurrentInstance();
|
||||
|
||||
const refLayout = useTemplateRef('refLayout');
|
||||
const isRouterAlive = ref(true),
|
||||
isLogin = ref(false),
|
||||
isNoAuthority = ref(false);
|
||||
// 当前登录用户,供 resetMenuPath 事件复用
|
||||
const currentUser = ref<any>(null);
|
||||
const layoutData: LayoutData = reactive({
|
||||
isAlone: false,
|
||||
appName: null,
|
||||
appList: [],
|
||||
modList: [],
|
||||
userInfo: null,
|
||||
baseHost: null,
|
||||
ssoHost: null,
|
||||
});
|
||||
const isRouterAlive = ref(false),
|
||||
StateType = ref<StateEnum>(StateEnum.Loading);
|
||||
|
||||
const reload = () => {
|
||||
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;
|
||||
}, 0);
|
||||
};
|
||||
provide("reload", reload);
|
||||
|
||||
const layoutInit = (info: any, modList: any[], appList: Array<any>, baseHost: string, ssoHost: string) => {
|
||||
layoutData.appName = "系统管理";
|
||||
layoutData.baseHost = baseHost;
|
||||
layoutData.ssoHost = ssoHost;
|
||||
|
||||
let { id, name, profilePicture } = info;
|
||||
layoutData.modList = modList || [];
|
||||
// 有子菜单时不算「单模块独占」模式,需展示左侧菜单
|
||||
const hasChildren = modList.some((m: any) => m.children && m.children.length > 0);
|
||||
layoutData.isAlone = modList.length <= 1 && !hasChildren;
|
||||
layoutData.userInfo = { id, name, avatar: profilePicture };
|
||||
layoutData.appList = appList || [];
|
||||
};
|
||||
|
||||
/**
|
||||
* 角色判定(降级兜底):GetUserScope 失败时按 GetUserInfo 的 user.type 推断——
|
||||
* type=1 → SuperAdmin,其余 → School。同时写入 currentRole 供下游
|
||||
* Utils.getCurrentRoleInfo 与请求头注入读取(保持兼容;正常路径以 GetUserScope 为准)。
|
||||
*/
|
||||
const setCurrentRole = (user: any) => {
|
||||
const userId = user?.id ?? null;
|
||||
const roleId = user?.type ?? null;
|
||||
|
||||
const currentRole: CurrentRole = {
|
||||
childId: null,
|
||||
roleId,
|
||||
schId: null,
|
||||
userId,
|
||||
};
|
||||
localStorage.setItem("currentRole", encodeURI(JSON.stringify(currentRole)));
|
||||
|
||||
// 主题样式已本地化到 src/assets/styles/layout.css(含 default-theme / base.layout.beta / _iview_override),不再请求远程样式
|
||||
|
||||
if (user?.type === 1) {
|
||||
store.setUserRole(UserRoleEnum.SuperAdmin);
|
||||
} else {
|
||||
// type=2 或其他 → School(默认)
|
||||
store.setUserRole(UserRoleEnum.School);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
onMounted(async () => {
|
||||
const { baseHost, ssoHost } = config;
|
||||
|
||||
try {
|
||||
var user: any = await alovaInstance.Request(Apis.Base.getUserInfo());
|
||||
|
||||
currentUser.value = user;
|
||||
|
||||
// 系统字典
|
||||
await store.fetchSysDict();
|
||||
|
||||
// 角色判定:优先 GetUserScope(后端单点判定四级访问级别,前端不重复实现规则),
|
||||
// 失败时降级走 setCurrentRole 的 type 推断
|
||||
setCurrentRole(user);
|
||||
try {
|
||||
const scope: any = await alovaInstance.Request(Apis.Base.getUserScope());
|
||||
if (scope && scope.level != null) {
|
||||
store.setUserRole(scope.level);
|
||||
store.setUserScope({
|
||||
bureauGroupId: scope.bureauGroupId ?? null,
|
||||
schoolGroupIds: scope.schoolGroupIds ?? null,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("获取访问范围失败,按用户类型降级判定角色", e);
|
||||
}
|
||||
|
||||
// NONE 级:整体「暂无权限」,不加载菜单(接口层已被后端 NONE 拦截器 403 双挡)
|
||||
store.setRoleLoaded(true);
|
||||
if (store.userRole === UserRoleEnum.None) {
|
||||
isNoAuthority.value = true;
|
||||
layoutInit(user, [], [], baseHost, ssoHost);
|
||||
isLogin.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// 首次导航早于角色落定被守卫放行:此处复核当前路由(直接敲 URL 场景),越权重定向 /no-auth
|
||||
if (!isRouteAllowed(router.currentRoute.value, store.userRole)) {
|
||||
router.replace("/no-auth");
|
||||
}
|
||||
|
||||
// 模块列表(按当前级别过滤菜单权限矩阵)
|
||||
await store.fetchModules();
|
||||
layoutInit(user, store.modules, store.modules, baseHost, ssoHost);
|
||||
|
||||
// 登录后直接跳转到左侧菜单第一个可用入口
|
||||
// (顶层可能是单页,如「组,用户管理」;也可能是分组,取其首个子项)
|
||||
// 仅在用户处于根路径时才跳转,避免刷新业务页(如 /app-manage/list)被强制拉回
|
||||
const firstModule = store.modules[0];
|
||||
const firstPage = firstModule?.children?.length
|
||||
? firstModule.children[0].page
|
||||
: firstModule?.page;
|
||||
if (firstPage && router.currentRoute.value.path === "/") {
|
||||
router.replace(firstPage);
|
||||
}
|
||||
|
||||
isLogin.value = true;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
if (error != "redirect") Message.error("载入用户信息失败!");
|
||||
}
|
||||
}, 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;
|
||||
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");
|
||||
})();
|
||||
|
||||
/**切换角色 */
|
||||
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);
|
||||
}
|
||||
if (findIndex === -1) 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);
|
||||
|
||||
/**
|
||||
* 需要被 keep-alive 缓存的组件名(按路由 meta.keepAlive 推导)。
|
||||
* include 按组件 name 匹配,因此对应页面需 defineOptions({ name }) 与之相等。
|
||||
*/
|
||||
const cachedNames = computed(() =>
|
||||
router.getRoutes().filter((r: any) => r.meta?.keepAlive).map((r: any) => r.name as string)
|
||||
);
|
||||
const mainStyle = computed(() => `background-image: url(${store.getCurWallPaperUrl})`);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<layout ref="refLayout" v-if="isLogin" :layoutData="layoutData"></layout>
|
||||
<div v-if="isLogin" :class="['base-wrap-main', layoutData.isAlone ? 'alone-page' : '']">
|
||||
<template v-if="!isNoAuthority">
|
||||
<div
|
||||
: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 }">
|
||||
<transition name="fade" mode="out-in">
|
||||
<keep-alive :include="cachedNames">
|
||||
<component :is="Component" :key="getRouterKey(route)" />
|
||||
</keep-alive>
|
||||
<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 v-else class="no-authority-wrap">
|
||||
<h3>暂无权限</h3>
|
||||
<h6>如需查看,请联络管理员为您分配权限</h6>
|
||||
</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">
|
||||
@@ -187,13 +173,11 @@ const cachedNames = computed(() =>
|
||||
.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);
|
||||
@@ -204,21 +188,17 @@ const cachedNames = computed(() =>
|
||||
.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%;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user