mirror of
https://github.com/soybeanjs/soybean-admin.git
synced 2025-09-22 03:26:38 +08:00
refactor(projects): 恢复pinia默认写法
This commit is contained in:
parent
28b5d22401
commit
b2a4ddf5e3
@ -4,6 +4,7 @@ import Components from 'unplugin-vue-components/vite'; // 从指定目录自动
|
|||||||
|
|
||||||
export default [
|
export default [
|
||||||
Components({
|
Components({
|
||||||
|
dts: false,
|
||||||
resolvers: [IconsResolver({ componentPrefix: 'icon' })]
|
resolvers: [IconsResolver({ componentPrefix: 'icon' })]
|
||||||
}),
|
}),
|
||||||
Icons({ scale: 1, defaultClass: 'inline-block' })
|
Icons({ scale: 1, defaultClass: 'inline-block' })
|
||||||
|
14
package.json
14
package.json
@ -22,7 +22,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@antv/g2plot": "^2.4.5",
|
"@antv/g2plot": "^2.4.7",
|
||||||
"@vueuse/core": "^7.5.3",
|
"@vueuse/core": "^7.5.3",
|
||||||
"axios": "^0.24.0",
|
"axios": "^0.24.0",
|
||||||
"clipboard": "^2.0.8",
|
"clipboard": "^2.0.8",
|
||||||
@ -40,7 +40,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^16.0.2",
|
"@commitlint/cli": "^16.0.2",
|
||||||
"@commitlint/config-conventional": "^16.0.0",
|
"@commitlint/config-conventional": "^16.0.0",
|
||||||
"@iconify/json": "^1.1.455",
|
"@iconify/json": "^1.1.457",
|
||||||
"@iconify/vue": "^3.1.2",
|
"@iconify/vue": "^3.1.2",
|
||||||
"@types/crypto-js": "^4.1.0",
|
"@types/crypto-js": "^4.1.0",
|
||||||
"@types/node": "^17.0.8",
|
"@types/node": "^17.0.8",
|
||||||
@ -54,7 +54,7 @@
|
|||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"cz-conventional-changelog": "^3.3.0",
|
"cz-conventional-changelog": "^3.3.0",
|
||||||
"cz-customizable": "^6.3.0",
|
"cz-customizable": "^6.3.0",
|
||||||
"eslint": "^8.6.0",
|
"eslint": "^8.7.0",
|
||||||
"eslint-config-airbnb-base": "^15.0.0",
|
"eslint-config-airbnb-base": "^15.0.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-plugin-import": "^2.25.4",
|
"eslint-plugin-import": "^2.25.4",
|
||||||
@ -66,17 +66,17 @@
|
|||||||
"patch-package": "^6.4.7",
|
"patch-package": "^6.4.7",
|
||||||
"postinstall-postinstall": "^2.1.0",
|
"postinstall-postinstall": "^2.1.0",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"rollup-plugin-visualizer": "^5.5.2",
|
"rollup-plugin-visualizer": "^5.5.4",
|
||||||
"sass": "^1.48.0",
|
"sass": "^1.48.0",
|
||||||
"typescript": "^4.5.4",
|
"typescript": "^4.5.4",
|
||||||
"unplugin-icons": "^0.13.0",
|
"unplugin-icons": "^0.13.0",
|
||||||
"unplugin-vue-components": "^0.17.11",
|
"unplugin-vue-components": "^0.17.13",
|
||||||
"vite": "^2.7.12",
|
"vite": "^2.7.12",
|
||||||
"vite-plugin-html": "^2.1.2",
|
"vite-plugin-html": "^2.1.2",
|
||||||
"vite-plugin-mock": "^2.9.6",
|
"vite-plugin-mock": "^2.9.6",
|
||||||
"vite-plugin-windicss": "^1.6.2",
|
"vite-plugin-windicss": "^1.6.2",
|
||||||
"vue-tsc": "^0.30.2",
|
"vue-tsc": "^0.30.4",
|
||||||
"vueuc": "^0.4.22",
|
"vueuc": "^0.4.23",
|
||||||
"windicss": "^3.4.2"
|
"windicss": "^3.4.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
870
pnpm-lock.yaml
870
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { subscribeStore } from '@/store';
|
||||||
|
import { useTheme } from '@/composables';
|
||||||
import AppProvider from './AppProvider.vue';
|
import AppProvider from './AppProvider.vue';
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
subscribeStore();
|
||||||
|
useTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
</script>
|
</script>
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
export * from './system';
|
export * from './system';
|
||||||
export * from './router';
|
export * from './router';
|
||||||
|
export * from './theme';
|
||||||
export * from './layout';
|
export * from './layout';
|
||||||
|
37
src/composables/common/theme.ts
Normal file
37
src/composables/common/theme.ts
Normal 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();
|
||||||
|
});
|
||||||
|
}
|
@ -42,5 +42,5 @@ export const ERROR_STATUS = {
|
|||||||
/** 不弹出错误信息的code */
|
/** 不弹出错误信息的code */
|
||||||
export const NO_ERROR_MSG_CODE: (string | number)[] = [];
|
export const NO_ERROR_MSG_CODE: (string | number)[] = [];
|
||||||
|
|
||||||
/** token失效需要刷新token的接口 */
|
/** token失效需要刷新token的code */
|
||||||
export const REFRESH_TOKEN_CODE: (string | number)[] = [66666];
|
export const REFRESH_TOKEN_CODE: (string | number)[] = [66666];
|
||||||
|
@ -3,6 +3,7 @@ import useBoolean from './useBoolean';
|
|||||||
import useLoading from './useLoading';
|
import useLoading from './useLoading';
|
||||||
import useLoadingEmpty from './useLoadingEmpty';
|
import useLoadingEmpty from './useLoadingEmpty';
|
||||||
import useReload from './useReload';
|
import useReload from './useReload';
|
||||||
|
import useBodyScroll from './useBodyScroll';
|
||||||
import useModalVisible from './useModalVisible';
|
import useModalVisible from './useModalVisible';
|
||||||
|
|
||||||
export { useContext, useBoolean, useLoading, useLoadingEmpty, useReload, useModalVisible };
|
export { useContext, useBoolean, useLoading, useLoadingEmpty, useReload, useBodyScroll, useModalVisible };
|
||||||
|
47
src/hooks/common/useBodyScroll.ts
Normal file
47
src/hooks/common/useBodyScroll.ts
Normal 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
|
||||||
|
};
|
||||||
|
}
|
@ -1,50 +1,18 @@
|
|||||||
import { computed, watch, onUnmounted } from 'vue';
|
import { watch, onUnmounted } from 'vue';
|
||||||
import type { ComputedRef } from 'vue';
|
|
||||||
import useBoolean from './useBoolean';
|
import useBoolean from './useBoolean';
|
||||||
|
import useBodyScroll from './useBodyScroll';
|
||||||
interface ScrollBodyStyle {
|
|
||||||
overflow: string;
|
|
||||||
paddingRight: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 使用弹窗
|
* 使用弹窗
|
||||||
* @param hideScroll - 关闭html滚动条
|
* @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 { bool: visible, setTrue: openModal, setFalse: closeModal, toggle: toggleModal } = useBoolean();
|
||||||
|
const { scrollBodyHandler } = useBodyScroll();
|
||||||
|
|
||||||
const defaultStyle: ScrollBodyStyle = {
|
function modalVisibleWatcher() {
|
||||||
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>) {
|
|
||||||
const stopHandle = watch(visible, async newValue => {
|
const stopHandle = watch(visible, async newValue => {
|
||||||
if (hideScroll) {
|
scrollBodyHandler(newValue);
|
||||||
if (newValue) {
|
|
||||||
setScrollBodyStyle();
|
|
||||||
} else {
|
|
||||||
setTimeout(() => {
|
|
||||||
resetScrollBodyStyle();
|
|
||||||
}, duration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
@ -52,18 +20,14 @@ export default function useModalVisible(hideScroll = true, duration = 300) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
if (hideScroll) {
|
||||||
getInitBodyStyle();
|
modalVisibleWatcher();
|
||||||
modalVisibleWatcher(computed(() => visible.value));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init();
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
visible,
|
visible,
|
||||||
openModal,
|
openModal,
|
||||||
closeModal,
|
closeModal,
|
||||||
toggleModal,
|
toggleModal
|
||||||
modalVisibleWatcher
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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;
|
routeName: string;
|
||||||
routePath: string;
|
routePath: string;
|
||||||
|
icon?: () => VNodeChild;
|
||||||
|
children?: GlobalMenuOption[];
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 面包屑 */
|
/** 面包屑 */
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<hover-container class="w-40px" content-class="hover:text-primary" tooltip-content="主题模式">
|
<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>
|
</hover-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -9,6 +9,5 @@ import { HoverContainer, DarkModeSwitch } from '@/components';
|
|||||||
import { useThemeStore } from '@/store';
|
import { useThemeStore } from '@/store';
|
||||||
|
|
||||||
const theme = useThemeStore();
|
const theme = useThemeStore();
|
||||||
const { setDarkMode } = useThemeStore();
|
|
||||||
</script>
|
</script>
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -45,7 +45,7 @@ function handleDropdown(optionKey: string) {
|
|||||||
positiveText: '确定',
|
positiveText: '确定',
|
||||||
negativeText: '取消',
|
negativeText: '取消',
|
||||||
onPositiveClick: () => {
|
onPositiveClick: () => {
|
||||||
auth.resetAuthStore(true);
|
auth.resetAuthStore();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<router-link :to="routeHomePath" class="flex-center w-full nowrap-hidden">
|
<router-link :to="routeHomePath" class="flex-center w-full nowrap-hidden">
|
||||||
<system-logo class="w-32px h-32px text-primary" />
|
<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>
|
</router-link>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ function getActiveKeysInMenus(menu: GlobalMenuOption) {
|
|||||||
keys.push(menu.routeName);
|
keys.push(menu.routeName);
|
||||||
}
|
}
|
||||||
if (menu.children) {
|
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;
|
return keys;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
type="primary"
|
type="primary"
|
||||||
:class="[{ '!right-330px': app.settingDrawerVisible }, app.settingDrawerVisible ? 'ease-out' : 'ease-in']"
|
: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"
|
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:close-outlined v-if="app.settingDrawerVisible" class="text-24px" />
|
||||||
<icon-ant-design:setting-outlined v-else 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';
|
import { useAppStore } from '@/store';
|
||||||
|
|
||||||
const app = useAppStore();
|
const app = useAppStore();
|
||||||
const { toggleSettingdrawerVisible } = useAppStore();
|
|
||||||
</script>
|
</script>
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<n-divider title-placement="center">界面功能</n-divider>
|
<n-divider title-placement="center">界面功能</n-divider>
|
||||||
<n-space vertical size="large">
|
<n-space vertical size="large">
|
||||||
<setting-menu label="固定头部和多页签">
|
<setting-menu label="固定头部和多页签">
|
||||||
<n-switch :value="theme.fixedHeaderAndTab" @update:value="setIsFixedHeaderAndTab" />
|
<n-switch :value="theme.fixedHeaderAndTab" @update:value="theme.setIsFixedHeaderAndTab" />
|
||||||
</setting-menu>
|
</setting-menu>
|
||||||
<setting-menu label="顶部菜单位置">
|
<setting-menu label="顶部菜单位置">
|
||||||
<n-select
|
<n-select
|
||||||
@ -10,7 +10,7 @@
|
|||||||
size="small"
|
size="small"
|
||||||
:value="theme.menu.horizontalPosition"
|
:value="theme.menu.horizontalPosition"
|
||||||
:options="theme.menu.horizontalPositionList"
|
:options="theme.menu.horizontalPositionList"
|
||||||
@update:value="setHorizontalMenuPosition"
|
@update:value="theme.setHorizontalMenuPosition"
|
||||||
/>
|
/>
|
||||||
</setting-menu>
|
</setting-menu>
|
||||||
<setting-menu label="头部高度">
|
<setting-menu label="头部高度">
|
||||||
@ -19,7 +19,7 @@
|
|||||||
size="small"
|
size="small"
|
||||||
:value="theme.header.height"
|
:value="theme.header.height"
|
||||||
:step="1"
|
:step="1"
|
||||||
@update:value="handleSetNumber($event, setHeaderHeight)"
|
@update:value="theme.setHeaderHeight"
|
||||||
/>
|
/>
|
||||||
</setting-menu>
|
</setting-menu>
|
||||||
<setting-menu label="多页签高度">
|
<setting-menu label="多页签高度">
|
||||||
@ -28,11 +28,11 @@
|
|||||||
size="small"
|
size="small"
|
||||||
:value="theme.tab.height"
|
:value="theme.tab.height"
|
||||||
:step="1"
|
:step="1"
|
||||||
@update:value="handleSetNumber($event, setTabHeight)"
|
@update:value="theme.setTabHeight"
|
||||||
/>
|
/>
|
||||||
</setting-menu>
|
</setting-menu>
|
||||||
<setting-menu label="多页签缓存">
|
<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>
|
||||||
<setting-menu label="侧边栏展开宽度">
|
<setting-menu label="侧边栏展开宽度">
|
||||||
<n-input-number
|
<n-input-number
|
||||||
@ -40,7 +40,7 @@
|
|||||||
size="small"
|
size="small"
|
||||||
:value="theme.sider.width"
|
:value="theme.sider.width"
|
||||||
:step="10"
|
:step="10"
|
||||||
@update:value="handleSetNumber($event, setSiderWidth)"
|
@update:value="theme.setSiderWidth"
|
||||||
/>
|
/>
|
||||||
</setting-menu>
|
</setting-menu>
|
||||||
<setting-menu label="左侧混合侧边栏展开宽度">
|
<setting-menu label="左侧混合侧边栏展开宽度">
|
||||||
@ -49,11 +49,11 @@
|
|||||||
size="small"
|
size="small"
|
||||||
:value="theme.sider.mixWidth"
|
:value="theme.sider.mixWidth"
|
||||||
:step="5"
|
:step="5"
|
||||||
@update:value="handleSetNumber($event, setMixSiderWidth)"
|
@update:value="theme.setMixSiderWidth"
|
||||||
/>
|
/>
|
||||||
</setting-menu>
|
</setting-menu>
|
||||||
<setting-menu label="固定底部">
|
<setting-menu label="固定底部">
|
||||||
<n-switch :value="theme.footer.fixed" @update:value="setFooterIsFixed" />
|
<n-switch :value="theme.footer.fixed" @update:value="theme.setFooterIsFixed" />
|
||||||
</setting-menu>
|
</setting-menu>
|
||||||
</n-space>
|
</n-space>
|
||||||
</template>
|
</template>
|
||||||
@ -64,21 +64,5 @@ import { useThemeStore } from '@/store';
|
|||||||
import SettingMenu from '../SettingMenu/index.vue';
|
import SettingMenu from '../SettingMenu/index.vue';
|
||||||
|
|
||||||
const theme = useThemeStore();
|
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>
|
</script>
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
<n-divider title-placement="center">界面显示</n-divider>
|
<n-divider title-placement="center">界面显示</n-divider>
|
||||||
<n-space vertical size="large">
|
<n-space vertical size="large">
|
||||||
<setting-menu label="面包屑">
|
<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>
|
||||||
<setting-menu label="面包屑图标">
|
<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>
|
||||||
<setting-menu label="多页签">
|
<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>
|
||||||
<setting-menu label="多页签风格">
|
<setting-menu label="多页签风格">
|
||||||
<n-select
|
<n-select
|
||||||
@ -16,11 +16,11 @@
|
|||||||
size="small"
|
size="small"
|
||||||
:value="theme.tab.mode"
|
:value="theme.tab.mode"
|
||||||
:options="theme.tab.modeList"
|
:options="theme.tab.modeList"
|
||||||
@update:value="setTabMode"
|
@update:value="theme.setTabMode"
|
||||||
/>
|
/>
|
||||||
</setting-menu>
|
</setting-menu>
|
||||||
<setting-menu label="页面切换动画">
|
<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>
|
||||||
<setting-menu label="页面切换动画类型">
|
<setting-menu label="页面切换动画类型">
|
||||||
<n-select
|
<n-select
|
||||||
@ -28,7 +28,7 @@
|
|||||||
size="small"
|
size="small"
|
||||||
:value="theme.page.animateMode"
|
:value="theme.page.animateMode"
|
||||||
:options="theme.page.animateModeList"
|
:options="theme.page.animateModeList"
|
||||||
@update:value="setPageAnimateMode"
|
@update:value="theme.setPageAnimateMode"
|
||||||
/>
|
/>
|
||||||
</setting-menu>
|
</setting-menu>
|
||||||
</n-space>
|
</n-space>
|
||||||
@ -40,13 +40,5 @@ import { useThemeStore } from '@/store';
|
|||||||
import SettingMenu from '../SettingMenu/index.vue';
|
import SettingMenu from '../SettingMenu/index.vue';
|
||||||
|
|
||||||
const theme = useThemeStore();
|
const theme = useThemeStore();
|
||||||
const {
|
|
||||||
setHeaderCrumbVisible,
|
|
||||||
setHeaderCrumbIconVisible,
|
|
||||||
setTabVisible,
|
|
||||||
setTabMode,
|
|
||||||
setPageIsAnimate,
|
|
||||||
setPageAnimateMode
|
|
||||||
} = useThemeStore();
|
|
||||||
</script>
|
</script>
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -12,7 +12,6 @@ export async function handlePagePermission(
|
|||||||
) {
|
) {
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
const route = useRouteStore();
|
const route = useRouteStore();
|
||||||
const { initDynamicRoute } = useRouteStore();
|
|
||||||
|
|
||||||
const isLogin = Boolean(getToken());
|
const isLogin = Boolean(getToken());
|
||||||
const permissions = to.meta.permissions || [];
|
const permissions = to.meta.permissions || [];
|
||||||
@ -21,7 +20,7 @@ export async function handlePagePermission(
|
|||||||
|
|
||||||
if (!route.isAddedDynamicRoute) {
|
if (!route.isAddedDynamicRoute) {
|
||||||
// 添加动态路由
|
// 添加动态路由
|
||||||
await initDynamicRoute(router);
|
await route.initDynamicRoute(router);
|
||||||
|
|
||||||
if (to.name === routeName('not-found-page')) {
|
if (to.name === routeName('not-found-page')) {
|
||||||
// 动态路由没有加载导致被not-found-page路由捕获,等待动态路由加载好了,回到之前的路由
|
// 动态路由没有加载导致被not-found-page路由捕获,等待动态路由加载好了,回到之前的路由
|
||||||
|
@ -5,7 +5,7 @@ import { fetchUpdateToken } from '../api';
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 刷新token
|
* 刷新token
|
||||||
* token失效时的请求配置
|
* @param axiosConfig - token失效时的请求配置
|
||||||
*/
|
*/
|
||||||
export async function refreshToken(axiosConfig: AxiosRequestConfig) {
|
export async function refreshToken(axiosConfig: AxiosRequestConfig) {
|
||||||
const { resetAuthStore } = useAuthStore();
|
const { resetAuthStore } = useAuthStore();
|
||||||
@ -21,6 +21,6 @@ export async function refreshToken(axiosConfig: AxiosRequestConfig) {
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
resetAuthStore(true);
|
resetAuthStore();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -7,3 +7,4 @@ export function setupStore(app: App) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export * from './modules';
|
export * from './modules';
|
||||||
|
export * from './subscribe';
|
||||||
|
@ -1,66 +1,63 @@
|
|||||||
import type { Ref } from 'vue';
|
import { nextTick } from 'vue';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { useReload, useModalVisible, useBoolean } from '@/hooks';
|
|
||||||
|
|
||||||
interface AppStore {
|
interface AppState {
|
||||||
/** 重载页面的标志 */
|
/** 重载页面(控制页面的显示) */
|
||||||
reloadFlag: Ref<boolean>;
|
reloadFlag: boolean;
|
||||||
/**
|
/** 项目配置的抽屉可见状态 */
|
||||||
* 触发重载页面
|
settingDrawerVisible: boolean;
|
||||||
* @param duration - 延迟时间(ms, 默认0)
|
|
||||||
*/
|
|
||||||
handleReload(duration?: number): void;
|
|
||||||
/** 设置抽屉可见状态 */
|
|
||||||
settingDrawerVisible: Ref<boolean>;
|
|
||||||
/** 打开设置抽屉 */
|
|
||||||
openSettingDrawer(): void;
|
|
||||||
/** 关闭设置抽屉 */
|
|
||||||
closeSettingDrawer(): void;
|
|
||||||
/** 切换抽屉可见状态 */
|
|
||||||
toggleSettingdrawerVisible(): void;
|
|
||||||
/** 侧边栏折叠状态 */
|
/** 侧边栏折叠状态 */
|
||||||
siderCollapse: Ref<boolean>;
|
siderCollapse: boolean;
|
||||||
/** 折叠/展开 侧边栏折叠状态 */
|
|
||||||
toggleSiderCollapse(): void;
|
|
||||||
/** 设置侧边栏折叠状态 */
|
|
||||||
setSiderCollapse(collapse: boolean): void;
|
|
||||||
/** vertical-mix模式下 侧边栏的固定状态 */
|
/** vertical-mix模式下 侧边栏的固定状态 */
|
||||||
mixSiderFixed: Ref<boolean>;
|
mixSiderFixed: boolean;
|
||||||
/** 设置 vertical-mix模式下 侧边栏的固定状态 */
|
|
||||||
setMixSiderIsFixed(isFixed: boolean): void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAppStore = defineStore('app-store', () => {
|
export const useAppStore = defineStore('app-store', {
|
||||||
// 重新加载页面
|
state: (): AppState => ({
|
||||||
const { reloadFlag, handleReload } = useReload();
|
reloadFlag: true,
|
||||||
|
settingDrawerVisible: false,
|
||||||
// 设置抽屉
|
siderCollapse: false,
|
||||||
const {
|
mixSiderFixed: false
|
||||||
visible: settingDrawerVisible,
|
}),
|
||||||
openModal: openSettingDrawer,
|
actions: {
|
||||||
closeModal: closeSettingDrawer,
|
/**
|
||||||
toggleModal: toggleSettingdrawerVisible
|
* 重载页面
|
||||||
} = useModalVisible();
|
* @param duration - 重载的延迟时间(ms)
|
||||||
|
*/
|
||||||
// 侧边栏的折叠状态
|
async reloadPage(duration = 0) {
|
||||||
const { bool: siderCollapse, setBool: setSiderCollapse, toggle: toggleSiderCollapse } = useBoolean();
|
this.reloadFlag = false;
|
||||||
|
await nextTick();
|
||||||
// vertical-mix模式下 侧边栏的固定状态
|
if (duration) {
|
||||||
const { bool: mixSiderFixed, setBool: setMixSiderIsFixed } = useBoolean();
|
setTimeout(() => {
|
||||||
|
this.reloadFlag = true;
|
||||||
const appStore: AppStore = {
|
}, duration);
|
||||||
reloadFlag,
|
} else {
|
||||||
handleReload,
|
this.reloadFlag = true;
|
||||||
settingDrawerVisible,
|
}
|
||||||
openSettingDrawer,
|
},
|
||||||
closeSettingDrawer,
|
/** 打开设置抽屉 */
|
||||||
toggleSettingdrawerVisible,
|
openSettingDrawer() {
|
||||||
siderCollapse,
|
this.settingDrawerVisible = true;
|
||||||
setSiderCollapse,
|
},
|
||||||
toggleSiderCollapse,
|
/** 关闭设置抽屉 */
|
||||||
mixSiderFixed,
|
closeSettingDrawer() {
|
||||||
setMixSiderIsFixed
|
this.settingDrawerVisible = false;
|
||||||
};
|
},
|
||||||
|
/** 切换抽屉可见状态 */
|
||||||
return appStore;
|
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 { unref } from 'vue';
|
||||||
import type { Ref, ComputedRef } from 'vue';
|
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { router as globalRouter } from '@/router';
|
import { router as globalRouter } from '@/router';
|
||||||
import { useRouterPush } from '@/composables';
|
import { useRouterPush } from '@/composables';
|
||||||
import { useLoading } from '@/hooks';
|
|
||||||
import { fetchLogin, fetchUserInfo } from '@/service';
|
import { fetchLogin, fetchUserInfo } from '@/service';
|
||||||
import { getUserInfo, getToken, setUserInfo, setToken, setRefreshToken, clearAuthStorage } from '@/utils';
|
import { getUserInfo, getToken, setUserInfo, setToken, setRefreshToken, clearAuthStorage } from '@/utils';
|
||||||
|
|
||||||
interface AuthStore {
|
interface AuthState {
|
||||||
/** 用户信息 */
|
/** 用户信息 */
|
||||||
userInfo: Auth.UserInfo;
|
userInfo: Auth.UserInfo;
|
||||||
/** 用户token */
|
/** 用户token */
|
||||||
token: Ref<string>;
|
token: string;
|
||||||
/** 是否登录 */
|
|
||||||
isLogin: ComputedRef<boolean>;
|
|
||||||
/**
|
|
||||||
* 重置authStore
|
|
||||||
* 是否需要跳转页面(例如当前页面是需要权限的,登出后需要跳转到登录页面)
|
|
||||||
*/
|
|
||||||
resetAuthStore(pushRoute: boolean): void;
|
|
||||||
/** 登录的加载状态 */
|
/** 登录的加载状态 */
|
||||||
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 phone - 手机号
|
||||||
* @param pwdOrCode - 密码或验证码
|
* @param pwdOrCode - 密码或验证码
|
||||||
* @param type - 登录方式: pwd - 密码登录; sms - 验证码登录
|
* @param type - 登录方式: pwd - 密码登录; sms - 验证码登录
|
||||||
*/
|
*/
|
||||||
login(phone: string, pwdOrCode: string, type: 'pwd' | 'sms'): void;
|
async login(phone: string, pwdOrCode: string, type: 'pwd' | 'sms') {
|
||||||
}
|
this.loginLoding = true;
|
||||||
|
|
||||||
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();
|
|
||||||
const { data } = await fetchLogin(phone, pwdOrCode, type);
|
const { data } = await fetchLogin(phone, pwdOrCode, type);
|
||||||
if (data) {
|
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;
|
|
||||||
});
|
});
|
||||||
|
@ -1,49 +1,38 @@
|
|||||||
import { ref } from 'vue';
|
|
||||||
import type { Ref } from 'vue';
|
|
||||||
import type { Router } from 'vue-router';
|
import type { Router } from 'vue-router';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { useBoolean } from '@/hooks';
|
|
||||||
import { fetchUserRoutes } from '@/service';
|
import { fetchUserRoutes } from '@/service';
|
||||||
import { transformAuthRouteToMenu, transformAuthRoutesToVueRoutes } from '@/utils';
|
import { transformAuthRouteToMenu, transformAuthRoutesToVueRoutes } from '@/utils';
|
||||||
import type { GlobalMenuOption } from '@/interface';
|
import type { GlobalMenuOption } from '@/interface';
|
||||||
|
|
||||||
/** 路由状态 */
|
interface RouteState {
|
||||||
interface RouteStore {
|
|
||||||
/** 是否添加过动态路由 */
|
/** 是否添加过动态路由 */
|
||||||
isAddedDynamicRoute: Ref<boolean>;
|
isAddedDynamicRoute: boolean;
|
||||||
/** 初始化动态路由 */
|
|
||||||
initDynamicRoute(router: Router): Promise<void>;
|
|
||||||
/** 菜单 */
|
/** 菜单 */
|
||||||
menus: Ref<GlobalMenuOption[]>;
|
menus: GlobalMenuOption[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useRouteStore = defineStore('route-store', () => {
|
export const useRouteStore = defineStore('route-store', {
|
||||||
const menus = ref<GlobalMenuOption[]>([]) as Ref<GlobalMenuOption[]>;
|
state: (): RouteState => ({
|
||||||
function getMenus(data: AuthRoute.Route[]) {
|
isAddedDynamicRoute: false,
|
||||||
const transform = transformAuthRouteToMenu(data);
|
menus: []
|
||||||
menus.value = transform;
|
}),
|
||||||
}
|
actions: {
|
||||||
|
/**
|
||||||
const { bool: isAddedDynamicRoute, setTrue: setAddedDynamicRoute } = useBoolean();
|
* 初始化动态路由
|
||||||
async function initDynamicRoute(router: Router) {
|
* @param router - 路由实例
|
||||||
|
*/
|
||||||
|
async initDynamicRoute(router: Router) {
|
||||||
const { data } = await fetchUserRoutes();
|
const { data } = await fetchUserRoutes();
|
||||||
if (data) {
|
if (data) {
|
||||||
getMenus(data.routes);
|
this.menus = transformAuthRouteToMenu(data.routes);
|
||||||
|
|
||||||
const vueRoutes = transformAuthRoutesToVueRoutes(data.routes);
|
const vueRoutes = transformAuthRoutesToVueRoutes(data.routes);
|
||||||
vueRoutes.forEach(route => {
|
vueRoutes.forEach(route => {
|
||||||
router.addRoute(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;
|
type ThemeVarsKeys = keyof ThemeVars;
|
||||||
|
|
||||||
/** 添加css vars至html */
|
/** 添加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 keys = Object.keys(themeVars) as ThemeVarsKeys[];
|
||||||
const style: string[] = [];
|
const style: string[] = [];
|
||||||
keys.forEach(key => {
|
keys.forEach(key => {
|
||||||
style.push(`--${kebabCase(key)}: ${themeVars[key]}`);
|
style.push(`--${kebabCase(key)}: ${themeVars[key]}`);
|
||||||
});
|
});
|
||||||
const styleStr = style.join(';');
|
const styleStr = style.join(';');
|
||||||
if (action === 'add') {
|
|
||||||
document.documentElement.style.cssText = styleStr;
|
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 暗黑模式 */
|
/** windicss 暗黑模式 */
|
||||||
|
@ -1,229 +1,140 @@
|
|||||||
import { ref, reactive, computed } from 'vue';
|
|
||||||
import type { Ref, ComputedRef } from 'vue';
|
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { darkTheme } from 'naive-ui';
|
import { darkTheme } from 'naive-ui';
|
||||||
import type { GlobalThemeOverrides, GlobalTheme } from 'naive-ui';
|
import { cloneDeep } from 'lodash-es';
|
||||||
import { themeSetting } from '@/settings';
|
import { themeSetting } from '@/settings';
|
||||||
import { useBoolean } from '@/hooks';
|
import type {
|
||||||
import { getColorPalette } from '@/utils';
|
ThemeSetting,
|
||||||
import type { ThemeSetting, ThemeHorizontalMenuPosition } from '@/interface';
|
ThemeLayoutMode,
|
||||||
|
ThemeTabMode,
|
||||||
|
ThemeHorizontalMenuPosition,
|
||||||
|
ThemeAnimateMode
|
||||||
|
} from '@/interface';
|
||||||
import { getNaiveThemeOverrides, addThemeCssVarsToHtml } from './helpers';
|
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 {
|
export const useThemeStore = defineStore('theme-store', {
|
||||||
/** 暗黑模式 */
|
state: (): ThemeState => cloneDeep(themeSetting),
|
||||||
darkMode: Ref<boolean>;
|
getters: {
|
||||||
/** 设置暗黑模式 */
|
|
||||||
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的主题配置 */
|
/** naiveUI的主题配置 */
|
||||||
naiveThemeOverrides: ComputedRef<GlobalThemeOverrides>;
|
naiveThemeOverrides(state) {
|
||||||
|
const overrides = getNaiveThemeOverrides({ primary: state.themeColor, ...state.otherColor });
|
||||||
|
addThemeCssVarsToHtml(overrides.common!);
|
||||||
|
return overrides;
|
||||||
|
},
|
||||||
/** naive-ui暗黑主题 */
|
/** naive-ui暗黑主题 */
|
||||||
naiveTheme: ComputedRef<BuiltInGlobalTheme | undefined>;
|
naiveTheme(state) {
|
||||||
/** 重置状态 */
|
return state.darkMode ? darkTheme : undefined;
|
||||||
resetThemeStore(): void;
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
export const useThemeStore = defineStore('theme-store', () => {
|
actions: {
|
||||||
// 暗黑模式
|
|
||||||
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状态 */
|
/** 重置theme状态 */
|
||||||
function resetThemeStore() {
|
resetThemeStore() {
|
||||||
setDarkMode(false);
|
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() {
|
setHeaderCrumbVisible(visible: boolean) {
|
||||||
const updatedThemeVars = { ...naiveThemeOverrides.value.common };
|
this.header.crumb.visible = visible;
|
||||||
addThemeCssVarsToHtml(updatedThemeVars);
|
},
|
||||||
|
/** 设置头部面包屑图标可见 */
|
||||||
|
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;
|
|
||||||
});
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
@ -22,7 +22,7 @@ function addPartialProps(menuItem: GlobalMenuOption, icon?: string, children?: G
|
|||||||
* 将权限路由转换成菜单
|
* 将权限路由转换成菜单
|
||||||
* @param routes - 路由
|
* @param routes - 路由
|
||||||
*/
|
*/
|
||||||
export function transformAuthRouteToMenu(routes: AuthRoute.Route[]) {
|
export function transformAuthRouteToMenu(routes: AuthRoute.Route[]): GlobalMenuOption[] {
|
||||||
const globalMenu: GlobalMenuOption[] = [];
|
const globalMenu: GlobalMenuOption[] = [];
|
||||||
routes.forEach(route => {
|
routes.forEach(route => {
|
||||||
const { name, path, meta } = route;
|
const { name, path, meta } = route;
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<dark-mode-switch
|
<dark-mode-switch
|
||||||
:dark="theme.darkMode"
|
:dark="theme.darkMode"
|
||||||
class="absolute left-48px top-24px z-3 text-20px"
|
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">
|
<n-card :bordered="false" size="large" class="z-4 !w-auto rounded-20px shadow-sm">
|
||||||
<div class="w-360px">
|
<div class="w-360px">
|
||||||
@ -53,7 +53,6 @@ interface LoginModule {
|
|||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
const theme = useThemeStore();
|
const theme = useThemeStore();
|
||||||
const { setDarkMode } = useThemeStore();
|
|
||||||
const { title } = useAppInfo();
|
const { title } = useAppInfo();
|
||||||
|
|
||||||
const modules: LoginModule[] = [
|
const modules: LoginModule[] = [
|
||||||
|
Loading…
Reference in New Issue
Block a user