feat(projects): theme store完成

This commit is contained in:
Soybean
2022-01-08 20:49:21 +08:00
parent 10e4d81bd6
commit bf020a8258
56 changed files with 1205 additions and 164 deletions

View File

@ -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;

View 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();
});
}

View File

@ -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
};