{{ title }}
diff --git a/src/components/LLocalePicker/index.vue b/src/components/LLocalePicker/index.vue
new file mode 100644
index 0000000..a4c9180
--- /dev/null
+++ b/src/components/LLocalePicker/index.vue
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+ {{ getLocaleText }}
+
+
+
+
diff --git a/src/components/form/bg.vue b/src/components/form/bg.vue
deleted file mode 100644
index 3c8bdd1..0000000
--- a/src/components/form/bg.vue
+++ /dev/null
@@ -1,3 +0,0 @@
-
- 222
-
diff --git a/src/composables/config/app-config.ts b/src/composables/config/app-config.ts
index a01df1c..ea6555b 100644
--- a/src/composables/config/app-config.ts
+++ b/src/composables/config/app-config.ts
@@ -1,6 +1,6 @@
import { storeToRefs } from 'pinia'
-import { HandlerSettingEnum, ThemeEnum } from '~/constants'
-import { _omit } from '~/utils'
+import { HandlerSettingEnum, NavBarModeEnum } from '~/constants'
+import { _merge, _omit } from '~/utils'
export function useAppConfig() {
const configStore = useAppConfigStore()
@@ -14,7 +14,19 @@ export function useAppConfig() {
} = appConfigOptions
function setAppConfig(configs: DeepPartial
) {
- configStore.setAppConfig(configs)
+ configStore.$patch((state) => {
+ _merge(state, preDealConfig(configs))
+ })
+ }
+
+ function preDealConfig(configs: DeepPartial) {
+ if (
+ configs.navBarMode
+ && configs.navBarMode === NavBarModeEnum.MIX_SIDEBAR
+ ) {
+ configs.logo = { show: true, visible: true }
+ }
+ return configs
}
function toggleOpenSettingDrawer() {
@@ -82,12 +94,11 @@ function handlerResults(
value: any,
configOptions: DefineAppConfigOptions,
): DeepPartial {
- const { themeColor, theme, sidebar, header } = configOptions
+ const { themeColor, sidebar, header } = configOptions
switch (event) {
case HandlerSettingEnum.CHANGE_LAYOUT:
- // eslint-disable-next-line no-case-declarations
+ {
const { mode, type, split } = value
- // eslint-disable-next-line no-case-declarations
const splitOpt = split === undefined ? { split } : {}
return {
navBarMode: type,
@@ -97,19 +108,17 @@ function handlerResults(
},
sidebar: { collapsed: false },
}
-
+ }
+ // TODO remove it
case HandlerSettingEnum.CHANGE_THEME_COLOR:
if (unref(themeColor) === value)
return {}
// changeTheme(value);
return { themeColor: value }
-
+ // TODO remove it
case HandlerSettingEnum.CHANGE_THEME:
- if (unref(theme) === value)
- return {}
-
- return { theme: value ? ThemeEnum.DARK : ThemeEnum.LIGHT }
+ return { theme: value }
case HandlerSettingEnum.MENU_HAS_DRAG:
return { menu: { canDrag: value } }
@@ -137,8 +146,9 @@ function handlerResults(
case HandlerSettingEnum.MENU_THEME:
// updateSidebarBgColor(value);
- if (unref(sidebar).bgColor === value)
+ if (unref(sidebar).bgColor === value) {
return {}
+ }
return { sidebar: { bgColor: value } }
case HandlerSettingEnum.MENU_SPLIT:
@@ -219,8 +229,9 @@ function handlerResults(
// ============header==================
case HandlerSettingEnum.HEADER_THEME:
// updateHeaderBgColor(value);
- if (unref(header).bgColor === value)
+ if (unref(header).bgColor === value) {
return {}
+ }
return { header: { bgColor: value } }
case HandlerSettingEnum.HEADER_SEARCH:
diff --git a/src/composables/dark.ts b/src/composables/dark.ts
deleted file mode 100644
index 5cd66bc..0000000
--- a/src/composables/dark.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-// these APIs are auto-imported from @vueuse/core
-export const isDark = useDark({
- selector: 'html',
- attribute: 'theme-mode',
- valueDark: 'dark',
- valueLight: '',
-})
-export const toggleDark = useToggle(isDark)
-export const preferredDark = usePreferredDark()
diff --git a/src/composables/i18n.ts b/src/composables/locale/i18n.ts
similarity index 59%
rename from src/composables/i18n.ts
rename to src/composables/locale/i18n.ts
index da26d7a..4e5a077 100644
--- a/src/composables/i18n.ts
+++ b/src/composables/locale/i18n.ts
@@ -1,4 +1,13 @@
-import { i18n } from '~/modules/i18n'
+import { i18n } from '~/modules/i18n/index'
+
+export interface I18nGlobalTranslation {
+ (key: string): string
+ (key: string, locale: string): string
+ (key: string, locale: string, list: unknown[]): string
+ (key: string, locale: string, named: Record): string
+ (key: string, list: unknown[]): string
+ (key: string, named: Record): string
+}
type I18nTranslationRestParameters = [string, any]
@@ -22,13 +31,14 @@ export function useI18n(namespace?: string) {
return normalFn
}
const { t, ...other } = i18n.global
- const tFn = (key: string, ...arg: any[]) => {
+ const tFn: I18nGlobalTranslation = (key: string, ...arg: any[]) => {
if (!key) {
return ''
}
if (!key.includes('.') && !namespace) {
return key
}
+ // @ts-expect-error no-err
return t(getKey(namespace, key), ...(arg as I18nTranslationRestParameters))
}
return {
diff --git a/src/composables/locale/locale.ts b/src/composables/locale/locale.ts
new file mode 100644
index 0000000..1b8769d
--- /dev/null
+++ b/src/composables/locale/locale.ts
@@ -0,0 +1,26 @@
+import { storeToRefs } from 'pinia'
+import { i18n, loadLanguageAsync } from '~/modules/i18n'
+
+export function useLocale() {
+ const localeStore = useLocaleStore()
+ const { setLocale } = localeStore
+ const { getLocale } = storeToRefs(localeStore)
+
+ const changeLocale = async (locale: string) => {
+ const currentLocale = unref(i18n.global.locale) as string
+ if (locale === currentLocale) {
+ return locale
+ }
+
+ await loadLanguageAsync(locale)
+
+ setLocale(locale)
+
+ return locale
+ }
+
+ return {
+ getLocale,
+ changeLocale,
+ }
+}
diff --git a/src/composables/promise.ts b/src/composables/promise.ts
new file mode 100644
index 0000000..ee9e1aa
--- /dev/null
+++ b/src/composables/promise.ts
@@ -0,0 +1,166 @@
+import type { ShallowRef, UnwrapRef } from 'vue'
+import { containsProp, isEqual } from '~/utils'
+
+export interface UsePromiseOption {
+ /**
+ * 是否立即执行
+ * default: true
+ */
+ immediate?: boolean
+ /**
+ * 参数变化时是否自动执行函数
+ * default: false
+ */
+ redo?: boolean
+ /**
+ * 仅当redo = true时生效, 防抖时间. 单位 ms
+ * default: 0(ms)
+ */
+ debounce?: number
+ /**
+ * 再次请求时是否忽略当前的loading状态(能否重复执行)
+ * default: false
+ */
+ ignoreLoading?: boolean
+ /**
+ * 函数执行失败时是否对外抛出异常
+ * default: false
+ */
+ throwOnFailed?: boolean
+}
+
+export interface UsePromiseReturnType {
+ data: ShallowRef
+ loading: Ref>
+ handleFn: () => Promise
+ error: ShallowRef
+ finished: Ref>
+}
+
+function isUsePromiseOption(obj: object): obj is UsePromiseOption {
+ return containsProp(
+ obj,
+ 'immediate',
+ 'redo',
+ 'debounce',
+ 'ignoreLoading',
+ 'throwOnFailed',
+ )
+}
+
+export function usePromise(
+ fn: (...args: any[]) => Promise,
+): UsePromiseReturnType
+
+export function usePromise(
+ fn: (...args: any[]) => Promise,
+ opt: UsePromiseOption,
+): UsePromiseReturnType
+
+export function usePromise(
+ fn: (...args: any[]) => Promise,
+ fnArgs: unknown,
+ opt?: UsePromiseOption,
+): UsePromiseReturnType
+
+export function usePromise(
+ fn: (...args: any[]) => Promise,
+ ...args: any[]
+): UsePromiseReturnType {
+ const data = shallowRef(null)
+ const loading = ref(false)
+ const finished = ref(false)
+ const error = shallowRef(null)
+
+ // fn params
+ const fnArgs = ref()
+
+ // config
+ let config: UsePromiseOption = {
+ immediate: true,
+ redo: false,
+ debounce: 0,
+ ignoreLoading: false,
+ throwOnFailed: false,
+ }
+
+ function handleFn(): Promise {
+ return new Promise((resolve, reject) => {
+ const { ignoreLoading, throwOnFailed } = config
+ if (!ignoreLoading && loading.value) {
+ return
+ }
+ loading.value = true
+ finished.value = false
+ fn(undefined, unref(fnArgs))
+ .then((res) => {
+ data.value = res
+ error.value = null
+ return resolve(res)
+ })
+ .catch((e) => {
+ data.value = null
+ error.value = e
+ if (throwOnFailed) {
+ return reject(e)
+ }
+ return resolve(null)
+ })
+ .finally(() => {
+ loading.value = false
+ finished.value = true
+ })
+ })
+ }
+
+ // 存在不在vue3 setup中执行.
+ const scoped = effectScope()
+
+ scoped.run(() => {
+ if (args.length > 0) {
+ if (isUsePromiseOption(args[0])) {
+ config = { ...config, ...args[0] }
+ }
+ else { fnArgs.value = args[0] }
+ }
+
+ if (args.length > 1) {
+ if (isUsePromiseOption(args[1])) {
+ config = { ...config, ...args[1] }
+ }
+ }
+
+ const { debounce, immediate, redo } = config
+ const debounceFn = useDebounceFn(() => {
+ return handleFn()
+ }, debounce)
+
+ if (immediate) {
+ debounceFn().then()
+ }
+
+ if (redo) {
+ watch(
+ fnArgs,
+ (newArgs, oldArgs) => {
+ if (!isEqual(newArgs, oldArgs)) {
+ debounceFn().then()
+ }
+ },
+ { deep: true },
+ )
+ }
+ })
+
+ tryOnBeforeUnmount(() => {
+ scoped.stop()
+ })
+
+ return {
+ data,
+ loading,
+ finished,
+ error,
+ handleFn,
+ }
+}
diff --git a/src/composables/router/menu.ts b/src/composables/router/menu.ts
index d117e2d..8f99c40 100644
--- a/src/composables/router/menu.ts
+++ b/src/composables/router/menu.ts
@@ -1,66 +1,54 @@
-import type { RouteRecordRaw } from 'vue-router'
+import { storeToRefs } from 'pinia'
+import { filterTree, getAllParentPath, hideFilter } from '~/utils'
export function useMenu() {
- const menuStore = useMenuStore()
- const userStore = useUserStore()
- const { resolve } = useRouter()
+ const routeStore = useRouteStore()
+ const { initMenu, menuListRef } = storeToRefs(routeStore)
- /** 判断给定的route中是否具有给定perms列表的权限 */
- function hasPermission(route: RouteRecordRaw, perms: any[] = []) {
- if (!route.meta?.perm) {
- return true
+ const getMenuList = async () => {
+ if (!unref(initMenu)) {
+ await routeStore.buildMenu()
}
-
- // 递归寻找子节点perm
- if (route.meta?.perm === true && route.children?.length) {
- return filterAsyncRoutes(route.children, perms).length
- }
-
- // 否则直接通过perm进行判断
- return perms.includes(
- Array.isArray(route.meta?.perm)
- ? route.meta.perm[0]?.perm
- : route.meta?.perm,
- )
+ const menuList = unref(menuListRef)
+ return filterTree(menuList, hideFilter)
}
- // 过滤掉所有perm不匹配的路由
- function filterAsyncRoutes(routes: RouteRecordRaw[], perms: any[]) {
- return routes.reduce((rs: RouteRecordRaw[], route) => {
- if (hasPermission(route, perms)) {
- rs.push({
- ...route,
- children: route.children ? filterAsyncRoutes(route.children, perms) : [],
- })
- }
- return rs
- }, [])
+ const getCurrentParentPath = async (currentPath: string) => {
+ const menus = await getMenuList()
+ const allParentPath = await getAllParentPath(menus, currentPath)
+ return allParentPath?.[0]
}
- async function generateRoutes() {
- const perms = userStore.userInfo?.perms ?? ['role', 'role/post']
- menuStore.menuList = filterAsyncRoutes(getRoutes(), perms)
+ const getShallowMenus = async () => {
+ const menus = await getMenuList()
+ const shallowMenuList = menus.map(item => ({
+ ...item,
+ children: undefined,
+ }))
+ // if (isRoleMode()) {
+ // const routes = router.getRoutes()
+ // return shallowMenuList.filter(basicFilter(routes))
+ // }
+ return shallowMenuList
}
- function getMenuList(routes: RouteRecordRaw[]) {
- return routes.reduce((rs: RouteRecordRaw[], route) => {
- if (!route.meta?.hidden && !route.meta?.hideInMenu) {
- rs.push({
- ...route,
- path: resolve(route.redirect || route).path,
- children: route.children ? getMenuList(route.children) : [],
- })
- }
- return rs
- }, []).sort((a, b) => {
- return (a.meta?.order || 999) - (b.meta?.order || 999)
- })
+ const getChildrenMenus = async (parentPath: string) => {
+ const menus = await getMenuList()
+ const parent = menus.find(item => item.path === parentPath)
+ if (!parent || !parent.children || !!parent?.meta?.hideChildrenInMenu) {
+ return [] as Menu[]
+ }
+ // if (isRoleMode()) {
+ // const routes = router.getRoutes()
+ // return filterTree(parent.children, basicFilter(routes))
+ // }
+ return parent.children
}
- const menuList = computed(() => getMenuList(menuStore.menuList))
-
return {
- menuList,
- generateRoutes,
+ getMenuList,
+ getShallowMenus,
+ getChildrenMenus,
+ getCurrentParentPath,
}
}
diff --git a/src/composables/router/routes.ts b/src/composables/router/routes.ts
deleted file mode 100644
index 836138c..0000000
--- a/src/composables/router/routes.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import type { RouteRecordRaw } from 'vue-router'
-import routes from '~pages'
-
-/**
- * 过滤所有hidden的路由
- */
-function filterHiddenRoutes(routes: RouteRecordRaw[]) {
- return routes.filter((i) => {
- if (i.children) {
- i.children = filterHiddenRoutes(i.children)
- }
- return !i.meta?.hidden
- })
-}
-
-/**
- * 获取真实存在的路由
- */
-export function getRoutes() {
- return filterHiddenRoutes(routes)
-}
diff --git a/src/composables/setting/menu-setting.ts b/src/composables/setting/menu-setting.ts
index 81f4dfb..b7e3283 100644
--- a/src/composables/setting/menu-setting.ts
+++ b/src/composables/setting/menu-setting.ts
@@ -1,4 +1,6 @@
import {
+ MenuModeEnum,
+ NavBarModeEnum,
SIDE_BAR_MINI_WIDTH,
SIDE_BAR_SHOW_TIT_MINI_WIDTH,
TriggerEnum,
@@ -8,59 +10,66 @@ const mixSideHasChildren = ref(false)
export function useMenuSetting() {
const { getFullContent: fullContent } = useFullContent()
- const configStore = useAppConfigStore()
+ const appConfig = useAppConfig()
const { getShowLogo } = useRootSetting()
- const getCollapsed = computed(() => configStore.sidebar.collapsed)
- const getMenuType = computed(() => configStore.navBarMode)
- const getMenuMode = computed(() => configStore.menu.mode)
- const getMenuFixed = computed(() => configStore.sidebar.fixed)
- const getShowMenu = computed(() => configStore.menu.show)
- const getMenuHidden = computed(() => !configStore.sidebar.visible)
- const getMenuWidth = computed(() => configStore.sidebar.width)
- const getTrigger = computed(() => configStore.sidebar.trigger)
- const getMenuTheme = computed(() => configStore.sidebar.theme)
- const getSplit = computed(() => configStore.menu.split)
- const getMenuBgColor = computed(() => configStore.sidebar.bgColor)
- const getMixSideTrigger = computed(() => configStore.menu.mixSideTrigger)
+ const getCollapsed = computed(() => appConfig.sidebar.value.collapsed)
+ const getMenuType = computed(() => unref(appConfig.navBarMode))
+ const getMenuMode = computed(() => appConfig.menu.value.mode)
+ const getMenuFixed = computed(() => appConfig.sidebar.value.fixed)
+ const getShowMenu = computed(() => appConfig.menu.value.show)
+ const getMenuHidden = computed(() => !appConfig.sidebar.value.visible)
+ const getMenuWidth = computed(() => appConfig.sidebar.value.width)
+ const getTrigger = computed(() => appConfig.sidebar.value.trigger)
+ const getMenuTheme = computed(() => appConfig.sidebar.value.theme)
+ const getSplit = computed(() => appConfig.menu.value.split)
+ const getMenuBgColor = computed(() => appConfig.sidebar.value.bgColor)
+ const getMixSideTrigger = computed(() => appConfig.menu.value.mixSideTrigger)
const getShowSidebar = computed(() => {
return (
unref(getSplit)
|| (unref(getShowMenu)
- && !unref(configStore.isHorizontal)
+ && !unref(appConfig.isHorizontal)
&& !unref(fullContent))
)
})
- const getCanDrag = computed(() => configStore.menu.canDrag)
- const getAccordion = computed(() => configStore.menu.accordion)
- const getMixSideFixed = computed(() => configStore.menu.mixSideFixed)
- const getTopMenuAlign = computed(() => configStore.menu.topMenuAlign)
- const getCloseMixSidebarOnChange = computed(() => configStore.closeMixSidebarOnChange)
- const getIsSidebarType = computed(() => configStore.isSidebar)
- const getIsTopMenu = computed(() => configStore.isTopMenu)
+ const getCanDrag = computed(() => appConfig.menu.value.canDrag)
+ const getAccordion = computed(() => appConfig.menu.value.accordion)
+ const getMixSideFixed = computed(() => appConfig.menu.value.mixSideFixed)
+ const getTopMenuAlign = computed(() => appConfig.menu.value.topMenuAlign)
+ const getCloseMixSidebarOnChange = computed(() => appConfig.closeMixSidebarOnChange.value)
+ const getIsSidebarType = computed(() => unref(getMenuType) === NavBarModeEnum.SIDEBAR)
+ const getIsTopMenu = computed(() => unref(getMenuType) === NavBarModeEnum.TOP_MENU)
const getMenuShowLogo = computed(() => unref(getShowLogo) && unref(getIsSidebarType))
- const getCollapsedShowTitle = computed(() => configStore.isCollapsedShowTitle)
- const getShowTopMenu = computed(() => unref(configStore.isHorizontal) || unref(getSplit))
+ const getCollapsedShowTitle = computed(() => appConfig.menu.value.collapsedShowTitle)
+ const getShowTopMenu = computed(() => unref(getMenuMode) === MenuModeEnum.HORIZONTAL || unref(getSplit))
+
const getShowHeaderTrigger = computed(() => {
if (
- configStore.isTopMenu
+ unref(getMenuType) === NavBarModeEnum.TOP_MENU
|| !unref(getShowMenu)
|| unref(getMenuHidden)
- )
+ ) {
return false
+ }
return unref(getTrigger) === TriggerEnum.HEADER
})
const getShowCenterTrigger = computed(() => unref(getTrigger) === TriggerEnum.CENTER)
const getShowFooterTrigger = computed(() => unref(getTrigger) === TriggerEnum.FOOTER)
- const getIsHorizontal = computed(() => configStore.isHorizontal)
- const getIsMixSidebar = computed(() => configStore.isMixSidebar)
- const getIsMixMode = computed(() => configStore.isMixMode)
+ const getIsHorizontal = computed(() => unref(getMenuMode) === MenuModeEnum.HORIZONTAL)
+ const getIsMixSidebar = computed(() => unref(getMenuType) === NavBarModeEnum.MIX_SIDEBAR)
+ const getIsMixMode = computed(() => {
+ return (
+ unref(getMenuMode) === MenuModeEnum.INLINE
+ && unref(getMenuType) === NavBarModeEnum.MIX
+ )
+ })
const getMiniWidthNumber = computed(() => {
- const { collapsedShowTitle } = configStore.menu
+ const { collapsedShowTitle } = appConfig.menu.value
return collapsedShowTitle
? SIDE_BAR_SHOW_TIT_MINI_WIDTH
: SIDE_BAR_MINI_WIDTH
@@ -93,14 +102,18 @@ export function useMenuSetting() {
return `calc(100% - ${unref(width)}px)`
})
+ const getIsFixed = computed(() => {
+ return unref(getMixSideFixed) && unref(mixSideHasChildren)
+ })
+
function setMenuSetting(menuSetting: Partial): void {
- configStore.setMenu(menuSetting)
+ appConfig.setAppConfig({ menu: menuSetting })
}
function setSidebarSetting(
sidebarSetting: Partial,
): void {
- configStore.setSidebar(sidebarSetting)
+ appConfig.setAppConfig({ sidebar: sidebarSetting })
}
function setSiderWidth(width: number) {
@@ -148,6 +161,7 @@ export function useMenuSetting() {
getCloseMixSidebarOnChange,
getMixSideTrigger,
getMixSideFixed,
+ getIsFixed,
mixSideHasChildren,
getMenuShowLogo,
}
diff --git a/src/composables/web/theme.ts b/src/composables/web/theme.ts
new file mode 100644
index 0000000..2bf5adc
--- /dev/null
+++ b/src/composables/web/theme.ts
@@ -0,0 +1,343 @@
+import { storeToRefs } from 'pinia'
+import { ThemeEnum } from '~/constants'
+import type { MaybeElementRef } from '~/utils'
+import { darken, generateColors, lighten, pickTextColorBasedOnBgColor, setCssVar, toggleClass } from '~/utils'
+
+const HEADER_HEIGHT = '--header-height'
+const HEADER_BG_COLOR_VAR = '--header-background-color'
+const HEADER_TEXT_COLOR_VAR = '--header-text-color'
+const HEADER_ACTION_HOVER_BG_COLOR_VAR = '--header-action-hover-bg-color'
+
+const ASIDE_WIDTH = '--aside-width'
+const ASIDE_DARK_BG_COLOR = '--aside-background-color'
+const ASIDE_TEXT_COLOR_VAR = '--aside-text-color'
+
+const TRIGGER_BG_COLOR_VAR = '--trigger-background-color'
+
+const TAB_BAR_HEIGHT = '--tab-bar-height'
+
+const FOOTER_HEIGHT = '--footer-height'
+
+const LIGHT_TEXT_COLOR = 'rgba(0,0,0,.85)'
+const DARK_TEXT_COLOR = '#fff'
+
+export function createMediaPrefersColorSchemeListen() {
+ const { setAppConfig } = useAppConfig()
+
+ // 监听系统主题变更
+ useEventListener(
+ window.matchMedia('(prefers-color-scheme: dark)'),
+ 'change',
+ (e: MediaQueryListEvent) => setAppConfig({ theme: e.matches ? ThemeEnum.DARK : ThemeEnum.LIGHT }),
+ )
+}
+
+function toggleGrayMode(val: boolean) {
+ toggleClass(val, 'gray-mode', document.documentElement)
+}
+
+function toggleColorWeak(val: boolean) {
+ toggleClass(val, 'color-weak', document.documentElement)
+}
+
+export function createGridLayoutListen(el: MaybeElementRef | null) {
+ const {
+ isTopMenu,
+ sidebar,
+ header,
+ footer,
+ tabTar,
+ getCollapsedShowTitle,
+ menu,
+ isMixSidebar,
+ } = useAppConfig()
+ const asideWidth = useCssVar(ASIDE_WIDTH, el, {
+ initialValue: `${unref(sidebar).width}px`,
+ })
+ const headerHeight = useCssVar(HEADER_HEIGHT, el, {
+ initialValue: `${unref(header).height}px`,
+ })
+ const tabBarHeight = useCssVar(TAB_BAR_HEIGHT, el, {
+ initialValue: `${unref(tabTar).height}px`,
+ })
+ const footerHeight = useCssVar(FOOTER_HEIGHT, el, {
+ initialValue: `${unref(footer).height}px`,
+ })
+
+ watchEffect(() => {
+ const getAsideWidth = () => {
+ if (unref(isTopMenu) || !unref(sidebar).visible)
+ return 0
+ if (unref(getCollapsedShowTitle)) {
+ return unref(menu).mixSideFixed && unref(isMixSidebar)
+ ? unref(sidebar).mixSidebarWidth + unref(menu).subMenuWidth
+ : unref(sidebar).mixSidebarWidth
+ }
+ if (unref(sidebar).collapsed) {
+ return unref(menu).mixSideFixed && unref(isMixSidebar)
+ ? unref(sidebar).collapsedWidth + unref(menu).subMenuWidth
+ : unref(sidebar).collapsedWidth
+ }
+ return unref(sidebar).width
+ }
+
+ const getHeaderHeight = () => {
+ if (!unref(header).visible)
+ return 0
+ return unref(header).height
+ }
+
+ const getTabBarHeight = () => {
+ if (!unref(tabTar).visible)
+ return 0
+ return unref(tabTar).height
+ }
+
+ const getFooterHeight = () => {
+ if (!unref(footer).visible)
+ return 0
+ return unref(footer).height
+ }
+ asideWidth.value = `${getAsideWidth()}px`
+ headerHeight.value = `${getHeaderHeight()}px`
+ tabBarHeight.value = `${getTabBarHeight()}px`
+ footerHeight.value = `${getFooterHeight()}px`
+ })
+}
+
+export function createThemeColorListen() {
+ const {
+ sidebar,
+ header,
+ grayMode,
+ colorWeak,
+ setAppConfig,
+ } = useAppConfig()
+
+ const headerBgColor = useCssVar(HEADER_BG_COLOR_VAR, null, {
+ initialValue: `${unref(header).bgColor}px`,
+ })
+
+ const headerTextColor = useCssVar(
+ HEADER_TEXT_COLOR_VAR,
+ null,
+ {
+ initialValue: LIGHT_TEXT_COLOR,
+ },
+ )
+ const headerActionHoverBgColor = useCssVar(
+ HEADER_ACTION_HOVER_BG_COLOR_VAR,
+ null,
+ )
+
+ const sidebarBgColor = useCssVar(
+ ASIDE_DARK_BG_COLOR,
+ null,
+ {
+ initialValue: `${unref(sidebar).bgColor}px`,
+ },
+ )
+
+ const asideTextColor = useCssVar(
+ ASIDE_TEXT_COLOR_VAR,
+ null,
+ {
+ initialValue: LIGHT_TEXT_COLOR,
+ },
+ )
+ const triggerBackgroundColor = useCssVar(
+ TRIGGER_BG_COLOR_VAR,
+ null,
+ )
+
+ watchEffect(() => {
+ headerBgColor.value = unref(header).bgColor
+ headerTextColor.value = pickTextColorBasedOnBgColor(
+ unref(header).bgColor,
+ LIGHT_TEXT_COLOR,
+ DARK_TEXT_COLOR,
+ )
+
+ if (['#fff', '#ffffff'].includes(unref(header).bgColor.toLowerCase())) {
+ headerActionHoverBgColor.value = darken(unref(header).bgColor, 6)
+ setAppConfig({ header: { theme: ThemeEnum.LIGHT } })
+ }
+ else {
+ headerActionHoverBgColor.value = lighten(unref(header).bgColor, 6)
+ setAppConfig({ header: { theme: ThemeEnum.DARK } })
+ }
+
+ sidebarBgColor.value = unref(sidebar).bgColor
+ asideTextColor.value = pickTextColorBasedOnBgColor(
+ unref(sidebar).bgColor,
+ LIGHT_TEXT_COLOR,
+ DARK_TEXT_COLOR,
+ )
+
+ if (['#fff', '#ffffff'].includes(unref(sidebar).bgColor.toLowerCase())) {
+ triggerBackgroundColor.value = darken(unref(sidebar).bgColor, 6)
+ setAppConfig({ sidebar: { theme: ThemeEnum.LIGHT } })
+ }
+ else {
+ triggerBackgroundColor.value = lighten(unref(sidebar).bgColor, 6)
+ setAppConfig({ sidebar: { theme: ThemeEnum.DARK } })
+ }
+
+ toggleGrayMode(unref(grayMode))
+ toggleColorWeak(unref(colorWeak))
+ // toggleClass(
+ // ThemeEnum.DARK === unref(theme),
+ // ThemeEnum.DARK,
+ // document.documentElement,
+ // )
+ })
+}
+
+export function useAppTheme() {
+ const themeStore = useThemeStore()
+ const { setThemeConfig, setSidebarTheme, setHeaderTheme } = themeStore
+ const { themeConfig: getThemeConfig, theme, sidebar, header } = storeToRefs(themeStore)
+
+ const darkRef = useDark({
+ selector: 'html',
+ attribute: 'theme-mode',
+ valueDark: 'dark',
+ valueLight: '',
+ })
+ const toggleDark = useToggle(darkRef)
+
+ const isDark = computed(() => unref(darkRef) && unref(theme) === ThemeEnum.DARK)
+ const toggleTheme = (dark: boolean) => {
+ theme.value = dark ? ThemeEnum.DARK : ThemeEnum.LIGHT
+ toggleDark(dark)
+ }
+
+ // sidebar
+ const isSidebarDark = computed(() => (unref(theme) === ThemeEnum.DARK || unref(sidebar) === ThemeEnum.DARK))
+ const toggleSidebarTheme = (dark: boolean) => {
+ setSidebarTheme(dark ? ThemeEnum.DARK : ThemeEnum.LIGHT)
+ }
+
+ const isHeaderDark = computed(() => (unref(theme) === ThemeEnum.DARK || unref(header) === ThemeEnum.DARK))
+ const toggleHeaderTheme = (dark: boolean) => {
+ setHeaderTheme(dark ? ThemeEnum.DARK : ThemeEnum.LIGHT)
+ }
+
+ const primaryColor = computed(() => {
+ return getThemeConfig.value.primaryColor
+ })
+
+ const infoColor = computed(() => {
+ return getThemeConfig.value.infoColor
+ })
+
+ const successColor = computed(() => {
+ return getThemeConfig.value.successColor
+ })
+
+ const warningColor = computed(() => {
+ return getThemeConfig.value.warningColor
+ })
+
+ const errorColor = computed(() => {
+ return getThemeConfig.value.errorColor
+ })
+
+ const themeColors = computed(() => {
+ let colors: ThemeColors = {}
+ const themeConfig = getThemeConfig.value
+
+ if (themeConfig.primaryColor) {
+ const primaryColorList = generateColors(themeConfig.primaryColor)
+ colors = {
+ ...colors,
+ ...{
+ primaryColor: primaryColorList[5],
+ primaryColorHover: primaryColorList[4],
+ primaryColorPressed: primaryColorList[4],
+ primaryColorSuppl: primaryColorList[6],
+ },
+ }
+ }
+
+ if (themeConfig.infoColor) {
+ const infoColorList = generateColors(themeConfig.infoColor)
+ colors = {
+ ...colors,
+ ...{
+ infoColor: infoColorList[5],
+ infoColorHover: infoColorList[4],
+ infoColorPressed: infoColorList[4],
+ infoColorSuppl: infoColorList[6],
+ },
+ }
+ }
+
+ if (themeConfig.successColor) {
+ const successColorList = generateColors(themeConfig.successColor)
+ colors = {
+ ...colors,
+ ...{
+ successColor: successColorList[5],
+ successColorHover: successColorList[4],
+ successColorPressed: successColorList[4],
+ successColorSuppl: successColorList[6],
+ },
+ }
+ }
+
+ if (themeConfig.warningColor) {
+ const warningColorList = generateColors(themeConfig.warningColor)
+ colors = {
+ ...colors,
+ ...{
+ warningColor: warningColorList[5],
+ warningColorHover: warningColorList[4],
+ warningColorPressed: warningColorList[4],
+ warningColorSuppl: warningColorList[6],
+ },
+ }
+ }
+
+ if (themeConfig.errorColor) {
+ const errorColorList = generateColors(themeConfig.errorColor)
+ colors = {
+ ...colors,
+ ...{
+ errorColor: errorColorList[5],
+ errorColorHover: errorColorList[4],
+ errorColorPressed: errorColorList[4],
+ errorColorSuppl: errorColorList[6],
+ },
+ }
+ }
+ return colors
+ })
+
+ watch(
+ themeColors,
+ (val) => {
+ val.primaryColor && setCssVar('--primary-color', val.primaryColor)
+ val.successColor && setCssVar('--success-color', val.successColor)
+ val.errorColor && setCssVar('--error-color', val.errorColor)
+ val.warningColor && setCssVar('--warning-color', val.warningColor)
+ },
+ { deep: true },
+ )
+
+ return {
+ isDark,
+ isSidebarDark,
+ isHeaderDark,
+ toggleTheme,
+ primaryColor,
+ infoColor,
+ successColor,
+ warningColor,
+ errorColor,
+ themeColors,
+ setThemeConfig,
+ toggleSidebarTheme,
+ toggleHeaderTheme,
+ }
+}
diff --git a/src/constants/app.ts b/src/constants/app.ts
index eef50b2..97e927d 100644
--- a/src/constants/app.ts
+++ b/src/constants/app.ts
@@ -23,6 +23,16 @@ export enum NavBarModeEnum {
TOP_MENU = 'top-menu',
}
+/**
+ * 权限模式(功能权限)
+ */
+export enum PermissionModeEnum {
+ // 角色权限模式
+ ROLE = 'role',
+ // 权限代码模式
+ PERM = 'perm',
+}
+
// Session过期处理方式
export enum SessionTimeoutProcessingEnum {
ROUTE_JUMP, // 路由跳转
@@ -54,3 +64,17 @@ export enum ErrorTypeEnum {
AJAX = 'ajax',
PROMISE = 'promise',
}
+
+// 异常类型
+export enum ExceptionEnum {
+ // 无权访问
+ PAGE_NOT_ACCESS = 403,
+ // 页面未找到
+ PAGE_NOT_FOUND = 404,
+ // 服务器错误
+ ERROR = 500,
+ // 网络错误
+ NET_WORK_ERROR = 10000,
+ // 页面无数据
+ PAGE_NOT_DATA = 10100,
+}
diff --git a/src/constants/design.ts b/src/constants/design.ts
index d543a26..f4b94d0 100644
--- a/src/constants/design.ts
+++ b/src/constants/design.ts
@@ -40,6 +40,19 @@ export const SIDE_BAR_BG_COLOR_LIST: string[] = [
'#383f45',
]
+export enum ThemeChangeEnum {
+ THEME_CHANGE,
+
+ THEME_PRIMARY_COLOR_CHANGE,
+ THEME_INFO_COLOR_CHANGE,
+ THEME_SUCCESS_COLOR_CHANGE,
+ THEME_WARNING_COLOR_CHANGE,
+ THEME_ERROR_COLOR_CHANGE,
+
+ THEME_HEADER_BG_COLOR_CHANGE,
+ THEME_SIDEBAR_BG_COLOR_CHANGE,
+}
+
// 设置事件Enum
export enum HandlerSettingEnum {
CHANGE_LAYOUT,
diff --git a/src/layouts/404.vue b/src/layouts/404.vue
deleted file mode 100644
index ca5ada7..0000000
--- a/src/layouts/404.vue
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-