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

@ -4,6 +4,7 @@ import Components from 'unplugin-vue-components/vite'; // 从指定目录自动
export default [
Components({
dts: false,
resolvers: [IconsResolver({ componentPrefix: 'icon' })]
}),
Icons({ scale: 1, defaultClass: 'inline-block' })

View File

@ -22,7 +22,7 @@
}
},
"dependencies": {
"@antv/g2plot": "^2.4.5",
"@antv/g2plot": "^2.4.7",
"@vueuse/core": "^7.5.3",
"axios": "^0.24.0",
"clipboard": "^2.0.8",
@ -40,7 +40,7 @@
"devDependencies": {
"@commitlint/cli": "^16.0.2",
"@commitlint/config-conventional": "^16.0.0",
"@iconify/json": "^1.1.455",
"@iconify/json": "^1.1.457",
"@iconify/vue": "^3.1.2",
"@types/crypto-js": "^4.1.0",
"@types/node": "^17.0.8",
@ -54,7 +54,7 @@
"cross-env": "^7.0.3",
"cz-conventional-changelog": "^3.3.0",
"cz-customizable": "^6.3.0",
"eslint": "^8.6.0",
"eslint": "^8.7.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.25.4",
@ -66,17 +66,17 @@
"patch-package": "^6.4.7",
"postinstall-postinstall": "^2.1.0",
"prettier": "^2.5.1",
"rollup-plugin-visualizer": "^5.5.2",
"rollup-plugin-visualizer": "^5.5.4",
"sass": "^1.48.0",
"typescript": "^4.5.4",
"unplugin-icons": "^0.13.0",
"unplugin-vue-components": "^0.17.11",
"unplugin-vue-components": "^0.17.13",
"vite": "^2.7.12",
"vite-plugin-html": "^2.1.2",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-windicss": "^1.6.2",
"vue-tsc": "^0.30.2",
"vueuc": "^0.4.22",
"vue-tsc": "^0.30.4",
"vueuc": "^0.4.23",
"windicss": "^3.4.2"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,15 @@
</template>
<script setup lang="ts">
import { subscribeStore } from '@/store';
import { useTheme } from '@/composables';
import AppProvider from './AppProvider.vue';
function init() {
subscribeStore();
useTheme();
}
init();
</script>
<style scoped></style>

View File

@ -1,3 +1,4 @@
export * from './system';
export * from './router';
export * from './theme';
export * from './layout';

View File

@ -0,0 +1,37 @@
import { watch, onUnmounted } from 'vue';
import { useOsTheme } from 'naive-ui';
import { useElementSize } from '@vueuse/core';
import { useThemeStore } from '@/store';
export function useTheme() {
const osTheme = useOsTheme();
const theme = useThemeStore();
const { width } = useElementSize(document.documentElement);
/** 监听操作系统主题模式 */
const stopHandle = watch(
osTheme,
newValue => {
const isDark = newValue === 'dark';
theme.setDarkMode(isDark);
},
{ immediate: true }
);
/**
*
* @description ,,
*/
const anotherStopHandle = watch(width, newValue => {
if (newValue < theme.layout.minWidth) {
document.documentElement.style.overflowX = 'auto';
} else {
document.documentElement.style.overflowX = 'hidden';
}
});
onUnmounted(() => {
stopHandle();
anotherStopHandle();
});
}

View File

@ -42,5 +42,5 @@ export const ERROR_STATUS = {
/** 不弹出错误信息的code */
export const NO_ERROR_MSG_CODE: (string | number)[] = [];
/** token失效需要刷新token的接口 */
/** token失效需要刷新token的code */
export const REFRESH_TOKEN_CODE: (string | number)[] = [66666];

View File

@ -3,6 +3,7 @@ import useBoolean from './useBoolean';
import useLoading from './useLoading';
import useLoadingEmpty from './useLoadingEmpty';
import useReload from './useReload';
import useBodyScroll from './useBodyScroll';
import useModalVisible from './useModalVisible';
export { useContext, useBoolean, useLoading, useLoadingEmpty, useReload, useModalVisible };
export { useContext, useBoolean, useLoading, useLoadingEmpty, useReload, useBodyScroll, useModalVisible };

View File

@ -0,0 +1,47 @@
interface ScrollBodyStyle {
overflow: string;
paddingRight: string;
}
/**
* body标签滚动
* @param duration -
*/
export default function useBodyScroll(duration = 300) {
const defaultStyle: ScrollBodyStyle = {
overflow: '',
paddingRight: ''
};
function getInitBodyStyle() {
const { overflow, paddingRight } = document.body.style;
Object.assign(defaultStyle, { overflow, paddingRight });
}
function setScrollBodyStyle() {
document.body.style.paddingRight = `${window.innerWidth - document.body.clientWidth}px`;
document.body.style.overflow = 'hidden';
}
function resetScrollBodyStyle() {
document.body.style.overflow = defaultStyle.overflow;
document.body.style.paddingRight = defaultStyle.paddingRight;
}
/**
* body的滚动条
* @param hideScroll -
*/
function scrollBodyHandler(hideScroll: boolean) {
if (hideScroll) {
setScrollBodyStyle();
} else {
setTimeout(() => {
resetScrollBodyStyle();
}, duration);
}
}
getInitBodyStyle();
return {
scrollBodyHandler
};
}

View File

@ -1,50 +1,18 @@
import { computed, watch, onUnmounted } from 'vue';
import type { ComputedRef } from 'vue';
import { watch, onUnmounted } from 'vue';
import useBoolean from './useBoolean';
interface ScrollBodyStyle {
overflow: string;
paddingRight: string;
}
import useBodyScroll from './useBodyScroll';
/**
* 使
* @param hideScroll - html滚动条
* @param duration -
*/
export default function useModalVisible(hideScroll = true, duration = 300) {
export default function useModalVisible(hideScroll = true) {
const { bool: visible, setTrue: openModal, setFalse: closeModal, toggle: toggleModal } = useBoolean();
const { scrollBodyHandler } = useBodyScroll();
const defaultStyle: ScrollBodyStyle = {
overflow: '',
paddingRight: ''
};
function getInitBodyStyle() {
if (hideScroll) {
const { overflow, paddingRight } = document.body.style;
Object.assign(defaultStyle, { overflow, paddingRight });
}
}
function setScrollBodyStyle() {
document.body.style.paddingRight = `${window.innerWidth - document.body.clientWidth}px`;
document.body.style.overflow = 'hidden';
}
function resetScrollBodyStyle() {
document.body.style.overflow = defaultStyle.overflow;
document.body.style.paddingRight = defaultStyle.paddingRight;
}
function modalVisibleWatcher(visible: ComputedRef<boolean>) {
function modalVisibleWatcher() {
const stopHandle = watch(visible, async newValue => {
if (hideScroll) {
if (newValue) {
setScrollBodyStyle();
} else {
setTimeout(() => {
resetScrollBodyStyle();
}, duration);
}
}
scrollBodyHandler(newValue);
});
onUnmounted(() => {
@ -52,18 +20,14 @@ export default function useModalVisible(hideScroll = true, duration = 300) {
});
}
function init() {
getInitBodyStyle();
modalVisibleWatcher(computed(() => visible.value));
if (hideScroll) {
modalVisibleWatcher();
}
init();
return {
visible,
openModal,
closeModal,
toggleModal,
modalVisibleWatcher
toggleModal
};
}

View File

@ -1,9 +1,14 @@
import type { MenuOption, DropdownOption } from 'naive-ui';
import type { VNodeChild } from 'vue';
import type { DropdownOption } from 'naive-ui';
/** 菜单项配置 */
export type GlobalMenuOption = MenuOption & {
export type GlobalMenuOption = {
key: string;
label: string;
routeName: string;
routePath: string;
icon?: () => VNodeChild;
children?: GlobalMenuOption[];
};
/** 面包屑 */

View File

@ -1,6 +1,6 @@
<template>
<hover-container class="w-40px" content-class="hover:text-primary" tooltip-content="主题模式">
<dark-mode-switch :dark="theme.darkMode" class="wh-full" @update:dark="setDarkMode" />
<dark-mode-switch :dark="theme.darkMode" class="wh-full" @update:dark="theme.setDarkMode" />
</hover-container>
</template>
@ -9,6 +9,5 @@ import { HoverContainer, DarkModeSwitch } from '@/components';
import { useThemeStore } from '@/store';
const theme = useThemeStore();
const { setDarkMode } = useThemeStore();
</script>
<style scoped></style>

View File

@ -45,7 +45,7 @@ function handleDropdown(optionKey: string) {
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
auth.resetAuthStore(true);
auth.resetAuthStore();
}
});
}

View File

@ -1,7 +1,9 @@
<template>
<router-link :to="routeHomePath" class="flex-center w-full nowrap-hidden">
<system-logo class="w-32px h-32px text-primary" />
<h2 v-if="showTitle" class="pl-8px text-16px font-bold text-primary">{{ title }}</h2>
<h2 v-show="showTitle" class="pl-8px text-16px font-bold text-primary transition duration-300 ease-in-out">
{{ title }}
</h2>
</router-link>
</template>

View File

@ -44,7 +44,7 @@ function getActiveKeysInMenus(menu: GlobalMenuOption) {
keys.push(menu.routeName);
}
if (menu.children) {
keys.push(...menu.children.map(item => getActiveKeysInMenus(item as GlobalMenuOption)).flat());
keys.push(...menu.children.map(item => getActiveKeysInMenus(item as GlobalMenuOption)).flat(1));
}
return keys;
}

View File

@ -3,7 +3,7 @@
type="primary"
:class="[{ '!right-330px': app.settingDrawerVisible }, app.settingDrawerVisible ? 'ease-out' : 'ease-in']"
class="fixed top-240px right-14px z-10000 w-42px h-42px !p-0 transition-all duration-300"
@click="toggleSettingdrawerVisible"
@click="app.toggleSettingdrawerVisible"
>
<icon-ant-design:close-outlined v-if="app.settingDrawerVisible" class="text-24px" />
<icon-ant-design:setting-outlined v-else class="text-24px" />
@ -15,6 +15,5 @@ import { NButton } from 'naive-ui';
import { useAppStore } from '@/store';
const app = useAppStore();
const { toggleSettingdrawerVisible } = useAppStore();
</script>
<style scoped></style>

View File

@ -2,7 +2,7 @@
<n-divider title-placement="center">界面功能</n-divider>
<n-space vertical size="large">
<setting-menu label="固定头部和多页签">
<n-switch :value="theme.fixedHeaderAndTab" @update:value="setIsFixedHeaderAndTab" />
<n-switch :value="theme.fixedHeaderAndTab" @update:value="theme.setIsFixedHeaderAndTab" />
</setting-menu>
<setting-menu label="顶部菜单位置">
<n-select
@ -10,7 +10,7 @@
size="small"
:value="theme.menu.horizontalPosition"
:options="theme.menu.horizontalPositionList"
@update:value="setHorizontalMenuPosition"
@update:value="theme.setHorizontalMenuPosition"
/>
</setting-menu>
<setting-menu label="头部高度">
@ -19,7 +19,7 @@
size="small"
:value="theme.header.height"
:step="1"
@update:value="handleSetNumber($event, setHeaderHeight)"
@update:value="theme.setHeaderHeight"
/>
</setting-menu>
<setting-menu label="多页签高度">
@ -28,11 +28,11 @@
size="small"
:value="theme.tab.height"
:step="1"
@update:value="handleSetNumber($event, setTabHeight)"
@update:value="theme.setTabHeight"
/>
</setting-menu>
<setting-menu label="多页签缓存">
<n-switch :value="theme.tab.isCache" @update:value="setTabIsCache" />
<n-switch :value="theme.tab.isCache" @update:value="theme.setTabIsCache" />
</setting-menu>
<setting-menu label="侧边栏展开宽度">
<n-input-number
@ -40,7 +40,7 @@
size="small"
:value="theme.sider.width"
:step="10"
@update:value="handleSetNumber($event, setSiderWidth)"
@update:value="theme.setSiderWidth"
/>
</setting-menu>
<setting-menu label="左侧混合侧边栏展开宽度">
@ -49,11 +49,11 @@
size="small"
:value="theme.sider.mixWidth"
:step="5"
@update:value="handleSetNumber($event, setMixSiderWidth)"
@update:value="theme.setMixSiderWidth"
/>
</setting-menu>
<setting-menu label="固定底部">
<n-switch :value="theme.footer.fixed" @update:value="setFooterIsFixed" />
<n-switch :value="theme.footer.fixed" @update:value="theme.setFooterIsFixed" />
</setting-menu>
</n-space>
</template>
@ -64,21 +64,5 @@ import { useThemeStore } from '@/store';
import SettingMenu from '../SettingMenu/index.vue';
const theme = useThemeStore();
const {
setHorizontalMenuPosition,
setIsFixedHeaderAndTab,
setHeaderHeight,
setTabHeight,
setSiderWidth,
setMixSiderWidth,
setTabIsCache,
setFooterIsFixed
} = useThemeStore();
function handleSetNumber(value: number | null, callback: (value: number) => void) {
if (value !== null) {
callback(value);
}
}
</script>
<style scoped></style>

View File

@ -2,13 +2,13 @@
<n-divider title-placement="center">界面显示</n-divider>
<n-space vertical size="large">
<setting-menu label="面包屑">
<n-switch :value="theme.header.crumb.visible" @update:value="setHeaderCrumbVisible" />
<n-switch :value="theme.header.crumb.visible" @update:value="theme.setHeaderCrumbVisible" />
</setting-menu>
<setting-menu label="面包屑图标">
<n-switch :value="theme.header.crumb.showIcon" @update:value="setHeaderCrumbIconVisible" />
<n-switch :value="theme.header.crumb.showIcon" @update:value="theme.setHeaderCrumbIconVisible" />
</setting-menu>
<setting-menu label="多页签">
<n-switch :value="theme.tab.visible" @update:value="setTabVisible" />
<n-switch :value="theme.tab.visible" @update:value="theme.setTabVisible" />
</setting-menu>
<setting-menu label="多页签风格">
<n-select
@ -16,11 +16,11 @@
size="small"
:value="theme.tab.mode"
:options="theme.tab.modeList"
@update:value="setTabMode"
@update:value="theme.setTabMode"
/>
</setting-menu>
<setting-menu label="页面切换动画">
<n-switch :value="theme.page.animate" @update:value="setPageIsAnimate" />
<n-switch :value="theme.page.animate" @update:value="theme.setPageIsAnimate" />
</setting-menu>
<setting-menu label="页面切换动画类型">
<n-select
@ -28,7 +28,7 @@
size="small"
:value="theme.page.animateMode"
:options="theme.page.animateModeList"
@update:value="setPageAnimateMode"
@update:value="theme.setPageAnimateMode"
/>
</setting-menu>
</n-space>
@ -40,13 +40,5 @@ import { useThemeStore } from '@/store';
import SettingMenu from '../SettingMenu/index.vue';
const theme = useThemeStore();
const {
setHeaderCrumbVisible,
setHeaderCrumbIconVisible,
setTabVisible,
setTabMode,
setPageIsAnimate,
setPageAnimateMode
} = useThemeStore();
</script>
<style scoped></style>

View File

@ -12,7 +12,6 @@ export async function handlePagePermission(
) {
const auth = useAuthStore();
const route = useRouteStore();
const { initDynamicRoute } = useRouteStore();
const isLogin = Boolean(getToken());
const permissions = to.meta.permissions || [];
@ -21,7 +20,7 @@ export async function handlePagePermission(
if (!route.isAddedDynamicRoute) {
// 添加动态路由
await initDynamicRoute(router);
await route.initDynamicRoute(router);
if (to.name === routeName('not-found-page')) {
// 动态路由没有加载导致被not-found-page路由捕获等待动态路由加载好了回到之前的路由

View File

@ -5,7 +5,7 @@ import { fetchUpdateToken } from '../api';
/**
* token
* token失效时的请求配置
* @param axiosConfig - token失效时的请求配置
*/
export async function refreshToken(axiosConfig: AxiosRequestConfig) {
const { resetAuthStore } = useAuthStore();
@ -21,6 +21,6 @@ export async function refreshToken(axiosConfig: AxiosRequestConfig) {
return config;
}
resetAuthStore(true);
resetAuthStore();
return null;
}

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>;
loginLoding: boolean;
}
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);
clearAuthStorage();
this.$reset();
if (route.meta.requiresAuth) {
toLogin();
}
},
/**
* token进行登录
* @param backendToken - token
*/
async loginByToken(backendToken: ApiAuth.Token) {
const { toLoginRedirect } = useRouterPush(false);
// 先把token存储到缓存中
const { token, refreshToken } = backendToken;
setToken(token);
setRefreshToken(refreshToken);
// 获取用户信息
const { data } = await fetchUserInfo();
if (data) {
// 成功后把用户信息存储到缓存中
setUserInfo(data);
// 更新状态
Object.assign(this, { userInfo: data, token });
// 跳转登录后的地址
toLoginRedirect();
// 登录成功弹出欢迎提示
window.$notification?.success({
title: '登录成功!',
content: `欢迎回来,${data.userName}!`,
duration: 3000
});
} else {
// 不成功则重置状态
this.resetAuthStore();
}
},
/**
*
* @param phone -
* @param pwdOrCode -
* @param type - 登录方式: pwd - ; sms -
*/
login(phone: string, pwdOrCode: string, type: 'pwd' | 'sms'): void;
}
export const useAuthStore = defineStore('auth-store', () => {
const { toLogin, toLoginRedirect } = useRouterPush(false);
const userInfo: Auth.UserInfo = reactive(getUserInfo());
function handleSetUserInfo(data: Auth.UserInfo) {
Object.assign(userInfo, data);
}
const token = ref(getToken());
function handleSetToken(data: string) {
token.value = data;
}
const isLogin = computed(() => Boolean(token.value));
const { loading: loginLoding, startLoading: startLoginLoading, endLoading: endLoginLoading } = useLoading();
function resetStore() {
handleSetUserInfo(getUserInfo());
handleSetToken(getToken());
}
function resetAuthStore(pushRoute: boolean = true) {
const route = unref(globalRouter.currentRoute);
clearAuthStorage();
resetStore();
if (pushRoute && route.meta.requiresAuth) {
toLogin();
}
}
async function login(phone: string, pwdOrCode: string, type: 'pwd' | 'sms') {
startLoginLoading();
async login(phone: string, pwdOrCode: string, type: 'pwd' | 'sms') {
this.loginLoding = true;
const { data } = await fetchLogin(phone, pwdOrCode, type);
if (data) {
await loginByToken(data);
await this.loginByToken(data);
}
this.loginLoding = false;
}
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;
}
const { bool: isAddedDynamicRoute, setTrue: setAddedDynamicRoute } = useBoolean();
async function initDynamicRoute(router: Router) {
export const useRouteStore = defineStore('route-store', {
state: (): RouteState => ({
isAddedDynamicRoute: false,
menus: []
}),
actions: {
/**
*
* @param router -
*/
async initDynamicRoute(router: Router) {
const { data } = await fetchUserRoutes();
if (data) {
getMenus(data.routes);
this.menus = transformAuthRouteToMenu(data.routes);
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');
}
/** 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'];
export const useThemeStore = defineStore('theme-store', {
state: (): ThemeState => cloneDeep(themeSetting),
getters: {
/** naiveUI的主题配置 */
naiveThemeOverrides: ComputedRef<GlobalThemeOverrides>;
naiveThemeOverrides(state) {
const overrides = getNaiveThemeOverrides({ primary: state.themeColor, ...state.otherColor });
addThemeCssVarsToHtml(overrides.common!);
return overrides;
},
/** naive-ui暗黑主题 */
naiveTheme: ComputedRef<BuiltInGlobalTheme | undefined>;
/** 重置状态 */
resetThemeStore(): void;
naiveTheme(state) {
return state.darkMode ? darkTheme : undefined;
}
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));
},
actions: {
/** 重置theme状态 */
function resetThemeStore() {
setDarkMode(false);
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;
}
/** 初始化css vars, 并添加至html */
function initThemeCssVars() {
const updatedThemeVars = { ...naiveThemeOverrides.value.common };
addThemeCssVarsToHtml(updatedThemeVars);
},
/** 设置头部面包屑可见 */
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 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

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

View File

@ -22,7 +22,7 @@ function addPartialProps(menuItem: GlobalMenuOption, icon?: string, children?: G
*
* @param routes -
*/
export function transformAuthRouteToMenu(routes: AuthRoute.Route[]) {
export function transformAuthRouteToMenu(routes: AuthRoute.Route[]): GlobalMenuOption[] {
const globalMenu: GlobalMenuOption[] = [];
routes.forEach(route => {
const { name, path, meta } = route;

View File

@ -3,7 +3,7 @@
<dark-mode-switch
:dark="theme.darkMode"
class="absolute left-48px top-24px z-3 text-20px"
@update:dark="setDarkMode"
@update:dark="theme.setDarkMode"
/>
<n-card :bordered="false" size="large" class="z-4 !w-auto rounded-20px shadow-sm">
<div class="w-360px">
@ -53,7 +53,6 @@ interface LoginModule {
const props = defineProps<Props>();
const theme = useThemeStore();
const { setDarkMode } = useThemeStore();
const { title } = useAppInfo();
const modules: LoginModule[] = [