This commit is contained in:
iqudoo
2026-06-05 17:22:32 +08:00
commit eb4e8f1a04
90 changed files with 21224 additions and 0 deletions

441
src/views/PageDashboard.vue Normal file
View File

@@ -0,0 +1,441 @@
<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>