Template
1
0
mirror of https://github.com/un-pany/v3-admin-vite.git synced 2025-04-20 19:09:21 +08:00

feat: 新增混合布局模式

This commit is contained in:
pany 2023-07-06 13:02:52 +08:00
parent c3ad3c0ce1
commit 442ae06c47
15 changed files with 431 additions and 235 deletions

View File

@ -1,13 +1,15 @@
import { getConfigLayout } from "@/utils/cache/local-storage"
/** 布局配置 */
/** 项目配置 */
export interface LayoutSettings {
/** 是否显示 Settings Panel */
showSettings: boolean
/** 布局模式 */
layoutMode: "left" | "top" | "left-top"
/** 是否显示标签栏 */
showTagsView: boolean
/** 是否显示侧边栏 Logo */
showSidebarLogo: boolean
/** 是否显示 Logo */
showLogo: boolean
/** 是否固定 Header */
fixedHeader: boolean
/** 是否显示消息通知 */
@ -25,10 +27,11 @@ export interface LayoutSettings {
}
export const layoutSettings: LayoutSettings = getConfigLayout() ?? {
layoutMode: "left",
showSettings: true,
showTagsView: true,
fixedHeader: true,
showSidebarLogo: true,
showLogo: true,
showNotify: true,
showThemeSwitch: true,
showScreenfull: true,

165
src/layout/LeftMode.vue Normal file
View 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
View 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>

View File

@ -30,10 +30,7 @@ const key = computed(() => {
@import "@/styles/mixins.scss";
.app-main {
min-height: calc(100vh - var(--v3-navigationbar-height));
width: 100%;
position: relative;
overflow: hidden;
background-color: var(--v3-body-bg-color);
}
@ -42,19 +39,4 @@ const key = computed(() => {
overflow: auto;
@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>

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

View File

@ -5,8 +5,8 @@ import { useAppStore } from "@/store/modules/app"
import { useSettingsStore } from "@/store/modules/settings"
import { useUserStore } from "@/store/modules/user"
import { UserFilled } from "@element-plus/icons-vue"
import Breadcrumb from "../Breadcrumb/index.vue"
import Hamburger from "../Hamburger/index.vue"
import Breadcrumb from "../Breadcrumb/index.vue"
import ThemeSwitch from "@/components/ThemeSwitch/index.vue"
import Screenfull from "@/components/Screenfull/index.vue"
import Notify from "@/components/Notify/index.vue"
@ -68,7 +68,7 @@ const logout = () => {
.navigation-bar {
height: var(--v3-navigationbar-height);
overflow: hidden;
background: #fff;
background: var(--v3-header-bg-color);
.hamburger {
display: flex;
align-items: center;

View File

@ -1,4 +1,5 @@
<script lang="ts" setup>
import { watchEffect } from "vue"
import { storeToRefs } from "pinia"
import { useSettingsStore } from "@/store/modules/settings"
import { removeConfigLayout } from "@/utils/cache/local-storage"
@ -8,8 +9,9 @@ const settingsStore = useSettingsStore()
/** 使用 storeToRefs 将提取的属性保持其响应性 */
const {
layoutMode,
showTagsView,
showSidebarLogo,
showLogo,
fixedHeader,
showNotify,
showThemeSwitch,
@ -22,7 +24,7 @@ const {
/** 定义 switch 设置项 */
const switchSettings = {
显示标签栏: showTagsView,
"显示侧边栏 Logo": showSidebarLogo,
"显示 Logo": showLogo,
"固定 Header": fixedHeader,
显示消息通知: showNotify,
显示切换主题按钮: showThemeSwitch,
@ -32,6 +34,11 @@ const switchSettings = {
显示色弱模式: showColorWeakness
}
/** 非左侧模式时Header 都是 fixed 布局 */
watchEffect(() => {
layoutMode.value !== "left" && (fixedHeader.value = true)
})
/** 重置配置 */
const reset = () => {
removeConfigLayout()
@ -41,10 +48,16 @@ const reset = () => {
<template>
<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">
<span class="setting-name">{{ settingName }}</span>
<el-switch v-model="settingValue.value" />
<el-switch v-model="settingValue.value" :disabled="layoutMode !== 'left' && settingName === '固定 Header'" />
</div>
<el-button type="danger" :icon="Refresh" @click="reset"> </el-button>
</div>
@ -57,7 +70,8 @@ const reset = () => {
padding: 20px;
.setting-item {
font-size: 14px;
padding: 6px 0;
color: var(--el-text-color-regular);
padding: 5px 0;
display: flex;
justify-content: space-between;
align-items: center;

View File

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

View File

@ -6,7 +6,7 @@ import { useAppStore } from "@/store/modules/app"
import { usePermissionStore } from "@/store/modules/permission"
import { useSettingsStore } from "@/store/modules/settings"
import SidebarItem from "./SidebarItem.vue"
import SidebarLogo from "./SidebarLogo.vue"
import Logo from "../Logo/index.vue"
import { getCssVariableValue } from "@/utils"
const v3SidebarMenuBgColor = getCssVariableValue("--v3-sidebar-menu-bg-color")
@ -18,7 +18,7 @@ const appStore = useAppStore()
const permissionStore = usePermissionStore()
const settingsStore = useSettingsStore()
const { showSidebarLogo } = storeToRefs(settingsStore)
const { layoutMode, showLogo } = storeToRefs(settingsStore)
const activeMenu = computed(() => {
const {
@ -29,11 +29,12 @@ const activeMenu = computed(() => {
})
const isCollapse = computed(() => !appStore.sidebar.opened)
const isLogo = computed(() => layoutMode.value === "left" && showLogo.value)
</script>
<template>
<div :class="{ 'has-logo': showSidebarLogo }">
<SidebarLogo v-if="showSidebarLogo" :collapse="isCollapse" />
<div :class="{ 'has-logo': isLogo }">
<Logo v-if="isLogo" :collapse="isCollapse" />
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
:default-active="activeMenu"

View File

@ -199,9 +199,8 @@ onMounted(() => {
.tags-view-container {
height: var(--v3-tagsview-height);
width: 100%;
background-color: #fff;
border-bottom: 1px solid #d8dce5;
box-shadow: 0 1px 3px 0 #00000010, 0 0 3px 0 #00000010;
background-color: var(--v3-header-bg-color);
box-shadow: 0 0 3px 0 #00000010;
.tags-view-wrapper {
.tags-view-item {
display: inline-block;

View File

@ -4,3 +4,4 @@ export { default as Settings } from "./Settings/index.vue"
export { default as Sidebar } from "./Sidebar/index.vue"
export { default as TagsView } from "./TagsView/index.vue"
export { default as RightPanel } from "./RightPanel/index.vue"
export { default as Logo } from "./Logo/index.vue"

View File

@ -1,71 +1,55 @@
<script lang="ts" setup>
import { computed } from "vue"
import { computed, watchEffect } from "vue"
import { storeToRefs } from "pinia"
import { useAppStore } from "@/store/modules/app"
import { useSettingsStore } from "@/store/modules/settings"
import { AppMain, NavigationBar, Settings, Sidebar, TagsView, RightPanel } from "./components"
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"
const appStore = useAppStore()
const settingsStore = useSettingsStore()
const { showGreyMode, showColorWeakness, showSettings, showTagsView, fixedHeader } = storeToRefs(settingsStore)
import { getCssVariableValue, setCssVariableValue } from "@/utils"
/** Layout 布局响应式 */
useResize()
/** 定义计算属性 layoutClasses用于控制布局的类名 */
const layoutClasses = computed(() => {
const appStore = useAppStore()
const settingsStore = useSettingsStore()
const { showSettings, layoutMode, showTagsView, showGreyMode, showColorWeakness } = storeToRefs(settingsStore)
const classes = computed(() => {
return {
hideSidebar: !appStore.sidebar.opened,
openSidebar: appStore.sidebar.opened,
withoutAnimation: appStore.sidebar.withoutAnimation,
mobile: appStore.device === DeviceEnum.Mobile,
showGreyMode: showGreyMode.value,
showColorWeakness: showColorWeakness.value
}
})
/** 用于处理点击 mobile 端侧边栏遮罩层的事件 */
const handleClickOutside = () => {
appStore.closeSidebar(false)
}
//#region Logo Header
const cssVariableName = "--v3-tagsview-height"
const v3TagsviewHeight = getCssVariableValue(cssVariableName)
watchEffect(() => {
showTagsView.value
? setCssVariableValue(cssVariableName, v3TagsviewHeight)
: setCssVariableValue(cssVariableName, "0px")
})
//#endregion
</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 }">
<NavigationBar />
<TagsView v-show="showTagsView" />
</div>
<!-- 页面主体内容 -->
<AppMain />
<!-- 右侧设置面板 -->
<RightPanel v-if="showSettings">
<Settings />
</RightPanel>
</div>
<div :class="classes">
<!-- 左侧模式 -->
<LeftMode v-if="layoutMode === 'left' || appStore.device === DeviceEnum.Mobile" />
<!-- 混合模式 -->
<LeftTopMode v-else-if="layoutMode === 'left-top'" />
<!-- 右侧设置面板 -->
<RightPanel v-if="showSettings">
<Settings />
</RightPanel>
</div>
</template>
<style lang="scss" scoped>
@import "@/styles/mixins.scss";
$transition-time: 0.35s;
.app-wrapper {
@include clearfix;
position: relative;
width: 100%;
}
.showGreyMode {
filter: grayscale(1);
}
@ -73,88 +57,4 @@ $transition-time: 0.35s;
.showColorWeakness {
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>

View File

@ -1,13 +1,21 @@
/** Layout 相关 */
.app-wrapper {
#app {
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-logo-container {
background-color: lighten($theme-bg-color, 2%) !important;
}
.el-menu {
background-color: lighten($theme-bg-color, 4%) !important;
.el-menu-item {
@ -24,6 +32,11 @@
}
}
// Header
.layout-header {
border-bottom: 1px solid lighten($theme-bg-color, 10%) !important;
}
// 顶部导航栏
.navigation-bar {
background-color: $theme-bg-color;
@ -37,7 +50,6 @@
// TagsView
.tags-view-container {
background-color: $theme-bg-color !important;
border-bottom: 1px solid lighten($theme-bg-color, 10%) !important;
.tags-view-item {
background-color: $theme-bg-color !important;
color: $font-color !important;
@ -58,9 +70,4 @@
}
}
}
// 右侧设置面板
.handle-button {
background-color: lighten($theme-bg-color, 20%) !important;
}
}

View File

@ -14,12 +14,12 @@
transform: translateX(30px);
}
// sidebar-logo-fade
.sidebar-logo-fade-enter-active,
.sidebar-logo-fade-leave-active {
// layout-logo-fade
.layout-logo-fade-enter-active,
.layout-logo-fade-leave-active {
transition: opacity 1.5s;
}
.sidebar-logo-fade-enter-from,
.sidebar-logo-fade-leave-to {
.layout-logo-fade-enter-from,
.layout-logo-fade-leave-to {
opacity: 0;
}

View File

@ -4,6 +4,7 @@
--v3-body-bg-color: #f2f3f5;
/** Header 区域 = NavigationBar 组件 + TagsView 组件 */
--v3-header-height: calc(var(--v3-navigationbar-height) + var(--v3-tagsview-height));
--v3-header-bg-color: #ffffff;
/** NavigationBar 组件 */
--v3-navigationbar-height: 50px;
/** Sidebar 组件 */
@ -15,8 +16,6 @@
--v3-sidebar-menu-hover-bg-color: #ffffff10;
--v3-sidebar-menu-text-color: #c0c4cc;
--v3-sidebar-menu-active-text-color: #ffffff;
/** SidebarLogo 组件 */
--v3-sidebarlogo-bg-color: #001428;
/** TagsView 组件 */
--v3-tagsview-height: 34px;
--v3-tagsview-tag-text-color: #495060;