feat(Layout): Add classic layout

This commit is contained in:
陈凯龙
2022-01-18 16:22:47 +08:00
parent 958edefe7b
commit 839b6015b8
26 changed files with 632 additions and 147 deletions

View File

@@ -2,13 +2,18 @@
import { ElBreadcrumb, ElBreadcrumbItem } from 'element-plus'
import { ref, watch, computed, unref, defineComponent, TransitionGroup } from 'vue'
import { useRouter } from 'vue-router'
// import { compile } from 'path-to-regexp'
import { usePermissionStore } from '@/store/modules/permission'
import { filterBreadcrumb } from './helper'
import { filter, treeToList } from '@/utils/tree'
import type { RouteLocationNormalizedLoaded, RouteMeta } from 'vue-router'
import { useI18n } from '@/hooks/web/useI18n'
import { Icon } from '@/components/Icon'
import { useAppStore } from '@/store/modules/app'
const appStore = useAppStore()
// 面包屑图标
const breadcrumbIcon = computed(() => appStore.getBreadcrumbIcon)
export default defineComponent({
name: 'Breadcrumb',
@@ -41,7 +46,7 @@ export default defineComponent({
const meta = v.meta as RouteMeta
return (
<ElBreadcrumbItem to={{ path: disabled ? '' : v.path }} key={v.name}>
{meta?.icon ? (
{meta?.icon && breadcrumbIcon.value ? (
<>
<Icon icon={meta.icon} class="mr-[5px]"></Icon> {t(v?.meta?.title)}
</>

View File

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

View File

@@ -0,0 +1,60 @@
<script setup lang="ts">
import { PropType, watch, unref, ref } from 'vue'
import { propTypes } from '@/utils/propTypes'
const props = defineProps({
schema: {
type: Array as PropType<string[]>,
default: () => []
},
modelValue: propTypes.string.def('')
})
const emit = defineEmits(['update:modelValue', 'change'])
const colorVal = ref(props.modelValue)
watch(
() => props.modelValue,
(val: string) => {
if (val === unref(colorVal)) return
colorVal.value = val
}
)
// 监听
watch(
() => colorVal.value,
(val: string) => {
emit('update:modelValue', val)
emit('change', val)
}
)
</script>
<template>
<div class="v-color-radio-picker flex flex-wrap space-x-14px">
<span
v-for="(item, i) in schema"
:key="`radio-${i}`"
class="v-color-radio-picker w-20px h-20px cursor-pointer rounded-2px border-solid border-gray-300 border-2px text-center leading-20px mb-5px"
:class="{ 'is-active': colorVal === item }"
:style="{
background: item
}"
@click="colorVal = item"
>
<Icon v-if="colorVal === item" color="#fff" icon="ep:check" :size="16" />
</span>
</div>
</template>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-color-radio-picker';
.@{prefix-cls} {
.is-active {
border-color: var(--el-color-primary);
}
}
</style>

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { provide, computed, watch } from 'vue'
import { provide, computed, watch, onMounted } from 'vue'
import { propTypes } from '@/utils/propTypes'
import { ElConfigProvider } from 'element-plus'
import { useLocaleStore } from '@/store/modules/locale'
@@ -9,8 +9,20 @@ import { setCssVar } from '@/utils'
const appStore = useAppStore()
const props = defineProps({
size: propTypes.oneOf<ElememtPlusSzie[]>(['default', 'small', 'large']).def('default')
})
provide('configGlobal', props)
// 初始化所有主题色
onMounted(() => {
appStore.setCssVarTheme()
})
const { width } = useWindowSize()
// 监听窗口变化
watch(
() => width.value,
(width: number) => {
@@ -29,19 +41,14 @@ watch(
}
)
// 多语言相关
const localeStore = useLocaleStore()
const locale = computed(() => localeStore.locale)
const props = defineProps({
size: propTypes.oneOf<ElememtPlusSzie[]>(['default', 'small', 'large']).def('default')
})
provide('configGlobal', props)
const currentLocale = computed(() => localeStore.currentLocale)
</script>
<template>
<ElConfigProvider :locale="locale.elLocale" :message="{ max: 1 }" :size="size">
<ElConfigProvider :locale="currentLocale.elLocale" :message="{ max: 1 }" :size="size">
<slot></slot>
</ElConfigProvider>
</template>

View File

@@ -8,13 +8,13 @@ const localeStore = useLocaleStore()
const langMap = computed(() => localeStore.getLocaleMap)
const currentLang = computed(() => localeStore.getLocale)
const currentLang = computed(() => localeStore.getCurrentLocale)
const setLang = (lang: LocaleType) => {
if (lang === unref(currentLang).lang) return
// 需要重新加载页面让整个语言多初始化
window.location.reload()
localeStore.setLocale({
localeStore.setCurrentLocale({
lang
})
const { changeLocale } = useLocale()

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, watch, computed } from 'vue'
import { ref, watch, computed, onMounted, unref } from 'vue'
import { useAppStore } from '@/store/modules/app'
const appStore = useAppStore()
@@ -12,6 +12,10 @@ const layout = computed(() => appStore.getLayout)
const collapse = computed(() => appStore.getCollapse)
onMounted(() => {
if (unref(collapse)) show.value = false
})
watch(
() => collapse.value,
(collapse: boolean) => {
@@ -37,7 +41,7 @@ watch(
{
'v-logo__Top': layout !== 'classic'
},
'flex h-[var(--logo-height)] items-center cursor-pointer pl-8px'
'flex h-[var(--logo-height)] items-center cursor-pointer pl-8px relative'
]"
to="/"
>
@@ -50,3 +54,18 @@ watch(
}}</div>
</router-link>
</template>
<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

@@ -14,6 +14,9 @@ export default defineComponent({
setup() {
const appStore = useAppStore()
// logo
const logo = computed(() => appStore.logo)
const { push, currentRoute } = useRouter()
const permissionStore = usePermissionStore()
@@ -61,8 +64,8 @@ export default defineComponent({
'bg-[var(--left-menu-bg-color)]'
]}
>
<Logo></Logo>
<ElScrollbar class={[{ '!h-[calc(100%-var(--top-tool-height))]': true }]}>
{logo.value ? <Logo></Logo> : undefined}
<ElScrollbar class={[{ '!h-[calc(100%-var(--logo-height))]': logo.value }]}>
<ElMenu
defaultActive={unref(activeMenu)}
mode={unref(menuMode)}
@@ -89,9 +92,28 @@ export default defineComponent({
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-menu';
.is-active--after {
position: absolute;
top: 0;
right: 0;
width: 4px;
height: 100%;
background-color: var(--el-color-primary);
content: '';
}
.@{prefix-cls} {
transition: width var(--transition-time-02);
&:after {
position: absolute;
top: 0;
right: 0;
height: 100%;
border-left: 1px solid var(--left-menu-border-color);
content: '';
}
:deep(.el-menu) {
width: 100% !important;
border-right: none;
@@ -123,6 +145,14 @@ export default defineComponent({
}
}
.el-menu-item.is-active {
position: relative;
&:after {
.is-active--after;
}
}
// 设置子菜单的背景颜色
.el-menu {
.el-sub-menu__title,
@@ -138,7 +168,12 @@ export default defineComponent({
& > .is-active,
& > .is-active > .el-sub-menu__title {
position: relative;
background-color: var(--left-menu-collapse-bg-active-color) !important;
&:after {
.is-active--after;
}
}
}
@@ -155,6 +190,16 @@ export default defineComponent({
<style lang="less">
@prefix-cls: ~'@{namespace}-menu-popper';
.is-active--after {
position: absolute;
top: 0;
right: 0;
width: 4px;
height: 100%;
background-color: var(--el-color-primary);
content: '';
}
.@{prefix-cls} {
&--vertical {
// 设置选中时子标题的颜色
@@ -175,11 +220,16 @@ export default defineComponent({
// 设置选中时的高亮背景
.el-menu-item.is-active {
position: relative;
background-color: var(--left-menu-bg-active-color) !important;
&:hover {
background-color: var(--left-menu-bg-active-color) !important;
}
&:after {
.is-active--after;
}
}
}
}

View File

@@ -1,8 +1,64 @@
<script setup lang="ts">
import { ElDrawer } from 'element-plus'
import { ref } from 'vue'
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 InterfaceDisplay from './components/InterfaceDisplay.vue'
const appStore = useAppStore()
const { t } = useI18n()
const drawer = ref(false)
// 主题色相关
const systemTheme = ref(appStore.getTheme.elColorPrimary)
const setSystemTheme = (color: string) => {
setCssVar('--el-color-primary', color)
appStore.setTheme({ elColorPrimary: color })
const leftMenuBgColor = useCssVar('--left-menu-bg-color', document.documentElement)
setMenuTheme(trim(unref(leftMenuBgColor)))
}
// 菜单主题相关
const menuTheme = ref(appStore.getTheme.leftMenuBgColor)
const setMenuTheme = (color: string) => {
const primaryColor = useCssVar('--el-color-primary', document.documentElement)
const isDarkColor = colorIsDark(color)
const theme: Recordable = {
// 左侧菜单边框颜色
leftMenuBorderColor: isDarkColor ? 'inherit' : '#eee',
// 左侧菜单背景颜色
leftMenuBgColor: color,
// 左侧菜单浅色背景颜色
leftMenuBgLightColor: isDarkColor ? lighten(color!, 6) : color,
// 左侧菜单选中背景颜色
leftMenuBgActiveColor: isDarkColor
? 'var(--el-color-primary)'
: hexToRGB(unref(primaryColor), 0.1),
// 左侧菜单收起选中背景颜色
leftMenuCollapseBgActiveColor: isDarkColor
? 'var(--el-color-primary)'
: hexToRGB(unref(primaryColor), 0.1),
// 左侧菜单字体颜色
leftMenuTextColor: isDarkColor ? '#bfcbd9' : '#333',
// 左侧菜单选中字体颜色
leftMenuTextActiveColor: isDarkColor ? '#fff' : 'var(--el-color-primary)',
// logo字体颜色
logoTitleTextColor: isDarkColor ? '#fff' : 'inherit',
// logo边框颜色
logoBorderColor: isDarkColor ? 'inherit' : '#eee'
}
appStore.setTheme(theme)
appStore.setCssVarTheme()
}
</script>
<template>
@@ -13,7 +69,58 @@ const drawer = ref(false)
<Icon icon="ant-design:setting-outlined" color="#fff" />
</div>
<ElDrawer v-model="drawer" :with-header="false" direction="rtl" size="300px">ddd</ElDrawer>
<ElDrawer v-model="drawer" direction="rtl" size="350px">
<template #title>
<span class="text-16px font-700">{{ t('setting.projectSetting') }}</span>
</template>
<div class="text-center">
<!-- 主题 -->
<ElDivider>{{ t('setting.theme') }}</ElDivider>
<ThemeSwitch />
<!-- 布局 -->
<ElDivider>{{ t('setting.layout') }}</ElDivider>
<!-- 系统主题 -->
<ElDivider>{{ t('setting.systemTheme') }}</ElDivider>
<ColorRadioPicker
v-model="systemTheme"
:schema="[
'#409eff',
'#009688',
'#536dfe',
'#ff5c93',
'#ee4f12',
'#0096c7',
'#9c27b0',
'#ff9800'
]"
@change="setSystemTheme"
/>
<!-- 菜单主题 -->
<ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
<ColorRadioPicker
v-model="menuTheme"
:schema="[
'#fff',
'#001529',
'#212121',
'#273352',
'#191b24',
'#383f45',
'#001628',
'#344058'
]"
@change="setMenuTheme"
/>
</div>
<!-- 界面显示 -->
<ElDivider>{{ t('setting.interfaceDisplay') }}</ElDivider>
<InterfaceDisplay />
</ElDrawer>
</template>
<style lang="less" scoped>

View File

@@ -0,0 +1,134 @@
<script setup lang="ts">
import { ElSwitch } from 'element-plus'
import { useI18n } from '@/hooks/web/useI18n'
import { useAppStore } from '@/store/modules/app'
import { ref } from 'vue'
const appStore = useAppStore()
const { t } = useI18n()
// 面包屑
const breadcrumb = ref(appStore.getBreadcrumb)
const breadcrumbChange = (show: boolean) => {
appStore.setBreadcrumb(show)
}
// 面包屑图标
const breadcrumbIcon = ref(appStore.getBreadcrumbIcon)
const breadcrumbIconChange = (show: boolean) => {
appStore.setBreadcrumbIcon(show)
}
// 折叠菜单
const collapse = ref(appStore.getCollapse)
const collapseChange = (show: boolean) => {
appStore.setCollapse(show)
}
// 折叠图标
const hamburger = ref(appStore.getHamburger)
const hamburgerChange = (show: boolean) => {
appStore.setHamburger(show)
}
// 全屏图标
const screenfull = ref(appStore.getScreenfull)
const screenfullChange = (show: boolean) => {
appStore.setScreenfull(show)
}
// 尺寸图标
const size = ref(appStore.getSize)
const sizeChange = (show: boolean) => {
appStore.setSize(show)
}
// 多语言图标
const locale = ref(appStore.getLocale)
const localeChange = (show: boolean) => {
appStore.setLocale(show)
}
// 标签页
const tagsView = ref(appStore.getTagsView)
const tagsViewChange = (show: boolean) => {
appStore.setTagsView(show)
}
// logo
const logo = ref(appStore.getLogo)
const logoChange = (show: boolean) => {
appStore.setLogo(show)
}
// 灰色模式
const greyMode = ref(appStore.getGreyMode)
const greyModeChange = (show: boolean) => {
appStore.setGreyMode(show)
}
</script>
<template>
<div class="v-interface-display">
<div class="flex justify-between items-center">
<span class="text-14px">{{ t('setting.breadcrumb') }}</span>
<ElSwitch v-model="breadcrumb" @change="breadcrumbChange" />
</div>
<div class="flex justify-between items-center">
<span class="text-14px">{{ t('setting.breadcrumbIcon') }}</span>
<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" />
</div>
<div class="flex justify-between items-center">
<span class="text-14px">{{ t('setting.screenfullIcon') }}</span>
<ElSwitch v-model="screenfull" @change="screenfullChange" />
</div>
<div class="flex justify-between items-center">
<span class="text-14px">{{ t('setting.sizeIcon') }}</span>
<ElSwitch v-model="size" @change="sizeChange" />
</div>
<div class="flex justify-between items-center">
<span class="text-14px">{{ t('setting.localeIcon') }}</span>
<ElSwitch v-model="locale" @change="localeChange" />
</div>
<div class="flex justify-between items-center">
<span class="text-14px">{{ t('setting.tagsView') }}</span>
<ElSwitch v-model="tagsView" @change="tagsViewChange" />
</div>
<div class="flex justify-between items-center">
<span class="text-14px">{{ t('setting.logo') }}</span>
<ElSwitch v-model="logo" @change="logoChange" />
</div>
<div class="flex justify-between items-center">
<span class="text-14px">{{ t('setting.greyMode') }}</span>
<ElSwitch v-model="greyMode" @change="greyModeChange" />
</div>
</div>
</template>

View File

@@ -9,13 +9,13 @@ const appStore = useAppStore()
const sizeMap = computed(() => appStore.sizeMap)
const setSize = (size: ElememtPlusSzie) => {
appStore.setSize(size)
const setCurrentSize = (size: ElememtPlusSzie) => {
appStore.setCurrentSize(size)
}
</script>
<template>
<ElDropdown trigger="click" @command="setSize">
<ElDropdown trigger="click" @command="setCurrentSize">
<Icon
:size="18"
icon="mdi:format-size"

View File

@@ -127,7 +127,7 @@ watch(
<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">
<Icon icon="ant-design:left-outlined" color="#333" />
<Icon icon="ep:d-arrow-left" color="#333" />
</span>
<div class="overflow-hidden flex-1">
<ElScrollbar>
@@ -216,7 +216,7 @@ watch(
</ElScrollbar>
</div>
<span class="v-tags-view__tool w-[40px] h-[40px] text-center leading-[40px] cursor-pointer">
<Icon icon="ant-design:right-outlined" color="#333" />
<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"