mirror of
https://github.com/m-xlsea/ruoyi-plus-soybean.git
synced 2025-09-24 07:49:47 +08:00
feat(projects): theme store完成
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
import type { Ref } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { useReload, useBoolean } from '@/hooks';
|
||||
import { useReload, useModalVisible, useBoolean } from '@/hooks';
|
||||
|
||||
interface AppStore {
|
||||
/** 重载页面的标志 */
|
||||
@ -18,6 +18,14 @@ interface AppStore {
|
||||
closeSettingDrawer(): void;
|
||||
/** 切换抽屉可见状态 */
|
||||
toggleSettingdrawerVisible(): void;
|
||||
/** 侧边栏折叠状态 */
|
||||
siderCollapse: Ref<boolean>;
|
||||
/** 设置侧边栏折叠状态 */
|
||||
setSiderCollapse(collapse: boolean): void;
|
||||
/** vertical-mix模式下 侧边栏的固定状态 */
|
||||
mixSiderFixed: Ref<boolean>;
|
||||
/** 设置 vertical-mix模式下 侧边栏的固定状态 */
|
||||
setMixSiderIsFixed(isFixed: boolean): void;
|
||||
}
|
||||
|
||||
export const useAppStore = defineStore('app-store', () => {
|
||||
@ -26,11 +34,17 @@ export const useAppStore = defineStore('app-store', () => {
|
||||
|
||||
// 设置抽屉
|
||||
const {
|
||||
bool: settingDrawerVisible,
|
||||
setTrue: openSettingDrawer,
|
||||
setFalse: closeSettingDrawer,
|
||||
toggle: toggleSettingdrawerVisible
|
||||
} = useBoolean();
|
||||
visible: settingDrawerVisible,
|
||||
openModal: openSettingDrawer,
|
||||
closeModal: closeSettingDrawer,
|
||||
toggleModal: toggleSettingdrawerVisible
|
||||
} = useModalVisible();
|
||||
|
||||
// 侧边栏的折叠状态
|
||||
const { bool: siderCollapse, setBool: setSiderCollapse } = useBoolean();
|
||||
|
||||
// vertical-mix模式下 侧边栏的固定状态
|
||||
const { bool: mixSiderFixed, setBool: setMixSiderIsFixed } = useBoolean();
|
||||
|
||||
const appStore: AppStore = {
|
||||
reloadFlag,
|
||||
@ -38,7 +52,11 @@ export const useAppStore = defineStore('app-store', () => {
|
||||
settingDrawerVisible,
|
||||
openSettingDrawer,
|
||||
closeSettingDrawer,
|
||||
toggleSettingdrawerVisible
|
||||
toggleSettingdrawerVisible,
|
||||
siderCollapse,
|
||||
setSiderCollapse,
|
||||
mixSiderFixed,
|
||||
setMixSiderIsFixed
|
||||
};
|
||||
|
||||
return appStore;
|
||||
|
253
src/store/modules/theme/hooks.ts
Normal file
253
src/store/modules/theme/hooks.ts
Normal file
@ -0,0 +1,253 @@
|
||||
import { watch, onUnmounted } from 'vue';
|
||||
import type { Ref, ComputedRef } from 'vue';
|
||||
import { useOsTheme } from 'naive-ui';
|
||||
import { useElementSize } from '@vueuse/core';
|
||||
import { objectAssign } from '@/utils';
|
||||
import type { ThemeSetting, ThemeLayoutMode, ThemeTabMode, ThemeAnimateMode } from '@/interface';
|
||||
import { handleWindicssDarkMode } from './helpers';
|
||||
|
||||
export interface LayoutFunc {
|
||||
/** 设置布局最小宽度 */
|
||||
setLayoutMinWidth(minWidth: number): void;
|
||||
/** 设置布局模式 */
|
||||
setLayoutMode(mode: ThemeLayoutMode): void;
|
||||
}
|
||||
export function useLayoutFunc(layout: ThemeSetting['layout']): LayoutFunc {
|
||||
function setLayout(data: Partial<ThemeSetting['layout']>) {
|
||||
objectAssign(layout, data);
|
||||
}
|
||||
|
||||
function setLayoutMinWidth(minWidth: number) {
|
||||
setLayout({ minWidth });
|
||||
}
|
||||
function setLayoutMode(mode: ThemeLayoutMode) {
|
||||
setLayout({ mode });
|
||||
}
|
||||
|
||||
return {
|
||||
setLayoutMinWidth,
|
||||
setLayoutMode
|
||||
};
|
||||
}
|
||||
|
||||
export interface HeaderFunc {
|
||||
/** 设置头部高度 */
|
||||
setHeaderHeight(height: number): void;
|
||||
/** 设置头部面包屑可见 */
|
||||
setHeaderCrumbVisible(visible: boolean): void;
|
||||
/** 设置头部面包屑图标可见 */
|
||||
setHeaderCrumbIconVisible(visible: boolean): void;
|
||||
}
|
||||
export function useHeaderFunc(header: ThemeSetting['header']): HeaderFunc {
|
||||
function setHeader(data: Partial<ThemeSetting['header']>) {
|
||||
objectAssign(header, data);
|
||||
}
|
||||
|
||||
function setHeaderHeight(height: number) {
|
||||
setHeader({ height });
|
||||
}
|
||||
function setHeaderCrumbVisible(visible: boolean) {
|
||||
setHeader({ crumb: { ...header.crumb, visible } });
|
||||
}
|
||||
function setHeaderCrumbIconVisible(visible: boolean) {
|
||||
setHeader({ crumb: { ...header.crumb, showIcon: visible } });
|
||||
}
|
||||
|
||||
return {
|
||||
setHeaderHeight,
|
||||
setHeaderCrumbVisible,
|
||||
setHeaderCrumbIconVisible
|
||||
};
|
||||
}
|
||||
|
||||
export interface TabFunc {
|
||||
/** 设置多页签可见 */
|
||||
setTabVisible(visible: boolean): void;
|
||||
/** 设置多页签高度 */
|
||||
setTabHeight(height: number): void;
|
||||
/** 设置多页签风格 */
|
||||
setTabMode(mode: ThemeTabMode): void;
|
||||
/** 设置多页签缓存 */
|
||||
setTabIsCache(isCache: boolean): void;
|
||||
}
|
||||
export function useTabFunc(tab: ThemeSetting['tab']): TabFunc {
|
||||
function setTab(data: Partial<ThemeSetting['tab']>) {
|
||||
objectAssign(tab, data);
|
||||
}
|
||||
|
||||
function setTabVisible(visible: boolean) {
|
||||
setTab({ visible });
|
||||
}
|
||||
function setTabHeight(height: number) {
|
||||
setTab({ height });
|
||||
}
|
||||
function setTabMode(mode: ThemeTabMode) {
|
||||
setTab({ mode });
|
||||
}
|
||||
function setTabIsCache(isCache: boolean) {
|
||||
setTab({ isCache });
|
||||
}
|
||||
|
||||
return {
|
||||
setTabVisible,
|
||||
setTabHeight,
|
||||
setTabMode,
|
||||
setTabIsCache
|
||||
};
|
||||
}
|
||||
|
||||
export interface SiderFunc {
|
||||
/** 侧边栏宽度 */
|
||||
setSiderWidth(width: number): void;
|
||||
/** 侧边栏折叠时的宽度 */
|
||||
setSiderCollapsedWidth(width: number): void;
|
||||
/** vertical-mix模式下侧边栏宽度 */
|
||||
setMixSiderWidth(width: number): void;
|
||||
/** vertical-mix模式下侧边栏折叠时的宽度 */
|
||||
setMixSiderCollapsedWidth(width: number): void;
|
||||
/** vertical-mix模式下侧边栏展示子菜单的宽度 */
|
||||
setMixSiderChildMenuWidth(width: number): void;
|
||||
}
|
||||
export function useSiderFunc(sider: ThemeSetting['sider']): SiderFunc {
|
||||
function setSider(data: Partial<ThemeSetting['sider']>) {
|
||||
objectAssign(sider, data);
|
||||
}
|
||||
|
||||
function setSiderWidth(width: number) {
|
||||
setSider({ width });
|
||||
}
|
||||
function setSiderCollapsedWidth(width: number) {
|
||||
setSider({ collapsedWidth: width });
|
||||
}
|
||||
function setMixSiderWidth(width: number) {
|
||||
setSider({ mixWidth: width });
|
||||
}
|
||||
function setMixSiderCollapsedWidth(width: number) {
|
||||
setSider({ mixCollapsedWidth: width });
|
||||
}
|
||||
function setMixSiderChildMenuWidth(width: number) {
|
||||
setSider({ mixChildMenuWidth: width });
|
||||
}
|
||||
|
||||
return {
|
||||
setSiderWidth,
|
||||
setSiderCollapsedWidth,
|
||||
setMixSiderWidth,
|
||||
setMixSiderCollapsedWidth,
|
||||
setMixSiderChildMenuWidth
|
||||
};
|
||||
}
|
||||
|
||||
export interface FooterFunc {
|
||||
/** 设置底部是否固定 */
|
||||
setFooterIsFixed(isFixed: boolean): void;
|
||||
/** 设置底部高度 */
|
||||
setFooterHeight(height: number): void;
|
||||
}
|
||||
export function useFooterFunc(footer: ThemeSetting['footer']): FooterFunc {
|
||||
function setFooter(data: Partial<ThemeSetting['footer']>) {
|
||||
objectAssign(footer, data);
|
||||
}
|
||||
|
||||
function setFooterIsFixed(isFixed: boolean) {
|
||||
setFooter({ fixed: isFixed });
|
||||
}
|
||||
function setFooterHeight(height: number) {
|
||||
setFooter({ height });
|
||||
}
|
||||
|
||||
return {
|
||||
setFooterIsFixed,
|
||||
setFooterHeight
|
||||
};
|
||||
}
|
||||
|
||||
export interface PageFunc {
|
||||
/** 设置切换页面时是否过渡动画 */
|
||||
setPageIsAnimate(animate: boolean): void;
|
||||
/** 设置页面过渡动画类型 */
|
||||
setPageAnimateMode(mode: ThemeAnimateMode): void;
|
||||
}
|
||||
export function usePageFunc(page: ThemeSetting['page']): PageFunc {
|
||||
function setPage(data: Partial<ThemeSetting['page']>) {
|
||||
objectAssign(page, data);
|
||||
}
|
||||
|
||||
function setPageIsAnimate(animate: boolean) {
|
||||
setPage({ animate });
|
||||
}
|
||||
function setPageAnimateMode(mode: ThemeAnimateMode) {
|
||||
setPage({ animateMode: mode });
|
||||
}
|
||||
|
||||
return {
|
||||
setPageIsAnimate,
|
||||
setPageAnimateMode
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作系统主题模式变化的回调函数
|
||||
* @param isDark - 暗黑模式
|
||||
*/
|
||||
type OsThemeCallback = (isDark: boolean) => void;
|
||||
|
||||
/** 监听操作系统主题模式 */
|
||||
export function osThemeWatcher(callback: OsThemeCallback) {
|
||||
/** 操作系统暗黑主题 */
|
||||
const osTheme = useOsTheme();
|
||||
|
||||
const stopHandle = watch(
|
||||
osTheme,
|
||||
newValue => {
|
||||
const isDark = newValue === 'dark';
|
||||
callback(isDark);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
stopHandle();
|
||||
});
|
||||
}
|
||||
|
||||
/** 应用windicss的暗黑模式 */
|
||||
export function setupWindicssDarkMode(darkMode: Ref<boolean>) {
|
||||
const { addDarkClass, removeDarkClass } = handleWindicssDarkMode();
|
||||
|
||||
const stopHandle = watch(
|
||||
() => darkMode.value,
|
||||
newValue => {
|
||||
if (newValue) {
|
||||
addDarkClass();
|
||||
} else {
|
||||
removeDarkClass();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
stopHandle();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用横向滚动
|
||||
* @description 页面切换时,过渡动画会产生水平方向的滚动条, 小于最小宽度时,不禁止
|
||||
*/
|
||||
export function setupHiddenScroll(minWidthOfLayout: ComputedRef<number>) {
|
||||
const { width } = useElementSize(document.documentElement);
|
||||
|
||||
const stopHandle = watch(width, newValue => {
|
||||
if (newValue < minWidthOfLayout.value) {
|
||||
document.documentElement.style.overflowX = 'auto';
|
||||
} else {
|
||||
document.documentElement.style.overflowX = 'hidden';
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
stopHandle();
|
||||
});
|
||||
}
|
@ -1,36 +1,67 @@
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { ref, reactive, computed } from 'vue';
|
||||
import type { Ref, ComputedRef } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { useThemeVars, darkTheme, useOsTheme } from 'naive-ui';
|
||||
import { darkTheme } from 'naive-ui';
|
||||
import type { GlobalThemeOverrides, GlobalTheme } from 'naive-ui';
|
||||
import { themeSetting } from '@/settings';
|
||||
import { useBoolean } from '@/hooks';
|
||||
import { getColorPalette } from '@/utils';
|
||||
import { getNaiveThemeOverrides, addThemeCssVarsToHtml, handleWindicssDarkMode } from './helpers';
|
||||
|
||||
interface OtherColor {
|
||||
/** 信息 */
|
||||
info: string;
|
||||
/** 成功 */
|
||||
success: string;
|
||||
/** 警告 */
|
||||
warning: string;
|
||||
/** 错误 */
|
||||
error: string;
|
||||
}
|
||||
import type { ThemeSetting, ThemeHorizontalMenuPosition } from '@/interface';
|
||||
import { getNaiveThemeOverrides, addThemeCssVarsToHtml } from './helpers';
|
||||
import {
|
||||
useLayoutFunc,
|
||||
useHeaderFunc,
|
||||
useTabFunc,
|
||||
useSiderFunc,
|
||||
useFooterFunc,
|
||||
usePageFunc,
|
||||
osThemeWatcher,
|
||||
setupWindicssDarkMode,
|
||||
setupHiddenScroll
|
||||
} from './hooks';
|
||||
import type { LayoutFunc, HeaderFunc, TabFunc, SiderFunc, FooterFunc, PageFunc } from './hooks';
|
||||
|
||||
type BuiltInGlobalTheme = Omit<Required<GlobalTheme>, 'InternalSelectMenu' | 'InternalSelection'>;
|
||||
|
||||
interface ThemeStore {
|
||||
interface ThemeStore extends LayoutFunc, HeaderFunc, TabFunc, SiderFunc, FooterFunc, PageFunc {
|
||||
/** 暗黑模式 */
|
||||
darkMode: Ref<boolean>;
|
||||
/** 设置暗黑模式 */
|
||||
setDarkMode(dark: boolean): void;
|
||||
/** 切换/关闭 暗黑模式 */
|
||||
toggleDarkMode(dark: boolean): void;
|
||||
/** 布局样式 */
|
||||
layout: ThemeSetting['layout'];
|
||||
/** 主题颜色 */
|
||||
themeColor: Ref<string>;
|
||||
/** 设置系统主题颜色 */
|
||||
setThemeColor(color: string): void;
|
||||
/** 主题颜色列表 */
|
||||
themeColorList: string[];
|
||||
/** 其他颜色 */
|
||||
otherColor: ComputedRef<OtherColor>;
|
||||
otherColor: ComputedRef<ThemeSetting['otherColor']>;
|
||||
/** 固定头部和多页签 */
|
||||
fixedHeaderAndTab: Ref<boolean>;
|
||||
/** 设置固定头部和多页签 */
|
||||
setIsFixedHeaderAndTab(isFixed: boolean): void;
|
||||
/** 重载按钮可见 */
|
||||
reloadVisible: Ref<boolean>;
|
||||
/** 设置 显示/隐藏 重载按钮 */
|
||||
setReloadVisible(visible: boolean): void;
|
||||
/** 头部 */
|
||||
header: ThemeSetting['header'];
|
||||
/** 多页签 */
|
||||
tab: ThemeSetting['tab'];
|
||||
/** 侧边栏 */
|
||||
sider: ThemeSetting['sider'];
|
||||
/** 菜单 */
|
||||
menu: ThemeSetting['menu'];
|
||||
/** 设置水平模式的菜单的位置 */
|
||||
setHorizontalMenuPosition(posiiton: ThemeHorizontalMenuPosition): void;
|
||||
/** 底部 */
|
||||
footer: ThemeSetting['footer'];
|
||||
/** 页面 */
|
||||
page: ThemeSetting['page'];
|
||||
/** naiveUI的主题配置 */
|
||||
naiveThemeOverrides: ComputedRef<GlobalThemeOverrides>;
|
||||
/** naive-ui暗黑主题 */
|
||||
@ -38,72 +69,148 @@ interface ThemeStore {
|
||||
}
|
||||
|
||||
export const useThemeStore = defineStore('theme-store', () => {
|
||||
const themeVars = useThemeVars();
|
||||
// 暗黑模式
|
||||
const { bool: darkMode, setBool: setDarkMode, toggle: toggleDarkMode } = useBoolean();
|
||||
const { addDarkClass, removeDarkClass } = handleWindicssDarkMode();
|
||||
|
||||
const themeColor = ref('#1890ff');
|
||||
const otherColor = computed<OtherColor>(() => ({
|
||||
info: getColorPalette(themeColor.value, 7),
|
||||
success: '#52c41a',
|
||||
warning: '#faad14',
|
||||
error: '#f5222d'
|
||||
// 布局
|
||||
const layout = reactive<ThemeSetting['layout']>({
|
||||
...themeSetting.layout
|
||||
});
|
||||
const { setLayoutMinWidth, setLayoutMode } = useLayoutFunc(layout);
|
||||
|
||||
// 主题色
|
||||
const themeColor = ref(themeSetting.themeColor);
|
||||
/** 设置系统主题颜色 */
|
||||
function setThemeColor(color: string) {
|
||||
themeColor.value = color;
|
||||
}
|
||||
const { themeColorList } = themeSetting;
|
||||
const otherColor = computed<ThemeSetting['otherColor']>(() => ({
|
||||
...themeSetting.otherColor,
|
||||
info: getColorPalette(themeColor.value, 7)
|
||||
}));
|
||||
|
||||
// 固定头部和多页签
|
||||
const { bool: fixedHeaderAndTab, setBool: setIsFixedHeaderAndTab } = useBoolean(themeSetting.fixedHeaderAndTab);
|
||||
|
||||
// 重载按钮
|
||||
const { bool: reloadVisible, setBool: setReloadVisible } = useBoolean(themeSetting.showReload);
|
||||
|
||||
// 头部
|
||||
const header = reactive<ThemeSetting['header']>({
|
||||
height: themeSetting.header.height,
|
||||
crumb: { ...themeSetting.header.crumb }
|
||||
});
|
||||
const { setHeaderHeight, setHeaderCrumbVisible, setHeaderCrumbIconVisible } = useHeaderFunc(header);
|
||||
|
||||
// 多页签
|
||||
const tab = reactive<ThemeSetting['tab']>({
|
||||
...themeSetting.tab
|
||||
});
|
||||
const { setTabVisible, setTabHeight, setTabMode, setTabIsCache } = useTabFunc(tab);
|
||||
|
||||
// 侧边栏
|
||||
const sider = reactive<ThemeSetting['sider']>({
|
||||
...themeSetting.sider
|
||||
});
|
||||
const {
|
||||
setSiderWidth,
|
||||
setSiderCollapsedWidth,
|
||||
setMixSiderWidth,
|
||||
setMixSiderCollapsedWidth,
|
||||
setMixSiderChildMenuWidth
|
||||
} = useSiderFunc(sider);
|
||||
|
||||
// 菜单
|
||||
const menu = reactive<ThemeSetting['menu']>({
|
||||
...themeSetting.menu
|
||||
});
|
||||
function setHorizontalMenuPosition(posiiton: ThemeHorizontalMenuPosition) {
|
||||
menu.horizontalPosition = posiiton;
|
||||
}
|
||||
|
||||
// 底部
|
||||
const footer = reactive<ThemeSetting['footer']>({
|
||||
...themeSetting.footer
|
||||
});
|
||||
const { setFooterIsFixed, setFooterHeight } = useFooterFunc(footer);
|
||||
|
||||
// 页面
|
||||
const page = reactive<ThemeSetting['page']>({
|
||||
...themeSetting.page
|
||||
});
|
||||
const { setPageIsAnimate, setPageAnimateMode } = usePageFunc(page);
|
||||
|
||||
// naive主题
|
||||
const naiveThemeOverrides = computed<GlobalThemeOverrides>(() =>
|
||||
getNaiveThemeOverrides({ primary: themeColor.value, ...otherColor.value })
|
||||
);
|
||||
|
||||
/** naive-ui暗黑主题 */
|
||||
const naiveTheme = computed(() => (darkMode.value ? darkTheme : undefined));
|
||||
|
||||
/** 操作系统暗黑主题 */
|
||||
const osTheme = useOsTheme();
|
||||
|
||||
/** 初始化css vars, 并添加至html */
|
||||
function initThemeCssVars() {
|
||||
const updatedThemeVars = { ...themeVars.value, ...naiveThemeOverrides.value.common };
|
||||
const updatedThemeVars = { ...naiveThemeOverrides.value.common };
|
||||
addThemeCssVarsToHtml(updatedThemeVars);
|
||||
}
|
||||
|
||||
function init() {
|
||||
initThemeCssVars();
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
// 监听操作系统主题模式
|
||||
watch(
|
||||
osTheme,
|
||||
newValue => {
|
||||
const isDark = newValue === 'dark';
|
||||
/** 系统主题适应操作系统 */
|
||||
function handleAdaptOsTheme() {
|
||||
osThemeWatcher(isDark => {
|
||||
if (isDark) {
|
||||
setDarkMode(true);
|
||||
} else {
|
||||
setDarkMode(false);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
// 监听主题的暗黑模式
|
||||
watch(
|
||||
() => darkMode.value,
|
||||
newValue => {
|
||||
if (newValue) {
|
||||
addDarkClass();
|
||||
} else {
|
||||
removeDarkClass();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
initThemeCssVars();
|
||||
handleAdaptOsTheme();
|
||||
setupWindicssDarkMode(darkMode);
|
||||
setupHiddenScroll(computed(() => layout.minWidth));
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
const themeStore: ThemeStore = {
|
||||
darkMode,
|
||||
setDarkMode,
|
||||
toggleDarkMode,
|
||||
layout,
|
||||
setLayoutMinWidth,
|
||||
setLayoutMode,
|
||||
themeColor,
|
||||
setThemeColor,
|
||||
themeColorList,
|
||||
otherColor,
|
||||
fixedHeaderAndTab,
|
||||
setIsFixedHeaderAndTab,
|
||||
reloadVisible,
|
||||
setReloadVisible,
|
||||
header,
|
||||
setHeaderHeight,
|
||||
setHeaderCrumbVisible,
|
||||
setHeaderCrumbIconVisible,
|
||||
tab,
|
||||
setTabVisible,
|
||||
setTabHeight,
|
||||
setTabMode,
|
||||
setTabIsCache,
|
||||
sider,
|
||||
setSiderWidth,
|
||||
setSiderCollapsedWidth,
|
||||
setMixSiderWidth,
|
||||
setMixSiderCollapsedWidth,
|
||||
setMixSiderChildMenuWidth,
|
||||
menu,
|
||||
setHorizontalMenuPosition,
|
||||
footer,
|
||||
setFooterIsFixed,
|
||||
setFooterHeight,
|
||||
page,
|
||||
setPageIsAnimate,
|
||||
setPageAnimateMode,
|
||||
naiveThemeOverrides,
|
||||
naiveTheme
|
||||
};
|
||||
|
Reference in New Issue
Block a user