forked from tools/tape-springboot-sysadmin
441 lines
11 KiB
Vue
441 lines
11 KiB
Vue
<template>
|
||
<a-layout class="layout" :style="menuStyle">
|
||
<!-- 移动设备上的遮罩层 -->
|
||
<div v-if="isMobile && siderVisible" class="mobile-mask" @click="toggleSider"></div>
|
||
<!-- 侧边栏 -->
|
||
<a-layout-sider class="sider hide-scrollbar" @collapse="handleCollapse" :collapsed="collapsed"
|
||
:class="{ 'mobile-sider': isMobile, 'mobile-sider-visible': siderVisible }">
|
||
<div class="logo" v-if="!collapsed">
|
||
<svg t="1779016191270" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="21892"
|
||
width="256" height="256" class="custom-svg">
|
||
<path
|
||
d="M886.97857168 763.55H587.02142833v112.69285753h74.95714334a37.54285751 37.54285751 0 0 1 0 75.08571415H362.02142833a37.54285751 37.54285751 0 0 1-1e-8-75.08571415h74.95714336v-112.69285753H137.02142832A75.02142833 75.02142833 0 0 1 62 688.46428583V162.8C62 121.33571417 95.55714247 87.71428583 137.02142832 87.71428583h749.95714336C928.44285753 87.71428583 962 121.33571417 962 162.8v525.66428585c0 41.46428585-33.55714249 75.08571415-75.02142833 75.08571415zM137.02142832 650.92142832h749.95714336V575.83571417H137.02142832v75.08571415z"
|
||
p-id="21893" fill="#ffffff"></path>
|
||
</svg>
|
||
<div class="logo-text-container">
|
||
<span class="logo-text">
|
||
{{ config.APP_TITLE }}
|
||
</span>
|
||
<div class="logo-subtitle">
|
||
{{ config.APP_SUB_TITLE }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="logo" v-else>
|
||
<svg t="1779016191270" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="21892"
|
||
width="256" height="256" class="custom-svg">
|
||
<path
|
||
d="M886.97857168 763.55H587.02142833v112.69285753h74.95714334a37.54285751 37.54285751 0 0 1 0 75.08571415H362.02142833a37.54285751 37.54285751 0 0 1-1e-8-75.08571415h74.95714336v-112.69285753H137.02142832A75.02142833 75.02142833 0 0 1 62 688.46428583V162.8C62 121.33571417 95.55714247 87.71428583 137.02142832 87.71428583h749.95714336C928.44285753 87.71428583 962 121.33571417 962 162.8v525.66428585c0 41.46428585-33.55714249 75.08571415-75.02142833 75.08571415zM137.02142832 650.92142832h749.95714336V575.83571417H137.02142832v75.08571415z"
|
||
p-id="21893" fill="#ffffff"></path>
|
||
</svg>
|
||
</div>
|
||
<IViewMenu v-model:selectedKeys="selectedKeys" :collapsed="collapsed" :menuConfig="menuConfig"
|
||
:bgColor="themeInfo.bgColor" :textColor="themeInfo.textColor" :selectedTextColor="themeInfo.selectedTextColor"
|
||
:selectedBgColor="themeInfo.selectedBgColor" :hoverTextColor="themeInfo.hoverTextColor"
|
||
:hoverBgColor="themeInfo.hoverBgColor" @item-click="handleMenuItemClick" />
|
||
</a-layout-sider>
|
||
|
||
<a-layout class="layout-content" :class="{ collapsed: collapsed && !isMobile, mobile: isMobile }">
|
||
<a-layout-header class="header" :class="{ 'mobile-header': isMobile }">
|
||
<div class="header-left">
|
||
<div class="header-left-icon">
|
||
<menu-unfold-outlined v-if="isMobile ? !siderVisible : collapsed" class="trigger" @click="toggleSider" />
|
||
<menu-fold-outlined v-else class="trigger" @click="toggleSider" />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="header-right">
|
||
<a-dropdown :trigger="['click']">
|
||
<a class="user-dropdown">
|
||
<span class="username" :title="unitName">{{ unitName }}</span>
|
||
<down-outlined />
|
||
</a>
|
||
<template #overlay>
|
||
<a-menu>
|
||
<a-menu-item key="1" @click="handleLogout">
|
||
<logout-outlined />
|
||
退出登录
|
||
</a-menu-item>
|
||
</a-menu>
|
||
</template>
|
||
</a-dropdown>
|
||
</div>
|
||
</a-layout-header>
|
||
<a-layout-content class="content hide-scrollbar">
|
||
<router-view></router-view>
|
||
</a-layout-content>
|
||
</a-layout>
|
||
<IViewUrlEmbedHost />
|
||
</a-layout>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, watch, computed, onMounted, onBeforeMount, onUnmounted } from "vue";
|
||
import { useRouter, useRoute } from "vue-router";
|
||
import { useUserStore } from "../store/user";
|
||
import { showSuccessTips } from "@/iview/utils/messageTips";
|
||
import {
|
||
MenuUnfoldOutlined,
|
||
MenuFoldOutlined,
|
||
LogoutOutlined,
|
||
DownOutlined,
|
||
} from "@ant-design/icons-vue";
|
||
import { menuConfig, getMenuKeyByPath } from "../config/menu";
|
||
import config from "@/config/index";
|
||
import IViewMenu from "@/iview/IViewMenu.vue";
|
||
import IViewUrlEmbedHost from "@/iview/IViewUrlEmbedHost.vue";
|
||
|
||
const router = useRouter();
|
||
const route = useRoute();
|
||
const userStore = useUserStore();
|
||
const themeInfo = ref({
|
||
siderColor: "#1677ff",
|
||
logoColor: "#FFFFFF",
|
||
textColor: "#FFFFFF",
|
||
selectedTextColor: "#ffffff",
|
||
selectedBgColor: "#0b68ff",
|
||
hoverTextColor: "#ffffff",
|
||
hoverBgColor: "#0b68ff",
|
||
});
|
||
|
||
// 侧边栏折叠状态(桌面端使用)
|
||
const collapsed = ref(true); // 默认折叠
|
||
// 是否是移动设备
|
||
const isMobile = ref(false);
|
||
// 侧边栏是否显示(移动端使用)
|
||
const siderVisible = ref(false);
|
||
|
||
// 监听窗口大小变化
|
||
const checkIsMobile = () => {
|
||
const newIsMobile = window.innerWidth <= 768;
|
||
// 如果状态变了才更新,避免不必要的渲染
|
||
if (newIsMobile !== isMobile.value) {
|
||
isMobile.value = newIsMobile;
|
||
// 如果变成移动设备,确保侧边栏隐藏
|
||
if (isMobile.value) {
|
||
siderVisible.value = false;
|
||
}
|
||
}
|
||
};
|
||
|
||
// 在组件挂载前初始化移动设备检测
|
||
onBeforeMount(() => {
|
||
checkIsMobile();
|
||
window.addEventListener("resize", checkIsMobile);
|
||
});
|
||
|
||
// 组件卸载时移除事件监听
|
||
onUnmounted(() => {
|
||
window.removeEventListener("resize", checkIsMobile);
|
||
});
|
||
|
||
// 当前选中的菜单项
|
||
const selectedKeys = ref([]);
|
||
|
||
// 侧边栏折叠状态
|
||
const handleCollapse = (collapsed) => {
|
||
collapsed.value = collapsed;
|
||
userStore.saveCollapseSidebar(collapsed);
|
||
};
|
||
|
||
// 计算菜单样式
|
||
const menuStyle = computed(() => {
|
||
return {
|
||
"--theme-sider-color": themeInfo.value.siderColor,
|
||
"--theme-logo-color": themeInfo.value.logoColor,
|
||
border: "none",
|
||
};
|
||
});
|
||
|
||
// 根据当前路由路径设置选中菜单项和展开状态
|
||
const setSelectedMenu = () => {
|
||
const path = route.path;
|
||
const menuKey = getMenuKeyByPath(path);
|
||
|
||
// IViewMenu 只支持一级菜单,直接使用 menuKey
|
||
selectedKeys.value = [menuKey];
|
||
};
|
||
|
||
// 初始化菜单选中状态
|
||
onMounted(() => {
|
||
setSelectedMenu();
|
||
// 重新获取用户信息
|
||
if (userStore.isLoggedIn) {
|
||
userStore.getUserProfile();
|
||
}
|
||
collapsed.value = userStore.isCollapseSidebar;
|
||
});
|
||
|
||
// 监听路由变化,更新选中的菜单项
|
||
watch(
|
||
() => route.path,
|
||
(newPath) => {
|
||
setSelectedMenu();
|
||
// 在移动设备上,路由变化时隐藏侧边栏
|
||
if (isMobile.value) {
|
||
siderVisible.value = false;
|
||
}
|
||
}
|
||
);
|
||
|
||
// 用户信息
|
||
const unitName = computed(() => {
|
||
const info = userStore.userInfo;
|
||
if (!info) return "";
|
||
return [info?.name, info?.username].filter(Boolean).join(" ");
|
||
});
|
||
|
||
// 切换侧边栏状态
|
||
const toggleSider = () => {
|
||
if (isMobile.value) {
|
||
// 在移动设备上,切换侧边栏显示/隐藏
|
||
siderVisible.value = !siderVisible.value;
|
||
} else {
|
||
// 在桌面设备上,切换侧边栏折叠/展开
|
||
collapsed.value = !collapsed.value;
|
||
userStore.saveCollapseSidebar(collapsed.value);
|
||
}
|
||
};
|
||
|
||
// 退出登录
|
||
const handleLogout = () => {
|
||
userStore.logout();
|
||
router.push("/login");
|
||
showSuccessTips("已退出登录");
|
||
};
|
||
|
||
// 菜单点击处理,在移动端时点击菜单项后自动隐藏侧边栏
|
||
const handleMenuClick = (path) => {
|
||
router.push(path);
|
||
if (isMobile.value) {
|
||
siderVisible.value = false;
|
||
}
|
||
};
|
||
|
||
// 处理菜单项点击(IViewMenu 组件使用)
|
||
const handleMenuItemClick = (item) => {
|
||
if (item.path) {
|
||
handleMenuClick(item.path);
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.layout {
|
||
min-height: 100vh;
|
||
width: 100%;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.sider {
|
||
position: fixed;
|
||
left: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
height: 100vh;
|
||
overflow-y: auto;
|
||
background: var(--theme-sider-color, #001529);
|
||
box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
|
||
transition: width 0.3s, transform 0.3s;
|
||
z-index: 10;
|
||
}
|
||
|
||
.mobile-sider {
|
||
transform: translateX(-100%);
|
||
/* 默认隐藏 */
|
||
}
|
||
|
||
.mobile-sider-visible {
|
||
transform: translateX(0);
|
||
/* 显示 */
|
||
}
|
||
|
||
.mobile-mask {
|
||
position: fixed;
|
||
top: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
left: 0;
|
||
background-color: rgba(0, 0, 0, 0.45);
|
||
z-index: 9;
|
||
}
|
||
|
||
.logo {
|
||
height: 64px;
|
||
padding: 16px;
|
||
text-align: left;
|
||
overflow: hidden;
|
||
white-space: nowrap;
|
||
transition: all 0.3s;
|
||
font-size: 12px;
|
||
color: var(--theme-logo-color, #ffffff);
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
gap: 5px;
|
||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||
}
|
||
|
||
.logo-text {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
margin-left: 5px;
|
||
}
|
||
|
||
.logo-subtitle {
|
||
font-size: 12px;
|
||
color: #ffffff;
|
||
margin-left: 5px;
|
||
}
|
||
|
||
.custom-svg {
|
||
width: 30px;
|
||
height: 30px;
|
||
}
|
||
|
||
.layout-content {
|
||
position: relative;
|
||
margin-left: 200px;
|
||
width: calc(100% - 200px);
|
||
height: 100vh;
|
||
height: 100dvh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.layout-content.mobile {
|
||
margin-left: 0;
|
||
width: 100%;
|
||
}
|
||
|
||
.header {
|
||
position: fixed;
|
||
top: 0;
|
||
right: 0;
|
||
left: 200px;
|
||
z-index: 8;
|
||
height: 64px;
|
||
background: #fff;
|
||
padding: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||
width: calc(100% - 200px);
|
||
}
|
||
|
||
.mobile-header {
|
||
left: 0;
|
||
width: 100%;
|
||
}
|
||
|
||
.header-left {
|
||
padding: 0 16px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.header-center {
|
||
flex: 1;
|
||
display: flex;
|
||
justify-content: center;
|
||
}
|
||
|
||
.header-right {
|
||
padding: 0 16px;
|
||
min-width: 0;
|
||
flex: 1;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.header-left-icon {
|
||
padding: 0 8px;
|
||
}
|
||
|
||
.trigger {
|
||
font-size: 18px;
|
||
cursor: pointer;
|
||
transition: color 0.3s;
|
||
}
|
||
|
||
.trigger:hover {
|
||
color: #1890ff;
|
||
}
|
||
|
||
.user-dropdown {
|
||
display: flex;
|
||
align-items: center;
|
||
color: rgba(0, 0, 0, 0.85);
|
||
cursor: pointer;
|
||
padding: 0 8px;
|
||
min-width: 0;
|
||
max-width: 100%;
|
||
}
|
||
|
||
.user-avatar {
|
||
margin-right: 8px;
|
||
}
|
||
|
||
.username {
|
||
margin-right: 8px;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
white-space: nowrap;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.user-role {
|
||
color: rgba(0, 0, 0, 0.45);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.content {
|
||
position: relative;
|
||
flex: 1;
|
||
min-height: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: 24px;
|
||
background: #fff;
|
||
box-sizing: border-box;
|
||
margin-top: 64px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.content-view {
|
||
flex: 1 1 0;
|
||
min-height: 0;
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
/* 当侧边栏折叠时调整布局 */
|
||
.layout-content.collapsed {
|
||
margin-left: 80px;
|
||
width: calc(100% - 80px);
|
||
}
|
||
|
||
.layout-content.collapsed .header {
|
||
left: 80px;
|
||
width: calc(100% - 80px);
|
||
}
|
||
|
||
/* 移动端适配样式 */
|
||
@media (max-width: 768px) {
|
||
.layout-content {
|
||
margin-left: 0;
|
||
width: 100%;
|
||
}
|
||
|
||
.header {
|
||
left: 0;
|
||
width: 100%;
|
||
}
|
||
|
||
.content {
|
||
padding: 16px;
|
||
}
|
||
}
|
||
</style> |