mirror of
https://github.com/m-xlsea/ruoyi-plus-soybean.git
synced 2025-09-24 07:49:47 +08:00
refactor(projects): 恢复pinia默认写法
This commit is contained in:
@ -7,3 +7,4 @@ export function setupStore(app: App) {
|
||||
}
|
||||
|
||||
export * from './modules';
|
||||
export * from './subscribe';
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
102
src/store/modules/theme-old/helpers.ts
Normal file
102
src/store/modules/theme-old/helpers.ts
Normal 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
|
||||
};
|
||||
}
|
229
src/store/modules/theme-old/index.ts
Normal file
229
src/store/modules/theme-old/index.ts
Normal 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;
|
||||
});
|
@ -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 暗黑模式 */
|
||||
|
@ -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;
|
||||
});
|
||||
|
13
src/store/subscribe/app.ts
Normal file
13
src/store/subscribe/app.ts
Normal 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);
|
||||
});
|
||||
}
|
6
src/store/subscribe/index.ts
Normal file
6
src/store/subscribe/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import subscribeAppStore from './app';
|
||||
|
||||
/** 订阅状态 */
|
||||
export function subscribeStore() {
|
||||
subscribeAppStore();
|
||||
}
|
31
src/store/subscribe/theme.ts
Normal file
31
src/store/subscribe/theme.ts
Normal 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
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user