feat(Layout): Add topLeft layout
This commit is contained in:
parent
839b6015b8
commit
71b1c5e10c
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
import ColorRadioPicker from './src/ColorRadioPicker.vue'
|
||||
|
||||
export { ColorRadioPicker }
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
|
|
|
@ -35,7 +35,9 @@ export default {
|
|||
localeIcon: '多语言图标',
|
||||
tagsView: '标签页',
|
||||
logo: '标志',
|
||||
greyMode: '灰色模式'
|
||||
greyMode: '灰色模式',
|
||||
fixedHeader: '固定头部',
|
||||
headerTheme: '头部主题'
|
||||
},
|
||||
size: {
|
||||
default: '默认',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
@import './var.css';
|
||||
@import './common.less';
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,7 +117,8 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
|
|||
},
|
||||
hmr: {
|
||||
overlay: false
|
||||
}
|
||||
},
|
||||
host: '0.0.0.0'
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: [
|
||||
|
|
|
@ -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: ''
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// })
|
||||
// ]
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue