feat(Layout): Add cutMenu layout
This commit is contained in:
parent
1522e925ba
commit
ff4dd3afbf
|
@ -34,7 +34,7 @@ initDark()
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
padding: 0;
|
padding: 0 !important;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
.size;
|
.size;
|
||||||
|
|
|
@ -6,7 +6,7 @@ const appStore = useAppStore()
|
||||||
|
|
||||||
const show = ref(true)
|
const show = ref(true)
|
||||||
|
|
||||||
const title = computed(() => appStore.getLogoTitle)
|
const title = computed(() => appStore.getTitle)
|
||||||
|
|
||||||
const layout = computed(() => appStore.getLayout)
|
const layout = computed(() => appStore.getLayout)
|
||||||
|
|
||||||
|
@ -19,15 +19,30 @@ onMounted(() => {
|
||||||
watch(
|
watch(
|
||||||
() => collapse.value,
|
() => collapse.value,
|
||||||
(collapse: boolean) => {
|
(collapse: boolean) => {
|
||||||
if (layout.value !== 'classic') {
|
if (unref(layout) === 'topLeft' || unref(layout) === 'cutMenu') {
|
||||||
|
show.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!collapse) {
|
||||||
|
setTimeout(() => {
|
||||||
|
show.value = !collapse
|
||||||
|
}, 400)
|
||||||
|
} else {
|
||||||
|
show.value = !collapse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => layout.value,
|
||||||
|
(layout) => {
|
||||||
|
if (layout === 'top' || layout === 'cutMenu') {
|
||||||
show.value = true
|
show.value = true
|
||||||
} else {
|
} else {
|
||||||
if (!collapse) {
|
if (unref(collapse)) {
|
||||||
setTimeout(() => {
|
show.value = false
|
||||||
show.value = !collapse
|
|
||||||
}, 400)
|
|
||||||
} else {
|
} else {
|
||||||
show.value = !collapse
|
show.value = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,7 +70,8 @@ watch(
|
||||||
'ml-10px text-16px font-700',
|
'ml-10px text-16px font-700',
|
||||||
{
|
{
|
||||||
'text-[var(--logo-title-text-color)]': layout === 'classic',
|
'text-[var(--logo-title-text-color)]': layout === 'classic',
|
||||||
'text-[var(--top-header-text-color)]': layout === 'topLeft'
|
'text-[var(--top-header-text-color)]':
|
||||||
|
layout === 'topLeft' || layout === 'top' || layout === 'cutMenu'
|
||||||
}
|
}
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
|
@ -66,15 +82,4 @@ watch(
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@prefix-cls: ~'@{namespace}-logo';
|
@prefix-cls: ~'@{namespace}-logo';
|
||||||
|
|
||||||
.@{prefix-cls} {
|
|
||||||
&:after {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
border-bottom: 1px solid var(--logo-border-color);
|
|
||||||
content: '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="tsx">
|
<script lang="tsx">
|
||||||
import { computed, defineComponent, unref } from 'vue'
|
import { computed, defineComponent, unref, PropType } from 'vue'
|
||||||
import { ElMenu, ElScrollbar } from 'element-plus'
|
import { ElMenu, ElScrollbar } from 'element-plus'
|
||||||
import { useAppStore } from '@/store/modules/app'
|
import { useAppStore } from '@/store/modules/app'
|
||||||
import { usePermissionStore } from '@/store/modules/permission'
|
import { usePermissionStore } from '@/store/modules/permission'
|
||||||
|
@ -10,25 +10,35 @@ import { isUrl } from '@/utils/is'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Menu',
|
name: 'Menu',
|
||||||
setup() {
|
props: {
|
||||||
|
menuSelect: {
|
||||||
|
type: Function as PropType<(index: string) => void>,
|
||||||
|
default: undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
|
||||||
|
const layout = computed(() => appStore.getLayout)
|
||||||
|
|
||||||
const { push, currentRoute } = useRouter()
|
const { push, currentRoute } = useRouter()
|
||||||
|
|
||||||
const permissionStore = usePermissionStore()
|
const permissionStore = usePermissionStore()
|
||||||
|
|
||||||
const menuMode = computed((): 'vertical' | 'horizontal' => {
|
const menuMode = computed((): 'vertical' | 'horizontal' => {
|
||||||
// 竖
|
// 竖
|
||||||
const vertical: LayoutType[] = ['classic', 'topLeft']
|
const vertical: LayoutType[] = ['classic', 'topLeft', 'cutMenu']
|
||||||
|
|
||||||
if (vertical.includes(appStore.getLayout)) {
|
if (vertical.includes(unref(layout))) {
|
||||||
return 'vertical'
|
return 'vertical'
|
||||||
} else {
|
} else {
|
||||||
return 'horizontal'
|
return 'horizontal'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const routers = computed(() => permissionStore.getRouters)
|
const routers = computed(() =>
|
||||||
|
unref(layout) === 'cutMenu' ? permissionStore.getMenuTabRouters : permissionStore.getRouters
|
||||||
|
)
|
||||||
|
|
||||||
const collapse = computed(() => appStore.getCollapse)
|
const collapse = computed(() => appStore.getCollapse)
|
||||||
|
|
||||||
|
@ -42,6 +52,10 @@ export default defineComponent({
|
||||||
})
|
})
|
||||||
|
|
||||||
const menuSelect = (index: string) => {
|
const menuSelect = (index: string) => {
|
||||||
|
if (props.menuSelect) {
|
||||||
|
props.menuSelect(index)
|
||||||
|
}
|
||||||
|
// 自定义事件
|
||||||
if (isUrl(index)) {
|
if (isUrl(index)) {
|
||||||
window.open(index)
|
window.open(index)
|
||||||
} else {
|
} else {
|
||||||
|
@ -52,19 +66,21 @@ export default defineComponent({
|
||||||
return () => (
|
return () => (
|
||||||
<div
|
<div
|
||||||
class={[
|
class={[
|
||||||
'v-menu',
|
`v-menu v-menu__${unref(menuMode)}`,
|
||||||
'h-[100%] overflow-hidden z-100 flex-col',
|
'h-[100%] overflow-hidden z-100 flex-col bg-[var(--left-menu-bg-color)]',
|
||||||
appStore.getCollapse
|
{
|
||||||
? 'w-[var(--left-menu-min-width)]'
|
'w-[var(--left-menu-min-width)]': unref(collapse) && unref(layout) !== 'cutMenu',
|
||||||
: 'w-[var(--left-menu-max-width)]',
|
'w-[var(--left-menu-max-width)]': !unref(collapse) && unref(layout) !== 'cutMenu'
|
||||||
'bg-[var(--left-menu-bg-color)]'
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<ElScrollbar>
|
<ElScrollbar>
|
||||||
<ElMenu
|
<ElMenu
|
||||||
defaultActive={unref(activeMenu)}
|
defaultActive={unref(activeMenu)}
|
||||||
mode={unref(menuMode)}
|
mode={unref(menuMode)}
|
||||||
collapse={unref(collapse)}
|
collapse={
|
||||||
|
unref(layout) === 'top' || unref(layout) === 'cutMenu' ? false : unref(collapse)
|
||||||
|
}
|
||||||
backgroundColor="var(--left-menu-bg-color)"
|
backgroundColor="var(--left-menu-bg-color)"
|
||||||
textColor="var(--left-menu-text-color)"
|
textColor="var(--left-menu-text-color)"
|
||||||
activeTextColor="var(--left-menu-text-active-color)"
|
activeTextColor="var(--left-menu-text-active-color)"
|
||||||
|
@ -180,6 +196,35 @@ export default defineComponent({
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 水平菜单
|
||||||
|
&__horizontal {
|
||||||
|
height: calc(~'var( - -top-tool-height)') !important;
|
||||||
|
|
||||||
|
:deep(.el-menu--horizontal) {
|
||||||
|
height: calc(~'var( - -top-tool-height)');
|
||||||
|
border-bottom: none;
|
||||||
|
// 重新设置底部高亮颜色
|
||||||
|
& > .el-sub-menu.is-active {
|
||||||
|
.el-sub-menu__title {
|
||||||
|
border-bottom-color: var(--el-color-primary) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu-item.is-active {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-menu__title {
|
||||||
|
max-height: calc(~'var( - -top-tool-height)') !important;
|
||||||
|
line-height: calc(~'var( - -top-tool-height)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
@ -196,36 +241,35 @@ export default defineComponent({
|
||||||
content: '';
|
content: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
.@{prefix-cls} {
|
.@{prefix-cls}--vertical,
|
||||||
&--vertical {
|
.@{prefix-cls}--horizontal {
|
||||||
// 设置选中时子标题的颜色
|
// 设置选中时子标题的颜色
|
||||||
.is-active {
|
.is-active {
|
||||||
& > .el-sub-menu__title {
|
& > .el-sub-menu__title {
|
||||||
color: var(--left-menu-text-active-color) !important;
|
color: var(--left-menu-text-active-color) !important;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 设置子菜单悬停的高亮和背景色
|
// 设置子菜单悬停的高亮和背景色
|
||||||
.el-sub-menu__title,
|
.el-sub-menu__title,
|
||||||
.el-menu-item {
|
.el-menu-item {
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--left-menu-text-active-color) !important;
|
color: var(--left-menu-text-active-color) !important;
|
||||||
background-color: var(--left-menu-bg-color) !important;
|
background-color: var(--left-menu-bg-color) !important;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 设置选中时的高亮背景
|
// 设置选中时的高亮背景
|
||||||
.el-menu-item.is-active {
|
.el-menu-item.is-active {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
background-color: var(--left-menu-bg-active-color) !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
background-color: var(--left-menu-bg-active-color) !important;
|
background-color: var(--left-menu-bg-active-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:after {
|
||||||
background-color: var(--left-menu-bg-active-color) !important;
|
.is-active--after;
|
||||||
}
|
|
||||||
|
|
||||||
&:after {
|
|
||||||
.is-active--after;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ElDrawer, ElDivider } from 'element-plus'
|
import { ElDrawer, ElDivider } from 'element-plus'
|
||||||
import { ref, unref } from 'vue'
|
import { ref, unref, computed, watch } from 'vue'
|
||||||
import { useI18n } from '@/hooks/web/useI18n'
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
import { ThemeSwitch } from '@/components/ThemeSwitch'
|
import { ThemeSwitch } from '@/components/ThemeSwitch'
|
||||||
import { colorIsDark, lighten, hexToRGB } from '@/utils/color'
|
import { colorIsDark, lighten, hexToRGB } from '@/utils/color'
|
||||||
|
@ -15,6 +15,8 @@ const appStore = useAppStore()
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const layout = computed(() => appStore.getLayout)
|
||||||
|
|
||||||
const drawer = ref(false)
|
const drawer = ref(false)
|
||||||
|
|
||||||
// 主题色相关
|
// 主题色相关
|
||||||
|
@ -34,14 +36,20 @@ const setHeaderTheme = (color: string) => {
|
||||||
const isDarkColor = colorIsDark(color)
|
const isDarkColor = colorIsDark(color)
|
||||||
const textColor = isDarkColor ? '#fff' : 'inherit'
|
const textColor = isDarkColor ? '#fff' : 'inherit'
|
||||||
const textHoverColor = isDarkColor ? lighten(color!, 6) : '#f6f6f6'
|
const textHoverColor = isDarkColor ? lighten(color!, 6) : '#f6f6f6'
|
||||||
|
const topToolBorderColor = isDarkColor ? color : '#eee'
|
||||||
setCssVar('--top-header-bg-color', color)
|
setCssVar('--top-header-bg-color', color)
|
||||||
setCssVar('--top-header-text-color', textColor)
|
setCssVar('--top-header-text-color', textColor)
|
||||||
setCssVar('--top-header-hover-color', textHoverColor)
|
setCssVar('--top-header-hover-color', textHoverColor)
|
||||||
|
setCssVar('--top-tool-border-color', topToolBorderColor)
|
||||||
appStore.setTheme({
|
appStore.setTheme({
|
||||||
topHeaderBgColor: color,
|
topHeaderBgColor: color,
|
||||||
topHeaderTextColor: textColor,
|
topHeaderTextColor: textColor,
|
||||||
topHeaderHoverColor: textHoverColor
|
topHeaderHoverColor: textHoverColor,
|
||||||
|
topToolBorderColor
|
||||||
})
|
})
|
||||||
|
if (unref(layout) === 'top') {
|
||||||
|
setMenuTheme(color)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 菜单主题相关
|
// 菜单主题相关
|
||||||
|
@ -72,11 +80,26 @@ const setMenuTheme = (color: string) => {
|
||||||
// logo字体颜色
|
// logo字体颜色
|
||||||
logoTitleTextColor: isDarkColor ? '#fff' : 'inherit',
|
logoTitleTextColor: isDarkColor ? '#fff' : 'inherit',
|
||||||
// logo边框颜色
|
// logo边框颜色
|
||||||
logoBorderColor: isDarkColor ? 'inherit' : '#eee'
|
logoBorderColor: isDarkColor ? color : '#eee'
|
||||||
}
|
}
|
||||||
appStore.setTheme(theme)
|
appStore.setTheme(theme)
|
||||||
appStore.setCssVarTheme()
|
appStore.setCssVarTheme()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 监听layout变化,重置一些主题色
|
||||||
|
watch(
|
||||||
|
() => layout.value,
|
||||||
|
(n, o) => {
|
||||||
|
if (o === 'top') {
|
||||||
|
menuTheme.value = '#fff'
|
||||||
|
setMenuTheme('#fff')
|
||||||
|
}
|
||||||
|
if ((o === 'classic' || o === 'topLeft') && n === 'top') {
|
||||||
|
menuTheme.value = headerTheme.value
|
||||||
|
setMenuTheme(unref(menuTheme))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -136,21 +159,23 @@ const setMenuTheme = (color: string) => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 菜单主题 -->
|
<!-- 菜单主题 -->
|
||||||
<ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
|
<template v-if="layout !== 'top'">
|
||||||
<ColorRadioPicker
|
<ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
|
||||||
v-model="menuTheme"
|
<ColorRadioPicker
|
||||||
:schema="[
|
v-model="menuTheme"
|
||||||
'#fff',
|
:schema="[
|
||||||
'#001529',
|
'#fff',
|
||||||
'#212121',
|
'#001529',
|
||||||
'#273352',
|
'#212121',
|
||||||
'#191b24',
|
'#273352',
|
||||||
'#383f45',
|
'#191b24',
|
||||||
'#001628',
|
'#383f45',
|
||||||
'#344058'
|
'#001628',
|
||||||
]"
|
'#344058'
|
||||||
@change="setMenuTheme"
|
]"
|
||||||
/>
|
@change="setMenuTheme"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 界面显示 -->
|
<!-- 界面显示 -->
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { ElSwitch } from 'element-plus'
|
import { ElSwitch } from 'element-plus'
|
||||||
import { useI18n } from '@/hooks/web/useI18n'
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
import { useAppStore } from '@/store/modules/app'
|
import { useAppStore } from '@/store/modules/app'
|
||||||
import { ref } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
|
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
|
||||||
|
@ -22,13 +22,6 @@ const breadcrumbIconChange = (show: boolean) => {
|
||||||
appStore.setBreadcrumbIcon(show)
|
appStore.setBreadcrumbIcon(show)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 折叠菜单
|
|
||||||
const collapse = ref(appStore.getCollapse)
|
|
||||||
|
|
||||||
const collapseChange = (show: boolean) => {
|
|
||||||
appStore.setCollapse(show)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 折叠图标
|
// 折叠图标
|
||||||
const hamburger = ref(appStore.getHamburger)
|
const hamburger = ref(appStore.getHamburger)
|
||||||
|
|
||||||
|
@ -84,6 +77,17 @@ const greyMode = ref(appStore.getGreyMode)
|
||||||
const greyModeChange = (show: boolean) => {
|
const greyModeChange = (show: boolean) => {
|
||||||
appStore.setGreyMode(show)
|
appStore.setGreyMode(show)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const layout = computed(() => appStore.getLayout)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => layout.value,
|
||||||
|
(n) => {
|
||||||
|
if (n === 'top') {
|
||||||
|
appStore.setCollapse(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -98,11 +102,6 @@ const greyModeChange = (show: boolean) => {
|
||||||
<ElSwitch v-model="breadcrumbIcon" @change="breadcrumbIconChange" />
|
<ElSwitch v-model="breadcrumbIcon" @change="breadcrumbIconChange" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<span class="text-14px">{{ t('setting.collapseMenu') }}</span>
|
|
||||||
<ElSwitch v-model="collapse" @change="collapseChange" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<span class="text-14px">{{ t('setting.hamburgerIcon') }}</span>
|
<span class="text-14px">{{ t('setting.hamburgerIcon') }}</span>
|
||||||
<ElSwitch v-model="hamburger" @change="hamburgerChange" />
|
<ElSwitch v-model="hamburger" @change="hamburgerChange" />
|
||||||
|
|
|
@ -5,14 +5,13 @@ import { computed } from 'vue'
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
|
||||||
const layout = computed(() => appStore.getLayout)
|
const layout = computed(() => appStore.getLayout)
|
||||||
console.log(layout.value)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="v-layout-radio-picker flex flex-wrap space-x-14px">
|
<div class="v-layout-radio-picker flex flex-wrap space-x-14px">
|
||||||
<div
|
<div
|
||||||
:class="[
|
:class="[
|
||||||
'v-layout-radio-picker__classic relative w-56px h-48px cursor-pointer bg-gray-100',
|
'v-layout-radio-picker__classic relative w-56px h-48px cursor-pointer bg-gray-300',
|
||||||
{
|
{
|
||||||
'is-acitve': layout === 'classic'
|
'is-acitve': layout === 'classic'
|
||||||
}
|
}
|
||||||
|
@ -21,13 +20,33 @@ console.log(layout.value)
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
:class="[
|
:class="[
|
||||||
'v-layout-radio-picker__top-left relative w-56px h-48px cursor-pointer bg-gray-100',
|
'v-layout-radio-picker__top-left relative w-56px h-48px cursor-pointer bg-gray-300',
|
||||||
{
|
{
|
||||||
'is-acitve': layout === 'topLeft'
|
'is-acitve': layout === 'topLeft'
|
||||||
}
|
}
|
||||||
]"
|
]"
|
||||||
@click="appStore.setLayout('topLeft')"
|
@click="appStore.setLayout('topLeft')"
|
||||||
></div>
|
></div>
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
'v-layout-radio-picker__top relative w-56px h-48px cursor-pointer bg-gray-300',
|
||||||
|
{
|
||||||
|
'is-acitve': layout === 'top'
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
@click="appStore.setLayout('top')"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
'v-layout-radio-picker__cut-menu relative w-56px h-48px cursor-pointer bg-gray-300',
|
||||||
|
{
|
||||||
|
'is-acitve': layout === 'cutMenu'
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
@click="appStore.setLayout('cutMenu')"
|
||||||
|
>
|
||||||
|
<div class="absolute h-full w-[33%] top-0 left-[10%] bg-gray-200"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -91,6 +110,51 @@ console.log(layout.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__top {
|
||||||
|
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: '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__cut-menu {
|
||||||
|
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: 10%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 4px 0 0 4px;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.is-acitve {
|
.is-acitve {
|
||||||
border-color: var(--el-color-primary);
|
border-color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
import TabMenu from './src/TabMenu.vue'
|
||||||
|
|
||||||
|
export { TabMenu }
|
|
@ -0,0 +1,211 @@
|
||||||
|
<script lang="tsx">
|
||||||
|
import { usePermissionStore } from '@/store/modules/permission'
|
||||||
|
import { useAppStore } from '@/store/modules/app'
|
||||||
|
import { computed, unref, defineComponent, watch, ref } from 'vue'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import { ElScrollbar } from 'element-plus'
|
||||||
|
import { Icon } from '@/components/Icon'
|
||||||
|
import { Menu } from '@/components/Menu'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { pathResolve } from '@/utils/routerHelper'
|
||||||
|
import { cloneDeep } from 'lodash-es'
|
||||||
|
import { filterMenusPath, initTabMap, tabPathMap } from './helper'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'TabMenu',
|
||||||
|
setup() {
|
||||||
|
const { push, currentRoute } = useRouter()
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const appStore = useAppStore()
|
||||||
|
|
||||||
|
const collapse = computed(() => appStore.getCollapse)
|
||||||
|
|
||||||
|
const permissionStore = usePermissionStore()
|
||||||
|
|
||||||
|
const routers = computed(() => permissionStore.getRouters)
|
||||||
|
|
||||||
|
const tabRouters = computed(() => unref(routers).filter((v) => !v?.meta?.hidden))
|
||||||
|
|
||||||
|
const setCollapse = () => {
|
||||||
|
appStore.setCollapse(!unref(collapse))
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => routers.value,
|
||||||
|
(routers: AppRouteRecordRaw[]) => {
|
||||||
|
initTabMap(routers)
|
||||||
|
filterMenusPath(routers, routers)
|
||||||
|
console.log(tabPathMap)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const showTitle = ref(true)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => collapse.value,
|
||||||
|
(collapse: boolean) => {
|
||||||
|
if (!collapse) {
|
||||||
|
setTimeout(() => {
|
||||||
|
showTitle.value = !collapse
|
||||||
|
}, 200)
|
||||||
|
} else {
|
||||||
|
showTitle.value = !collapse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 是否显示菜单
|
||||||
|
const showMenu = ref(false)
|
||||||
|
|
||||||
|
// tab高亮
|
||||||
|
const tabActive = ref('')
|
||||||
|
|
||||||
|
// tab点击事件
|
||||||
|
const tabClick = (item: AppRouteRecordRaw) => {
|
||||||
|
tabActive.value = item.children ? item.path : item.path.split('/')[0]
|
||||||
|
if (item.children) {
|
||||||
|
showMenu.value = !unref(showMenu)
|
||||||
|
if (unref(showMenu)) {
|
||||||
|
permissionStore.setMenuTabRouters(
|
||||||
|
cloneDeep(item.children).map((v) => {
|
||||||
|
v.path = pathResolve(unref(tabActive), v.path)
|
||||||
|
return v
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
push(item.path)
|
||||||
|
permissionStore.setMenuTabRouters([])
|
||||||
|
showMenu.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置高亮
|
||||||
|
const isActice = (currentPath: string) => {
|
||||||
|
const { path } = unref(currentRoute)
|
||||||
|
if (tabPathMap[currentPath].includes(path)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const mouseleave = () => {
|
||||||
|
if (!unref(showMenu)) return
|
||||||
|
showMenu.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<div
|
||||||
|
class={[
|
||||||
|
'v-tab-menu relative bg-[var(--left-menu-bg-color)] top-1px',
|
||||||
|
{
|
||||||
|
'w-[var(--tab-menu-max-width)]': !unref(collapse),
|
||||||
|
'w-[var(--tab-menu-min-width)]': unref(collapse)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
onMouseleave={mouseleave}
|
||||||
|
>
|
||||||
|
<ElScrollbar class="!h-[calc(100%-var(--tab-menu-collapse-height)-1px)]">
|
||||||
|
<div>
|
||||||
|
{() => {
|
||||||
|
return unref(tabRouters).map((v) => {
|
||||||
|
const item = (
|
||||||
|
v?.children?.length && v?.children?.length > 1
|
||||||
|
? v
|
||||||
|
: {
|
||||||
|
...(v?.children && v?.children[0]),
|
||||||
|
path: pathResolve(v.path, (v?.children && v?.children[0])?.path as string)
|
||||||
|
}
|
||||||
|
) as AppRouteRecordRaw
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={[
|
||||||
|
'v-tab-menu-item text-center text-12px relative py-12px cursor-pointer',
|
||||||
|
{
|
||||||
|
'is-active': isActice(v.path)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
onClick={() => {
|
||||||
|
tabClick(item)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Icon icon={item?.meta?.icon}></Icon>
|
||||||
|
</div>
|
||||||
|
{!unref(showTitle) ? undefined : (
|
||||||
|
<p class="break-words mt-5px px-2px">{t(item.meta?.title)}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</ElScrollbar>
|
||||||
|
<div
|
||||||
|
class="v-tab-menu-collapse text-center h-[var(--tab-menu-collapse-height)] leading-[var(--tab-menu-collapse-height)] cursor-pointer"
|
||||||
|
onClick={setCollapse}
|
||||||
|
>
|
||||||
|
<Icon icon={unref(collapse) ? 'ep:d-arrow-right' : 'ep:d-arrow-left'}></Icon>
|
||||||
|
</div>
|
||||||
|
<Menu
|
||||||
|
class={[
|
||||||
|
'!absolute top-0 border-left-1 border-solid border-[var(--left-menu-bg-light-color)]',
|
||||||
|
{
|
||||||
|
'!left-[var(--tab-menu-min-width)]': unref(collapse),
|
||||||
|
'!left-[var(--tab-menu-max-width)]': !unref(collapse),
|
||||||
|
'!w-[calc(var(--left-menu-max-width)+1px)]': unref(showMenu),
|
||||||
|
'!w-0': !unref(showMenu)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
style="transition: width var(--transition-time-02), left var(--transition-time-02);"
|
||||||
|
></Menu>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
@prefix-cls: ~'@{namespace}-tab-menu';
|
||||||
|
|
||||||
|
.@{prefix-cls} {
|
||||||
|
transition: all var(--transition-time-02);
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 1px;
|
||||||
|
height: 100%;
|
||||||
|
border-left: 1px solid var(--left-menu-border-color);
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
color: var(--left-menu-text-color);
|
||||||
|
transition: all var(--transition-time-02);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--left-menu-text-active-color);
|
||||||
|
// background-color: var(--left-menu-bg-active-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-collapse {
|
||||||
|
color: var(--left-menu-text-color);
|
||||||
|
background-color: var(--left-menu-bg-light-color);
|
||||||
|
border-top: 1px solid var(--left-menu-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-active {
|
||||||
|
color: var(--left-menu-text-active-color);
|
||||||
|
background-color: var(--left-menu-bg-active-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { getAllParentPath } from '@/components/Menu/src/helper'
|
||||||
|
import type { RouteMeta } from 'vue-router'
|
||||||
|
import { isUrl } from '@/utils/is'
|
||||||
|
import { cloneDeep } from 'lodash-es'
|
||||||
|
import { reactive } from 'vue'
|
||||||
|
|
||||||
|
export type TabMapTypes = {
|
||||||
|
[key: string]: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tabPathMap = reactive<TabMapTypes>({})
|
||||||
|
|
||||||
|
export const initTabMap = (routes: AppRouteRecordRaw[]) => {
|
||||||
|
for (const v of routes) {
|
||||||
|
const meta = (v.meta ?? {}) as RouteMeta
|
||||||
|
if (!meta?.hidden) {
|
||||||
|
tabPathMap[v.path] = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const filterMenusPath = (
|
||||||
|
routes: AppRouteRecordRaw[],
|
||||||
|
allRoutes: AppRouteRecordRaw[]
|
||||||
|
): AppRouteRecordRaw[] => {
|
||||||
|
const res: AppRouteRecordRaw[] = []
|
||||||
|
for (const v of routes) {
|
||||||
|
let data: Nullable<AppRouteRecordRaw> = null
|
||||||
|
const meta = (v.meta ?? {}) as RouteMeta
|
||||||
|
if (!meta.hidden) {
|
||||||
|
const allParentPaht = getAllParentPath<AppRouteRecordRaw>(allRoutes, v.path)
|
||||||
|
|
||||||
|
const fullPath = isUrl(v.path) ? v.path : allParentPaht.join('/')
|
||||||
|
|
||||||
|
data = cloneDeep(v)
|
||||||
|
data.path = fullPath
|
||||||
|
if (v.children && data) {
|
||||||
|
data.children = filterMenusPath(v.children, allRoutes)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
res.push(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allParentPaht.length && Reflect.has(tabPathMap, allParentPaht[0])) {
|
||||||
|
tabPathMap[allParentPaht[0]].push(fullPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
|
@ -132,8 +132,8 @@ watch(
|
||||||
<Icon icon="ep:d-arrow-left" color="#333" />
|
<Icon icon="ep:d-arrow-left" color="#333" />
|
||||||
</span>
|
</span>
|
||||||
<div class="overflow-hidden flex-1">
|
<div class="overflow-hidden flex-1">
|
||||||
<ElScrollbar>
|
<ElScrollbar class="h-full">
|
||||||
<div class="flex h-[var(--tags-view-height)]">
|
<div class="flex h-full">
|
||||||
<ContextMenu
|
<ContextMenu
|
||||||
:schema="[
|
:schema="[
|
||||||
{
|
{
|
||||||
|
@ -202,7 +202,10 @@ watch(
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<router-link :to="{ ...item }" custom v-slot="{ navigate }">
|
<router-link :to="{ ...item }" custom v-slot="{ navigate }">
|
||||||
<div @click="navigate" class="h-full flex justify-center items-center">
|
<div
|
||||||
|
@click="navigate"
|
||||||
|
class="h-full flex justify-center items-center whitespace-nowrap"
|
||||||
|
>
|
||||||
{{ t(item?.meta?.title as string) }}
|
{{ t(item?.meta?.title as string) }}
|
||||||
<Icon
|
<Icon
|
||||||
class="v-tags-view__item--close"
|
class="v-tags-view__item--close"
|
||||||
|
@ -291,6 +294,10 @@ watch(
|
||||||
@prefix-cls: ~'@{namespace}-tags-view';
|
@prefix-cls: ~'@{namespace}-tags-view';
|
||||||
|
|
||||||
.@{prefix-cls} {
|
.@{prefix-cls} {
|
||||||
|
:deep(.el-scrollbar__view) {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
&__tool {
|
&__tool {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
@ -302,11 +309,12 @@ watch(
|
||||||
|
|
||||||
&:after {
|
&:after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 1px;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: calc(~'100% - 1px');
|
||||||
border: 1px solid var(--top-tool-border-color);
|
border-right: 1px solid var(--tags-view-border-color);
|
||||||
|
border-left: 1px solid var(--tags-view-border-color);
|
||||||
content: '';
|
content: '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { useCache } from '@/hooks/web/useCache'
|
||||||
|
|
||||||
const { wsCache } = useCache()
|
const { wsCache } = useCache()
|
||||||
|
|
||||||
export type LayoutType = 'classic' | 'topLeft' | 'leftTop' | 'top' | 'test'
|
export type LayoutType = 'classic' | 'topLeft' | 'top' | 'cutMenu'
|
||||||
|
|
||||||
export interface AppState {
|
export interface AppState {
|
||||||
breadcrumb: boolean
|
breadcrumb: boolean
|
||||||
|
@ -15,11 +15,11 @@ export interface AppState {
|
||||||
tagsView: boolean
|
tagsView: boolean
|
||||||
logo: boolean
|
logo: boolean
|
||||||
fixedHeader: boolean
|
fixedHeader: boolean
|
||||||
|
fixedMenu: boolean
|
||||||
greyMode: boolean
|
greyMode: boolean
|
||||||
|
|
||||||
layout: LayoutType
|
layout: LayoutType
|
||||||
title: string
|
title: string
|
||||||
logoTitle: string
|
|
||||||
userInfo: string
|
userInfo: string
|
||||||
isDark: boolean
|
isDark: boolean
|
||||||
currentSize: ElememtPlusSzie
|
currentSize: ElememtPlusSzie
|
||||||
|
@ -39,11 +39,11 @@ export const appModules: AppState = {
|
||||||
tagsView: true, // 标签页
|
tagsView: true, // 标签页
|
||||||
logo: true, // logo
|
logo: true, // logo
|
||||||
fixedHeader: true, // 固定toolheader
|
fixedHeader: true, // 固定toolheader
|
||||||
|
fixedMenu: false, // 固定切割菜单
|
||||||
greyMode: false, // 是否开始灰色模式,用于特殊悼念日
|
greyMode: false, // 是否开始灰色模式,用于特殊悼念日
|
||||||
|
|
||||||
layout: wsCache.get('layout') || 'classic', // layout布局
|
layout: wsCache.get('layout') || 'classic', // layout布局
|
||||||
title: 'butterfly-admin', // 标题
|
title: 'ButterflyAdmin', // 标题
|
||||||
logoTitle: 'ButterflyAdmin', // logo标题
|
|
||||||
userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段,避免与其他项目冲突
|
userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段,避免与其他项目冲突
|
||||||
isDark: wsCache.get('isDark') || false, // 是否是暗黑模式
|
isDark: wsCache.get('isDark') || false, // 是否是暗黑模式
|
||||||
currentSize: wsCache.get('default') || 'default', // 组件尺寸
|
currentSize: wsCache.get('default') || 'default', // 组件尺寸
|
||||||
|
@ -75,6 +75,8 @@ export const appModules: AppState = {
|
||||||
// 头部字体颜色
|
// 头部字体颜色
|
||||||
topHeaderTextColor: 'inherit',
|
topHeaderTextColor: 'inherit',
|
||||||
// 头部悬停颜色
|
// 头部悬停颜色
|
||||||
topHeaderHoverColor: '#f6f6f6'
|
topHeaderHoverColor: '#f6f6f6',
|
||||||
|
// 头部边框颜色
|
||||||
|
topToolBorderColor: '#eee'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="tsx">
|
<script lang="tsx">
|
||||||
import { computed, defineComponent, unref } from 'vue'
|
import { computed, defineComponent, unref } from 'vue'
|
||||||
import { useAppStore } from '@/store/modules/app'
|
import { useAppStore } from '@/store/modules/app'
|
||||||
import { Backtop } from '@/components/Backtop'
|
// import { Backtop } from '@/components/Backtop'
|
||||||
import { Setting } from '@/components/Setting'
|
import { Setting } from '@/components/Setting'
|
||||||
import { useRenderLayout } from './components/useRenderLayout'
|
import { useRenderLayout } from './components/useRenderLayout'
|
||||||
|
|
||||||
|
@ -27,6 +27,12 @@ const renderLayout = () => {
|
||||||
case 'topLeft':
|
case 'topLeft':
|
||||||
const { renderTopLeft } = useRenderLayout()
|
const { renderTopLeft } = useRenderLayout()
|
||||||
return renderTopLeft()
|
return renderTopLeft()
|
||||||
|
case 'top':
|
||||||
|
const { renderTop } = useRenderLayout()
|
||||||
|
return renderTop()
|
||||||
|
case 'cutMenu':
|
||||||
|
const { renderCutMenu } = useRenderLayout()
|
||||||
|
return renderCutMenu()
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -46,7 +52,7 @@ export default defineComponent({
|
||||||
|
|
||||||
{renderLayout()}
|
{renderLayout()}
|
||||||
|
|
||||||
<Backtop></Backtop>
|
{/*<Backtop></Backtop>*/}
|
||||||
|
|
||||||
<Setting></Setting>
|
<Setting></Setting>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -22,6 +22,9 @@ const screenfull = computed(() => appStore.getScreenfull)
|
||||||
// 尺寸图标
|
// 尺寸图标
|
||||||
const size = computed(() => appStore.getSize)
|
const size = computed(() => appStore.getSize)
|
||||||
|
|
||||||
|
// 布局
|
||||||
|
const layout = computed(() => appStore.getLayout)
|
||||||
|
|
||||||
// 多语言图标
|
// 多语言图标
|
||||||
const locale = computed(() => appStore.getLocale)
|
const locale = computed(() => appStore.getLocale)
|
||||||
|
|
||||||
|
@ -35,12 +38,14 @@ export default defineComponent({
|
||||||
'h-[var(--top-tool-height)] relative px-[var(--top-tool-p-x)] flex items-center justify-between'
|
'h-[var(--top-tool-height)] relative px-[var(--top-tool-p-x)] flex items-center justify-between'
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<div class="h-full flex items-center">
|
{layout.value !== 'top' ? (
|
||||||
{hamburger.value ? (
|
<div class="h-full flex items-center">
|
||||||
<Collapse class="hover-tigger" color="var(--top-header-text-color)"></Collapse>
|
{hamburger.value && layout.value !== 'cutMenu' ? (
|
||||||
) : undefined}
|
<Collapse class="hover-tigger" color="var(--top-header-text-color)"></Collapse>
|
||||||
{breadcrumb.value ? <Breadcrumb class="<md:hidden"></Breadcrumb> : undefined}
|
) : undefined}
|
||||||
</div>
|
{breadcrumb.value ? <Breadcrumb class="<md:hidden"></Breadcrumb> : undefined}
|
||||||
|
</div>
|
||||||
|
) : undefined}
|
||||||
<div class="h-full flex items-center">
|
<div class="h-full flex items-center">
|
||||||
{screenfull.value ? (
|
{screenfull.value ? (
|
||||||
<Screenfull class="hover-tigger" color="var(--top-header-text-color)"></Screenfull>
|
<Screenfull class="hover-tigger" color="var(--top-header-text-color)"></Screenfull>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useAppStore } from '@/store/modules/app'
|
import { useAppStore } from '@/store/modules/app'
|
||||||
import { Menu } from '@/components/Menu'
|
import { Menu } from '@/components/Menu'
|
||||||
|
import { TabMenu } from '@/components/TabMenu'
|
||||||
import { TagsView } from '@/components/TagsView'
|
import { TagsView } from '@/components/TagsView'
|
||||||
import { Logo } from '@/components/Logo'
|
import { Logo } from '@/components/Logo'
|
||||||
import AppView from './AppView.vue'
|
import AppView from './AppView.vue'
|
||||||
|
@ -32,7 +33,7 @@ export const useRenderLayout = () => {
|
||||||
{logo.value ? (
|
{logo.value ? (
|
||||||
<Logo
|
<Logo
|
||||||
class={[
|
class={[
|
||||||
'bg-[var(--left-menu-bg-color)]',
|
'bg-[var(--left-menu-bg-color)] border-bottom-1 border-solid border-[var(--logo-border-color)]',
|
||||||
{
|
{
|
||||||
'!pl-0': mobile.value && collapse.value,
|
'!pl-0': mobile.value && collapse.value,
|
||||||
'w-[var(--left-menu-min-width)]': appStore.getCollapse,
|
'w-[var(--left-menu-min-width)]': appStore.getCollapse,
|
||||||
|
@ -80,9 +81,11 @@ export const useRenderLayout = () => {
|
||||||
]}
|
]}
|
||||||
style="transition: all var(--transition-time-02);"
|
style="transition: all var(--transition-time-02);"
|
||||||
>
|
>
|
||||||
<ToolHeader class="border-bottom bg-[var(--top-header-bg-color)]"></ToolHeader>
|
<ToolHeader class="border-bottom-1 border-solid border-[var(--top-tool-border-color)] bg-[var(--top-header-bg-color)]"></ToolHeader>
|
||||||
|
|
||||||
{tagsView.value ? <TagsView class="border-bottom"></TagsView> : undefined}
|
{tagsView.value ? (
|
||||||
|
<TagsView class="border-bottom-1 border-solid border-[var(--tags-view-border-color)]"></TagsView>
|
||||||
|
) : undefined}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AppView></AppView>
|
<AppView></AppView>
|
||||||
|
@ -95,12 +98,12 @@ export const useRenderLayout = () => {
|
||||||
const renderTopLeft = () => {
|
const renderTopLeft = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div class="flex items-center bg-[var(--top-header-bg-color)]">
|
<div class="flex items-center bg-[var(--top-header-bg-color)] border-bottom-1 border-solid border-[var(--top-tool-border-color)]">
|
||||||
<Logo class="hover-tigger !pr-15px"></Logo>
|
{logo.value ? <Logo class="hover-tigger !pr-15px"></Logo> : undefined}
|
||||||
|
|
||||||
<ToolHeader class="flex-1"></ToolHeader>
|
<ToolHeader class="flex-1"></ToolHeader>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute top-[var(--logo-height)] left-0 w-full h-[calc(100%-var(--logo-height))] flex">
|
<div class="absolute top-[var(--logo-height)+1px] left-0 w-full h-[calc(100%-1px-var(--logo-height))] flex">
|
||||||
<Menu class="!h-full"></Menu>
|
<Menu class="!h-full"></Menu>
|
||||||
<div
|
<div
|
||||||
class={[
|
class={[
|
||||||
|
@ -127,7 +130,7 @@ export const useRenderLayout = () => {
|
||||||
{tagsView.value ? (
|
{tagsView.value ? (
|
||||||
<TagsView
|
<TagsView
|
||||||
class={[
|
class={[
|
||||||
'border-bottom border-top',
|
'border-bottom-1 border-solid border-[var(--tags-view-border-color)]',
|
||||||
{
|
{
|
||||||
'!fixed top-0 left-0 z-10': fixedHeader.value,
|
'!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)]':
|
'w-[calc(100%-var(--left-menu-min-width))] left-[var(--left-menu-min-width)] mt-[var(--logo-height)]':
|
||||||
|
@ -148,8 +151,103 @@ export const useRenderLayout = () => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const renderTop = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div class="flex items-center justify-between bg-[var(--top-header-bg-color)] border-bottom-1 border-solid border-[var(--top-tool-border-color)]">
|
||||||
|
{logo.value ? <Logo class="hover-tigger"></Logo> : undefined}
|
||||||
|
<Menu class="flex-1 px-10px h-[var(--top-tool-height)]"></Menu>
|
||||||
|
<ToolHeader></ToolHeader>
|
||||||
|
</div>
|
||||||
|
<div class="v-app-right h-full w-full">
|
||||||
|
<ElScrollbar
|
||||||
|
class={[
|
||||||
|
'v-content',
|
||||||
|
{
|
||||||
|
'mt-[var(--tags-view-height)]': fixedHeader.value
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{tagsView.value ? (
|
||||||
|
<TagsView
|
||||||
|
class={[
|
||||||
|
'border-bottom-1 border-solid border-[var(--tags-view-border-color)]',
|
||||||
|
{
|
||||||
|
'!fixed w-full top-[var(--top-tool-height)] left-0': fixedHeader.value
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
style="transition: all var(--transition-time-02);"
|
||||||
|
></TagsView>
|
||||||
|
) : undefined}
|
||||||
|
|
||||||
|
<AppView></AppView>
|
||||||
|
</ElScrollbar>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderCutMenu = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div class="flex items-center bg-[var(--top-header-bg-color)] border-bottom-1 border-solid border-[var(--top-tool-border-color)]">
|
||||||
|
{logo.value ? <Logo class="hover-tigger !pr-15px"></Logo> : undefined}
|
||||||
|
|
||||||
|
<ToolHeader class="flex-1"></ToolHeader>
|
||||||
|
</div>
|
||||||
|
<div class="absolute top-[var(--logo-height)] left-0 w-full h-[calc(100%-var(--logo-height))] flex">
|
||||||
|
<TabMenu></TabMenu>
|
||||||
|
{/* <Menu class="!h-full"></Menu> */}
|
||||||
|
<div
|
||||||
|
class={[
|
||||||
|
'v-app-right',
|
||||||
|
'h-[100%]',
|
||||||
|
{
|
||||||
|
'w-[calc(100%-var(--tab-menu-min-width))] left-[var(--tab-menu-min-width)]':
|
||||||
|
collapse.value,
|
||||||
|
'w-[calc(100%-var(--tab-menu-max-width))] left-[var(--tab-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-1 border-solid border-[var(--tags-view-border-color)]',
|
||||||
|
{
|
||||||
|
'!fixed top-0 left-0 z-10': fixedHeader.value,
|
||||||
|
'w-[calc(100%-var(--tab-menu-min-width))] left-[var(--tab-menu-min-width)] mt-[var(--logo-height)]':
|
||||||
|
collapse.value && fixedHeader.value,
|
||||||
|
'w-[calc(100%-var(--tab-menu-max-width))] left-[var(--tab-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 {
|
return {
|
||||||
renderClassic,
|
renderClassic,
|
||||||
renderTopLeft
|
renderTopLeft,
|
||||||
|
renderTop,
|
||||||
|
renderCutMenu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,8 @@ export default {
|
||||||
logo: 'Logo',
|
logo: 'Logo',
|
||||||
greyMode: 'Grey mode',
|
greyMode: 'Grey mode',
|
||||||
fixedHeader: 'Fixed header',
|
fixedHeader: 'Fixed header',
|
||||||
headerTheme: 'Header theme'
|
headerTheme: 'Header theme',
|
||||||
|
cutMenu: 'Cut Menu'
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: 'Default',
|
default: 'Default',
|
||||||
|
|
|
@ -37,7 +37,8 @@ export default {
|
||||||
logo: '标志',
|
logo: '标志',
|
||||||
greyMode: '灰色模式',
|
greyMode: '灰色模式',
|
||||||
fixedHeader: '固定头部',
|
fixedHeader: '固定头部',
|
||||||
headerTheme: '头部主题'
|
headerTheme: '头部主题',
|
||||||
|
cutMenu: '切割菜单'
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: '默认',
|
default: '默认',
|
||||||
|
|
|
@ -42,6 +42,9 @@ export const useAppStore = defineStore({
|
||||||
getFixedHeader(): boolean {
|
getFixedHeader(): boolean {
|
||||||
return this.fixedHeader
|
return this.fixedHeader
|
||||||
},
|
},
|
||||||
|
getFixedMenu(): boolean {
|
||||||
|
return this.fixedMenu
|
||||||
|
},
|
||||||
getGreyMode(): boolean {
|
getGreyMode(): boolean {
|
||||||
return this.greyMode
|
return this.greyMode
|
||||||
},
|
},
|
||||||
|
@ -52,9 +55,6 @@ export const useAppStore = defineStore({
|
||||||
getTitle(): string {
|
getTitle(): string {
|
||||||
return this.title
|
return this.title
|
||||||
},
|
},
|
||||||
getLogoTitle(): string {
|
|
||||||
return this.logoTitle
|
|
||||||
},
|
|
||||||
getUserInfo(): string {
|
getUserInfo(): string {
|
||||||
return this.userInfo
|
return this.userInfo
|
||||||
},
|
},
|
||||||
|
@ -105,6 +105,9 @@ export const useAppStore = defineStore({
|
||||||
setFixedHeader(fixedHeader: boolean) {
|
setFixedHeader(fixedHeader: boolean) {
|
||||||
this.fixedHeader = fixedHeader
|
this.fixedHeader = fixedHeader
|
||||||
},
|
},
|
||||||
|
setFixedMenu(fixedMenu: boolean) {
|
||||||
|
this.fixedMenu = fixedMenu
|
||||||
|
},
|
||||||
setGreyMode(greyMode: boolean) {
|
setGreyMode(greyMode: boolean) {
|
||||||
this.greyMode = greyMode
|
this.greyMode = greyMode
|
||||||
},
|
},
|
||||||
|
@ -120,9 +123,6 @@ export const useAppStore = defineStore({
|
||||||
setTitle(title: string) {
|
setTitle(title: string) {
|
||||||
this.title = title
|
this.title = title
|
||||||
},
|
},
|
||||||
setLogoTitle(logoTitle: string) {
|
|
||||||
this.logoTitle = logoTitle
|
|
||||||
},
|
|
||||||
setIsDark(isDark: boolean) {
|
setIsDark(isDark: boolean) {
|
||||||
this.isDark = isDark
|
this.isDark = isDark
|
||||||
if (this.isDark) {
|
if (this.isDark) {
|
||||||
|
|
|
@ -15,7 +15,6 @@ export interface PermissionState {
|
||||||
routers: AppRouteRecordRaw[]
|
routers: AppRouteRecordRaw[]
|
||||||
addRouters: AppRouteRecordRaw[]
|
addRouters: AppRouteRecordRaw[]
|
||||||
isAddRouters: boolean
|
isAddRouters: boolean
|
||||||
activeTab: string
|
|
||||||
menuTabRouters: AppRouteRecordRaw[]
|
menuTabRouters: AppRouteRecordRaw[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,8 +24,7 @@ export const usePermissionStore = defineStore({
|
||||||
routers: [],
|
routers: [],
|
||||||
addRouters: [],
|
addRouters: [],
|
||||||
isAddRouters: false,
|
isAddRouters: false,
|
||||||
menuTabRouters: [],
|
menuTabRouters: []
|
||||||
activeTab: ''
|
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
getRouters(): AppRouteRecordRaw[] {
|
getRouters(): AppRouteRecordRaw[] {
|
||||||
|
@ -38,9 +36,6 @@ export const usePermissionStore = defineStore({
|
||||||
getIsAddRouters(): boolean {
|
getIsAddRouters(): boolean {
|
||||||
return this.isAddRouters
|
return this.isAddRouters
|
||||||
},
|
},
|
||||||
getActiveTab(): string {
|
|
||||||
return this.activeTab
|
|
||||||
},
|
|
||||||
getMenuTabRouters(): AppRouteRecordRaw[] {
|
getMenuTabRouters(): AppRouteRecordRaw[] {
|
||||||
return this.menuTabRouters
|
return this.menuTabRouters
|
||||||
}
|
}
|
||||||
|
@ -84,9 +79,6 @@ export const usePermissionStore = defineStore({
|
||||||
},
|
},
|
||||||
setMenuTabRouters(routers: AppRouteRecordRaw[]): void {
|
setMenuTabRouters(routers: AppRouteRecordRaw[]): void {
|
||||||
this.menuTabRouters = routers
|
this.menuTabRouters = routers
|
||||||
},
|
|
||||||
setAcitveTab(activeTab: string): void {
|
|
||||||
this.activeTab = activeTab
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -7,6 +7,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.border-bottom {
|
.border-bottom {
|
||||||
|
border-bottom: 1px solid var(--top-tool-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-bottom--after {
|
||||||
@apply relative;
|
@apply relative;
|
||||||
&:after {
|
&:after {
|
||||||
content: '';
|
content: '';
|
||||||
|
@ -16,6 +20,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.border-top {
|
.border-top {
|
||||||
|
border-top: 1px solid var(--top-tool-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-top--before {
|
||||||
@apply relative;
|
@apply relative;
|
||||||
&:before {
|
&:before {
|
||||||
content: '';
|
content: '';
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
@import './var.css';
|
@import './var.css';
|
||||||
@import './common.less';
|
// @import './common.less';
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
--dark-bg-color: #293146;
|
--dark-bg-color: #293146;
|
||||||
|
|
||||||
/* left menu start */
|
/* left menu start */
|
||||||
--left-menu-border-color: 'inherit';
|
--left-menu-border-color: '#eee';
|
||||||
|
|
||||||
--left-menu-max-width: 200px;
|
--left-menu-max-width: 200px;
|
||||||
|
|
||||||
|
@ -43,8 +43,20 @@
|
||||||
--top-tool-border-color: #eee;
|
--top-tool-border-color: #eee;
|
||||||
|
|
||||||
--tags-view-height: 35px;
|
--tags-view-height: 35px;
|
||||||
|
|
||||||
|
--tags-view-border-color: #eee;
|
||||||
/* header start */
|
/* header start */
|
||||||
|
|
||||||
|
/* tab menu start */
|
||||||
|
--tab-menu-max-width: 80px;
|
||||||
|
|
||||||
|
--tab-menu-min-width: 30px;
|
||||||
|
|
||||||
|
--tab-menu-collapse-height: 36px;
|
||||||
|
|
||||||
|
--tab-menu-border-color: #eee;
|
||||||
|
/* tab menu end */
|
||||||
|
|
||||||
--app-content-padding: 20px;
|
--app-content-padding: 20px;
|
||||||
|
|
||||||
--transition-time-02: 0.2s;
|
--transition-time-02: 0.2s;
|
||||||
|
|
|
@ -147,7 +147,7 @@ export const forEach = <T = any>(
|
||||||
const list: any[] = [...tree]
|
const list: any[] = [...tree]
|
||||||
const { children } = config
|
const { children } = config
|
||||||
for (let i = 0; i < list.length; i++) {
|
for (let i = 0; i < list.length; i++) {
|
||||||
//func 返回true就终止遍历,避免大量节点场景下无意义循环,引起浏览器卡顿
|
// func 返回true就终止遍历,避免大量节点场景下无意义循环,引起浏览器卡顿
|
||||||
if (func(list[i])) {
|
if (func(list[i])) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { defineConfig } from 'windicss/helpers'
|
import { defineConfig } from 'windicss/helpers'
|
||||||
// import plugin from 'windicss/plugin'
|
import plugin from 'windicss/plugin'
|
||||||
|
|
||||||
// function range(size, startAt = 1) {
|
function range(size, startAt = 1) {
|
||||||
// return Array.from(Array(size).keys()).map((i) => i + startAt)
|
return Array.from(Array(size).keys()).map((i) => i + startAt)
|
||||||
// }
|
}
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
extract: {
|
extract: {
|
||||||
|
@ -34,34 +34,38 @@ export default defineConfig({
|
||||||
// // ...range(50).map((i) => `mb-${i}px`),
|
// // ...range(50).map((i) => `mb-${i}px`),
|
||||||
// // ...range(50).map((i) => `ml-${i}px`)
|
// // ...range(50).map((i) => `ml-${i}px`)
|
||||||
// }
|
// }
|
||||||
}
|
},
|
||||||
// plugins: [
|
plugins: [
|
||||||
// plugin(({ addComponents }) => {
|
plugin(({ addComponents }) => {
|
||||||
// addComponents({
|
const obj = {}
|
||||||
// '.hover-tigger': {
|
range(50).map((i) => {
|
||||||
// display: 'flex',
|
obj[`.border-top-${i}`] = {
|
||||||
// height: '100%',
|
borderTopWidth: `${i}px`
|
||||||
// padding: '1px 10px 0',
|
}
|
||||||
// cursor: 'pointer',
|
obj[`.border-left-${i}`] = {
|
||||||
// alignItems: 'center',
|
borderLeftWidth: `${i}px`
|
||||||
// transition: 'background var(--transition-time-02)',
|
}
|
||||||
// '&:hover': {
|
obj[`.border-right-${i}`] = {
|
||||||
// backgroundColor: '#f6f6f6'
|
borderRightWidth: `${i}px`
|
||||||
// }
|
}
|
||||||
// },
|
obj[`.border-bottom-${i}`] = {
|
||||||
// '.border-bottom': {
|
borderBottomWidth: `${i}px`
|
||||||
// position: 'relative',
|
}
|
||||||
// '&:after': {
|
})
|
||||||
// position: 'absolute',
|
addComponents({
|
||||||
// bottom: '0',
|
'.hover-tigger': {
|
||||||
// left: '0',
|
display: 'flex',
|
||||||
// width: '100%',
|
height: '100%',
|
||||||
// height: '1px',
|
padding: '1px 10px 0',
|
||||||
// borderTop: '1px solid var(--top-tool-border-color)',
|
cursor: 'pointer',
|
||||||
// content: ''
|
alignItems: 'center',
|
||||||
// }
|
transition: 'background var(--transition-time-02)',
|
||||||
// }
|
'&:hover': {
|
||||||
// })
|
backgroundColor: 'var(--top-header-hover-color)'
|
||||||
// })
|
}
|
||||||
// ]
|
},
|
||||||
|
...obj
|
||||||
|
})
|
||||||
|
})
|
||||||
|
]
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue