refactor(projects): 恢复pinia默认写法

This commit is contained in:
Soybean
2022-01-16 20:13:11 +08:00
parent 28b5d22401
commit b2a4ddf5e3
34 changed files with 1242 additions and 965 deletions

View File

@ -7,3 +7,4 @@ export function setupStore(app: App) {
}
export * from './modules';
export * from './subscribe';

View File

@ -1,66 +1,63 @@
import type { Ref } from 'vue';
import { nextTick } from 'vue';
import { defineStore } from 'pinia';
import { useReload, useModalVisible, useBoolean } from '@/hooks';
interface AppStore {
/** 重载页面的标志 */
reloadFlag: Ref<boolean>;
/**
* 触发重载页面
* @param duration - 延迟时间(ms, 默认0)
*/
handleReload(duration?: number): void;
/** 设置抽屉可见状态 */
settingDrawerVisible: Ref<boolean>;
/** 打开设置抽屉 */
openSettingDrawer(): void;
/** 关闭设置抽屉 */
closeSettingDrawer(): void;
/** 切换抽屉可见状态 */
toggleSettingdrawerVisible(): void;
interface AppState {
/** 重载页面(控制页面的显示) */
reloadFlag: boolean;
/** 项目配置的抽屉可见状态 */
settingDrawerVisible: boolean;
/** 侧边栏折叠状态 */
siderCollapse: Ref<boolean>;
/** 折叠/展开 侧边栏折叠状态 */
toggleSiderCollapse(): void;
/** 设置侧边栏折叠状态 */
setSiderCollapse(collapse: boolean): void;
siderCollapse: boolean;
/** vertical-mix模式下 侧边栏的固定状态 */
mixSiderFixed: Ref<boolean>;
/** 设置 vertical-mix模式下 侧边栏的固定状态 */
setMixSiderIsFixed(isFixed: boolean): void;
mixSiderFixed: boolean;
}
export const useAppStore = defineStore('app-store', () => {
// 重新加载页面
const { reloadFlag, handleReload } = useReload();
// 设置抽屉
const {
visible: settingDrawerVisible,
openModal: openSettingDrawer,
closeModal: closeSettingDrawer,
toggleModal: toggleSettingdrawerVisible
} = useModalVisible();
// 侧边栏的折叠状态
const { bool: siderCollapse, setBool: setSiderCollapse, toggle: toggleSiderCollapse } = useBoolean();
// vertical-mix模式下 侧边栏的固定状态
const { bool: mixSiderFixed, setBool: setMixSiderIsFixed } = useBoolean();
const appStore: AppStore = {
reloadFlag,
handleReload,
settingDrawerVisible,
openSettingDrawer,
closeSettingDrawer,
toggleSettingdrawerVisible,
siderCollapse,
setSiderCollapse,
toggleSiderCollapse,
mixSiderFixed,
setMixSiderIsFixed
};
return appStore;
export const useAppStore = defineStore('app-store', {
state: (): AppState => ({
reloadFlag: true,
settingDrawerVisible: false,
siderCollapse: false,
mixSiderFixed: false
}),
actions: {
/**
* 重载页面
* @param duration - 重载的延迟时间(ms)
*/
async reloadPage(duration = 0) {
this.reloadFlag = false;
await nextTick();
if (duration) {
setTimeout(() => {
this.reloadFlag = true;
}, duration);
} else {
this.reloadFlag = true;
}
},
/** 打开设置抽屉 */
openSettingDrawer() {
this.settingDrawerVisible = true;
},
/** 关闭设置抽屉 */
closeSettingDrawer() {
this.settingDrawerVisible = false;
},
/** 切换抽屉可见状态 */
toggleSettingdrawerVisible() {
this.settingDrawerVisible = !this.settingDrawerVisible;
},
/** 设置侧边栏折叠状态 */
setSiderCollapse(collapse: boolean) {
this.siderCollapse = collapse;
},
/** 折叠/展开 侧边栏折叠状态 */
toggleSiderCollapse() {
this.siderCollapse = !this.siderCollapse;
},
/** 设置 vertical-mix模式下 侧边栏的固定状态 */
setMixSiderIsFixed(isFixed: boolean) {
this.mixSiderFixed = isFixed;
}
}
});

View File

@ -1,112 +1,92 @@
import { ref, computed, reactive, unref } from 'vue';
import type { Ref, ComputedRef } from 'vue';
import { unref } from 'vue';
import { defineStore } from 'pinia';
import { router as globalRouter } from '@/router';
import { useRouterPush } from '@/composables';
import { useLoading } from '@/hooks';
import { fetchLogin, fetchUserInfo } from '@/service';
import { getUserInfo, getToken, setUserInfo, setToken, setRefreshToken, clearAuthStorage } from '@/utils';
interface AuthStore {
interface AuthState {
/** 用户信息 */
userInfo: Auth.UserInfo;
/** 用户token */
token: Ref<string>;
/** 是否登录 */
isLogin: ComputedRef<boolean>;
/**
* 重置authStore
* 是否需要跳转页面(例如当前页面是需要权限的,登出后需要跳转到登录页面)
*/
resetAuthStore(pushRoute: boolean): void;
token: string;
/** 登录的加载状态 */
loginLoding: Ref<boolean>;
/**
* 登录
* @param phone - 手机号
* @param pwdOrCode - 密码或验证码
* @param type - 登录方式: pwd - 密码登录; sms - 验证码登录
*/
login(phone: string, pwdOrCode: string, type: 'pwd' | 'sms'): void;
loginLoding: boolean;
}
export const useAuthStore = defineStore('auth-store', () => {
const { toLogin, toLoginRedirect } = useRouterPush(false);
export const useAuthStore = defineStore('auth-store', {
state: (): AuthState => ({
userInfo: getUserInfo(),
token: getToken(),
loginLoding: false
}),
getters: {
/** 是否登录 */
isLogin(state) {
return Boolean(state.token);
}
},
actions: {
/** 重置auth状态 */
resetAuthStore() {
const { toLogin } = useRouterPush(false);
const route = unref(globalRouter.currentRoute);
const userInfo: Auth.UserInfo = reactive(getUserInfo());
function handleSetUserInfo(data: Auth.UserInfo) {
Object.assign(userInfo, data);
}
clearAuthStorage();
this.$reset();
const token = ref(getToken());
function handleSetToken(data: string) {
token.value = data;
}
if (route.meta.requiresAuth) {
toLogin();
}
},
/**
* 根据token进行登录
* @param backendToken - 返回的token
*/
async loginByToken(backendToken: ApiAuth.Token) {
const { toLoginRedirect } = useRouterPush(false);
const isLogin = computed(() => Boolean(token.value));
// 先把token存储到缓存中
const { token, refreshToken } = backendToken;
setToken(token);
setRefreshToken(refreshToken);
const { loading: loginLoding, startLoading: startLoginLoading, endLoading: endLoginLoading } = useLoading();
// 获取用户信息
const { data } = await fetchUserInfo();
if (data) {
// 成功后把用户信息存储到缓存中
setUserInfo(data);
function resetStore() {
handleSetUserInfo(getUserInfo());
handleSetToken(getToken());
}
// 更新状态
Object.assign(this, { userInfo: data, token });
function resetAuthStore(pushRoute: boolean = true) {
const route = unref(globalRouter.currentRoute);
// 跳转登录后的地址
toLoginRedirect();
clearAuthStorage();
resetStore();
if (pushRoute && route.meta.requiresAuth) {
toLogin();
// 登录成功弹出欢迎提示
window.$notification?.success({
title: '登录成功!',
content: `欢迎回来,${data.userName}!`,
duration: 3000
});
} else {
// 不成功则重置状态
this.resetAuthStore();
}
},
/**
* 登录
* @param phone - 手机号
* @param pwdOrCode - 密码或验证码
* @param type - 登录方式: pwd - 密码登录; sms - 验证码登录
*/
async login(phone: string, pwdOrCode: string, type: 'pwd' | 'sms') {
this.loginLoding = true;
const { data } = await fetchLogin(phone, pwdOrCode, type);
if (data) {
await this.loginByToken(data);
}
this.loginLoding = false;
}
}
async function login(phone: string, pwdOrCode: string, type: 'pwd' | 'sms') {
startLoginLoading();
const { data } = await fetchLogin(phone, pwdOrCode, type);
if (data) {
await loginByToken(data);
}
endLoginLoading();
}
async function loginByToken(backendToken: ApiAuth.Token) {
// 1.先把token存储到缓存中
const { token, refreshToken } = backendToken;
setToken(token);
setRefreshToken(refreshToken);
// 2.获取用户信息
const { data } = await fetchUserInfo();
if (data) {
// 成功后把用户信息存储到缓存中
setUserInfo(data);
handleSetToken(token);
handleSetUserInfo(data);
// 3. 跳转登录后的地址
toLoginRedirect();
// 4.登录成功弹出欢迎提示
window.$notification?.success({
title: '登录成功!',
content: `欢迎回来,${userInfo.userName}!`,
duration: 3000
});
} else {
// 不成功则重置状态
resetAuthStore(false);
}
}
const authStore: AuthStore = {
userInfo,
token,
isLogin,
resetAuthStore,
loginLoding,
login
};
return authStore;
});

View File

@ -1,49 +1,38 @@
import { ref } from 'vue';
import type { Ref } from 'vue';
import type { Router } from 'vue-router';
import { defineStore } from 'pinia';
import { useBoolean } from '@/hooks';
import { fetchUserRoutes } from '@/service';
import { transformAuthRouteToMenu, transformAuthRoutesToVueRoutes } from '@/utils';
import type { GlobalMenuOption } from '@/interface';
/** 路由状态 */
interface RouteStore {
interface RouteState {
/** 是否添加过动态路由 */
isAddedDynamicRoute: Ref<boolean>;
/** 初始化动态路由 */
initDynamicRoute(router: Router): Promise<void>;
isAddedDynamicRoute: boolean;
/** 菜单 */
menus: Ref<GlobalMenuOption[]>;
menus: GlobalMenuOption[];
}
export const useRouteStore = defineStore('route-store', () => {
const menus = ref<GlobalMenuOption[]>([]) as Ref<GlobalMenuOption[]>;
function getMenus(data: AuthRoute.Route[]) {
const transform = transformAuthRouteToMenu(data);
menus.value = transform;
}
export const useRouteStore = defineStore('route-store', {
state: (): RouteState => ({
isAddedDynamicRoute: false,
menus: []
}),
actions: {
/**
* 初始化动态路由
* @param router - 路由实例
*/
async initDynamicRoute(router: Router) {
const { data } = await fetchUserRoutes();
if (data) {
this.menus = transformAuthRouteToMenu(data.routes);
const { bool: isAddedDynamicRoute, setTrue: setAddedDynamicRoute } = useBoolean();
async function initDynamicRoute(router: Router) {
const { data } = await fetchUserRoutes();
if (data) {
getMenus(data.routes);
const vueRoutes = transformAuthRoutesToVueRoutes(data.routes);
vueRoutes.forEach(route => {
router.addRoute(route);
});
const vueRoutes = transformAuthRoutesToVueRoutes(data.routes);
vueRoutes.forEach(route => {
router.addRoute(route);
});
setAddedDynamicRoute();
this.isAddedDynamicRoute = true;
}
}
}
const routeStore: RouteStore = {
isAddedDynamicRoute,
initDynamicRoute,
menus
};
return routeStore;
});

View File

@ -0,0 +1,102 @@
import type { GlobalThemeOverrides } from 'naive-ui';
import { kebabCase } from 'lodash-es';
import { getColorPalette, addColorAlpha } from '@/utils';
type ColorType = 'primary' | 'info' | 'success' | 'warning' | 'error';
type ColorScene = '' | 'Suppl' | 'Hover' | 'Pressed' | 'Active';
type ColorKey = `${ColorType}Color${ColorScene}`;
type ThemeColor = {
[key in ColorKey]?: string;
};
interface ColorAction {
scene: ColorScene;
handler: (color: string) => string;
}
/** 获取主题颜色的各种场景对应的颜色 */
function getThemeColors(colors: [ColorType, string][]) {
const colorActions: ColorAction[] = [
{ scene: '', handler: color => color },
{ scene: 'Suppl', handler: color => color },
{ scene: 'Hover', handler: color => getColorPalette(color, 5) },
{ scene: 'Pressed', handler: color => getColorPalette(color, 7) },
{ scene: 'Active', handler: color => addColorAlpha(color, 0.1) }
];
const themeColor: ThemeColor = {};
colors.forEach(color => {
colorActions.forEach(action => {
const [colorType, colorValue] = color;
const colorKey: ColorKey = `${colorType}Color${action.scene}`;
themeColor[colorKey] = action.handler(colorValue);
});
});
return themeColor;
}
/** 获取naive的主题颜色 */
export function getNaiveThemeOverrides(colors: { [key in ColorType]: string }): GlobalThemeOverrides {
const { primary, info, success, warning, error } = colors;
const themeColors = getThemeColors([
['primary', primary],
['info', info],
['success', success],
['warning', warning],
['error', error]
]);
const colorLoading = primary;
return {
common: {
...themeColors
},
LoadingBar: {
colorLoading
}
};
}
type ThemeVars = Exclude<GlobalThemeOverrides['common'], undefined>;
type ThemeVarsKeys = keyof ThemeVars;
/** 添加css vars至html */
export function addThemeCssVarsToHtml(themeVars: ThemeVars, action: 'add' | 'update' = 'add') {
const keys = Object.keys(themeVars) as ThemeVarsKeys[];
const style: string[] = [];
keys.forEach(key => {
style.push(`--${kebabCase(key)}: ${themeVars[key]}`);
});
const styleStr = style.join(';');
if (action === 'add') {
document.documentElement.style.cssText = styleStr;
} else {
document.documentElement.style.cssText += styleStr;
}
}
/**
* 根据主题颜色更新css vars
* @param primaryColor
*/
export function updateThemeCssVarsByPrimary(primaryColor: string) {
const themeColor = getThemeColors([['primary', primaryColor]]);
addThemeCssVarsToHtml(themeColor, 'update');
}
/** windicss 暗黑模式 */
export function handleWindicssDarkMode() {
const DARK_CLASS = 'dark';
function addDarkClass() {
document.documentElement.classList.add(DARK_CLASS);
}
function removeDarkClass() {
document.documentElement.classList.remove(DARK_CLASS);
}
return {
addDarkClass,
removeDarkClass
};
}

View File

@ -0,0 +1,229 @@
import { ref, reactive, computed } from 'vue';
import type { Ref, ComputedRef } from 'vue';
import { defineStore } from 'pinia';
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 type { ThemeSetting, ThemeHorizontalMenuPosition } from '@/interface';
import { getNaiveThemeOverrides, addThemeCssVarsToHtml } from './helpers';
import {
useLayoutFunc,
useHeaderFunc,
useTabFunc,
useSiderFunc,
useFooterFunc,
usePageFunc,
osThemeWatcher,
setupWindicssDarkMode,
setupHiddenScroll,
themeColorWatcher
} from './hooks';
import type { LayoutFunc, HeaderFunc, TabFunc, SiderFunc, FooterFunc, PageFunc } from './hooks';
type BuiltInGlobalTheme = Omit<Required<GlobalTheme>, 'InternalSelectMenu' | 'InternalSelection'>;
interface ThemeStore extends LayoutFunc, HeaderFunc, TabFunc, SiderFunc, FooterFunc, PageFunc {
/** 暗黑模式 */
darkMode: Ref<boolean>;
/** 设置暗黑模式 */
setDarkMode(dark: boolean): void;
/** 切换/关闭 暗黑模式 */
toggleDarkMode(): void;
/** 布局样式 */
layout: ThemeSetting['layout'];
/** 主题颜色 */
themeColor: Ref<string>;
/** 设置系统主题颜色 */
setThemeColor(color: string): void;
/** 主题颜色列表 */
themeColorList: string[];
/** 其他颜色 */
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暗黑主题 */
naiveTheme: ComputedRef<BuiltInGlobalTheme | undefined>;
/** 重置状态 */
resetThemeStore(): void;
}
export const useThemeStore = defineStore('theme-store', () => {
// 暗黑模式
const { bool: darkMode, setBool: setDarkMode, toggle: toggleDarkMode } = useBoolean();
// 布局
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 })
);
const naiveTheme = computed(() => (darkMode.value ? darkTheme : undefined));
/** 重置theme状态 */
function resetThemeStore() {
setDarkMode(false);
}
/** 初始化css vars, 并添加至html */
function initThemeCssVars() {
const updatedThemeVars = { ...naiveThemeOverrides.value.common };
addThemeCssVarsToHtml(updatedThemeVars);
}
/** 系统主题适应操作系统 */
function handleAdaptOsTheme() {
osThemeWatcher(isDark => {
if (isDark) {
setDarkMode(true);
} else {
setDarkMode(false);
}
});
}
function init() {
initThemeCssVars();
handleAdaptOsTheme();
setupWindicssDarkMode(darkMode);
setupHiddenScroll(computed(() => layout.minWidth));
themeColorWatcher(themeColor);
}
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,
resetThemeStore
};
return themeStore;
});

View File

@ -63,27 +63,14 @@ type ThemeVars = Exclude<GlobalThemeOverrides['common'], undefined>;
type ThemeVarsKeys = keyof ThemeVars;
/** 添加css vars至html */
export function addThemeCssVarsToHtml(themeVars: ThemeVars, action: 'add' | 'update' = 'add') {
export function addThemeCssVarsToHtml(themeVars: ThemeVars) {
const keys = Object.keys(themeVars) as ThemeVarsKeys[];
const style: string[] = [];
keys.forEach(key => {
style.push(`--${kebabCase(key)}: ${themeVars[key]}`);
});
const styleStr = style.join(';');
if (action === 'add') {
document.documentElement.style.cssText = styleStr;
} else {
document.documentElement.style.cssText += styleStr;
}
}
/**
* 根据主题颜色更新css vars
* @param primaryColor
*/
export function updateThemeCssVarsByPrimary(primaryColor: string) {
const themeColor = getThemeColors([['primary', primaryColor]]);
addThemeCssVarsToHtml(themeColor, 'update');
document.documentElement.style.cssText = styleStr;
}
/** windicss 暗黑模式 */

View File

@ -1,229 +1,140 @@
import { ref, reactive, computed } from 'vue';
import type { Ref, ComputedRef } from 'vue';
import { defineStore } from 'pinia';
import { darkTheme } from 'naive-ui';
import type { GlobalThemeOverrides, GlobalTheme } from 'naive-ui';
import { cloneDeep } from 'lodash-es';
import { themeSetting } from '@/settings';
import { useBoolean } from '@/hooks';
import { getColorPalette } from '@/utils';
import type { ThemeSetting, ThemeHorizontalMenuPosition } from '@/interface';
import type {
ThemeSetting,
ThemeLayoutMode,
ThemeTabMode,
ThemeHorizontalMenuPosition,
ThemeAnimateMode
} from '@/interface';
import { getNaiveThemeOverrides, addThemeCssVarsToHtml } from './helpers';
import {
useLayoutFunc,
useHeaderFunc,
useTabFunc,
useSiderFunc,
useFooterFunc,
usePageFunc,
osThemeWatcher,
setupWindicssDarkMode,
setupHiddenScroll,
themeColorWatcher
} from './hooks';
import type { LayoutFunc, HeaderFunc, TabFunc, SiderFunc, FooterFunc, PageFunc } from './hooks';
type BuiltInGlobalTheme = Omit<Required<GlobalTheme>, 'InternalSelectMenu' | 'InternalSelection'>;
type ThemeState = ThemeSetting;
interface ThemeStore extends LayoutFunc, HeaderFunc, TabFunc, SiderFunc, FooterFunc, PageFunc {
/** 暗黑模式 */
darkMode: Ref<boolean>;
/** 设置暗黑模式 */
setDarkMode(dark: boolean): void;
/** 切换/关闭 暗黑模式 */
toggleDarkMode(): void;
/** 布局样式 */
layout: ThemeSetting['layout'];
/** 主题颜色 */
themeColor: Ref<string>;
/** 设置系统主题颜色 */
setThemeColor(color: string): void;
/** 主题颜色列表 */
themeColorList: string[];
/** 其他颜色 */
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暗黑主题 */
naiveTheme: ComputedRef<BuiltInGlobalTheme | undefined>;
/** 重置状态 */
resetThemeStore(): void;
}
export const useThemeStore = defineStore('theme-store', () => {
// 暗黑模式
const { bool: darkMode, setBool: setDarkMode, toggle: toggleDarkMode } = useBoolean();
// 布局
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 })
);
const naiveTheme = computed(() => (darkMode.value ? darkTheme : undefined));
/** 重置theme状态 */
function resetThemeStore() {
setDarkMode(false);
}
/** 初始化css vars, 并添加至html */
function initThemeCssVars() {
const updatedThemeVars = { ...naiveThemeOverrides.value.common };
addThemeCssVarsToHtml(updatedThemeVars);
}
/** 系统主题适应操作系统 */
function handleAdaptOsTheme() {
osThemeWatcher(isDark => {
if (isDark) {
setDarkMode(true);
} else {
setDarkMode(false);
export const useThemeStore = defineStore('theme-store', {
state: (): ThemeState => cloneDeep(themeSetting),
getters: {
/** naiveUI的主题配置 */
naiveThemeOverrides(state) {
const overrides = getNaiveThemeOverrides({ primary: state.themeColor, ...state.otherColor });
addThemeCssVarsToHtml(overrides.common!);
return overrides;
},
/** naive-ui暗黑主题 */
naiveTheme(state) {
return state.darkMode ? darkTheme : undefined;
}
},
actions: {
/** 重置theme状态 */
resetThemeStore() {
this.$reset();
},
/** 设置暗黑模式 */
setDarkMode(darkMode: boolean) {
this.darkMode = darkMode;
},
/** 切换/关闭 暗黑模式 */
toggleDarkMode() {
this.darkMode = !this.darkMode;
},
/** 设置布局最小宽度 */
setLayoutMinWidth(minWidth: number) {
this.layout.minWidth = minWidth;
},
/** 设置布局模式 */
setLayoutMode(mode: ThemeLayoutMode) {
this.layout.mode = mode;
},
/** 设置系统主题颜色 */
setThemeColor(themeColor: string) {
this.themeColor = themeColor;
},
/** 设置固定头部和多页签 */
setIsFixedHeaderAndTab(isFixed: boolean) {
this.fixedHeaderAndTab = isFixed;
},
/** 设置重载按钮可见状态 */
setReloadVisible(visible: boolean) {
this.showReload = visible;
},
/** 设置头部高度 */
setHeaderHeight(height: number | null) {
if (height) {
this.header.height = height;
}
});
},
/** 设置头部面包屑可见 */
setHeaderCrumbVisible(visible: boolean) {
this.header.crumb.visible = visible;
},
/** 设置头部面包屑图标可见 */
setHeaderCrumbIconVisible(visible: boolean) {
this.header.crumb.showIcon = visible;
},
/** 设置多页签可见 */
setTabVisible(visible: boolean) {
this.tab.visible = visible;
},
/** 设置多页签高度 */
setTabHeight(height: number | null) {
if (height) {
this.tab.height = height;
}
},
/** 设置多页签风格 */
setTabMode(mode: ThemeTabMode) {
this.tab.mode = mode;
},
/** 设置多页签缓存 */
setTabIsCache(isCache: boolean) {
this.tab.isCache = isCache;
},
/** 侧边栏宽度 */
setSiderWidth(width: number | null) {
if (width) {
this.sider.width = width;
}
},
/** 侧边栏折叠时的宽度 */
setSiderCollapsedWidth(width: number) {
this.sider.collapsedWidth = width;
},
/** vertical-mix模式下侧边栏宽度 */
setMixSiderWidth(width: number | null) {
if (width) {
this.sider.mixWidth = width;
}
},
/** vertical-mix模式下侧边栏折叠时的宽度 */
setMixSiderCollapsedWidth(width: number) {
this.sider.mixCollapsedWidth = width;
},
/** vertical-mix模式下侧边栏展示子菜单的宽度 */
setMixSiderChildMenuWidth(width: number) {
this.sider.mixChildMenuWidth = width;
},
/** 设置水平模式的菜单的位置 */
setHorizontalMenuPosition(posiiton: ThemeHorizontalMenuPosition) {
this.menu.horizontalPosition = posiiton;
},
/** 设置底部是否固定 */
setFooterIsFixed(isFixed: boolean) {
this.footer.fixed = isFixed;
},
/** 设置底部高度 */
setFooterHeight(height: number) {
this.footer.height = height;
},
/** 设置切换页面时是否过渡动画 */
setPageIsAnimate(animate: boolean) {
this.page.animate = animate;
},
/** 设置页面过渡动画类型 */
setPageAnimateMode(mode: ThemeAnimateMode) {
this.page.animateMode = mode;
}
}
function init() {
initThemeCssVars();
handleAdaptOsTheme();
setupWindicssDarkMode(darkMode);
setupHiddenScroll(computed(() => layout.minWidth));
themeColorWatcher(themeColor);
}
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,
resetThemeStore
};
return themeStore;
});

View File

@ -0,0 +1,13 @@
import { useBodyScroll } from '@/hooks';
import { useAppStore } from '../modules';
/** 订阅app store */
export default function subscribeAppStore() {
const app = useAppStore();
const { scrollBodyHandler } = useBodyScroll();
app.$subscribe((_mutation, state) => {
// 弹窗打开时禁止滚动条
scrollBodyHandler(state.settingDrawerVisible);
});
}

View File

@ -0,0 +1,6 @@
import subscribeAppStore from './app';
/** 订阅状态 */
export function subscribeStore() {
subscribeAppStore();
}

View File

@ -0,0 +1,31 @@
import { useThemeStore } from '../modules';
/** 订阅app store */
export default function subscribeAppStore() {
const theme = useThemeStore();
const { addDarkClass, removeDarkClass } = handleWindicssDarkMode();
theme.$subscribe((_mutation, state) => {
// 监听暗黑模式
if (state.darkMode) {
addDarkClass();
} else {
removeDarkClass();
}
});
}
/** windicss 暗黑模式 */
function handleWindicssDarkMode() {
const DARK_CLASS = 'dark';
function addDarkClass() {
document.documentElement.classList.add(DARK_CLASS);
}
function removeDarkClass() {
document.documentElement.classList.remove(DARK_CLASS);
}
return {
addDarkClass,
removeDarkClass
};
}