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

@ -14,5 +14,6 @@
"i18n-ally.enabledParsers": ["ts"], "i18n-ally.enabledParsers": ["ts"],
"i18n-ally.sourceLanguage": "en", "i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-CN", "i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": ["vue", "react"] "i18n-ally.enabledFrameworks": ["vue", "react"],
"god.tsconfig": "./tsconfig.json"
} }

View File

@ -6,7 +6,9 @@ import { isDark } from '@/utils/is'
const appStore = useAppStore() const appStore = useAppStore()
const size = computed(() => appStore.size) const currentSize = computed(() => appStore.getCurrentSize)
const greyMode = computed(() => appStore.getGreyMode)
const initDark = () => { const initDark = () => {
const isDarkTheme = isDark() const isDarkTheme = isDark()
@ -17,12 +19,14 @@ initDark()
</script> </script>
<template> <template>
<ConfigGlobal :size="size"> <ConfigGlobal :size="currentSize">
<RouterView /> <RouterView :class="{ 'v-grey__mode': greyMode }" />
</ConfigGlobal> </ConfigGlobal>
</template> </template>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-grey';
.size { .size {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -39,4 +43,13 @@ body {
.size; .size;
} }
} }
.@{prefix-cls}__mode {
-webkit-filter: grayscale(100%);
-moz-filter: grayscale(100%);
-ms-filter: grayscale(100%);
-o-filter: grayscale(100%);
filter: grayscale(100%);
filter: progid:dximagetransform.microsoft.basicimage(grayscale=1);
}
</style> </style>

View File

@ -2,13 +2,18 @@
import { ElBreadcrumb, ElBreadcrumbItem } from 'element-plus' import { ElBreadcrumb, ElBreadcrumbItem } from 'element-plus'
import { ref, watch, computed, unref, defineComponent, TransitionGroup } from 'vue' import { ref, watch, computed, unref, defineComponent, TransitionGroup } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
// import { compile } from 'path-to-regexp'
import { usePermissionStore } from '@/store/modules/permission' import { usePermissionStore } from '@/store/modules/permission'
import { filterBreadcrumb } from './helper' import { filterBreadcrumb } from './helper'
import { filter, treeToList } from '@/utils/tree' import { filter, treeToList } from '@/utils/tree'
import type { RouteLocationNormalizedLoaded, RouteMeta } from 'vue-router' import type { RouteLocationNormalizedLoaded, RouteMeta } from 'vue-router'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import { Icon } from '@/components/Icon' import { Icon } from '@/components/Icon'
import { useAppStore } from '@/store/modules/app'
const appStore = useAppStore()
//
const breadcrumbIcon = computed(() => appStore.getBreadcrumbIcon)
export default defineComponent({ export default defineComponent({
name: 'Breadcrumb', name: 'Breadcrumb',
@ -41,7 +46,7 @@ export default defineComponent({
const meta = v.meta as RouteMeta const meta = v.meta as RouteMeta
return ( return (
<ElBreadcrumbItem to={{ path: disabled ? '' : v.path }} key={v.name}> <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)} <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"> <script setup lang="ts">
import { provide, computed, watch } from 'vue' import { provide, computed, watch, onMounted } from 'vue'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { ElConfigProvider } from 'element-plus' import { ElConfigProvider } from 'element-plus'
import { useLocaleStore } from '@/store/modules/locale' import { useLocaleStore } from '@/store/modules/locale'
@ -9,8 +9,20 @@ import { setCssVar } from '@/utils'
const appStore = useAppStore() const appStore = useAppStore()
const props = defineProps({
size: propTypes.oneOf<ElememtPlusSzie[]>(['default', 'small', 'large']).def('default')
})
provide('configGlobal', props)
//
onMounted(() => {
appStore.setCssVarTheme()
})
const { width } = useWindowSize() const { width } = useWindowSize()
//
watch( watch(
() => width.value, () => width.value,
(width: number) => { (width: number) => {
@ -29,19 +41,14 @@ watch(
} }
) )
//
const localeStore = useLocaleStore() const localeStore = useLocaleStore()
const locale = computed(() => localeStore.locale) const currentLocale = computed(() => localeStore.currentLocale)
const props = defineProps({
size: propTypes.oneOf<ElememtPlusSzie[]>(['default', 'small', 'large']).def('default')
})
provide('configGlobal', props)
</script> </script>
<template> <template>
<ElConfigProvider :locale="locale.elLocale" :message="{ max: 1 }" :size="size"> <ElConfigProvider :locale="currentLocale.elLocale" :message="{ max: 1 }" :size="size">
<slot></slot> <slot></slot>
</ElConfigProvider> </ElConfigProvider>
</template> </template>

View File

@ -8,13 +8,13 @@ const localeStore = useLocaleStore()
const langMap = computed(() => localeStore.getLocaleMap) const langMap = computed(() => localeStore.getLocaleMap)
const currentLang = computed(() => localeStore.getLocale) const currentLang = computed(() => localeStore.getCurrentLocale)
const setLang = (lang: LocaleType) => { const setLang = (lang: LocaleType) => {
if (lang === unref(currentLang).lang) return if (lang === unref(currentLang).lang) return
// //
window.location.reload() window.location.reload()
localeStore.setLocale({ localeStore.setCurrentLocale({
lang lang
}) })
const { changeLocale } = useLocale() const { changeLocale } = useLocale()

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch, computed } from 'vue' import { ref, watch, computed, onMounted, unref } from 'vue'
import { useAppStore } from '@/store/modules/app' import { useAppStore } from '@/store/modules/app'
const appStore = useAppStore() const appStore = useAppStore()
@ -12,6 +12,10 @@ const layout = computed(() => appStore.getLayout)
const collapse = computed(() => appStore.getCollapse) const collapse = computed(() => appStore.getCollapse)
onMounted(() => {
if (unref(collapse)) show.value = false
})
watch( watch(
() => collapse.value, () => collapse.value,
(collapse: boolean) => { (collapse: boolean) => {
@ -37,7 +41,7 @@ watch(
{ {
'v-logo__Top': layout !== 'classic' '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="/" to="/"
> >
@ -50,3 +54,18 @@ watch(
}}</div> }}</div>
</router-link> </router-link>
</template> </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() { setup() {
const appStore = useAppStore() const appStore = useAppStore()
// logo
const logo = computed(() => appStore.logo)
const { push, currentRoute } = useRouter() const { push, currentRoute } = useRouter()
const permissionStore = usePermissionStore() const permissionStore = usePermissionStore()
@ -61,8 +64,8 @@ export default defineComponent({
'bg-[var(--left-menu-bg-color)]' 'bg-[var(--left-menu-bg-color)]'
]} ]}
> >
<Logo></Logo> {logo.value ? <Logo></Logo> : undefined}
<ElScrollbar class={[{ '!h-[calc(100%-var(--top-tool-height))]': true }]}> <ElScrollbar class={[{ '!h-[calc(100%-var(--logo-height))]': logo.value }]}>
<ElMenu <ElMenu
defaultActive={unref(activeMenu)} defaultActive={unref(activeMenu)}
mode={unref(menuMode)} mode={unref(menuMode)}
@ -89,9 +92,28 @@ export default defineComponent({
<style lang="less" scoped> <style lang="less" scoped>
@prefix-cls: ~'@{namespace}-menu'; @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} { .@{prefix-cls} {
transition: width var(--transition-time-02); 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) { :deep(.el-menu) {
width: 100% !important; width: 100% !important;
border-right: none; border-right: none;
@ -123,6 +145,14 @@ export default defineComponent({
} }
} }
.el-menu-item.is-active {
position: relative;
&:after {
.is-active--after;
}
}
// //
.el-menu { .el-menu {
.el-sub-menu__title, .el-sub-menu__title,
@ -138,7 +168,12 @@ export default defineComponent({
& > .is-active, & > .is-active,
& > .is-active > .el-sub-menu__title { & > .is-active > .el-sub-menu__title {
position: relative;
background-color: var(--left-menu-collapse-bg-active-color) !important; background-color: var(--left-menu-collapse-bg-active-color) !important;
&:after {
.is-active--after;
}
} }
} }
@ -155,6 +190,16 @@ export default defineComponent({
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-menu-popper'; @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} { .@{prefix-cls} {
&--vertical { &--vertical {
// //
@ -175,11 +220,16 @@ export default defineComponent({
// //
.el-menu-item.is-active { .el-menu-item.is-active {
position: relative;
background-color: var(--left-menu-bg-active-color) !important; background-color: var(--left-menu-bg-active-color) !important;
&:hover { &:hover {
background-color: var(--left-menu-bg-active-color) !important; background-color: var(--left-menu-bg-active-color) !important;
} }
&:after {
.is-active--after;
}
} }
} }
} }

View File

@ -1,8 +1,64 @@
<script setup lang="ts"> <script setup lang="ts">
import { ElDrawer } from 'element-plus' import { ElDrawer, ElDivider } from 'element-plus'
import { ref } from 'vue' 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 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> </script>
<template> <template>
@ -13,7 +69,58 @@ const drawer = ref(false)
<Icon icon="ant-design:setting-outlined" color="#fff" /> <Icon icon="ant-design:setting-outlined" color="#fff" />
</div> </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> </template>
<style lang="less" scoped> <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 sizeMap = computed(() => appStore.sizeMap)
const setSize = (size: ElememtPlusSzie) => { const setCurrentSize = (size: ElememtPlusSzie) => {
appStore.setSize(size) appStore.setCurrentSize(size)
} }
</script> </script>
<template> <template>
<ElDropdown trigger="click" @command="setSize"> <ElDropdown trigger="click" @command="setCurrentSize">
<Icon <Icon
:size="18" :size="18"
icon="mdi:format-size" icon="mdi:format-size"

View File

@ -127,7 +127,7 @@ watch(
<template> <template>
<div class="v-tags-view h-[var(--tags-view-height)] flex w-full"> <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"> <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> </span>
<div class="overflow-hidden flex-1"> <div class="overflow-hidden flex-1">
<ElScrollbar> <ElScrollbar>
@ -216,7 +216,7 @@ watch(
</ElScrollbar> </ElScrollbar>
</div> </div>
<span class="v-tags-view__tool w-[40px] h-[40px] text-center leading-[40px] cursor-pointer"> <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>
<span <span
class="v-tags-view__tool w-[40px] h-[40px] text-center leading-[40px] cursor-pointer" class="v-tags-view__tool w-[40px] h-[40px] text-center leading-[40px] cursor-pointer"

View File

@ -5,47 +5,68 @@ const { wsCache } = useCache()
export type LayoutType = 'classic' | 'leftTop' | 'top' | 'test' export type LayoutType = 'classic' | 'leftTop' | 'top' | 'test'
export interface AppState { export interface AppState {
breadcrumb: boolean
breadcrumbIcon: boolean
collapse: boolean collapse: boolean
showTags: boolean hamburger: boolean
showLogo: boolean screenfull: boolean
showNavbar: boolean size: boolean
fixedHeader: boolean locale: boolean
tagsView: boolean
logo: boolean
greyMode: boolean
layout: LayoutType layout: LayoutType
showBreadcrumb: boolean
showHamburger: boolean
showScreenfull: boolean
showUserInfo: boolean
title: string title: string
logoTitle: string logoTitle: string
userInfo: string userInfo: string
greyMode: boolean
showBackTop: boolean
showMenuTab: boolean
isDark: boolean isDark: boolean
size: ElememtPlusSzie currentSize: ElememtPlusSzie
sizeMap: ElememtPlusSzie[] sizeMap: ElememtPlusSzie[]
mobile: boolean mobile: boolean
theme: Recordable
} }
export const appModules: AppState = { export const appModules: AppState = {
collapse: false, // 菜单栏是否栏缩收 breadcrumb: true, // 面包屑
showLogo: true, // 是否显示logo breadcrumbIcon: true, // 面包屑图标
showTags: true, // 是否显示标签栏 collapse: false, // 折叠菜单
showNavbar: true, // 是否显示navbar hamburger: true, // 折叠图标
fixedHeader: true, // 是否固定header screenfull: true, // 全屏图标
size: true, // 尺寸图标
locale: true, // 多语言图标
tagsView: true, // 标签页
logo: true, // logo
greyMode: false, // 是否开始灰色模式,用于特殊悼念日
layout: 'classic', // layout布局 layout: 'classic', // layout布局
showBreadcrumb: true, // 是否显示面包屑
showHamburger: true, // 是否显示侧边栏缩收按钮
showScreenfull: true, // 是否全屏按钮
showUserInfo: true, // 是否显示用户头像
title: 'butterfly-admin', // 标题 title: 'butterfly-admin', // 标题
logoTitle: 'ButterflyAdmin', // logo标题 logoTitle: 'ButterflyAdmin', // logo标题
userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段,避免与其他项目冲突 userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段,避免与其他项目冲突
greyMode: false, // 是否开始灰色模式,用于特殊悼念日
showBackTop: true, // 是否显示回到顶部
showMenuTab: false, // 是否固定一级菜单
isDark: wsCache.get('isDark') || false, // 是否是暗黑模式 isDark: wsCache.get('isDark') || false, // 是否是暗黑模式
size: wsCache.get('default') || 'default', // 组件尺寸 currentSize: wsCache.get('default') || 'default', // 组件尺寸
sizeMap: ['default', 'large', 'small'], sizeMap: ['default', 'large', 'small'],
mobile: false // 是否是移动端 mobile: false, // 是否是移动端
theme: wsCache.get('theme') || {
// 主题色
elColorPrimary: '#409eff',
// 左侧菜单边框颜色
leftMenuBorderColor: 'inherit',
// 左侧菜单背景颜色
leftMenuBgColor: '#001529',
// 左侧菜单浅色背景颜色
leftMenuBgLightColor: '#0f2438',
// 左侧菜单选中背景颜色
leftMenuBgActiveColor: 'var(--el-color-primary)',
// 左侧菜单收起选中背景颜色
leftMenuCollapseBgActiveColor: 'var(--el-color-primary)',
// 左侧菜单字体颜色
leftMenuTextColor: '#bfcbd9',
// 左侧菜单选中字体颜色
leftMenuTextActiveColor: '#fff',
// logo字体颜色
logoTitleTextColor: '#fff',
// logo边框颜色
logoBorderColor: 'inherit'
}
} }

View File

@ -8,14 +8,13 @@ export const elLocaleMap = {
'zh-CN': zhCn, 'zh-CN': zhCn,
en: en en: en
} }
export interface LocaleState { export interface LocaleState {
locale: LocaleDropdownType currentLocale: LocaleDropdownType
localeMap: LocaleDropdownType[] localeMap: LocaleDropdownType[]
} }
export const localeModules: LocaleState = { export const localeModules: LocaleState = {
locale: { currentLocale: {
lang: wsCache.get('lang') || 'zh-CN', lang: wsCache.get('lang') || 'zh-CN',
elLocale: elLocaleMap[wsCache.get('lang') || 'zh-CN'] elLocale: elLocaleMap[wsCache.get('lang') || 'zh-CN']
}, },

View File

@ -10,7 +10,7 @@ const setI18nLanguage = (locale: LocaleType) => {
} else { } else {
;(i18n.global.locale as any).value = locale ;(i18n.global.locale as any).value = locale
} }
localeStore.setLocale({ localeStore.setCurrentLocale({
lang: locale lang: locale
}) })
setHtmlPageLang(locale) setHtmlPageLang(locale)

View File

@ -15,10 +15,30 @@ import AppView from './components/AppView.vue'
const appStore = useAppStore() const appStore = useAppStore()
//
const mobile = computed(() => appStore.getMobile) const mobile = computed(() => appStore.getMobile)
//
const breadcrumb = computed(() => appStore.getBreadcrumb)
//
const collapse = computed(() => appStore.getCollapse) const collapse = computed(() => appStore.getCollapse)
//
const hamburger = computed(() => appStore.getHamburger)
//
const screenfull = computed(() => appStore.getScreenfull)
//
const size = computed(() => appStore.getSize)
//
const locale = computed(() => appStore.getLocale)
//
const tagsView = computed(() => appStore.getTagsView)
const classSuffix = computed(() => appStore.getLayout) const classSuffix = computed(() => appStore.getLayout)
const handleClickOutside = () => { const handleClickOutside = () => {
@ -57,19 +77,22 @@ export default defineComponent({
]} ]}
> >
<div class="h-full flex items-center"> <div class="h-full flex items-center">
<Collapse class="header__tigger"></Collapse> {hamburger.value ? <Collapse class="header__tigger"></Collapse> : undefined}
<Breadcrumb class="<md:hidden"></Breadcrumb> {breadcrumb.value ? <Breadcrumb class="<md:hidden"></Breadcrumb> : undefined}
</div> </div>
<div class="h-full flex items-center"> <div class="h-full flex items-center">
<Screenfull class="header__tigger"></Screenfull> {screenfull.value ? <Screenfull class="header__tigger"></Screenfull> : undefined}
<SizeDropdown class="header__tigger"></SizeDropdown> {size.value ? <SizeDropdown class="header__tigger"></SizeDropdown> : undefined}
<LocaleDropdown class="header__tigger"></LocaleDropdown> {locale.value ? <LocaleDropdown class="header__tigger"></LocaleDropdown> : undefined}
<UserInfo class="header__tigger"></UserInfo> <UserInfo class="header__tigger"></UserInfo>
</div> </div>
</div> </div>
<div class="v-app-right__tags-view relative"> {tagsView.value ? (
<TagsView></TagsView> <div class="v-app-right__tags-view relative">
</div> <TagsView></TagsView>
</div>
) : undefined}
<AppView></AppView> <AppView></AppView>
</div> </div>

View File

@ -19,6 +19,24 @@ export default {
closeOther: 'Close other', closeOther: 'Close other',
closeAll: 'Close all' closeAll: 'Close all'
}, },
setting: {
projectSetting: 'Project setting',
theme: 'Theme',
layout: 'Layout',
systemTheme: 'System theme',
menuTheme: 'Menu theme',
interfaceDisplay: 'Interface display',
breadcrumb: 'Breadcrumb',
breadcrumbIcon: 'Breadcrumb icon',
collapseMenu: 'Collapse menu',
hamburgerIcon: 'Hamburger icon',
screenfullIcon: 'Screenfull icon',
sizeIcon: 'Size icon',
localeIcon: 'Locale icon',
tagsView: 'Tags view',
logo: 'Logo',
greyMode: 'Grey mode'
},
size: { size: {
default: 'Default', default: 'Default',
large: 'Large', large: 'Large',
@ -46,9 +64,6 @@ export default {
menu12: 'Menu1-2', menu12: 'Menu1-2',
menu2: 'Menu2' menu2: 'Menu2'
}, },
mock: {
loginErr: 'Wrong account or password'
},
formDemo: { formDemo: {
input: 'Input', input: 'Input',
inputNumber: 'InputNumber', inputNumber: 'InputNumber',

View File

@ -19,6 +19,24 @@ export default {
closeOther: '关闭其他标签页', closeOther: '关闭其他标签页',
closeAll: '关闭全部标签页' closeAll: '关闭全部标签页'
}, },
setting: {
projectSetting: '项目配置',
theme: '主题',
layout: '布局',
systemTheme: '系统主题',
menuTheme: '菜单主题',
interfaceDisplay: '界面显示',
breadcrumb: '面包屑',
breadcrumbIcon: '面包屑图标',
collapseMenu: '折叠菜单',
hamburgerIcon: '折叠图标',
screenfullIcon: '全屏图标',
sizeIcon: '尺寸图标',
localeIcon: '多语言图标',
tagsView: '标签页',
logo: '标志',
greyMode: '灰色模式'
},
size: { size: {
default: '默认', default: '默认',
large: '大', large: '大',
@ -46,9 +64,6 @@ export default {
menu12: '菜单1-2', menu12: '菜单1-2',
menu2: '菜单2' menu2: '菜单2'
}, },
mock: {
loginErr: '账号或密码错误'
},
formDemo: { formDemo: {
input: '输入框', input: '输入框',
inputNumber: '数字输入框', inputNumber: '数字输入框',

View File

@ -8,14 +8,14 @@ export let i18n: ReturnType<typeof createI18n>
const createI18nOptions = async (): Promise<I18nOptions> => { const createI18nOptions = async (): Promise<I18nOptions> => {
const localeStore = useLocaleStoreWithOut() const localeStore = useLocaleStoreWithOut()
const locale = localeStore.getLocale const locale = localeStore.getCurrentLocale
const localeMap = localeStore.getLocaleMap const localeMap = localeStore.getLocaleMap
const defaultLocal = await import(`../../locales/${locale.lang}.ts`) const defaultLocal = await import(`../../locales/${locale.lang}.ts`)
const message = defaultLocal.default ?? {} const message = defaultLocal.default ?? {}
setHtmlPageLang(locale.lang) setHtmlPageLang(locale.lang)
localeStore.setLocale({ localeStore.setCurrentLocale({
lang: locale.lang lang: locale.lang
// elLocale: elLocal // elLocale: elLocal
}) })

View File

@ -3,6 +3,7 @@ import { store } from '../index'
import { useCache } from '@/hooks/web/useCache' import { useCache } from '@/hooks/web/useCache'
import { appModules } from '@/config/app' import { appModules } from '@/config/app'
import type { AppState, LayoutType } from '@/config/app' import type { AppState, LayoutType } from '@/config/app'
import { setCssVar, humpToUnderline } from '@/utils'
const { wsCache } = useCache() const { wsCache } = useCache()
@ -10,36 +11,40 @@ export const useAppStore = defineStore({
id: 'app', id: 'app',
state: (): AppState => appModules, state: (): AppState => appModules,
getters: { getters: {
getBreadcrumb(): boolean {
return this.breadcrumb
},
getBreadcrumbIcon(): boolean {
return this.breadcrumbIcon
},
getCollapse(): boolean { getCollapse(): boolean {
return this.collapse return this.collapse
}, },
getShowLogo(): boolean { getHamburger(): boolean {
return this.showLogo return this.hamburger
}, },
getShowTags(): boolean { getScreenfull(): boolean {
return this.showTags return this.screenfull
}, },
getShowNavbar(): boolean { getSize(): boolean {
return this.showNavbar return this.size
}, },
getFixedHeader(): boolean { getLocale(): boolean {
return this.fixedHeader return this.locale
}, },
getTagsView(): boolean {
return this.tagsView
},
getLogo(): boolean {
return this.logo
},
getGreyMode(): boolean {
return this.greyMode
},
getLayout(): LayoutType { getLayout(): LayoutType {
return this.layout return this.layout
}, },
getShowBreadcrumb(): boolean {
return this.showBreadcrumb
},
getShowHamburger(): boolean {
return this.showHamburger
},
getShowScreenfull(): boolean {
return this.showScreenfull
},
getShowUserInfo(): boolean {
return this.showUserInfo
},
getTitle(): string { getTitle(): string {
return this.title return this.title
}, },
@ -49,74 +54,63 @@ export const useAppStore = defineStore({
getUserInfo(): string { getUserInfo(): string {
return this.userInfo return this.userInfo
}, },
getGreyMode(): boolean {
return this.greyMode
},
getShowBackTop(): boolean {
return this.showBackTop
},
getShowMenuTab(): boolean {
return this.showMenuTab
},
getIsDark(): boolean { getIsDark(): boolean {
return this.isDark return this.isDark
}, },
getSize(): ElememtPlusSzie { getCurrentSize(): ElememtPlusSzie {
return this.size return this.currentSize
}, },
getSizeMap(): ElememtPlusSzie[] { getSizeMap(): ElememtPlusSzie[] {
return this.sizeMap return this.sizeMap
}, },
getMobile(): boolean { getMobile(): boolean {
return this.mobile return this.mobile
},
getTheme(): Recordable {
return this.theme
} }
}, },
actions: { actions: {
setBreadcrumb(breadcrumb: boolean) {
this.breadcrumb = breadcrumb
},
setBreadcrumbIcon(breadcrumbIcon: boolean) {
this.breadcrumbIcon = breadcrumbIcon
},
setCollapse(collapse: boolean) { setCollapse(collapse: boolean) {
this.collapse = collapse this.collapse = collapse
}, },
setShowLogo(showLogo: boolean) { setHamburger(hamburger: boolean) {
this.showLogo = showLogo this.hamburger = hamburger
}, },
setShowTags(showTags: boolean) { setScreenfull(screenfull: boolean) {
this.showTags = showTags this.screenfull = screenfull
}, },
setShowNavbar(showNavbar: boolean) { setSize(size: boolean) {
this.showNavbar = showNavbar this.size = size
}, },
setFixedHeader(fixedHeader: boolean) { setLocale(locale: boolean) {
this.fixedHeader = fixedHeader this.locale = locale
}, },
setTagsView(tagsView: boolean) {
this.tagsView = tagsView
},
setLogo(logo: boolean) {
this.logo = logo
},
setGreyMode(greyMode: boolean) {
this.greyMode = greyMode
},
setLayout(layout: LayoutType) { setLayout(layout: LayoutType) {
this.layout = layout this.layout = layout
}, },
setBreadcrumb(showBreadcrumb: boolean) {
this.showBreadcrumb = showBreadcrumb
},
setHamburger(showHamburger: boolean) {
this.showHamburger = showHamburger
},
setScreenfull(showScreenfull: boolean) {
this.showScreenfull = showScreenfull
},
setUserInfo(showUserInfo: boolean) {
this.showUserInfo = showUserInfo
},
setTitle(title: string) { setTitle(title: string) {
this.title = title this.title = title
}, },
setLogoTitle(logoTitle: string) { setLogoTitle(logoTitle: string) {
this.logoTitle = logoTitle this.logoTitle = logoTitle
}, },
setGreyMode(greyMode: boolean) {
this.greyMode = greyMode
},
setShowBackTop(showBackTop: boolean) {
this.showBackTop = showBackTop
},
setShowMenuTab(showMenuTab: boolean) {
this.showMenuTab = showMenuTab
},
setIsDark(isDark: boolean) { setIsDark(isDark: boolean) {
this.isDark = isDark this.isDark = isDark
if (this.isDark) { if (this.isDark) {
@ -128,12 +122,21 @@ export const useAppStore = defineStore({
} }
wsCache.set('isDark', this.isDark) wsCache.set('isDark', this.isDark)
}, },
setSize(size: ElememtPlusSzie) { setCurrentSize(currentSize: ElememtPlusSzie) {
this.size = size this.currentSize = currentSize
wsCache.set('size', this.size) wsCache.set('currentSize', this.currentSize)
}, },
setMobile(mobile: boolean) { setMobile(mobile: boolean) {
this.mobile = mobile this.mobile = mobile
},
setTheme(theme: Recordable) {
this.theme = Object.assign(this.theme, theme)
wsCache.set('theme', this.theme)
},
setCssVarTheme() {
for (const key in this.theme) {
setCssVar(`--${humpToUnderline(key)}`, this.theme[key])
}
} }
} }
}) })

View File

@ -10,18 +10,18 @@ export const useLocaleStore = defineStore({
id: 'locales', id: 'locales',
state: (): LocaleState => localeModules, state: (): LocaleState => localeModules,
getters: { getters: {
getLocale(): LocaleDropdownType { getCurrentLocale(): LocaleDropdownType {
return this.locale return this.currentLocale
}, },
getLocaleMap(): LocaleDropdownType[] { getLocaleMap(): LocaleDropdownType[] {
return this.localeMap return this.localeMap
} }
}, },
actions: { actions: {
setLocale(localeMap: LocaleDropdownType) { setCurrentLocale(localeMap: LocaleDropdownType) {
// this.locale = Object.assign(this.locale, localeMap) // this.locale = Object.assign(this.locale, localeMap)
this.locale.lang = localeMap?.lang this.currentLocale.lang = localeMap?.lang
this.locale.elLocale = elLocaleMap[localeMap?.lang] this.currentLocale.elLocale = elLocaleMap[localeMap?.lang]
wsCache.set('lang', localeMap?.lang) wsCache.set('lang', localeMap?.lang)
} }
} }

View File

@ -2,6 +2,8 @@
--dark-bg-color: #293146; --dark-bg-color: #293146;
/* left menu start */ /* left menu start */
--left-menu-border-color: 'inherit';
--left-menu-max-width: 200px; --left-menu-max-width: 200px;
--left-menu-min-width: 64px; --left-menu-min-width: 64px;
@ -23,10 +25,12 @@
--logo-height: 50px; --logo-height: 50px;
--logo-title-text-color: #fff; --logo-title-text-color: #fff;
--logo-border-color: 'inherit';
/* logo end */ /* logo end */
/* header start */ /* header start */
--top-tool-height: 40px; --top-tool-height: var(--logo-height);
--top-tool-p-x: 0; --top-tool-p-x: 0;

View File

@ -30,7 +30,7 @@ export const rgbToHex = (r: number, g: number, b: number) => {
* @param {string} hex The color to transform * @param {string} hex The color to transform
* @returns The RGB representation of the passed color * @returns The RGB representation of the passed color
*/ */
export const hexToRGB = (hex: string) => { export const hexToRGB = (hex: string, opacity?: number) => {
let sHex = hex.toLowerCase() let sHex = hex.toLowerCase()
if (isHexColor(hex)) { if (isHexColor(hex)) {
if (sHex.length === 4) { if (sHex.length === 4) {
@ -44,7 +44,9 @@ export const hexToRGB = (hex: string) => {
for (let i = 1; i < 7; i += 2) { for (let i = 1; i < 7; i += 2) {
sColorChange.push(parseInt('0x' + sHex.slice(i, i + 2))) sColorChange.push(parseInt('0x' + sHex.slice(i, i + 2)))
} }
return 'RGB(' + sColorChange.join(',') + ')' return opacity
? 'RGBA(' + sColorChange.join(',') + ',' + opacity + ')'
: 'RGB(' + sColorChange.join(',') + ')'
} }
return sHex return sHex
} }

View File

@ -59,3 +59,7 @@ export const findIndex = <T = Recordable>(ary: Array<T>, fn: Fn): number => {
}) })
return index return index
} }
export const trim = (str: string) => {
return str.replace(/(^\s*)|(\s*$)/g, '')
}

View File

@ -44,7 +44,7 @@ const { t } = useI18n()
<div class="flex justify-end items-center space-x-10px"> <div class="flex justify-end items-center space-x-10px">
<ThemeSwitch /> <ThemeSwitch />
<LocaleDropdown class="<xl:!text-white dark:!text-white" /> <LocaleDropdown class="<xl:text-white dark:text-white" />
</div> </div>
</div> </div>
<Transition appear enter-active-class="animate__animated animate__bounceInRight"> <Transition appear enter-active-class="animate__animated animate__bounceInRight">