feat(Layout): Add topLeft layout

This commit is contained in:
陈凯龙 2022-01-19 16:29:35 +08:00
parent 839b6015b8
commit 71b1c5e10c
28 changed files with 571 additions and 166 deletions

View File

@ -83,12 +83,27 @@ export default defineComponent({
</script>
<style lang="less" scoped>
:deep(.el-breadcrumb__item) {
:deep(.el-breadcrumb__item):not(:last-child) {
display: flex;
.el-breadcrumb__inner {
display: flex;
align-items: center;
color: var(--top-header-text-color);
&:hover {
color: var(--el-color-primary);
}
}
}
:deep(.el-breadcrumb__item):last-child {
.el-breadcrumb__inner {
color: rgba(255, 255, 255, 0.6);
&:hover {
color: rgba(255, 255, 255, 0.6);
}
}
}
</style>

View File

@ -1,6 +1,11 @@
<script setup lang="ts">
import { computed, unref } from 'vue'
import { useAppStore } from '@/store/modules/app'
import { propTypes } from '@/utils/propTypes'
defineProps({
color: propTypes.string.def('')
})
const appStore = useAppStore()
@ -17,6 +22,7 @@ const toggleCollapse = () => {
<Icon
:size="18"
:icon="collapse ? 'ant-design:menu-unfold-outlined' : 'ant-design:menu-fold-outlined'"
:color="color"
class="cursor-pointer"
@click="toggleCollapse"
/>

View File

@ -1,3 +0,0 @@
import ColorRadioPicker from './src/ColorRadioPicker.vue'
export { ColorRadioPicker }

View File

@ -3,6 +3,11 @@ import { computed, unref } from 'vue'
import { ElDropdown, ElDropdownMenu, ElDropdownItem } from 'element-plus'
import { useLocaleStore } from '@/store/modules/locale'
import { useLocale } from '@/hooks/web/useLocale'
import { propTypes } from '@/utils/propTypes'
defineProps({
color: propTypes.string.def('')
})
const localeStore = useLocaleStore()
@ -24,7 +29,13 @@ const setLang = (lang: LocaleType) => {
<template>
<ElDropdown trigger="click" @command="setLang">
<Icon :size="18" icon="ion:language-sharp" class="cursor-pointer" :class="$attrs.class" />
<Icon
:size="18"
icon="ion:language-sharp"
class="cursor-pointer"
:class="$attrs.class"
:color="color"
/>
<template #dropdown>
<ElDropdownMenu>
<ElDropdownItem v-for="item in langMap" :key="item.lang" :command="item.lang">

View File

@ -41,7 +41,7 @@ watch(
{
'v-logo__Top': layout !== 'classic'
},
'flex h-[var(--logo-height)] items-center cursor-pointer pl-8px relative'
'flex !h-[var(--logo-height)] items-center cursor-pointer pl-8px relative'
]"
to="/"
>
@ -49,9 +49,18 @@ watch(
src="@/assets/imgs/logo.png"
class="w-[calc(var(--logo-height)-10px)] h-[calc(var(--logo-height)-10px)]"
/>
<div v-if="show" class="text-[var(--logo-title-text-color)] ml-10px text-16px font-700">{{
title
}}</div>
<div
v-if="show"
:class="[
'ml-10px text-16px font-700',
{
'text-[var(--logo-title-text-color)]': layout === 'classic',
'text-[var(--top-header-text-color)]': layout === 'topLeft'
}
]"
>
{{ title }}
</div>
</router-link>
</template>

View File

@ -7,23 +7,19 @@ import type { LayoutType } from '@/config/app'
import { useRenderMenuItem } from './components/useRenderMenuItem'
import { useRouter } from 'vue-router'
import { isUrl } from '@/utils/is'
import { Logo } from '@/components/Logo'
export default defineComponent({
name: 'Menu',
setup() {
const appStore = useAppStore()
// logo
const logo = computed(() => appStore.logo)
const { push, currentRoute } = useRouter()
const permissionStore = usePermissionStore()
const menuMode = computed((): 'vertical' | 'horizontal' => {
//
const vertical: LayoutType[] = ['classic']
const vertical: LayoutType[] = ['classic', 'topLeft']
if (vertical.includes(appStore.getLayout)) {
return 'vertical'
@ -64,8 +60,7 @@ export default defineComponent({
'bg-[var(--left-menu-bg-color)]'
]}
>
{logo.value ? <Logo></Logo> : undefined}
<ElScrollbar class={[{ '!h-[calc(100%-var(--logo-height))]': logo.value }]}>
<ElScrollbar>
<ElMenu
defaultActive={unref(activeMenu)}
mode={unref(menuMode)}
@ -103,6 +98,7 @@ export default defineComponent({
}
.@{prefix-cls} {
position: relative;
transition: width var(--transition-time-02);
&:after {

View File

@ -1,6 +1,11 @@
<script setup lang="ts">
import { Icon } from '@/components/Icon'
import { useFullscreen } from '@vueuse/core'
import { propTypes } from '@/utils/propTypes'
defineProps({
color: propTypes.string.def('')
})
const { toggle, isFullscreen } = useFullscreen()
@ -11,6 +16,10 @@ const toggleFullscreen = () => {
<template>
<div @click="toggleFullscreen">
<Icon :size="18" :icon="isFullscreen ? 'zmdi:fullscreen-exit' : 'zmdi:fullscreen'" />
<Icon
:size="18"
:icon="isFullscreen ? 'zmdi:fullscreen-exit' : 'zmdi:fullscreen'"
:color="color"
/>
</div>
</template>

View File

@ -3,12 +3,13 @@ import { ElDrawer, ElDivider } from 'element-plus'
import { ref, unref } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { ThemeSwitch } from '@/components/ThemeSwitch'
import { ColorRadioPicker } from '@/components/ColorRadioPicker'
import { colorIsDark, lighten, hexToRGB } from '@/utils/color'
import { useCssVar } from '@vueuse/core'
import { useAppStore } from '@/store/modules/app'
import { trim, setCssVar } from '@/utils'
import ColorRadioPicker from './components/ColorRadioPicker.vue'
import InterfaceDisplay from './components/InterfaceDisplay.vue'
import LayoutRadioPicker from './components/LayoutRadioPicker.vue'
const appStore = useAppStore()
@ -26,6 +27,23 @@ const setSystemTheme = (color: string) => {
setMenuTheme(trim(unref(leftMenuBgColor)))
}
//
const headerTheme = ref(appStore.getTheme.topHeaderBgColor)
const setHeaderTheme = (color: string) => {
const isDarkColor = colorIsDark(color)
const textColor = isDarkColor ? '#fff' : 'inherit'
const textHoverColor = isDarkColor ? lighten(color!, 6) : '#f6f6f6'
setCssVar('--top-header-bg-color', color)
setCssVar('--top-header-text-color', textColor)
setCssVar('--top-header-hover-color', textHoverColor)
appStore.setTheme({
topHeaderBgColor: color,
topHeaderTextColor: textColor,
topHeaderHoverColor: textHoverColor
})
}
//
const menuTheme = ref(appStore.getTheme.leftMenuBgColor)
@ -81,6 +99,7 @@ const setMenuTheme = (color: string) => {
<!-- 布局 -->
<ElDivider>{{ t('setting.layout') }}</ElDivider>
<LayoutRadioPicker />
<!-- 系统主题 -->
<ElDivider>{{ t('setting.systemTheme') }}</ElDivider>
@ -99,6 +118,23 @@ const setMenuTheme = (color: string) => {
@change="setSystemTheme"
/>
<!-- 头部主题 -->
<ElDivider>{{ t('setting.headerTheme') }}</ElDivider>
<ColorRadioPicker
v-model="headerTheme"
:schema="[
'#fff',
'#151515',
'#5172dc',
'#e74c3c',
'#24292e',
'#394664',
'#009688',
'#383f45'
]"
@change="setHeaderTheme"
/>
<!-- 菜单主题 -->
<ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
<ColorRadioPicker

View File

@ -71,6 +71,13 @@ const logoChange = (show: boolean) => {
appStore.setLogo(show)
}
//
const fixedHeader = ref(appStore.getFixedHeader)
const fixedHeaderChange = (show: boolean) => {
appStore.setFixedHeader(show)
}
//
const greyMode = ref(appStore.getGreyMode)
@ -126,6 +133,11 @@ const greyModeChange = (show: boolean) => {
<ElSwitch v-model="logo" @change="logoChange" />
</div>
<div class="flex justify-between items-center">
<span class="text-14px">{{ t('setting.fixedHeader') }}</span>
<ElSwitch v-model="fixedHeader" @change="fixedHeaderChange" />
</div>
<div class="flex justify-between items-center">
<span class="text-14px">{{ t('setting.greyMode') }}</span>
<ElSwitch v-model="greyMode" @change="greyModeChange" />

View File

@ -0,0 +1,98 @@
<script setup lang="ts">
import { useAppStore } from '@/store/modules/app'
import { computed } from 'vue'
const appStore = useAppStore()
const layout = computed(() => appStore.getLayout)
console.log(layout.value)
</script>
<template>
<div class="v-layout-radio-picker flex flex-wrap space-x-14px">
<div
:class="[
'v-layout-radio-picker__classic relative w-56px h-48px cursor-pointer bg-gray-100',
{
'is-acitve': layout === 'classic'
}
]"
@click="appStore.setLayout('classic')"
></div>
<div
:class="[
'v-layout-radio-picker__top-left relative w-56px h-48px cursor-pointer bg-gray-100',
{
'is-acitve': layout === 'topLeft'
}
]"
@click="appStore.setLayout('topLeft')"
></div>
</div>
</template>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-layout-radio-picker';
.@{prefix-cls} {
&__classic {
border: 2px solid #e5e7eb;
border-radius: 4px;
&:before {
position: absolute;
top: 0;
left: 0;
z-index: 1;
width: 33%;
height: 100%;
background-color: #273352;
border-radius: 4px 0 0 4px;
content: '';
}
&:after {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 25%;
background-color: #fff;
border-radius: 4px 4px 0 4px;
content: '';
}
}
&__top-left {
border: 2px solid #e5e7eb;
border-radius: 4px;
&:before {
position: absolute;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 33%;
background-color: #273352;
border-radius: 4px 4px 0 0;
content: '';
}
&:after {
position: absolute;
top: 0;
left: 0;
width: 33%;
height: 100%;
background-color: #fff;
border-radius: 4px 0 0 4px;
content: '';
}
}
.is-acitve {
border-color: var(--el-color-primary);
}
}
</style>

View File

@ -3,6 +3,12 @@ import { computed } from 'vue'
import { ElDropdown, ElDropdownMenu, ElDropdownItem } from 'element-plus'
import { useAppStore } from '@/store/modules/app'
import { useI18n } from '@/hooks/web/useI18n'
import { propTypes } from '@/utils/propTypes'
defineProps({
color: propTypes.string.def('')
})
const { t } = useI18n()
const appStore = useAppStore()
@ -16,12 +22,7 @@ const setCurrentSize = (size: ElememtPlusSzie) => {
<template>
<ElDropdown trigger="click" @command="setCurrentSize">
<Icon
:size="18"
icon="mdi:format-size"
color="var(--el-text-color-primary)"
class="cursor-pointer"
/>
<Icon :size="18" icon="mdi:format-size" :color="color" class="cursor-pointer" />
<template #dropdown>
<ElDropdownMenu>
<ElDropdownItem v-for="item in sizeMap" :key="item" :command="item">

View File

@ -125,8 +125,10 @@ watch(
</script>
<template>
<div class="v-tags-view h-[var(--tags-view-height)] flex w-full">
<span class="v-tags-view__tool w-[40px] h-[40px] text-center leading-[40px] cursor-pointer">
<div class="v-tags-view h-[var(--tags-view-height)] flex w-full relative">
<span
class="v-tags-view__tool w-[var(--tags-view-height)] h-[var(--tags-view-height)] text-center leading-[var(--tags-view-height)] cursor-pointer"
>
<Icon icon="ep:d-arrow-left" color="#333" />
</span>
<div class="overflow-hidden flex-1">
@ -215,11 +217,13 @@ watch(
</div>
</ElScrollbar>
</div>
<span class="v-tags-view__tool w-[40px] h-[40px] text-center leading-[40px] cursor-pointer">
<span
class="v-tags-view__tool w-[var(--tags-view-height)] h-[var(--tags-view-height)] text-center leading-[var(--tags-view-height)] cursor-pointer"
>
<Icon icon="ep:d-arrow-right" color="#333" />
</span>
<span
class="v-tags-view__tool w-[40px] h-[40px] text-center leading-[40px] cursor-pointer"
class="v-tags-view__tool w-[var(--tags-view-height)] h-[var(--tags-view-height)] text-center leading-[var(--tags-view-height)] cursor-pointer"
@click="refreshSelectedTag(selectedTag)"
>
<Icon icon="ant-design:reload-outlined" color="#333" />
@ -275,7 +279,7 @@ watch(
]"
>
<span
class="v-tags-view__tool w-[40px] h-[40px] text-center leading-[40px] cursor-pointer block"
class="v-tags-view__tool w-[var(--tags-view-height)] h-[var(--tags-view-height)] text-center leading-[var(--tags-view-height)] cursor-pointer block"
>
<Icon icon="ant-design:setting-outlined" color="#333" />
</span>

View File

@ -34,7 +34,7 @@ const loginOut = () => {
<ElDropdown trigger="click">
<div class="flex items-center">
<img src="@/assets/imgs/avatar.png" alt="" class="w-[calc(var(--tags-view-height)-10px)]" />
<span class="<lg:hidden text-14px pl-[5px] text-dark-50">Archer</span>
<span class="<lg:hidden text-14px pl-[5px] text-[var(--top-header-text-color)]">Archer</span>
</div>
<template #dropdown>
<ElDropdownMenu>

View File

@ -2,7 +2,7 @@ import { useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache()
export type LayoutType = 'classic' | 'leftTop' | 'top' | 'test'
export type LayoutType = 'classic' | 'topLeft' | 'leftTop' | 'top' | 'test'
export interface AppState {
breadcrumb: boolean
@ -14,6 +14,7 @@ export interface AppState {
locale: boolean
tagsView: boolean
logo: boolean
fixedHeader: boolean
greyMode: boolean
layout: LayoutType
@ -37,9 +38,10 @@ export const appModules: AppState = {
locale: true, // 多语言图标
tagsView: true, // 标签页
logo: true, // logo
fixedHeader: true, // 固定toolheader
greyMode: false, // 是否开始灰色模式,用于特殊悼念日
layout: 'classic', // layout布局
layout: wsCache.get('layout') || 'classic', // layout布局
title: 'butterfly-admin', // 标题
logoTitle: 'ButterflyAdmin', // logo标题
userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段,避免与其他项目冲突
@ -67,6 +69,12 @@ export const appModules: AppState = {
// logo字体颜色
logoTitleTextColor: '#fff',
// logo边框颜色
logoBorderColor: 'inherit'
logoBorderColor: 'inherit',
// 头部背景颜色
topHeaderBgColor: '#fff',
// 头部字体颜色
topHeaderTextColor: 'inherit',
// 头部悬停颜色
topHeaderHoverColor: '#f6f6f6'
}
}

View File

@ -1,100 +1,50 @@
<script lang="tsx">
import { computed, defineComponent } from 'vue'
import { computed, defineComponent, unref } from 'vue'
import { useAppStore } from '@/store/modules/app'
import { Menu } from '@/components/Menu'
import { Collapse } from '@/components/Collapse'
import { LocaleDropdown } from '@/components/LocaleDropdown'
import { SizeDropdown } from '@/components/SizeDropdown'
import { UserInfo } from '@/components/UserInfo'
import { Screenfull } from '@/components/Screenfull'
import { Breadcrumb } from '@/components/Breadcrumb'
import { TagsView } from '@/components/TagsView'
import { Backtop } from '@/components/Backtop'
import { Setting } from '@/components/Setting'
import AppView from './components/AppView.vue'
import { useRenderLayout } from './components/useRenderLayout'
const appStore = useAppStore()
//
const mobile = computed(() => appStore.getMobile)
//
const breadcrumb = computed(() => appStore.getBreadcrumb)
//
const collapse = computed(() => appStore.getCollapse)
//
const hamburger = computed(() => appStore.getHamburger)
//
const screenfull = computed(() => appStore.getScreenfull)
//
const size = computed(() => appStore.getSize)
//
const locale = computed(() => appStore.getLocale)
//
const tagsView = computed(() => appStore.getTagsView)
const classSuffix = computed(() => appStore.getLayout)
const layout = computed(() => appStore.getLayout)
const handleClickOutside = () => {
appStore.setCollapse(true)
}
const renderLayout = () => {
switch (unref(layout)) {
case 'classic':
const { renderClassic } = useRenderLayout()
return renderClassic()
case 'topLeft':
const { renderTopLeft } = useRenderLayout()
return renderTopLeft()
default:
break
}
}
export default defineComponent({
name: 'Layout',
setup() {
return () => (
<section class={['v-app', `v-app__${classSuffix.value}`, 'w-[100%] h-[100%] relative']}>
<section class={['v-app', `v-app__${layout.value}`, 'w-[100%] h-[100%] relative']}>
{mobile.value && !collapse.value ? (
<div
class="absolute top-0 left-0 w-full h-full opacity-30 z-99 bg-[var(--el-color-black)]"
onClick={handleClickOutside}
></div>
) : undefined}
<Menu class="absolute top-0 left-0"></Menu>
<div
class={[
'v-app-right',
'absolute top-0 h-[100%]',
collapse.value
? 'w-[calc(100%-var(--left-menu-min-width))]'
: 'w-[calc(100%-var(--left-menu-max-width))]',
collapse.value
? 'left-[var(--left-menu-min-width)]'
: 'left-[var(--left-menu-max-width)]',
'<md:(!left-0 !w-[100%])'
]}
>
<div
class={[
'v-app-right__tool',
'h-[var(--top-tool-height)] relative px-[var(--top-tool-p-x)] flex items-center justify-between'
]}
>
<div class="h-full flex items-center">
{hamburger.value ? <Collapse class="header__tigger"></Collapse> : undefined}
{breadcrumb.value ? <Breadcrumb class="<md:hidden"></Breadcrumb> : undefined}
</div>
<div class="h-full flex items-center">
{screenfull.value ? <Screenfull class="header__tigger"></Screenfull> : undefined}
{size.value ? <SizeDropdown class="header__tigger"></SizeDropdown> : undefined}
{locale.value ? <LocaleDropdown class="header__tigger"></LocaleDropdown> : undefined}
<UserInfo class="header__tigger"></UserInfo>
</div>
</div>
{tagsView.value ? (
<div class="v-app-right__tags-view relative">
<TagsView></TagsView>
</div>
) : undefined}
<AppView></AppView>
</div>
{renderLayout()}
<Backtop></Backtop>
@ -107,36 +57,4 @@ export default defineComponent({
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-app';
.header__tigger {
display: flex;
height: 100%;
padding: 1px 10px 0;
cursor: pointer;
align-items: center;
transition: background var(--transition-time-02);
&:hover {
background-color: #f6f6f6;
}
}
.@{prefix-cls} {
&-right {
transition: left var(--transition-time-02);
&__tool,
&__tags-view {
&::after {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 1px;
border-top: 1px solid var(--top-tool-border-color);
content: '';
}
}
}
}
</style>

View File

@ -10,22 +10,13 @@ const getCaches = computed((): string[] => {
</script>
<template>
<el-scrollbar
:class="[
'v-content',
{
'!h-[calc(100%-var(--top-tool-height)-var(--tags-view-height))]': true
}
]"
>
<section class="p-[var(--app-content-padding)]">
<router-view>
<template #default="{ Component, route }">
<keep-alive :include="getCaches">
<component :is="Component" :key="route.fullPath" />
</keep-alive>
</template>
</router-view>
</section>
</el-scrollbar>
<section class="p-[var(--app-content-padding)] w-[100%]">
<router-view>
<template #default="{ Component, route }">
<keep-alive :include="getCaches">
<component :is="Component" :key="route.fullPath" />
</keep-alive>
</template>
</router-view>
</section>
</template>

View File

@ -0,0 +1,71 @@
<script lang="tsx">
import { defineComponent, computed } from 'vue'
import { Collapse } from '@/components/Collapse'
import { LocaleDropdown } from '@/components/LocaleDropdown'
import { SizeDropdown } from '@/components/SizeDropdown'
import { UserInfo } from '@/components/UserInfo'
import { Screenfull } from '@/components/Screenfull'
import { Breadcrumb } from '@/components/Breadcrumb'
import { useAppStore } from '@/store/modules/app'
const appStore = useAppStore()
//
const breadcrumb = computed(() => appStore.getBreadcrumb)
//
const hamburger = computed(() => appStore.getHamburger)
//
const screenfull = computed(() => appStore.getScreenfull)
//
const size = computed(() => appStore.getSize)
//
const locale = computed(() => appStore.getLocale)
export default defineComponent({
name: 'ToolHeader',
setup() {
return () => (
<div
class={[
'v-tool-header',
'h-[var(--top-tool-height)] relative px-[var(--top-tool-p-x)] flex items-center justify-between'
]}
>
<div class="h-full flex items-center">
{hamburger.value ? (
<Collapse class="hover-tigger" color="var(--top-header-text-color)"></Collapse>
) : undefined}
{breadcrumb.value ? <Breadcrumb class="<md:hidden"></Breadcrumb> : undefined}
</div>
<div class="h-full flex items-center">
{screenfull.value ? (
<Screenfull class="hover-tigger" color="var(--top-header-text-color)"></Screenfull>
) : undefined}
{size.value ? (
<SizeDropdown class="hover-tigger" color="var(--top-header-text-color)"></SizeDropdown>
) : undefined}
{locale.value ? (
<LocaleDropdown
class="hover-tigger"
color="var(--top-header-text-color)"
></LocaleDropdown>
) : undefined}
<UserInfo class="hover-tigger"></UserInfo>
</div>
</div>
)
}
})
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-tool-header';
.@{prefix-cls} {
transition: left var(--transition-time-02);
}
</style>

View File

@ -0,0 +1,155 @@
import { computed } from 'vue'
import { useAppStore } from '@/store/modules/app'
import { Menu } from '@/components/Menu'
import { TagsView } from '@/components/TagsView'
import { Logo } from '@/components/Logo'
import AppView from './AppView.vue'
import ToolHeader from './ToolHeader.vue'
import { ElScrollbar } from 'element-plus'
const appStore = useAppStore()
// 标签页
const tagsView = computed(() => appStore.getTagsView)
// 菜单折叠
const collapse = computed(() => appStore.getCollapse)
// logo
const logo = computed(() => appStore.logo)
// 固定头部
const fixedHeader = computed(() => appStore.getFixedHeader)
// 是否是移动端
const mobile = computed(() => appStore.getMobile)
export const useRenderLayout = () => {
const renderClassic = () => {
return (
<>
<div class={['absolute top-0 left-0 h-full', { '!fixed z-99': mobile.value }]}>
{logo.value ? (
<Logo
class={[
'bg-[var(--left-menu-bg-color)]',
{
'!pl-0': mobile.value && collapse.value,
'w-[var(--left-menu-min-width)]': appStore.getCollapse,
'w-[var(--left-menu-max-width)]': !appStore.getCollapse
}
]}
style="transition: all var(--transition-time-02);"
></Logo>
) : undefined}
<Menu class={[{ '!h-[calc(100%-var(--logo-height))]': logo.value }]}></Menu>
</div>
<div
class={[
'v-app-right',
'absolute top-0 h-[100%]',
{
'w-[calc(100%-var(--left-menu-min-width))] left-[var(--left-menu-min-width)]':
collapse.value && !mobile.value && !mobile.value,
'w-[calc(100%-var(--left-menu-max-width))] left-[var(--left-menu-max-width)]':
!collapse.value && !mobile.value && !mobile.value,
'fixed !w-full !left-0': mobile.value
}
]}
style="transition: all var(--transition-time-02);"
>
<ElScrollbar
class={[
'v-content',
{
'!h-[calc(100%-var(--top-tool-height)-var(--tags-view-height))] mt-[calc(var(--top-tool-height)+var(--tags-view-height))]':
fixedHeader.value
}
]}
>
<div
class={[
{
'fixed top-0 left-0 z-10': fixedHeader.value,
'w-[calc(100%-var(--left-menu-min-width))] left-[var(--left-menu-min-width)]':
collapse.value && fixedHeader.value && !mobile.value,
'w-[calc(100%-var(--left-menu-max-width))] left-[var(--left-menu-max-width)]':
!collapse.value && fixedHeader.value && !mobile.value,
'!w-full !left-0': mobile.value
}
]}
style="transition: all var(--transition-time-02);"
>
<ToolHeader class="border-bottom bg-[var(--top-header-bg-color)]"></ToolHeader>
{tagsView.value ? <TagsView class="border-bottom"></TagsView> : undefined}
</div>
<AppView></AppView>
</ElScrollbar>
</div>
</>
)
}
const renderTopLeft = () => {
return (
<>
<div class="flex items-center bg-[var(--top-header-bg-color)]">
<Logo class="hover-tigger !pr-15px"></Logo>
<ToolHeader class="flex-1"></ToolHeader>
</div>
<div class="absolute top-[var(--logo-height)] left-0 w-full h-[calc(100%-var(--logo-height))] flex">
<Menu class="!h-full"></Menu>
<div
class={[
'v-app-right',
'h-[100%]',
{
'w-[calc(100%-var(--left-menu-min-width))] left-[var(--left-menu-min-width)]':
collapse.value,
'w-[calc(100%-var(--left-menu-max-width))] left-[var(--left-menu-max-width)]':
!collapse.value
}
]}
style="transition: all var(--transition-time-02);"
>
<ElScrollbar
class={[
'v-content',
{
'!h-[calc(100%-var(--tags-view-height))] mt-[calc(var(--tags-view-height))]':
fixedHeader.value && tagsView.value
}
]}
>
{tagsView.value ? (
<TagsView
class={[
'border-bottom border-top',
{
'!fixed top-0 left-0 z-10': fixedHeader.value,
'w-[calc(100%-var(--left-menu-min-width))] left-[var(--left-menu-min-width)] mt-[var(--logo-height)]':
collapse.value && fixedHeader.value,
'w-[calc(100%-var(--left-menu-max-width))] left-[var(--left-menu-max-width)] mt-[var(--logo-height)]':
!collapse.value && fixedHeader.value
}
]}
style="transition: all var(--transition-time-02);"
></TagsView>
) : undefined}
<AppView></AppView>
</ElScrollbar>
</div>
</div>
</>
)
}
return {
renderClassic,
renderTopLeft
}
}

View File

@ -35,7 +35,9 @@ export default {
localeIcon: 'Locale icon',
tagsView: 'Tags view',
logo: 'Logo',
greyMode: 'Grey mode'
greyMode: 'Grey mode',
fixedHeader: 'Fixed header',
headerTheme: 'Header theme'
},
size: {
default: 'Default',

View File

@ -35,7 +35,9 @@ export default {
localeIcon: '多语言图标',
tagsView: '标签页',
logo: '标志',
greyMode: '灰色模式'
greyMode: '灰色模式',
fixedHeader: '固定头部',
headerTheme: '头部主题'
},
size: {
default: '默认',

View File

@ -4,6 +4,7 @@ import { useCache } from '@/hooks/web/useCache'
import { appModules } from '@/config/app'
import type { AppState, LayoutType } from '@/config/app'
import { setCssVar, humpToUnderline } from '@/utils'
import { ElMessage } from 'element-plus'
const { wsCache } = useCache()
@ -38,6 +39,9 @@ export const useAppStore = defineStore({
getLogo(): boolean {
return this.logo
},
getFixedHeader(): boolean {
return this.fixedHeader
},
getGreyMode(): boolean {
return this.greyMode
},
@ -98,12 +102,20 @@ export const useAppStore = defineStore({
setLogo(logo: boolean) {
this.logo = logo
},
setFixedHeader(fixedHeader: boolean) {
this.fixedHeader = fixedHeader
},
setGreyMode(greyMode: boolean) {
this.greyMode = greyMode
},
setLayout(layout: LayoutType) {
if (this.mobile && layout !== 'classic') {
ElMessage.warning('移动端模式下不支持切换其他布局')
return
}
this.layout = layout
wsCache.set('layout', this.layout)
},
setTitle(title: string) {
this.title = title

25
src/styles/common.less Normal file
View File

@ -0,0 +1,25 @@
.hover-tigger {
@apply flex h-full pt-1px px-10px cursor-pointer items-center;
transition: background var(--transition-time-02);
&:hover {
background-color: var(--top-header-hover-color);
}
}
.border-bottom {
@apply relative;
&:after {
content: '';
border-top: 1px solid var(--top-tool-border-color);
@apply absolute bottom-0 left-0 w-full h-1px;
}
}
.border-top {
@apply relative;
&:before {
content: '';
border-top: 1px solid var(--top-tool-border-color);
@apply absolute top-0 left-0 w-full h-1px;
}
}

View File

@ -1 +1,2 @@
@import './var.css';
@import './common.less';

View File

@ -30,13 +30,19 @@
/* logo end */
/* header start */
--top-header-bg-color: '#fff';
--top-header-text-color: 'inherit';
--top-header-hover-color: #f6f6f6;
--top-tool-height: var(--logo-height);
--top-tool-p-x: 0;
--top-tool-border-color: #eee;
--tags-view-height: 40px;
--tags-view-height: 35px;
/* header start */
--app-content-padding: 20px;

View File

@ -132,7 +132,8 @@ const signIn = async () => {
addRoute(route as RouteRecordRaw) // 访
})
permissionStore.setIsAddRouters(true)
push({ path: redirect.value || permissionStore.addRouters[0].path })
// push({ path: redirect.value || permissionStore.addRouters[0].path })
push({ path: permissionStore.addRouters[0].path })
}
}
}

View File

@ -117,7 +117,8 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
},
hmr: {
overlay: false
}
},
host: '0.0.0.0'
},
optimizeDeps: {
include: [

View File

@ -1,4 +1,5 @@
import { defineConfig } from 'windicss/helpers'
// import plugin from 'windicss/plugin'
// function range(size, startAt = 1) {
// return Array.from(Array(size).keys()).map((i) => i + startAt)
@ -34,16 +35,33 @@ export default defineConfig({
// // ...range(50).map((i) => `ml-${i}px`)
// }
}
// Plugin: [
// require('@windicss/plugin-animations')({
// settings: {
// animatedSpeed: 1000,
// heartBeatSpeed: 1000,
// hingeSpeed: 2000,
// bounceInSpeed: 750,
// bounceOutSpeed: 750,
// animationDelaySpeed: 1000
// }
// plugins: [
// plugin(({ addComponents }) => {
// addComponents({
// '.hover-tigger': {
// display: 'flex',
// height: '100%',
// padding: '1px 10px 0',
// cursor: 'pointer',
// alignItems: 'center',
// transition: 'background var(--transition-time-02)',
// '&:hover': {
// backgroundColor: '#f6f6f6'
// }
// },
// '.border-bottom': {
// position: 'relative',
// '&:after': {
// position: 'absolute',
// bottom: '0',
// left: '0',
// width: '100%',
// height: '1px',
// borderTop: '1px solid var(--top-tool-border-color)',
// content: ''
// }
// }
// })
// })
// ]
})