mirror of
https://github.com/soybeanjs/soybean-admin.git
synced 2025-09-17 17:26:38 +08:00
feat(projects): 添加生产的主题配置缓存
This commit is contained in:
parent
5c1b086cb4
commit
718c36263e
@ -76,7 +76,7 @@ Soybean Admin 是一个基于 Vue3、Vite、TypeScript、Naive UI 的免费中
|
|||||||
- [x] 引入ECharts替换AntV G2Plot
|
- [x] 引入ECharts替换AntV G2Plot
|
||||||
- [x] 图表示例:ECharts、AntV G2
|
- [x] 图表示例:ECharts、AntV G2
|
||||||
- [x] 多页签:支持query、hash等参数,同一页面支持多个Tab
|
- [x] 多页签:支持query、hash等参数,同一页面支持多个Tab
|
||||||
- [ ] 缓存主题配置
|
- [x] 缓存主题配置
|
||||||
- [ ] 添加锁屏组件、全局Iframe组件
|
- [ ] 添加锁屏组件、全局Iframe组件
|
||||||
- [ ] 示例页面完善
|
- [ ] 示例页面完善
|
||||||
- [ ] 表单、表格示例
|
- [ ] 表单、表格示例
|
||||||
|
@ -15,10 +15,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { zhCN, dateZhCN } from 'naive-ui';
|
import { zhCN, dateZhCN } from 'naive-ui';
|
||||||
import { useThemeStore, subscribeStore } from '@/store';
|
import { useThemeStore, subscribeStore } from '@/store';
|
||||||
|
import { useGlobalEvents } from '@/composables';
|
||||||
|
|
||||||
const theme = useThemeStore();
|
const theme = useThemeStore();
|
||||||
|
|
||||||
subscribeStore();
|
subscribeStore();
|
||||||
|
useGlobalEvents();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
14
src/composables/events.ts
Normal file
14
src/composables/events.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { useEventListener } from '@vueuse/core';
|
||||||
|
import { useThemeStore, useTabStore } from '@/store';
|
||||||
|
|
||||||
|
/** 全局事件 */
|
||||||
|
export function useGlobalEvents() {
|
||||||
|
const theme = useThemeStore();
|
||||||
|
const tab = useTabStore();
|
||||||
|
|
||||||
|
/** 页面离开时缓存多页签数据 */
|
||||||
|
useEventListener(window, 'beforeunload', () => {
|
||||||
|
theme.cacheThemeSettings();
|
||||||
|
tab.cacheTabRoutes();
|
||||||
|
});
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
export * from './system';
|
export * from './system';
|
||||||
export * from './router';
|
export * from './router';
|
||||||
export * from './layout';
|
export * from './layout';
|
||||||
|
export * from './events';
|
||||||
export * from './echarts';
|
export * from './echarts';
|
||||||
|
@ -15,6 +15,8 @@ export enum EnumStorageKey {
|
|||||||
'refresh-token' = '__REFRESH_TOKEN__',
|
'refresh-token' = '__REFRESH_TOKEN__',
|
||||||
/** 用户信息 */
|
/** 用户信息 */
|
||||||
'user-info' = '__USER_INFO__',
|
'user-info' = '__USER_INFO__',
|
||||||
|
/** 主题配置 */
|
||||||
|
'theme-settings' = '__THEME_SETTINGS__',
|
||||||
/** 多页签路由信息 */
|
/** 多页签路由信息 */
|
||||||
'multi-tab-routes' = '__MULTI_TAB_ROUTES__'
|
'multi-tab-routes' = '__MULTI_TAB_ROUTES__'
|
||||||
}
|
}
|
||||||
|
19
src/layouts/common/GlobalHeader/components/SettingButton.vue
Normal file
19
src/layouts/common/GlobalHeader/components/SettingButton.vue
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<hover-container
|
||||||
|
class="w-40px h-full"
|
||||||
|
tooltip-content="主题配置"
|
||||||
|
:inverted="theme.header.inverted"
|
||||||
|
@click="app.toggleSettingDrawerVisible"
|
||||||
|
>
|
||||||
|
<icon-ant-design-setting-outlined class="text-20px" />
|
||||||
|
</hover-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useAppStore, useThemeStore } from '@/store';
|
||||||
|
|
||||||
|
const app = useAppStore();
|
||||||
|
const theme = useThemeStore();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
@ -6,5 +6,16 @@ import FullScreen from './FullScreen.vue';
|
|||||||
import ThemeMode from './ThemeMode.vue';
|
import ThemeMode from './ThemeMode.vue';
|
||||||
import UserAvatar from './UserAvatar.vue';
|
import UserAvatar from './UserAvatar.vue';
|
||||||
import SystemMessage from './SystemMessage.vue';
|
import SystemMessage from './SystemMessage.vue';
|
||||||
|
import SettingButton from './SettingButton.vue';
|
||||||
|
|
||||||
export { MenuCollapse, GlobalBreadcrumb, HeaderMenu, GithubSite, FullScreen, ThemeMode, UserAvatar, SystemMessage };
|
export {
|
||||||
|
MenuCollapse,
|
||||||
|
GlobalBreadcrumb,
|
||||||
|
HeaderMenu,
|
||||||
|
GithubSite,
|
||||||
|
FullScreen,
|
||||||
|
ThemeMode,
|
||||||
|
UserAvatar,
|
||||||
|
SystemMessage,
|
||||||
|
SettingButton
|
||||||
|
};
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
<full-screen />
|
<full-screen />
|
||||||
<theme-mode />
|
<theme-mode />
|
||||||
<system-message />
|
<system-message />
|
||||||
|
<setting-button v-if="isProd" />
|
||||||
<user-avatar />
|
<user-avatar />
|
||||||
</div>
|
</div>
|
||||||
</dark-mode-container>
|
</dark-mode-container>
|
||||||
@ -29,7 +30,8 @@ import {
|
|||||||
FullScreen,
|
FullScreen,
|
||||||
ThemeMode,
|
ThemeMode,
|
||||||
UserAvatar,
|
UserAvatar,
|
||||||
SystemMessage
|
SystemMessage,
|
||||||
|
SettingButton
|
||||||
} from './components';
|
} from './components';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -44,6 +46,8 @@ interface Props {
|
|||||||
defineProps<Props>();
|
defineProps<Props>();
|
||||||
|
|
||||||
const theme = useThemeStore();
|
const theme = useThemeStore();
|
||||||
|
|
||||||
|
const isProd = import.meta.env.PROD;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -27,11 +27,9 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, computed, nextTick, watch } from 'vue';
|
import { ref, reactive, computed, nextTick, watch } from 'vue';
|
||||||
import { useEventListener } from '@vueuse/core';
|
|
||||||
import { ChromeTab, ButtonTab } from '@soybeanjs/vue-admin-tab';
|
import { ChromeTab, ButtonTab } from '@soybeanjs/vue-admin-tab';
|
||||||
import { Icon } from '@iconify/vue';
|
import { Icon } from '@iconify/vue';
|
||||||
import { useThemeStore, useTabStore } from '@/store';
|
import { useThemeStore, useTabStore } from '@/store';
|
||||||
import { setTabRoutes } from '@/utils';
|
|
||||||
import { ContextMenu } from './components';
|
import { ContextMenu } from './components';
|
||||||
|
|
||||||
interface Emits {
|
interface Emits {
|
||||||
@ -95,11 +93,6 @@ watch(
|
|||||||
immediate: true
|
immediate: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
/** 页面离开时缓存多页签数据 */
|
|
||||||
useEventListener(window, 'beforeunload', () => {
|
|
||||||
setTabRoutes(tab.tabs);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<theme-config />
|
<theme-config />
|
||||||
</n-drawer-content>
|
</n-drawer-content>
|
||||||
</n-drawer>
|
</n-drawer>
|
||||||
<drawer-button />
|
<drawer-button v-if="isDev" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -17,6 +17,8 @@ import { useAppStore } from '@/store';
|
|||||||
import { DrawerButton, DarkMode, LayoutMode, ThemeColorSelect, PageFunc, PageView, ThemeConfig } from './components';
|
import { DrawerButton, DarkMode, LayoutMode, ThemeColorSelect, PageFunc, PageView, ThemeConfig } from './components';
|
||||||
|
|
||||||
const app = useAppStore();
|
const app = useAppStore();
|
||||||
|
|
||||||
|
const isDev = import.meta.env.DEV;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import type { RouteRecordNormalized, RouteLocationNormalizedLoaded } from 'vue-router';
|
import type { RouteRecordNormalized, RouteLocationNormalizedLoaded } from 'vue-router';
|
||||||
|
import { EnumStorageKey } from '@/enum';
|
||||||
|
import { setLocal, getLocal } from '@/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据vue路由获取tab路由
|
* 根据vue路由获取tab路由
|
||||||
@ -55,3 +57,30 @@ function hasFullPath(
|
|||||||
): route is RouteLocationNormalizedLoaded {
|
): route is RouteLocationNormalizedLoaded {
|
||||||
return Boolean((route as RouteLocationNormalizedLoaded).fullPath);
|
return Boolean((route as RouteLocationNormalizedLoaded).fullPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 缓存多页签数据 */
|
||||||
|
export function setTabRoutes(data: GlobalTabRoute[]) {
|
||||||
|
setLocal(EnumStorageKey['multi-tab-routes'], data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取缓存的多页签数据 */
|
||||||
|
export function getTabRoutes() {
|
||||||
|
const routes: GlobalTabRoute[] = [];
|
||||||
|
const data = getLocal<GlobalTabRoute[]>(EnumStorageKey['multi-tab-routes']);
|
||||||
|
if (data) {
|
||||||
|
const defaultTabRoutes = data.map(item => ({
|
||||||
|
...item,
|
||||||
|
scrollPosition: {
|
||||||
|
left: 0,
|
||||||
|
top: 0
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
routes.push(...defaultTabRoutes);
|
||||||
|
}
|
||||||
|
return routes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 清空多页签数据 */
|
||||||
|
export function clearTabRoutes() {
|
||||||
|
setTabRoutes([]);
|
||||||
|
}
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
import type { Router, RouteLocationNormalizedLoaded } from 'vue-router';
|
import type { Router, RouteLocationNormalizedLoaded } from 'vue-router';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { useRouterPush } from '@/composables';
|
import { useRouterPush } from '@/composables';
|
||||||
import { getTabRoutes, clearTabRoutes } from '@/utils';
|
|
||||||
import { useThemeStore } from '../theme';
|
import { useThemeStore } from '../theme';
|
||||||
import { getTabRouteByVueRoute, isInTabRoutes, getIndexInTabRoutes, getIndexInTabRoutesByRouteName } from './helpers';
|
import {
|
||||||
|
getTabRouteByVueRoute,
|
||||||
|
isInTabRoutes,
|
||||||
|
getIndexInTabRoutes,
|
||||||
|
getIndexInTabRoutesByRouteName,
|
||||||
|
setTabRoutes,
|
||||||
|
getTabRoutes,
|
||||||
|
clearTabRoutes
|
||||||
|
} from './helpers';
|
||||||
|
|
||||||
interface TabState {
|
interface TabState {
|
||||||
/** 多页签数据 */
|
/** 多页签数据 */
|
||||||
@ -43,6 +50,10 @@ export const useTabStore = defineStore('tab-store', {
|
|||||||
clearTabRoutes();
|
clearTabRoutes();
|
||||||
this.$reset();
|
this.$reset();
|
||||||
},
|
},
|
||||||
|
/** 缓存页签路由数据 */
|
||||||
|
cacheTabRoutes() {
|
||||||
|
setTabRoutes(this.tabs);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* 设置当前路由对应的页签为激活状态
|
* 设置当前路由对应的页签为激活状态
|
||||||
* @param fullPath - 路由fullPath
|
* @param fullPath - 路由fullPath
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
import type { GlobalThemeOverrides } from 'naive-ui';
|
import type { GlobalThemeOverrides } from 'naive-ui';
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
import { themeSetting } from '@/settings';
|
import { themeSetting } from '@/settings';
|
||||||
import { getThemeColor, getColorPalette, addColorAlpha } from '@/utils';
|
import { EnumStorageKey } from '@/enum';
|
||||||
|
import { getThemeColor, getColorPalette, addColorAlpha, setLocal, getLocal, removeLocal } from '@/utils';
|
||||||
|
|
||||||
|
/** 初始化主题配置 */
|
||||||
|
export function initThemeSettings() {
|
||||||
|
const isProd = import.meta.env.PROD;
|
||||||
|
// 生产环境才缓存主题配置,本地开发实时调整配置更改配置的json
|
||||||
|
const storageSettings = getThemeSettings();
|
||||||
|
if (isProd && storageSettings) {
|
||||||
|
return storageSettings;
|
||||||
|
}
|
||||||
|
|
||||||
/** 获取主题配置 */
|
|
||||||
export function getThemeSettings() {
|
|
||||||
const themeColor = getThemeColor() || themeSetting.themeColor;
|
const themeColor = getThemeColor() || themeSetting.themeColor;
|
||||||
const info = themeSetting.isCustomizeInfoColor ? themeSetting.otherColor.info : getColorPalette(themeColor, 7);
|
const info = themeSetting.isCustomizeInfoColor ? themeSetting.otherColor.info : getColorPalette(themeColor, 7);
|
||||||
const otherColor = { ...themeSetting.otherColor, info };
|
const otherColor = { ...themeSetting.otherColor, info };
|
||||||
@ -70,3 +78,18 @@ export function getNaiveThemeOverrides(colors: Record<ColorType, string>): Globa
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 获取缓存中的主题配置 */
|
||||||
|
function getThemeSettings() {
|
||||||
|
return getLocal<Theme.Setting>(EnumStorageKey['theme-settings']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取缓存中的主题配置 */
|
||||||
|
export function setThemeSettings(settings: Theme.Setting) {
|
||||||
|
return setLocal(EnumStorageKey['theme-settings'], settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 清除缓存配置 */
|
||||||
|
export function clearThemeSettings() {
|
||||||
|
removeLocal(EnumStorageKey['theme-settings']);
|
||||||
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { darkTheme } from 'naive-ui';
|
import { darkTheme } from 'naive-ui';
|
||||||
import { getThemeSettings, getNaiveThemeOverrides } from './helpers';
|
import { initThemeSettings, getNaiveThemeOverrides, setThemeSettings, clearThemeSettings } from './helpers';
|
||||||
|
|
||||||
type ThemeState = Theme.Setting;
|
type ThemeState = Theme.Setting;
|
||||||
|
|
||||||
export const useThemeStore = defineStore('theme-store', {
|
export const useThemeStore = defineStore('theme-store', {
|
||||||
state: (): ThemeState => getThemeSettings(),
|
state: (): ThemeState => initThemeSettings(),
|
||||||
getters: {
|
getters: {
|
||||||
/** naiveUI的主题配置 */
|
/** naiveUI的主题配置 */
|
||||||
naiveThemeOverrides(state) {
|
naiveThemeOverrides(state) {
|
||||||
@ -24,8 +24,16 @@ export const useThemeStore = defineStore('theme-store', {
|
|||||||
actions: {
|
actions: {
|
||||||
/** 重置theme状态 */
|
/** 重置theme状态 */
|
||||||
resetThemeStore() {
|
resetThemeStore() {
|
||||||
|
clearThemeSettings();
|
||||||
this.$reset();
|
this.$reset();
|
||||||
},
|
},
|
||||||
|
/** 缓存主题配置 */
|
||||||
|
cacheThemeSettings() {
|
||||||
|
const isProd = import.meta.env.PROD;
|
||||||
|
if (isProd) {
|
||||||
|
setThemeSettings(this.$state);
|
||||||
|
}
|
||||||
|
},
|
||||||
/** 设置暗黑模式 */
|
/** 设置暗黑模式 */
|
||||||
setDarkMode(darkMode: boolean) {
|
setDarkMode(darkMode: boolean) {
|
||||||
this.darkMode = darkMode;
|
this.darkMode = darkMode;
|
||||||
|
@ -4,5 +4,4 @@ export * from './cache';
|
|||||||
export * from './auth';
|
export * from './auth';
|
||||||
export * from './menu';
|
export * from './menu';
|
||||||
export * from './breadcrumb';
|
export * from './breadcrumb';
|
||||||
export * from './tab';
|
|
||||||
export * from './regexp';
|
export * from './regexp';
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
import { EnumStorageKey } from '@/enum';
|
|
||||||
import { setLocal, getLocal } from '../storage';
|
|
||||||
|
|
||||||
/** 缓存多页签数据 */
|
|
||||||
export function setTabRoutes(data: GlobalTabRoute[]) {
|
|
||||||
setLocal(EnumStorageKey['multi-tab-routes'], data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 获取缓存的多页签数据 */
|
|
||||||
export function getTabRoutes() {
|
|
||||||
const routes: GlobalTabRoute[] = [];
|
|
||||||
const data = getLocal<GlobalTabRoute[]>(EnumStorageKey['multi-tab-routes']);
|
|
||||||
if (data) {
|
|
||||||
const defaultTabRoutes = data.map(item => ({
|
|
||||||
...item,
|
|
||||||
scrollPosition: {
|
|
||||||
left: 0,
|
|
||||||
top: 0
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
routes.push(...defaultTabRoutes);
|
|
||||||
}
|
|
||||||
return routes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 清空多页签数据 */
|
|
||||||
export function clearTabRoutes() {
|
|
||||||
setTabRoutes([]);
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user