feat(Layout): Add cutMenu layout

This commit is contained in:
陈凯龙 2022-01-21 16:17:40 +08:00
parent 1522e925ba
commit ff4dd3afbf
23 changed files with 711 additions and 171 deletions

View File

@ -34,7 +34,7 @@ initDark()
html,
body {
padding: 0;
padding: 0 !important;
margin: 0;
overflow: hidden;
.size;

View File

@ -6,7 +6,7 @@ const appStore = useAppStore()
const show = ref(true)
const title = computed(() => appStore.getLogoTitle)
const title = computed(() => appStore.getTitle)
const layout = computed(() => appStore.getLayout)
@ -19,9 +19,10 @@ onMounted(() => {
watch(
() => collapse.value,
(collapse: boolean) => {
if (layout.value !== 'classic') {
if (unref(layout) === 'topLeft' || unref(layout) === 'cutMenu') {
show.value = true
} else {
return
}
if (!collapse) {
setTimeout(() => {
show.value = !collapse
@ -30,6 +31,20 @@ watch(
show.value = !collapse
}
}
)
watch(
() => layout.value,
(layout) => {
if (layout === 'top' || layout === 'cutMenu') {
show.value = true
} else {
if (unref(collapse)) {
show.value = false
} else {
show.value = true
}
}
}
)
</script>
@ -55,7 +70,8 @@ watch(
'ml-10px text-16px font-700',
{
'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>
@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>

View File

@ -1,5 +1,5 @@
<script lang="tsx">
import { computed, defineComponent, unref } from 'vue'
import { computed, defineComponent, unref, PropType } from 'vue'
import { ElMenu, ElScrollbar } from 'element-plus'
import { useAppStore } from '@/store/modules/app'
import { usePermissionStore } from '@/store/modules/permission'
@ -10,25 +10,35 @@ import { isUrl } from '@/utils/is'
export default defineComponent({
name: 'Menu',
setup() {
props: {
menuSelect: {
type: Function as PropType<(index: string) => void>,
default: undefined
}
},
setup(props) {
const appStore = useAppStore()
const layout = computed(() => appStore.getLayout)
const { push, currentRoute } = useRouter()
const permissionStore = usePermissionStore()
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'
} else {
return 'horizontal'
}
})
const routers = computed(() => permissionStore.getRouters)
const routers = computed(() =>
unref(layout) === 'cutMenu' ? permissionStore.getMenuTabRouters : permissionStore.getRouters
)
const collapse = computed(() => appStore.getCollapse)
@ -42,6 +52,10 @@ export default defineComponent({
})
const menuSelect = (index: string) => {
if (props.menuSelect) {
props.menuSelect(index)
}
//
if (isUrl(index)) {
window.open(index)
} else {
@ -52,19 +66,21 @@ export default defineComponent({
return () => (
<div
class={[
'v-menu',
'h-[100%] overflow-hidden z-100 flex-col',
appStore.getCollapse
? 'w-[var(--left-menu-min-width)]'
: 'w-[var(--left-menu-max-width)]',
'bg-[var(--left-menu-bg-color)]'
`v-menu v-menu__${unref(menuMode)}`,
'h-[100%] overflow-hidden z-100 flex-col bg-[var(--left-menu-bg-color)]',
{
'w-[var(--left-menu-min-width)]': unref(collapse) && unref(layout) !== 'cutMenu',
'w-[var(--left-menu-max-width)]': !unref(collapse) && unref(layout) !== 'cutMenu'
}
]}
>
<ElScrollbar>
<ElMenu
defaultActive={unref(activeMenu)}
mode={unref(menuMode)}
collapse={unref(collapse)}
collapse={
unref(layout) === 'top' || unref(layout) === 'cutMenu' ? false : unref(collapse)
}
backgroundColor="var(--left-menu-bg-color)"
textColor="var(--left-menu-text-color)"
activeTextColor="var(--left-menu-text-active-color)"
@ -180,6 +196,35 @@ export default defineComponent({
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>
@ -196,8 +241,8 @@ export default defineComponent({
content: '';
}
.@{prefix-cls} {
&--vertical {
.@{prefix-cls}--vertical,
.@{prefix-cls}--horizontal {
//
.is-active {
& > .el-sub-menu__title {
@ -228,5 +273,4 @@ export default defineComponent({
}
}
}
}
</style>

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
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 { ThemeSwitch } from '@/components/ThemeSwitch'
import { colorIsDark, lighten, hexToRGB } from '@/utils/color'
@ -15,6 +15,8 @@ const appStore = useAppStore()
const { t } = useI18n()
const layout = computed(() => appStore.getLayout)
const drawer = ref(false)
//
@ -34,14 +36,20 @@ const setHeaderTheme = (color: string) => {
const isDarkColor = colorIsDark(color)
const textColor = isDarkColor ? '#fff' : 'inherit'
const textHoverColor = isDarkColor ? lighten(color!, 6) : '#f6f6f6'
const topToolBorderColor = isDarkColor ? color : '#eee'
setCssVar('--top-header-bg-color', color)
setCssVar('--top-header-text-color', textColor)
setCssVar('--top-header-hover-color', textHoverColor)
setCssVar('--top-tool-border-color', topToolBorderColor)
appStore.setTheme({
topHeaderBgColor: color,
topHeaderTextColor: textColor,
topHeaderHoverColor: textHoverColor
topHeaderHoverColor: textHoverColor,
topToolBorderColor
})
if (unref(layout) === 'top') {
setMenuTheme(color)
}
}
//
@ -72,11 +80,26 @@ const setMenuTheme = (color: string) => {
// logo
logoTitleTextColor: isDarkColor ? '#fff' : 'inherit',
// logo
logoBorderColor: isDarkColor ? 'inherit' : '#eee'
logoBorderColor: isDarkColor ? color : '#eee'
}
appStore.setTheme(theme)
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>
<template>
@ -136,6 +159,7 @@ const setMenuTheme = (color: string) => {
/>
<!-- 菜单主题 -->
<template v-if="layout !== 'top'">
<ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
<ColorRadioPicker
v-model="menuTheme"
@ -151,6 +175,7 @@ const setMenuTheme = (color: string) => {
]"
@change="setMenuTheme"
/>
</template>
</div>
<!-- 界面显示 -->

View File

@ -2,7 +2,7 @@
import { ElSwitch } from 'element-plus'
import { useI18n } from '@/hooks/web/useI18n'
import { useAppStore } from '@/store/modules/app'
import { ref } from 'vue'
import { computed, ref, watch } from 'vue'
const appStore = useAppStore()
@ -22,13 +22,6 @@ const breadcrumbIconChange = (show: boolean) => {
appStore.setBreadcrumbIcon(show)
}
//
const collapse = ref(appStore.getCollapse)
const collapseChange = (show: boolean) => {
appStore.setCollapse(show)
}
//
const hamburger = ref(appStore.getHamburger)
@ -84,6 +77,17 @@ const greyMode = ref(appStore.getGreyMode)
const greyModeChange = (show: boolean) => {
appStore.setGreyMode(show)
}
const layout = computed(() => appStore.getLayout)
watch(
() => layout.value,
(n) => {
if (n === 'top') {
appStore.setCollapse(false)
}
}
)
</script>
<template>
@ -98,11 +102,6 @@ const greyModeChange = (show: boolean) => {
<ElSwitch v-model="breadcrumbIcon" @change="breadcrumbIconChange" />
</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">
<span class="text-14px">{{ t('setting.hamburgerIcon') }}</span>
<ElSwitch v-model="hamburger" @change="hamburgerChange" />

View File

@ -5,14 +5,13 @@ 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',
'v-layout-radio-picker__classic relative w-56px h-48px cursor-pointer bg-gray-300',
{
'is-acitve': layout === 'classic'
}
@ -21,13 +20,33 @@ console.log(layout.value)
></div>
<div
: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'
}
]"
@click="appStore.setLayout('topLeft')"
></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>
</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 {
border-color: var(--el-color-primary);
}

View File

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

View File

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

View File

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

View File

@ -132,8 +132,8 @@ watch(
<Icon icon="ep:d-arrow-left" color="#333" />
</span>
<div class="overflow-hidden flex-1">
<ElScrollbar>
<div class="flex h-[var(--tags-view-height)]">
<ElScrollbar class="h-full">
<div class="flex h-full">
<ContextMenu
:schema="[
{
@ -202,7 +202,10 @@ watch(
]"
>
<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) }}
<Icon
class="v-tags-view__item--close"
@ -291,6 +294,10 @@ watch(
@prefix-cls: ~'@{namespace}-tags-view';
.@{prefix-cls} {
:deep(.el-scrollbar__view) {
height: 100%;
}
&__tool {
position: relative;
@ -302,11 +309,12 @@ watch(
&:after {
position: absolute;
top: 0;
top: 1px;
left: 0;
width: 100%;
height: 100%;
border: 1px solid var(--top-tool-border-color);
height: calc(~'100% - 1px');
border-right: 1px solid var(--tags-view-border-color);
border-left: 1px solid var(--tags-view-border-color);
content: '';
}
}

View File

@ -2,7 +2,7 @@ import { useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache()
export type LayoutType = 'classic' | 'topLeft' | 'leftTop' | 'top' | 'test'
export type LayoutType = 'classic' | 'topLeft' | 'top' | 'cutMenu'
export interface AppState {
breadcrumb: boolean
@ -15,11 +15,11 @@ export interface AppState {
tagsView: boolean
logo: boolean
fixedHeader: boolean
fixedMenu: boolean
greyMode: boolean
layout: LayoutType
title: string
logoTitle: string
userInfo: string
isDark: boolean
currentSize: ElememtPlusSzie
@ -39,11 +39,11 @@ export const appModules: AppState = {
tagsView: true, // 标签页
logo: true, // logo
fixedHeader: true, // 固定toolheader
fixedMenu: false, // 固定切割菜单
greyMode: false, // 是否开始灰色模式,用于特殊悼念日
layout: wsCache.get('layout') || 'classic', // layout布局
title: 'butterfly-admin', // 标题
logoTitle: 'ButterflyAdmin', // logo标题
title: 'ButterflyAdmin', // 标题
userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段,避免与其他项目冲突
isDark: wsCache.get('isDark') || false, // 是否是暗黑模式
currentSize: wsCache.get('default') || 'default', // 组件尺寸
@ -75,6 +75,8 @@ export const appModules: AppState = {
// 头部字体颜色
topHeaderTextColor: 'inherit',
// 头部悬停颜色
topHeaderHoverColor: '#f6f6f6'
topHeaderHoverColor: '#f6f6f6',
// 头部边框颜色
topToolBorderColor: '#eee'
}
}

View File

@ -1,7 +1,7 @@
<script lang="tsx">
import { computed, defineComponent, unref } from 'vue'
import { useAppStore } from '@/store/modules/app'
import { Backtop } from '@/components/Backtop'
// import { Backtop } from '@/components/Backtop'
import { Setting } from '@/components/Setting'
import { useRenderLayout } from './components/useRenderLayout'
@ -27,6 +27,12 @@ const renderLayout = () => {
case 'topLeft':
const { renderTopLeft } = useRenderLayout()
return renderTopLeft()
case 'top':
const { renderTop } = useRenderLayout()
return renderTop()
case 'cutMenu':
const { renderCutMenu } = useRenderLayout()
return renderCutMenu()
default:
break
}
@ -46,7 +52,7 @@ export default defineComponent({
{renderLayout()}
<Backtop></Backtop>
{/*<Backtop></Backtop>*/}
<Setting></Setting>
</section>

View File

@ -22,6 +22,9 @@ const screenfull = computed(() => appStore.getScreenfull)
//
const size = computed(() => appStore.getSize)
//
const layout = computed(() => appStore.getLayout)
//
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'
]}
>
{layout.value !== 'top' ? (
<div class="h-full flex items-center">
{hamburger.value ? (
{hamburger.value && layout.value !== 'cutMenu' ? (
<Collapse class="hover-tigger" color="var(--top-header-text-color)"></Collapse>
) : undefined}
{breadcrumb.value ? <Breadcrumb class="<md:hidden"></Breadcrumb> : undefined}
</div>
) : undefined}
<div class="h-full flex items-center">
{screenfull.value ? (
<Screenfull class="hover-tigger" color="var(--top-header-text-color)"></Screenfull>

View File

@ -1,6 +1,7 @@
import { computed } from 'vue'
import { useAppStore } from '@/store/modules/app'
import { Menu } from '@/components/Menu'
import { TabMenu } from '@/components/TabMenu'
import { TagsView } from '@/components/TagsView'
import { Logo } from '@/components/Logo'
import AppView from './AppView.vue'
@ -32,7 +33,7 @@ export const useRenderLayout = () => {
{logo.value ? (
<Logo
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,
'w-[var(--left-menu-min-width)]': appStore.getCollapse,
@ -80,9 +81,11 @@ export const useRenderLayout = () => {
]}
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>
<AppView></AppView>
@ -95,12 +98,12 @@ export const useRenderLayout = () => {
const renderTopLeft = () => {
return (
<>
<div class="flex items-center bg-[var(--top-header-bg-color)]">
<Logo class="hover-tigger !pr-15px"></Logo>
<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">
<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>
<div
class={[
@ -127,7 +130,7 @@ export const useRenderLayout = () => {
{tagsView.value ? (
<TagsView
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,
'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 {
renderClassic,
renderTopLeft
renderTopLeft,
renderTop,
renderCutMenu
}
}

View File

@ -37,7 +37,8 @@ export default {
logo: 'Logo',
greyMode: 'Grey mode',
fixedHeader: 'Fixed header',
headerTheme: 'Header theme'
headerTheme: 'Header theme',
cutMenu: 'Cut Menu'
},
size: {
default: 'Default',

View File

@ -37,7 +37,8 @@ export default {
logo: '标志',
greyMode: '灰色模式',
fixedHeader: '固定头部',
headerTheme: '头部主题'
headerTheme: '头部主题',
cutMenu: '切割菜单'
},
size: {
default: '默认',

View File

@ -42,6 +42,9 @@ export const useAppStore = defineStore({
getFixedHeader(): boolean {
return this.fixedHeader
},
getFixedMenu(): boolean {
return this.fixedMenu
},
getGreyMode(): boolean {
return this.greyMode
},
@ -52,9 +55,6 @@ export const useAppStore = defineStore({
getTitle(): string {
return this.title
},
getLogoTitle(): string {
return this.logoTitle
},
getUserInfo(): string {
return this.userInfo
},
@ -105,6 +105,9 @@ export const useAppStore = defineStore({
setFixedHeader(fixedHeader: boolean) {
this.fixedHeader = fixedHeader
},
setFixedMenu(fixedMenu: boolean) {
this.fixedMenu = fixedMenu
},
setGreyMode(greyMode: boolean) {
this.greyMode = greyMode
},
@ -120,9 +123,6 @@ export const useAppStore = defineStore({
setTitle(title: string) {
this.title = title
},
setLogoTitle(logoTitle: string) {
this.logoTitle = logoTitle
},
setIsDark(isDark: boolean) {
this.isDark = isDark
if (this.isDark) {

View File

@ -15,7 +15,6 @@ export interface PermissionState {
routers: AppRouteRecordRaw[]
addRouters: AppRouteRecordRaw[]
isAddRouters: boolean
activeTab: string
menuTabRouters: AppRouteRecordRaw[]
}
@ -25,8 +24,7 @@ export const usePermissionStore = defineStore({
routers: [],
addRouters: [],
isAddRouters: false,
menuTabRouters: [],
activeTab: ''
menuTabRouters: []
}),
getters: {
getRouters(): AppRouteRecordRaw[] {
@ -38,9 +36,6 @@ export const usePermissionStore = defineStore({
getIsAddRouters(): boolean {
return this.isAddRouters
},
getActiveTab(): string {
return this.activeTab
},
getMenuTabRouters(): AppRouteRecordRaw[] {
return this.menuTabRouters
}
@ -84,9 +79,6 @@ export const usePermissionStore = defineStore({
},
setMenuTabRouters(routers: AppRouteRecordRaw[]): void {
this.menuTabRouters = routers
},
setAcitveTab(activeTab: string): void {
this.activeTab = activeTab
}
}
})

View File

@ -7,6 +7,10 @@
}
.border-bottom {
border-bottom: 1px solid var(--top-tool-border-color);
}
.border-bottom--after {
@apply relative;
&:after {
content: '';
@ -16,6 +20,10 @@
}
.border-top {
border-top: 1px solid var(--top-tool-border-color);
}
.border-top--before {
@apply relative;
&:before {
content: '';

View File

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

View File

@ -2,7 +2,7 @@
--dark-bg-color: #293146;
/* left menu start */
--left-menu-border-color: 'inherit';
--left-menu-border-color: '#eee';
--left-menu-max-width: 200px;
@ -43,8 +43,20 @@
--top-tool-border-color: #eee;
--tags-view-height: 35px;
--tags-view-border-color: #eee;
/* 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;
--transition-time-02: 0.2s;

View File

@ -1,9 +1,9 @@
import { defineConfig } from 'windicss/helpers'
// import plugin from 'windicss/plugin'
import plugin from 'windicss/plugin'
// function range(size, startAt = 1) {
// return Array.from(Array(size).keys()).map((i) => i + startAt)
// }
function range(size, startAt = 1) {
return Array.from(Array(size).keys()).map((i) => i + startAt)
}
export default defineConfig({
extract: {
@ -34,34 +34,38 @@ export default defineConfig({
// // ...range(50).map((i) => `mb-${i}px`),
// // ...range(50).map((i) => `ml-${i}px`)
// }
},
plugins: [
plugin(({ addComponents }) => {
const obj = {}
range(50).map((i) => {
obj[`.border-top-${i}`] = {
borderTopWidth: `${i}px`
}
obj[`.border-left-${i}`] = {
borderLeftWidth: `${i}px`
}
obj[`.border-right-${i}`] = {
borderRightWidth: `${i}px`
}
obj[`.border-bottom-${i}`] = {
borderBottomWidth: `${i}px`
}
// 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: ''
// }
// }
// })
// })
// ]
})
addComponents({
'.hover-tigger': {
display: 'flex',
height: '100%',
padding: '1px 10px 0',
cursor: 'pointer',
alignItems: 'center',
transition: 'background var(--transition-time-02)',
'&:hover': {
backgroundColor: 'var(--top-header-hover-color)'
}
},
...obj
})
})
]
})