Files
UserbaseFront/src/components/layout.vue
T

260 lines
9.6 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.
<script setup lang="ts">
import { ref, inject, onBeforeMount, watch, toRefs, nextTick, computed } from "vue";
import { LayoutData } from "@/types/Basics";
import { useRoute, useRouter } from "vue-router";
import { Message } from "view-ui-plus";
import useCurrentInstance from "@/utils/hook/useCurrentInstance";
let props = defineProps<{ layoutData: LayoutData }>();
const reload: Function = inject("reload") || new Function("console.error('empty')");
const appPanel = ref();
let route = useRoute();
const router = useRouter();
const isPanel = ref(false),
currPath = ref<string>(""),
navPath = ref<any[]>([]);
const { baseHost, ssoHost, appName } = props.layoutData;
const { appList, userInfo, isAlone, modList } = toRefs(props.layoutData);
const modArr = computed(() => modList.value)
const { $bus } = useCurrentInstance();
const pathNavStyle = computed(() => (isAlone.value ? { left: "0", width: "100%" } : null));
onBeforeMount(async () => {
// 初次加载时手动同步一次(参考 bschstandard created 中手动触发 routeChange)
routeChange(route);
});
// 路由变化时实时同步左侧高亮与面包屑
watch(
() => route.fullPath,
() => routeChange(route)
);
/**
* 将菜单树扁平化(参考 bschstandard.frame.layout 的 flaten)
* - 子节点携带父节点的 id 作为 pid,用于面包屑与高亮联动
*/
const flaten = (tree: any[], pid = 0): any[] => {
const arr: any[] = [];
tree.forEach(element => {
const { id, name, page, modId } = element;
arr.push({ id, name, page, modId, pid });
if (element.children && element.children.length) {
arr.push(...flaten(element.children, id));
}
});
return arr;
};
const menuArr = computed(() => flaten(modList.value || []));
/**
* 同步当前路由对应的左侧高亮项与面包屑
* - ① 命中菜单树 modId:高亮自身;分组下的子项面包屑为「父级 / 自身」
* - ② 未命中但带 meta.parentId 的深层子页(如 应用类别管理):
* 用 parentId 找父菜单 → 高亮父菜单、面包屑为「(祖先/)父级(可点) / 当前页(不可点)」
* —— 对应效果「应用管理 > 应用类别管理」(参考 bschstandard.frame.layout 的 parentId 回退)
* - ③ 未在菜单树中的旧路由(/subject 等)降级按路由表兜底,仅展示自身
*/
const routeChange = (to: any) => {
const meta: any = to.meta || {};
const { modId, title, parentId } = meta;
const navArr: any[] = [];
// ① 命中菜单节点
const find: any = menuArr.value.find(x => x.modId === modId);
if (find) {
currPath.value = find.page || "";
if (find.pid) {
const pfind = menuArr.value.find(x => x.id === find.pid);
if (pfind) navArr.push({ name: pfind.name });
}
navArr.push({ name: find.name || title, page: find.page });
navPath.value = navArr;
return;
}
// ② 深层子页:按 meta.parentId 找父菜单,高亮父菜单并展开多级面包屑
if (parentId) {
const parent = menuArr.value.find(x => x.modId === parentId);
if (parent) {
currPath.value = parent.page || ""; // 高亮父菜单(如「应用管理」)
if (parent.pid) {
const gp = menuArr.value.find(x => x.id === parent.pid);
if (gp) navArr.push({ name: gp.name }); // 祖先(若父菜单本身在分组下)
}
navArr.push({ name: parent.name, page: parent.page }); // 父级可点击
navArr.push({ name: title }); // 当前深层页,不可点击
navPath.value = navArr;
return;
}
}
// ③ 旧路由兜底:路由表中带 modId 的,仅展示自身
const r = router.getRoutes().find(rt => (rt.meta as any)?.modId === modId);
if (r) {
currPath.value = r.path;
navPath.value = [{ name: (r.meta as any).title, page: r.path }];
return;
}
currPath.value = "";
navPath.value = [];
};
const showPanel = async () => {
if (appList.value.length == 0) {
Message.info("暂无常用应用!");
return;
}
isPanel.value = false;
await nextTick();
isPanel.value = true;
await nextTick();
var showPanelClick = () => {
appPanel.value.onclick = (e: any) => {
e.stopPropagation();
};
document.addEventListener(
"click",
() => {
isPanel.value = false;
},
{ once: true }
);
};
setTimeout(showPanelClick, 0);
};
const linkMenu = (page: string) => {
const path = route.path;
if (page === path) reload();
else router.push(page);
};
const linkUserInfo = () => {
window.location.href = baseHost + "/v/userinfo";
};
const logout = () => {
let url = window.location.href;
url = "/logout?service=" + url;
window.location.href = ssoHost + url;
};
const childChange = (index: number) => {
modArr.value[index].childState = !modArr.value[index].childState;
};
function linkHomePage() {
//@ts-ignore
const bschUtil = BschLoadUtil.getInstance();
if (!bschUtil.getIsSelf()) {
bschUtil.goBackDesktop();
} else {
window.location.href = baseHost + "/BschHome/front/index.html";
}
}
</script>
<template>
<div class="layout-wrap">
<header class="header-container fix">
<div class="fl align-middle icon-box">
<div title="常用应用" :class="['icon-btn', isPanel ? 'active' : '']" @click="showPanel">
<i class="iconfont icon-apps"></i>
</div>
</div>
<div class="delimiter fl align-middle"></div>
<h4 class="fl align-middle">{{ appName }}</h4>
<div class="school-bg"></div>
<div class="header-aside fr">
<img :src="userInfo.avatar" class="fl align-middle" @click="linkUserInfo" />
<a class="name fl align-middle text-ellipsis" href="javascript:void(0);" @click="linkUserInfo">
{{ userInfo.name }}
</a>
<div class="delimiter fl align-middle"></div>
<a href="javascript:;" @click="logout" class="logout fl align-middle" title="退出登陆">
<i class="iconfont icon-logout"></i>
</a>
</div>
</header>
<div class="left-menu" v-if="!isAlone">
<div class="menu-top-box"></div>
<ul>
<template v-for="(item, index) in modArr">
<template v-if="item.children && item.children.length > 0">
<li>
<a href="javascript:;" @click="childChange(index)">
<i :class="item.childState ? 'arrow-down' : 'arrow-up'"></i>
{{ item.name }}
</a>
</li>
<transition name="fade_child_ul">
<div class="child-item-wrap" v-show="item.childState">
<li :class="citem.page == currPath ? 'active child-item' : 'child-item'"
v-for="citem in item.children">
<a href="javascript:;" @click="linkMenu(citem.page)">
{{ citem.name }}
</a>
</li>
</div>
</transition>
</template>
<li v-else :class="item.page == currPath ? 'active' : ''" :key="'menu_li_' + index">
<a href="javascript:;" @click="linkMenu(item.page)">
{{ item.name }}
</a>
</li>
</template>
</ul>
<div class="clear-box"></div>
</div>
<div class="path-nav" :style="pathNavStyle">
<Breadcrumb separator=">">
<a href="javascript:;" @click="linkHomePage">
<BreadcrumbItem>
<i class="iconfont icon-category"></i>
首页
</BreadcrumbItem>
</a>
<BreadcrumbItem v-if="!layoutData.isAlone" to="/">{{ appName }}</BreadcrumbItem>
<template v-for="(item, index) in navPath">
<BreadcrumbItem v-if="index != navPath.length - 1" :to="item.page">
{{ item.name }}
</BreadcrumbItem>
<BreadcrumbItem v-else>{{ item.name }}</BreadcrumbItem>
</template>
</Breadcrumb>
</div>
<transition name="fade_apps">
<div class="app-panel" ref="appPanel" v-if="isPanel">
<div class="tips-box"></div>
<ul class="fix">
<li class="fl" v-for="item in appList">
<a :href="item.url" target="_bank" rel="noopener noreferrer">
<div class="icon-box">
<img :src="item.icon" alt="" />
</div>
<p class="text-ellipsis">{{ item.name }}</p>
</a>
</li>
</ul>
<div class="link-panel">
<a :href="baseHost + '/BschHome/front/index.html#/apps'" target="_bank" rel="noopener noreferrer">
全部应用
<i>→</i>
</a>
</div>
</div>
</transition>
</div>
</template>