Files
springboot-shop-sysadmin/src/views/PageDashboard.vue
iqudoo eb4e8f1a04 init
2026-06-05 17:22:32 +08:00

441 lines
11 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.

<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>