| src/assets/styles/sidebar.scss | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/layout/components/Sidebar/Logo.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/layout/components/Sidebar/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/layout/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/assets/styles/sidebar.scss
@@ -88,11 +88,11 @@ } // menu hover .sub-menu-title-noDropdown, .el-sub-menu__title { &:hover { background-color: var(--menu-hover) !important; } .submenu-title-noDropdown, .el-sub-menu__title { &:hover { background-color: var(--menu-hover) !important; } } & .theme-light .is-active > .el-sub-menu__title { color: var(--current-color) !important; @@ -120,47 +120,86 @@ } } .hideSidebar { .sidebar-container { width: 54px !important; } .hideSidebar { .sidebar-container { width: 68px !important; padding-left: 12px; padding-right: 0; } .main-container { margin-left: 84px; } .main-container { margin-left: 54px; } .sub-menu-title-noDropdown { padding: 0 !important; position: relative; .el-tooltip { padding: 0 !important; .svg-icon { margin-left: 20px; } } } .el-sub-menu { overflow: hidden; & > .el-sub-menu__title { padding: 0 !important; .svg-icon { margin-left: 20px; } } } .el-menu--collapse { .el-sub-menu { & > .el-sub-menu__title { & > span { height: 0; width: 0; overflow: hidden; visibility: hidden; .submenu-title-noDropdown { padding: 0 !important; position: relative; display: flex !important; align-items: center; justify-content: center; .el-tooltip { padding: 0 !important; display: inline-flex !important; align-items: center; justify-content: center; width: 100%; .svg-icon { margin-left: 0; } } .el-menu-tooltip__trigger { width: 100%; display: inline-flex !important; align-items: center; justify-content: center; .svg-icon { width: 18px; height: 18px; margin-right: 0; flex-shrink: 0; } } } .el-sub-menu { overflow: hidden; & > .el-sub-menu__title { padding: 0 !important; display: flex !important; align-items: center; justify-content: center; .svg-icon { margin-left: 0; } } } .el-menu--collapse { > .el-menu-item, .el-sub-menu { & > .el-sub-menu__title, &.el-menu-item { display: flex !important; align-items: center; justify-content: center; .svg-icon { width: 18px; height: 18px; margin-right: 0; flex-shrink: 0; } & > span { height: 0; width: 0; overflow: hidden; visibility: hidden; display: inline-block; } & > i { @@ -169,11 +208,11 @@ overflow: hidden; visibility: hidden; display: inline-block; } } } } } } } } } } .el-menu--collapse .el-menu .el-sub-menu { min-width: $base-sidebar-width !important; src/layout/components/Sidebar/Logo.vue
@@ -1,143 +1,142 @@ <template> <div class="sidebar-logo-container" :class="{ 'collapse': collapse }"> <transition name="sidebarLogoFade"> <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/"> <img v-if="logoUrl" :src="logoUrl" class="sidebar-logo" @error="handleImageError" alt="公司Logo" /> <h1 v-if="!logoUrl" class="sidebar-title">{{ title }}</h1> </router-link> <router-link v-else key="expand" class="sidebar-logo-link" to="/"> <img v-if="logoUrl" :src="logoUrl" class="sidebar-logo" @error="handleImageError" alt="公司Logo" /> <h1 v-if="!logoUrl" class="sidebar-title">{{ title }}</h1> </router-link> </transition> </div> </template> <script setup> import { ref, computed, onMounted, watch } from 'vue' import useUserStore from '@/store/modules/user' import defaultLogo from '@/assets/logo/logo.png' // 导入默认logo defineProps({ collapse: { type: Boolean, required: true } }) const title = import.meta.env.VITE_APP_TITLE const userStore = useUserStore() // 处理工厂名称,生成合法的文件名 const cleanFactoryName = computed(() => { if (!userStore.currentFactoryName) return '' return userStore.currentFactoryName.trim() }) // 动态logo路径 const logoUrl = ref('') // 检查logo是否存在并设置url const updateLogoUrl = () => { if (!cleanFactoryName.value) { logoUrl.value = defaultLogo return } // 使用Vite的动态导入 try { const dynamicLogo = import.meta.glob('/src/assets/logo/*.png', { eager: true }) const logoPath = `/src/assets/logo/${cleanFactoryName.value}.png` if (dynamicLogo[logoPath]) { logoUrl.value = dynamicLogo[logoPath].default } else { logoUrl.value = defaultLogo } } catch (error) { console.error('加载工厂Logo失败:', error) logoUrl.value = defaultLogo } } // 初始化和监听变化 onMounted(() => { updateLogoUrl() // 监听工厂名称变化 watch(() => userStore.currentFactoryName, updateLogoUrl) }) // 图片加载错误处理 const handleImageError = (event) => { console.warn('Logo加载失败,使用默认Logo') logoUrl.value = defaultLogo } </script> <style lang="scss" scoped> @import '@/assets/styles/variables.module.scss'; .sidebarLogoFade-enter-active { transition: opacity 1.5s; } .sidebarLogoFade-enter, .sidebarLogoFade-leave-to { opacity: 0; } .sidebar-logo-container { position: relative; width: 100% !important; height: 56px !important; line-height: 56px; background: rgba(255, 255, 255, 0.78); border: 1px solid var(--surface-border); border-radius: 0 22px 22px 0; text-align: center; overflow: hidden; box-shadow: var(--shadow-sm); & .sidebar-logo-link { height: 100%; width: 100%; display: flex; align-items: center; justify-content: center; padding: 0 18px 0 14px; & .sidebar-logo { width: auto; max-width: 250px; max-height: 50px; height: auto; vertical-align: middle; object-fit: contain; object-position: center; } & .sidebar-title { display: inline-block; margin: 0; color: var(--text-primary); font-weight: 600; line-height: 1.2; font-size: 14px; font-family: "Segoe UI", "PingFang SC", sans-serif; vertical-align: middle; } } &.collapse { .sidebar-logo-link { padding: 0; } .sidebar-logo { max-width: 30px; max-height: 30px; } } } </style> <template> <div class="sidebar-logo-container" :class="{ collapse }"> <transition name="sidebarLogoFade"> <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/"> <img :src="faviconUrl" class="sidebar-logo sidebar-favicon" alt="站点图标" /> </router-link> <router-link v-else key="expand" class="sidebar-logo-link" to="/"> <img v-if="logoUrl" :src="logoUrl" class="sidebar-logo" @error="handleImageError" alt="公司Logo" /> <h1 v-if="!logoUrl" class="sidebar-title">{{ title }}</h1> </router-link> </transition> </div> </template> <script setup> import { ref, computed, onMounted, watch } from 'vue' import useUserStore from '@/store/modules/user' import defaultLogo from '@/assets/logo/logo.png' defineProps({ collapse: { type: Boolean, required: true } }) const title = import.meta.env.VITE_APP_TITLE const userStore = useUserStore() const baseUrl = import.meta.env.BASE_URL || '/' const faviconUrl = `${baseUrl.replace(/\/?$/, '/') }favicon.ico`.replace(/([^:]\/)\/+/g, '$1') const cleanFactoryName = computed(() => { if (!userStore.currentFactoryName) return '' return userStore.currentFactoryName.trim() }) const logoUrl = ref('') const updateLogoUrl = () => { if (!cleanFactoryName.value) { logoUrl.value = defaultLogo return } try { const dynamicLogo = import.meta.glob('/src/assets/logo/*.png', { eager: true }) const logoPath = `/src/assets/logo/${cleanFactoryName.value}.png` if (dynamicLogo[logoPath]) { logoUrl.value = dynamicLogo[logoPath].default } else { logoUrl.value = defaultLogo } } catch (error) { console.error('加载工厂 Logo 失败:', error) logoUrl.value = defaultLogo } } onMounted(() => { updateLogoUrl() watch(() => userStore.currentFactoryName, updateLogoUrl) }) const handleImageError = () => { logoUrl.value = defaultLogo } </script> <style lang="scss" scoped> @import '@/assets/styles/variables.module.scss'; .sidebarLogoFade-enter-active { transition: opacity 1.5s; } .sidebarLogoFade-enter, .sidebarLogoFade-leave-to { opacity: 0; } .sidebar-logo-container { position: relative; width: 100% !important; height: 56px !important; line-height: 56px; background: rgba(255, 255, 255, 0.78); border: 1px solid var(--surface-border); border-radius: 0 22px 22px 0; text-align: center; overflow: hidden; box-shadow: var(--shadow-sm); .sidebar-logo-link { height: 100%; width: 100%; display: flex; align-items: center; justify-content: center; padding: 0 18px 0 14px; } .sidebar-logo { width: auto; max-width: 250px; max-height: 50px; height: auto; vertical-align: middle; object-fit: contain; object-position: center; } .sidebar-title { display: inline-block; margin: 0; color: var(--text-primary); font-weight: 600; line-height: 1.2; font-size: 14px; font-family: "Segoe UI", "PingFang SC", sans-serif; vertical-align: middle; } &.collapse { .sidebar-logo-link { padding: 0; } .sidebar-logo { max-width: 30px; max-height: 30px; } .sidebar-favicon { width: 24px; height: 24px; max-width: 24px; max-height: 24px; } } } </style> src/layout/components/Sidebar/index.vue
@@ -1,75 +1,79 @@ <template> <div :class="{ 'has-logo': showLogo }" class="sidebar-container"> <logo v-if="showLogo" :collapse="isCollapse" /> <el-scrollbar wrap-class="scrollbar-wrapper"> <el-menu :default-active="activeMenu" :collapse="isCollapse" :background-color="getMenuBackground" :text-color="getMenuTextColor" :unique-opened="true" :active-text-color="theme" :collapse-transition="false" mode="vertical" :class="sideTheme"> <sidebar-item v-for="(route, index) in sidebarRouters" :key="route.path + index" :item="route" :base-path="route.path" /> </el-menu> </el-scrollbar> </div> </template> <script setup> import Logo from './Logo' import SidebarItem from './SidebarItem' import variables from '@/assets/styles/variables.module.scss' import useAppStore from '@/store/modules/app' import useSettingsStore from '@/store/modules/settings' import usePermissionStore from '@/store/modules/permission' const route = useRoute() const appStore = useAppStore() const settingsStore = useSettingsStore() const permissionStore = usePermissionStore() const sidebarRouters = computed(() => permissionStore.sidebarRouters) const showLogo = computed(() => settingsStore.sidebarLogo) const sideTheme = computed(() => settingsStore.sideTheme) const theme = computed(() => settingsStore.theme) const isCollapse = computed(() => !appStore.sidebar.opened) // 获取菜单背景色 const getMenuBackground = computed(() => { if (settingsStore.isDark) { return 'var(--sidebar-bg)' } // 浅色主题时,直接用主题色 return sideTheme.value === 'theme-dark' ? variables.menuBg : settingsStore.theme }) // 获取菜单文字颜色 const getMenuTextColor = computed(() => { if (settingsStore.isDark) { return 'var(--sidebar-text)' } return sideTheme.value === 'theme-dark' ? variables.menuText : variables.menuLightText }) const activeMenu = computed(() => { const { meta, path } = route if (meta.activeMenu) { return meta.activeMenu } return path }) </script> <style lang="scss" scoped> .sidebar-container { background-color: v-bind(getMenuBackground); .scrollbar-wrapper { background-color: v-bind(getMenuBackground); } .el-menu { border: none; height: 100%; width: 100% !important; <template> <div :class="{ 'has-logo': showLogo }" class="sidebar-container"> <logo v-if="showLogo" :collapse="isCollapse" /> <el-scrollbar wrap-class="scrollbar-wrapper"> <el-menu :default-active="activeMenu" :collapse="isCollapse" :background-color="getMenuBackground" :text-color="getMenuTextColor" :unique-opened="true" :active-text-color="theme" :collapse-transition="false" mode="vertical" :class="sideTheme" > <sidebar-item v-for="(route, index) in sidebarRouters" :key="route.path + index" :item="route" :base-path="route.path" /> </el-menu> </el-scrollbar> </div> </template> <script setup> import Logo from './Logo' import SidebarItem from './SidebarItem' import variables from '@/assets/styles/variables.module.scss' import useAppStore from '@/store/modules/app' import useSettingsStore from '@/store/modules/settings' import usePermissionStore from '@/store/modules/permission' const route = useRoute() const appStore = useAppStore() const settingsStore = useSettingsStore() const permissionStore = usePermissionStore() const sidebarRouters = computed(() => permissionStore.sidebarRouters) const showLogo = computed(() => settingsStore.sidebarLogo) const sideTheme = computed(() => settingsStore.sideTheme) const theme = computed(() => settingsStore.theme) const isCollapse = computed(() => !appStore.sidebar.opened) const getMenuBackground = computed(() => 'var(--sidebar-bg)') const getMenuTextColor = computed(() => { if (settingsStore.isDark) { return 'var(--sidebar-text)' } return sideTheme.value === 'theme-dark' ? variables.menuText : variables.menuLightText }) const activeMenu = computed(() => { const { meta, path } = route if (meta.activeMenu) { return meta.activeMenu } return path }) </script> <style lang="scss" scoped> .sidebar-container { background-color: v-bind(getMenuBackground); .scrollbar-wrapper { background-color: v-bind(getMenuBackground); } .el-menu { border: none; height: 100%; width: 100% !important; .el-menu-item, .el-sub-menu__title { margin-bottom: 6px; @@ -88,7 +92,7 @@ font-weight: 600; } } .el-sub-menu__title { color: v-bind(getMenuTextColor); } src/layout/index.vue
@@ -104,9 +104,9 @@ padding-bottom: 8px; } .hideSidebar .fixed-header { width: calc(100% - 70px); } .hideSidebar .fixed-header { width: calc(100% - 100px); } .sidebarHide .fixed-header { width: calc(100% - 32px);