You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

228 lines
5.8 KiB

<template>
<div :class="classObj" class="app-wrapper" :style="{ '--current-color': theme }">
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
<!-- iframe 全屏模式:隐藏侧边栏、标签栏、路由内容,只保留导航栏 + iframe -->
<template v-if="permissionStore.iframeMode">
<div class="main-container iframe-mode" :class="{ 'dashboard-mode': isDashboardRoute }" style="margin-left: 0;">
<div :class="{ 'fixed-header': fixedHeader }" style="width: 100%;">
<navbar ref="navbarRef" />
</div>
<section class="iframe-main" :class="{ 'iframe-main-full': isDashboardRoute }">
<iframe v-if="permissionStore.iframeUrl" :src="permissionStore.iframeUrl" class="iframe-full" frameborder="0" />
<router-view v-else class="iframe-full-page" />
</section>
<settings ref="settingRef" />
</div>
</template>
<!-- 正常菜单模式 -->
<template v-else>
<!-- 顶部头部整行 -->
<div class="top-header">
<navbar ref="navbarRef" />
</div>
<!-- 下方:左侧边栏 + 右内容 -->
<side-bar v-if="!sidebar.hide" class="sidebar-container" />
<div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }" class="main-container">
<div :class="{ 'fixed-header': fixedHeader }">
<tags-view v-if="needTagsView" />
</div>
<app-main />
<settings ref="settingRef" />
</div>
</template>
</div>
</template>
<script setup lang="ts">
import SideBar from './components/Sidebar/index.vue';
import { AppMain, Navbar, Settings, TagsView } from './components';
import { useAppStore } from '@/store/modules/app';
import { useSettingsStore } from '@/store/modules/settings';
import { usePermissionStore } from '@/store/modules/permission';
import { initWebSocket } from '@/utils/websocket';
import { initSSE } from '@/utils/sse';
import router from '@/router';
const permissionStore = usePermissionStore();
const settingsStore = useSettingsStore();
const theme = computed(() => settingsStore.theme);
const sidebar = computed(() => useAppStore().sidebar);
const device = computed(() => useAppStore().device);
const needTagsView = computed(() => settingsStore.tagsView);
const fixedHeader = computed(() => settingsStore.fixedHeader);
const classObj = computed(() => ({
hideSidebar: !sidebar.value.opened,
openSidebar: sidebar.value.opened,
withoutAnimation: sidebar.value.withoutAnimation,
mobile: device.value === 'mobile'
}));
const { width } = useWindowSize();
const WIDTH = 992; // refer to Bootstrap's responsive design
watchEffect(() => {
if (device.value === 'mobile') {
useAppStore().closeSideBar({ withoutAnimation: false });
}
if (width.value - 1 < WIDTH) {
useAppStore().toggleDevice('mobile');
useAppStore().closeSideBar({ withoutAnimation: true });
} else {
useAppStore().toggleDevice('desktop');
}
});
const navbarRef = ref<InstanceType<typeof Navbar>>();
const settingRef = ref<InstanceType<typeof Settings>>();
const isDashboardRoute = computed(() => {
return permissionStore.fullscreenRoute === router.currentRoute.value.path && !permissionStore.iframeUrl;
});
onMounted(() => {
nextTick(() => {
if (permissionStore.iframeMode && permissionStore.fullscreenRoute && router.currentRoute.value.path !== permissionStore.fullscreenRoute) {
router.replace(permissionStore.fullscreenRoute);
}
});
});
onMounted(() => {
const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
initWebSocket(protocol + window.location.host + import.meta.env.VITE_APP_BASE_API + '/resource/websocket');
});
onMounted(() => {
initSSE(import.meta.env.VITE_APP_BASE_API + '/resource/sse');
});
const handleClickOutside = () => {
useAppStore().closeSideBar({ withoutAnimation: false });
};
const setLayout = () => {
settingRef.value?.openSetting();
};
</script>
<style lang="scss" scoped>
@use '@/assets/styles/mixin.scss';
@use '@/assets/styles/variables.module.scss' as *;
.app-wrapper {
@include mixin.clearfix;
position: relative;
height: 100%;
width: 100%;
&.mobile.openSidebar {
position: fixed;
top: 0;
}
}
.drawer-bg {
background: #000;
opacity: 0.3;
width: 100%;
top: 0;
height: 100%;
position: absolute;
z-index: 999;
}
.top-header {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 50px;
z-index: 1002;
background: $fixed-header-bg;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
}
.main-container {
padding-top: 80px;
box-sizing: border-box;
}
.fixed-header {
position: fixed;
top: 50px;
right: 0;
z-index: 9;
width: calc(100% - #{$base-sidebar-width});
transition: width 0.28s;
background: $fixed-header-bg;
}
.hideSidebar .fixed-header {
width: calc(100% - 54px);
}
.sidebarHide .fixed-header {
width: 100%;
}
.mobile .fixed-header {
width: 100%;
}
.iframe-mode {
padding-top: 0;
.fixed-header {
top: 0;
}
}
.iframe-mode.dashboard-mode {
}
.iframe-mode.dashboard-mode .iframe-main {
}
.iframe-main {
width: 100%;
position: relative;
overflow: hidden;
}
.iframe-main-full {
height: calc(100vh - 50px);
overflow: hidden;
}
.fixed-header + .iframe-main {
padding-top: 50px;
}
.iframe-full {
width: 100%;
height: calc(100vh - 50px);
border: none;
display: block;
}
.iframe-full-page {
width: 100%;
min-height: calc(100vh - 50px);
overflow: auto;
}
.iframe-mode .dashboard-navbar {
// background: linear-gradient(180deg, #0c3d8f 0%, #0a2e72 100%);
border-bottom: 1px solid rgba(64, 160, 255, 0.2);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.iframe-mode .dashboard-navbar .nav-item:hover {
background: rgba(255, 255, 255, 0.12);
}
.iframe-mode .dashboard-navbar .user-info:hover {
background: rgba(255, 255, 255, 0.15);
}
</style>