feat: 新增混合布局模式
This commit is contained in:
parent
c3ad3c0ce1
commit
442ae06c47
@ -1,13 +1,15 @@
|
|||||||
import { getConfigLayout } from "@/utils/cache/local-storage"
|
import { getConfigLayout } from "@/utils/cache/local-storage"
|
||||||
|
|
||||||
/** 布局配置 */
|
/** 项目配置 */
|
||||||
export interface LayoutSettings {
|
export interface LayoutSettings {
|
||||||
/** 是否显示 Settings Panel */
|
/** 是否显示 Settings Panel */
|
||||||
showSettings: boolean
|
showSettings: boolean
|
||||||
|
/** 布局模式 */
|
||||||
|
layoutMode: "left" | "top" | "left-top"
|
||||||
/** 是否显示标签栏 */
|
/** 是否显示标签栏 */
|
||||||
showTagsView: boolean
|
showTagsView: boolean
|
||||||
/** 是否显示侧边栏 Logo */
|
/** 是否显示 Logo */
|
||||||
showSidebarLogo: boolean
|
showLogo: boolean
|
||||||
/** 是否固定 Header */
|
/** 是否固定 Header */
|
||||||
fixedHeader: boolean
|
fixedHeader: boolean
|
||||||
/** 是否显示消息通知 */
|
/** 是否显示消息通知 */
|
||||||
@ -25,10 +27,11 @@ export interface LayoutSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const layoutSettings: LayoutSettings = getConfigLayout() ?? {
|
export const layoutSettings: LayoutSettings = getConfigLayout() ?? {
|
||||||
|
layoutMode: "left",
|
||||||
showSettings: true,
|
showSettings: true,
|
||||||
showTagsView: true,
|
showTagsView: true,
|
||||||
fixedHeader: true,
|
fixedHeader: true,
|
||||||
showSidebarLogo: true,
|
showLogo: true,
|
||||||
showNotify: true,
|
showNotify: true,
|
||||||
showThemeSwitch: true,
|
showThemeSwitch: true,
|
||||||
showScreenfull: true,
|
showScreenfull: true,
|
||||||
|
165
src/layout/LeftMode.vue
Normal file
165
src/layout/LeftMode.vue
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed } from "vue"
|
||||||
|
import { storeToRefs } from "pinia"
|
||||||
|
import { useAppStore } from "@/store/modules/app"
|
||||||
|
import { useSettingsStore } from "@/store/modules/settings"
|
||||||
|
import { AppMain, NavigationBar, Sidebar, TagsView } from "./components"
|
||||||
|
import { DeviceEnum } from "@/constants/app-key"
|
||||||
|
|
||||||
|
const appStore = useAppStore()
|
||||||
|
const settingsStore = useSettingsStore()
|
||||||
|
|
||||||
|
const { showTagsView, fixedHeader } = storeToRefs(settingsStore)
|
||||||
|
|
||||||
|
/** 定义计算属性 layoutClasses,用于控制布局的类名 */
|
||||||
|
const layoutClasses = computed(() => {
|
||||||
|
return {
|
||||||
|
hideSidebar: !appStore.sidebar.opened,
|
||||||
|
openSidebar: appStore.sidebar.opened,
|
||||||
|
withoutAnimation: appStore.sidebar.withoutAnimation,
|
||||||
|
mobile: appStore.device === DeviceEnum.Mobile
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 用于处理点击 mobile 端侧边栏遮罩层的事件 */
|
||||||
|
const handleClickOutside = () => {
|
||||||
|
appStore.closeSidebar(false)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="layoutClasses" class="app-wrapper">
|
||||||
|
<!-- mobile 端侧边栏遮罩层 -->
|
||||||
|
<div v-if="layoutClasses.mobile && layoutClasses.openSidebar" class="drawer-bg" @click="handleClickOutside" />
|
||||||
|
<!-- 左侧边栏 -->
|
||||||
|
<Sidebar class="sidebar-container" />
|
||||||
|
<!-- 主容器 -->
|
||||||
|
<div :class="{ hasTagsView: showTagsView }" class="main-container">
|
||||||
|
<!-- 头部导航栏和标签栏 -->
|
||||||
|
<div :class="{ 'fixed-header': fixedHeader }" class="layout-header">
|
||||||
|
<NavigationBar />
|
||||||
|
<TagsView v-show="showTagsView" />
|
||||||
|
</div>
|
||||||
|
<!-- 页面主体内容 -->
|
||||||
|
<AppMain class="app-main" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "@/styles/mixins.scss";
|
||||||
|
$transition-time: 0.35s;
|
||||||
|
|
||||||
|
.app-wrapper {
|
||||||
|
@include clearfix;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-bg {
|
||||||
|
background-color: #000;
|
||||||
|
opacity: 0.3;
|
||||||
|
width: 100%;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-container {
|
||||||
|
transition: width $transition-time;
|
||||||
|
width: var(--v3-sidebar-width) !important;
|
||||||
|
height: 100%;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1001;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-container {
|
||||||
|
min-height: 100%;
|
||||||
|
transition: margin-left $transition-time;
|
||||||
|
margin-left: var(--v3-sidebar-width);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-header {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 9;
|
||||||
|
width: calc(100% - var(--v3-sidebar-width));
|
||||||
|
transition: width $transition-time;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-header {
|
||||||
|
box-shadow: var(--el-box-shadow-lighter);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-main {
|
||||||
|
min-height: calc(100vh - var(--v3-navigationbar-height));
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-header + .app-main {
|
||||||
|
padding-top: var(--v3-navigationbar-height);
|
||||||
|
height: 100vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hasTagsView {
|
||||||
|
.app-main {
|
||||||
|
min-height: calc(100vh - var(--v3-header-height));
|
||||||
|
}
|
||||||
|
.fixed-header + .app-main {
|
||||||
|
padding-top: var(--v3-header-height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hideSidebar {
|
||||||
|
.sidebar-container {
|
||||||
|
width: var(--v3-sidebar-hide-width) !important;
|
||||||
|
}
|
||||||
|
.main-container {
|
||||||
|
margin-left: var(--v3-sidebar-hide-width);
|
||||||
|
}
|
||||||
|
.fixed-header {
|
||||||
|
width: calc(100% - var(--v3-sidebar-hide-width));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 适配 mobile 端
|
||||||
|
.mobile {
|
||||||
|
.sidebar-container {
|
||||||
|
transition: transform $transition-time;
|
||||||
|
width: var(--v3-sidebar-width) !important;
|
||||||
|
}
|
||||||
|
.main-container {
|
||||||
|
margin-left: 0px;
|
||||||
|
}
|
||||||
|
.fixed-header {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
&.openSidebar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
&.hideSidebar {
|
||||||
|
.sidebar-container {
|
||||||
|
pointer-events: none;
|
||||||
|
transition-duration: 0.3s;
|
||||||
|
transform: translate3d(calc(0px - var(--v3-sidebar-width)), 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.withoutAnimation {
|
||||||
|
.sidebar-container,
|
||||||
|
.main-container {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
108
src/layout/LeftTopMode.vue
Normal file
108
src/layout/LeftTopMode.vue
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed } from "vue"
|
||||||
|
import { storeToRefs } from "pinia"
|
||||||
|
import { useAppStore } from "@/store/modules/app"
|
||||||
|
import { useSettingsStore } from "@/store/modules/settings"
|
||||||
|
import { AppMain, NavigationBar, Sidebar, TagsView, Logo } from "./components"
|
||||||
|
|
||||||
|
const appStore = useAppStore()
|
||||||
|
const settingsStore = useSettingsStore()
|
||||||
|
|
||||||
|
const { showTagsView, showLogo } = storeToRefs(settingsStore)
|
||||||
|
|
||||||
|
/** 定义计算属性 layoutClasses,用于控制布局的类名 */
|
||||||
|
const layoutClasses = computed(() => {
|
||||||
|
return {
|
||||||
|
hideSidebar: !appStore.sidebar.opened
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="layoutClasses" class="app-wrapper">
|
||||||
|
<!-- 头部导航栏和标签栏 -->
|
||||||
|
<div class="fixed-header layout-header">
|
||||||
|
<Logo v-if="showLogo" :collapse="false" class="logo" />
|
||||||
|
<div class="content">
|
||||||
|
<NavigationBar />
|
||||||
|
<TagsView v-show="showTagsView" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 主容器 -->
|
||||||
|
<div :class="{ hasTagsView: showTagsView }" class="main-container">
|
||||||
|
<!-- 左侧边栏 -->
|
||||||
|
<Sidebar class="sidebar-container" />
|
||||||
|
<!-- 页面主体内容 -->
|
||||||
|
<AppMain class="app-main" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "@/styles/mixins.scss";
|
||||||
|
$transition-time: 0.35s;
|
||||||
|
|
||||||
|
.app-wrapper {
|
||||||
|
@include clearfix;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-header {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1002;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
.logo {
|
||||||
|
width: var(--v3-sidebar-width);
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-header {
|
||||||
|
box-shadow: var(--el-box-shadow-lighter);
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-container {
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-container {
|
||||||
|
transition: width $transition-time;
|
||||||
|
width: var(--v3-sidebar-width) !important;
|
||||||
|
height: 100%;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1001;
|
||||||
|
overflow: hidden;
|
||||||
|
padding-top: var(--v3-navigationbar-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-main {
|
||||||
|
transition: padding-left $transition-time;
|
||||||
|
padding-top: var(--v3-navigationbar-height);
|
||||||
|
padding-left: var(--v3-sidebar-width);
|
||||||
|
height: 100vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hideSidebar {
|
||||||
|
.sidebar-container {
|
||||||
|
width: var(--v3-sidebar-hide-width) !important;
|
||||||
|
}
|
||||||
|
.app-main {
|
||||||
|
padding-left: var(--v3-sidebar-hide-width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hasTagsView {
|
||||||
|
.sidebar-container {
|
||||||
|
padding-top: var(--v3-header-height);
|
||||||
|
}
|
||||||
|
.app-main {
|
||||||
|
padding-top: var(--v3-header-height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -30,10 +30,7 @@ const key = computed(() => {
|
|||||||
@import "@/styles/mixins.scss";
|
@import "@/styles/mixins.scss";
|
||||||
|
|
||||||
.app-main {
|
.app-main {
|
||||||
min-height: calc(100vh - var(--v3-navigationbar-height));
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
background-color: var(--v3-body-bg-color);
|
background-color: var(--v3-body-bg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,19 +39,4 @@ const key = computed(() => {
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
@include scrollbar;
|
@include scrollbar;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fixed-header + .app-main {
|
|
||||||
padding-top: var(--v3-navigationbar-height);
|
|
||||||
height: 100vh;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hasTagsView {
|
|
||||||
.app-main {
|
|
||||||
min-height: calc(100vh - var(--v3-header-height));
|
|
||||||
}
|
|
||||||
.fixed-header + .app-main {
|
|
||||||
padding-top: var(--v3-header-height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
70
src/layout/components/Logo/index.vue
Normal file
70
src/layout/components/Logo/index.vue
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed } from "vue"
|
||||||
|
import { storeToRefs } from "pinia"
|
||||||
|
import { useSettingsStore } from "@/store/modules/settings"
|
||||||
|
import { getCssVariableValue } from "@/utils"
|
||||||
|
import logo from "@/assets/layout/logo.png?url"
|
||||||
|
import logoText1 from "@/assets/layout/logo-text-1.png?url"
|
||||||
|
import logoText2 from "@/assets/layout/logo-text-2.png?url"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
collapse?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
collapse: true
|
||||||
|
})
|
||||||
|
|
||||||
|
const settingsStore = useSettingsStore()
|
||||||
|
const { layoutMode } = storeToRefs(settingsStore)
|
||||||
|
|
||||||
|
const bgCloor = computed(() => {
|
||||||
|
return layoutMode.value !== "left"
|
||||||
|
? getCssVariableValue("--v3-header-bg-color")
|
||||||
|
: getCssVariableValue("--v3-sidebar-menu-bg-color")
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="layout-logo-container" :class="{ collapse: props.collapse }">
|
||||||
|
<transition name="layout-logo-fade">
|
||||||
|
<router-link v-if="props.collapse" key="collapse" to="/">
|
||||||
|
<img :src="logo" class="layout-logo" />
|
||||||
|
</router-link>
|
||||||
|
<router-link v-else key="expand" to="/">
|
||||||
|
<img :src="layoutMode !== 'left' ? logoText2 : logoText1" class="layout-logo-text" />
|
||||||
|
</router-link>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.layout-logo-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: var(--v3-header-height);
|
||||||
|
line-height: var(--v3-header-height);
|
||||||
|
background-color: v-bind(bgCloor);
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
.layout-logo {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.layout-logo-text {
|
||||||
|
height: 100%;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse {
|
||||||
|
.layout-logo {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
vertical-align: middle;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.layout-logo-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -5,8 +5,8 @@ import { useAppStore } from "@/store/modules/app"
|
|||||||
import { useSettingsStore } from "@/store/modules/settings"
|
import { useSettingsStore } from "@/store/modules/settings"
|
||||||
import { useUserStore } from "@/store/modules/user"
|
import { useUserStore } from "@/store/modules/user"
|
||||||
import { UserFilled } from "@element-plus/icons-vue"
|
import { UserFilled } from "@element-plus/icons-vue"
|
||||||
import Breadcrumb from "../Breadcrumb/index.vue"
|
|
||||||
import Hamburger from "../Hamburger/index.vue"
|
import Hamburger from "../Hamburger/index.vue"
|
||||||
|
import Breadcrumb from "../Breadcrumb/index.vue"
|
||||||
import ThemeSwitch from "@/components/ThemeSwitch/index.vue"
|
import ThemeSwitch from "@/components/ThemeSwitch/index.vue"
|
||||||
import Screenfull from "@/components/Screenfull/index.vue"
|
import Screenfull from "@/components/Screenfull/index.vue"
|
||||||
import Notify from "@/components/Notify/index.vue"
|
import Notify from "@/components/Notify/index.vue"
|
||||||
@ -68,7 +68,7 @@ const logout = () => {
|
|||||||
.navigation-bar {
|
.navigation-bar {
|
||||||
height: var(--v3-navigationbar-height);
|
height: var(--v3-navigationbar-height);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #fff;
|
background: var(--v3-header-bg-color);
|
||||||
.hamburger {
|
.hamburger {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { watchEffect } from "vue"
|
||||||
import { storeToRefs } from "pinia"
|
import { storeToRefs } from "pinia"
|
||||||
import { useSettingsStore } from "@/store/modules/settings"
|
import { useSettingsStore } from "@/store/modules/settings"
|
||||||
import { removeConfigLayout } from "@/utils/cache/local-storage"
|
import { removeConfigLayout } from "@/utils/cache/local-storage"
|
||||||
@ -8,8 +9,9 @@ const settingsStore = useSettingsStore()
|
|||||||
|
|
||||||
/** 使用 storeToRefs 将提取的属性保持其响应性 */
|
/** 使用 storeToRefs 将提取的属性保持其响应性 */
|
||||||
const {
|
const {
|
||||||
|
layoutMode,
|
||||||
showTagsView,
|
showTagsView,
|
||||||
showSidebarLogo,
|
showLogo,
|
||||||
fixedHeader,
|
fixedHeader,
|
||||||
showNotify,
|
showNotify,
|
||||||
showThemeSwitch,
|
showThemeSwitch,
|
||||||
@ -22,7 +24,7 @@ const {
|
|||||||
/** 定义 switch 设置项 */
|
/** 定义 switch 设置项 */
|
||||||
const switchSettings = {
|
const switchSettings = {
|
||||||
显示标签栏: showTagsView,
|
显示标签栏: showTagsView,
|
||||||
"显示侧边栏 Logo": showSidebarLogo,
|
"显示 Logo": showLogo,
|
||||||
"固定 Header": fixedHeader,
|
"固定 Header": fixedHeader,
|
||||||
显示消息通知: showNotify,
|
显示消息通知: showNotify,
|
||||||
显示切换主题按钮: showThemeSwitch,
|
显示切换主题按钮: showThemeSwitch,
|
||||||
@ -32,6 +34,11 @@ const switchSettings = {
|
|||||||
显示色弱模式: showColorWeakness
|
显示色弱模式: showColorWeakness
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 非左侧模式时,Header 都是 fixed 布局 */
|
||||||
|
watchEffect(() => {
|
||||||
|
layoutMode.value !== "left" && (fixedHeader.value = true)
|
||||||
|
})
|
||||||
|
|
||||||
/** 重置配置 */
|
/** 重置配置 */
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
removeConfigLayout()
|
removeConfigLayout()
|
||||||
@ -41,10 +48,16 @@ const reset = () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="setting-container">
|
<div class="setting-container">
|
||||||
<h4>系统布局配置</h4>
|
<h4>布局配置</h4>
|
||||||
|
<el-radio-group v-model="layoutMode">
|
||||||
|
<el-radio label="left">左侧模式</el-radio>
|
||||||
|
<el-radio label="top">顶部模式(开发中)</el-radio>
|
||||||
|
<el-radio label="left-top">混合模式</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
<h4>功能配置</h4>
|
||||||
<div class="setting-item" v-for="(settingValue, settingName, index) in switchSettings" :key="index">
|
<div class="setting-item" v-for="(settingValue, settingName, index) in switchSettings" :key="index">
|
||||||
<span class="setting-name">{{ settingName }}</span>
|
<span class="setting-name">{{ settingName }}</span>
|
||||||
<el-switch v-model="settingValue.value" />
|
<el-switch v-model="settingValue.value" :disabled="layoutMode !== 'left' && settingName === '固定 Header'" />
|
||||||
</div>
|
</div>
|
||||||
<el-button type="danger" :icon="Refresh" @click="reset">重 置</el-button>
|
<el-button type="danger" :icon="Refresh" @click="reset">重 置</el-button>
|
||||||
</div>
|
</div>
|
||||||
@ -57,7 +70,8 @@ const reset = () => {
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
.setting-item {
|
.setting-item {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding: 6px 0;
|
color: var(--el-text-color-regular);
|
||||||
|
padding: 5px 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
interface Props {
|
|
||||||
collapse?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
|
||||||
collapse: true
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="sidebar-logo-container" :class="{ collapse: props.collapse }">
|
|
||||||
<transition name="sidebar-logo-fade">
|
|
||||||
<router-link v-if="props.collapse" key="collapse" to="/">
|
|
||||||
<img src="@/assets/layout/logo.png" class="sidebar-logo" />
|
|
||||||
</router-link>
|
|
||||||
<router-link v-else key="expand" to="/">
|
|
||||||
<img src="@/assets/layout/logo-text-1.png" class="sidebar-logo-text" />
|
|
||||||
</router-link>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.sidebar-logo-container {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: var(--v3-header-height);
|
|
||||||
line-height: var(--v3-header-height);
|
|
||||||
background-color: var(--v3-sidebarlogo-bg-color);
|
|
||||||
text-align: center;
|
|
||||||
overflow: hidden;
|
|
||||||
.sidebar-logo {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.sidebar-logo-text {
|
|
||||||
height: 100%;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.collapse {
|
|
||||||
.sidebar-logo {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
vertical-align: middle;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
.sidebar-logo-text {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -6,7 +6,7 @@ import { useAppStore } from "@/store/modules/app"
|
|||||||
import { usePermissionStore } from "@/store/modules/permission"
|
import { usePermissionStore } from "@/store/modules/permission"
|
||||||
import { useSettingsStore } from "@/store/modules/settings"
|
import { useSettingsStore } from "@/store/modules/settings"
|
||||||
import SidebarItem from "./SidebarItem.vue"
|
import SidebarItem from "./SidebarItem.vue"
|
||||||
import SidebarLogo from "./SidebarLogo.vue"
|
import Logo from "../Logo/index.vue"
|
||||||
import { getCssVariableValue } from "@/utils"
|
import { getCssVariableValue } from "@/utils"
|
||||||
|
|
||||||
const v3SidebarMenuBgColor = getCssVariableValue("--v3-sidebar-menu-bg-color")
|
const v3SidebarMenuBgColor = getCssVariableValue("--v3-sidebar-menu-bg-color")
|
||||||
@ -18,7 +18,7 @@ const appStore = useAppStore()
|
|||||||
const permissionStore = usePermissionStore()
|
const permissionStore = usePermissionStore()
|
||||||
const settingsStore = useSettingsStore()
|
const settingsStore = useSettingsStore()
|
||||||
|
|
||||||
const { showSidebarLogo } = storeToRefs(settingsStore)
|
const { layoutMode, showLogo } = storeToRefs(settingsStore)
|
||||||
|
|
||||||
const activeMenu = computed(() => {
|
const activeMenu = computed(() => {
|
||||||
const {
|
const {
|
||||||
@ -29,11 +29,12 @@ const activeMenu = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const isCollapse = computed(() => !appStore.sidebar.opened)
|
const isCollapse = computed(() => !appStore.sidebar.opened)
|
||||||
|
const isLogo = computed(() => layoutMode.value === "left" && showLogo.value)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="{ 'has-logo': showSidebarLogo }">
|
<div :class="{ 'has-logo': isLogo }">
|
||||||
<SidebarLogo v-if="showSidebarLogo" :collapse="isCollapse" />
|
<Logo v-if="isLogo" :collapse="isCollapse" />
|
||||||
<el-scrollbar wrap-class="scrollbar-wrapper">
|
<el-scrollbar wrap-class="scrollbar-wrapper">
|
||||||
<el-menu
|
<el-menu
|
||||||
:default-active="activeMenu"
|
:default-active="activeMenu"
|
||||||
|
@ -199,9 +199,8 @@ onMounted(() => {
|
|||||||
.tags-view-container {
|
.tags-view-container {
|
||||||
height: var(--v3-tagsview-height);
|
height: var(--v3-tagsview-height);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #fff;
|
background-color: var(--v3-header-bg-color);
|
||||||
border-bottom: 1px solid #d8dce5;
|
box-shadow: 0 0 3px 0 #00000010;
|
||||||
box-shadow: 0 1px 3px 0 #00000010, 0 0 3px 0 #00000010;
|
|
||||||
.tags-view-wrapper {
|
.tags-view-wrapper {
|
||||||
.tags-view-item {
|
.tags-view-item {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -4,3 +4,4 @@ export { default as Settings } from "./Settings/index.vue"
|
|||||||
export { default as Sidebar } from "./Sidebar/index.vue"
|
export { default as Sidebar } from "./Sidebar/index.vue"
|
||||||
export { default as TagsView } from "./TagsView/index.vue"
|
export { default as TagsView } from "./TagsView/index.vue"
|
||||||
export { default as RightPanel } from "./RightPanel/index.vue"
|
export { default as RightPanel } from "./RightPanel/index.vue"
|
||||||
|
export { default as Logo } from "./Logo/index.vue"
|
||||||
|
@ -1,71 +1,55 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from "vue"
|
import { computed, watchEffect } from "vue"
|
||||||
import { storeToRefs } from "pinia"
|
import { storeToRefs } from "pinia"
|
||||||
import { useAppStore } from "@/store/modules/app"
|
import { useAppStore } from "@/store/modules/app"
|
||||||
import { useSettingsStore } from "@/store/modules/settings"
|
import { useSettingsStore } from "@/store/modules/settings"
|
||||||
import { AppMain, NavigationBar, Settings, Sidebar, TagsView, RightPanel } from "./components"
|
|
||||||
import useResize from "./hooks/useResize"
|
import useResize from "./hooks/useResize"
|
||||||
|
import LeftMode from "./LeftMode.vue"
|
||||||
|
import LeftTopMode from "./LeftTopMode.vue"
|
||||||
|
import { Settings, RightPanel } from "./components"
|
||||||
import { DeviceEnum } from "@/constants/app-key"
|
import { DeviceEnum } from "@/constants/app-key"
|
||||||
|
import { getCssVariableValue, setCssVariableValue } from "@/utils"
|
||||||
const appStore = useAppStore()
|
|
||||||
const settingsStore = useSettingsStore()
|
|
||||||
|
|
||||||
const { showGreyMode, showColorWeakness, showSettings, showTagsView, fixedHeader } = storeToRefs(settingsStore)
|
|
||||||
|
|
||||||
/** Layout 布局响应式 */
|
/** Layout 布局响应式 */
|
||||||
useResize()
|
useResize()
|
||||||
|
|
||||||
/** 定义计算属性 layoutClasses,用于控制布局的类名 */
|
const appStore = useAppStore()
|
||||||
const layoutClasses = computed(() => {
|
const settingsStore = useSettingsStore()
|
||||||
|
|
||||||
|
const { showSettings, layoutMode, showTagsView, showGreyMode, showColorWeakness } = storeToRefs(settingsStore)
|
||||||
|
|
||||||
|
const classes = computed(() => {
|
||||||
return {
|
return {
|
||||||
hideSidebar: !appStore.sidebar.opened,
|
|
||||||
openSidebar: appStore.sidebar.opened,
|
|
||||||
withoutAnimation: appStore.sidebar.withoutAnimation,
|
|
||||||
mobile: appStore.device === DeviceEnum.Mobile,
|
|
||||||
showGreyMode: showGreyMode.value,
|
showGreyMode: showGreyMode.value,
|
||||||
showColorWeakness: showColorWeakness.value
|
showColorWeakness: showColorWeakness.value
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 用于处理点击 mobile 端侧边栏遮罩层的事件 */
|
//#region 隐藏标签栏时删除其高度,是为了让 Logo 组件高度和 Header 区域高度始终一致
|
||||||
const handleClickOutside = () => {
|
const cssVariableName = "--v3-tagsview-height"
|
||||||
appStore.closeSidebar(false)
|
const v3TagsviewHeight = getCssVariableValue(cssVariableName)
|
||||||
}
|
watchEffect(() => {
|
||||||
|
showTagsView.value
|
||||||
|
? setCssVariableValue(cssVariableName, v3TagsviewHeight)
|
||||||
|
: setCssVariableValue(cssVariableName, "0px")
|
||||||
|
})
|
||||||
|
//#endregion
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="layoutClasses" class="app-wrapper">
|
<div :class="classes">
|
||||||
<!-- mobile 端侧边栏遮罩层 -->
|
<!-- 左侧模式 -->
|
||||||
<div v-if="layoutClasses.mobile && layoutClasses.openSidebar" class="drawer-bg" @click="handleClickOutside" />
|
<LeftMode v-if="layoutMode === 'left' || appStore.device === DeviceEnum.Mobile" />
|
||||||
<!-- 左侧边栏 -->
|
<!-- 混合模式 -->
|
||||||
<Sidebar class="sidebar-container" />
|
<LeftTopMode v-else-if="layoutMode === 'left-top'" />
|
||||||
<!-- 主容器 -->
|
|
||||||
<div :class="{ hasTagsView: showTagsView }" class="main-container">
|
|
||||||
<!-- 头部导航栏和标签栏 -->
|
|
||||||
<div :class="{ 'fixed-header': fixedHeader }">
|
|
||||||
<NavigationBar />
|
|
||||||
<TagsView v-show="showTagsView" />
|
|
||||||
</div>
|
|
||||||
<!-- 页面主体内容 -->
|
|
||||||
<AppMain />
|
|
||||||
<!-- 右侧设置面板 -->
|
<!-- 右侧设置面板 -->
|
||||||
<RightPanel v-if="showSettings">
|
<RightPanel v-if="showSettings">
|
||||||
<Settings />
|
<Settings />
|
||||||
</RightPanel>
|
</RightPanel>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "@/styles/mixins.scss";
|
|
||||||
$transition-time: 0.35s;
|
|
||||||
|
|
||||||
.app-wrapper {
|
|
||||||
@include clearfix;
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.showGreyMode {
|
.showGreyMode {
|
||||||
filter: grayscale(1);
|
filter: grayscale(1);
|
||||||
}
|
}
|
||||||
@ -73,88 +57,4 @@ $transition-time: 0.35s;
|
|||||||
.showColorWeakness {
|
.showColorWeakness {
|
||||||
filter: invert(0.8);
|
filter: invert(0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer-bg {
|
|
||||||
background-color: #000;
|
|
||||||
opacity: 0.3;
|
|
||||||
width: 100%;
|
|
||||||
top: 0;
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-container {
|
|
||||||
min-height: 100%;
|
|
||||||
transition: margin-left $transition-time;
|
|
||||||
margin-left: var(--v3-sidebar-width);
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-container {
|
|
||||||
transition: width $transition-time;
|
|
||||||
width: var(--v3-sidebar-width) !important;
|
|
||||||
height: 100%;
|
|
||||||
position: fixed;
|
|
||||||
font-size: 0px;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 1001;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fixed-header {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
z-index: 9;
|
|
||||||
width: calc(100% - var(--v3-sidebar-width));
|
|
||||||
transition: width $transition-time;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hideSidebar {
|
|
||||||
.main-container {
|
|
||||||
margin-left: var(--v3-sidebar-hide-width);
|
|
||||||
}
|
|
||||||
.sidebar-container {
|
|
||||||
width: var(--v3-sidebar-hide-width) !important;
|
|
||||||
}
|
|
||||||
.fixed-header {
|
|
||||||
width: calc(100% - var(--v3-sidebar-hide-width));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 适配 mobile 端
|
|
||||||
.mobile {
|
|
||||||
.main-container {
|
|
||||||
margin-left: 0px;
|
|
||||||
}
|
|
||||||
.sidebar-container {
|
|
||||||
transition: transform $transition-time;
|
|
||||||
width: var(--v3-sidebar-width) !important;
|
|
||||||
}
|
|
||||||
&.openSidebar {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
&.hideSidebar {
|
|
||||||
.sidebar-container {
|
|
||||||
pointer-events: none;
|
|
||||||
transition-duration: 0.3s;
|
|
||||||
transform: translate3d(calc(0px - var(--v3-sidebar-width)), 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fixed-header {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.withoutAnimation {
|
|
||||||
.main-container,
|
|
||||||
.sidebar-container {
|
|
||||||
transition: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,13 +1,21 @@
|
|||||||
/** Layout 相关 */
|
/** Layout 相关 */
|
||||||
|
|
||||||
.app-wrapper {
|
#app {
|
||||||
color: $font-color;
|
color: $font-color;
|
||||||
|
// 右侧设置面板
|
||||||
|
.handle-button {
|
||||||
|
background-color: lighten($theme-bg-color, 20%) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-wrapper {
|
||||||
|
// Logo
|
||||||
|
.layout-logo-container {
|
||||||
|
background-color: lighten($theme-bg-color, 2%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
// 侧边栏
|
// 侧边栏
|
||||||
.sidebar-container {
|
.sidebar-container {
|
||||||
.sidebar-logo-container {
|
|
||||||
background-color: lighten($theme-bg-color, 2%) !important;
|
|
||||||
}
|
|
||||||
.el-menu {
|
.el-menu {
|
||||||
background-color: lighten($theme-bg-color, 4%) !important;
|
background-color: lighten($theme-bg-color, 4%) !important;
|
||||||
.el-menu-item {
|
.el-menu-item {
|
||||||
@ -24,6 +32,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Header
|
||||||
|
.layout-header {
|
||||||
|
border-bottom: 1px solid lighten($theme-bg-color, 10%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
// 顶部导航栏
|
// 顶部导航栏
|
||||||
.navigation-bar {
|
.navigation-bar {
|
||||||
background-color: $theme-bg-color;
|
background-color: $theme-bg-color;
|
||||||
@ -37,7 +50,6 @@
|
|||||||
// TagsView
|
// TagsView
|
||||||
.tags-view-container {
|
.tags-view-container {
|
||||||
background-color: $theme-bg-color !important;
|
background-color: $theme-bg-color !important;
|
||||||
border-bottom: 1px solid lighten($theme-bg-color, 10%) !important;
|
|
||||||
.tags-view-item {
|
.tags-view-item {
|
||||||
background-color: $theme-bg-color !important;
|
background-color: $theme-bg-color !important;
|
||||||
color: $font-color !important;
|
color: $font-color !important;
|
||||||
@ -58,9 +70,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 右侧设置面板
|
|
||||||
.handle-button {
|
|
||||||
background-color: lighten($theme-bg-color, 20%) !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -14,12 +14,12 @@
|
|||||||
transform: translateX(30px);
|
transform: translateX(30px);
|
||||||
}
|
}
|
||||||
|
|
||||||
// sidebar-logo-fade
|
// layout-logo-fade
|
||||||
.sidebar-logo-fade-enter-active,
|
.layout-logo-fade-enter-active,
|
||||||
.sidebar-logo-fade-leave-active {
|
.layout-logo-fade-leave-active {
|
||||||
transition: opacity 1.5s;
|
transition: opacity 1.5s;
|
||||||
}
|
}
|
||||||
.sidebar-logo-fade-enter-from,
|
.layout-logo-fade-enter-from,
|
||||||
.sidebar-logo-fade-leave-to {
|
.layout-logo-fade-leave-to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
--v3-body-bg-color: #f2f3f5;
|
--v3-body-bg-color: #f2f3f5;
|
||||||
/** Header 区域 = NavigationBar 组件 + TagsView 组件 */
|
/** Header 区域 = NavigationBar 组件 + TagsView 组件 */
|
||||||
--v3-header-height: calc(var(--v3-navigationbar-height) + var(--v3-tagsview-height));
|
--v3-header-height: calc(var(--v3-navigationbar-height) + var(--v3-tagsview-height));
|
||||||
|
--v3-header-bg-color: #ffffff;
|
||||||
/** NavigationBar 组件 */
|
/** NavigationBar 组件 */
|
||||||
--v3-navigationbar-height: 50px;
|
--v3-navigationbar-height: 50px;
|
||||||
/** Sidebar 组件 */
|
/** Sidebar 组件 */
|
||||||
@ -15,8 +16,6 @@
|
|||||||
--v3-sidebar-menu-hover-bg-color: #ffffff10;
|
--v3-sidebar-menu-hover-bg-color: #ffffff10;
|
||||||
--v3-sidebar-menu-text-color: #c0c4cc;
|
--v3-sidebar-menu-text-color: #c0c4cc;
|
||||||
--v3-sidebar-menu-active-text-color: #ffffff;
|
--v3-sidebar-menu-active-text-color: #ffffff;
|
||||||
/** SidebarLogo 组件 */
|
|
||||||
--v3-sidebarlogo-bg-color: #001428;
|
|
||||||
/** TagsView 组件 */
|
/** TagsView 组件 */
|
||||||
--v3-tagsview-height: 34px;
|
--v3-tagsview-height: 34px;
|
||||||
--v3-tagsview-tag-text-color: #495060;
|
--v3-tagsview-tag-text-color: #495060;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user