diff --git a/locales/en-US.json b/locales/en-US.json index 21cbc8e3..119f8788 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -2,7 +2,8 @@ "GlobalMenuOptions": { "Dashboard": "Home", "Rely": "Rely", - "RelyAbout": "Rely About" + "RelyAbout": "Rely About", + "Error": "Error Page" }, "LayoutHeaderTooltipOptions": { "Reload": "Reload Current Page", diff --git a/locales/zh-CN.json b/locales/zh-CN.json index aacbb22f..df32458c 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -2,7 +2,8 @@ "GlobalMenuOptions": { "Dashboard": "首页", "Rely": "依赖项", - "RelyAbout": "关于" + "RelyAbout": "关于", + "Error": "错误页" }, "LayoutHeaderTooltipOptions": { "Reload": "刷新当前页面", diff --git a/package.json b/package.json index cbf19721..97421ecc 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "crypto-js": "^4.1.1", "naive-ui": "^2.34.0", "pinia": "^2.0.17", + "pinia-plugin-persistedstate": "^2.4.0", "sass": "^1.54.3", "scrollreveal": "^4.0.9", "vue": "^3.2.37", diff --git a/src/icons/error.svg b/src/icons/error.svg new file mode 100644 index 00000000..ad9fb923 --- /dev/null +++ b/src/icons/error.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/layout/SiderBar/Components/SettingDrawer/index.scss b/src/layout/SiderBar/Components/SettingDrawer/index.scss deleted file mode 100644 index 56effcc9..00000000 --- a/src/layout/SiderBar/Components/SettingDrawer/index.scss +++ /dev/null @@ -1,3 +0,0 @@ -.setting-drawer__space { - width: 100%; -} diff --git a/src/layout/Menu/index.tsx b/src/layout/components/Menu/index.tsx similarity index 86% rename from src/layout/Menu/index.tsx rename to src/layout/components/Menu/index.tsx index 27231754..fba5acdc 100644 --- a/src/layout/Menu/index.tsx +++ b/src/layout/components/Menu/index.tsx @@ -6,7 +6,11 @@ const LayoutMenu = defineComponent({ setup() { const menuStore = useMenu() const { menuModelValueChange, setupAppRoutes, collapsedMenu } = menuStore - const modelMenuKey = ref(menuStore.menuKey) + const modelMenuKey = computed({ + get: () => menuStore.menuKey, + // eslint-disable-next-line @typescript-eslint/no-empty-function + set: () => {}, + }) const modelMenuOptions = computed(() => menuStore.options) const modelCollapsed = computed(() => menuStore.collapsed) diff --git a/src/layout/components/MenuTag/index.scss b/src/layout/components/MenuTag/index.scss new file mode 100644 index 00000000..5d2977bf --- /dev/null +++ b/src/layout/components/MenuTag/index.scss @@ -0,0 +1,14 @@ +$space: $layoutRouterViewContainer / 2; + +.menu-tag { + height: $layoutMenuHeight; + + & .menu-tag-sapce { + width: calc(100% - $space * 2); + padding: $layoutRouterViewContainer / 2; + } + + & .n-tag { + cursor: pointer; + } +} diff --git a/src/layout/components/MenuTag/index.tsx b/src/layout/components/MenuTag/index.tsx new file mode 100644 index 00000000..344a5cbc --- /dev/null +++ b/src/layout/components/MenuTag/index.tsx @@ -0,0 +1,61 @@ +import './index.scss' +import { NScrollbar, NTag, NSpace } from 'naive-ui' +import { useMenu } from '@/store' + +import type { MenuOption } from 'naive-ui' + +const MenuTag = defineComponent({ + name: 'MenuTag', + setup() { + const menuStore = useMenu() + const { menuTagOptions, menuKey } = storeToRefs(menuStore) + const { menuModelValueChange, spliceMenTagOptions } = menuStore + + const handleCloseTag = (idx: number) => { + spliceMenTagOptions(idx) + + if (menuKey.value !== '/dashboard') { + const options = menuTagOptions.value as MenuOption[] + const length = options.length + + const tag = options[length - 1] + + menuModelValueChange(tag.key as string, tag) + } + } + + const handleTagClick = (item: MenuOption) => { + menuModelValueChange(item.key as string, item) + } + + return { + menuTagOptions, + menuModelValueChange, + handleCloseTag, + menuKey, + handleTagClick, + } + }, + render() { + return ( + + + {this.menuTagOptions.map((curr: MenuOption, idx) => ( + 1 + } + onClose={() => this.handleCloseTag(idx)} + type={curr.key === this.menuKey ? 'success' : 'default'} + onClick={this.handleTagClick.bind(this, curr)} + > + {typeof curr.label === 'function' ? curr.label() : curr.label} + + ))} + + + ) + }, +}) + +export default MenuTag diff --git a/src/layout/SiderBar/Components/SettingDrawer/hook.ts b/src/layout/components/SiderBar/Components/SettingDrawer/hook.ts similarity index 100% rename from src/layout/SiderBar/Components/SettingDrawer/hook.ts rename to src/layout/components/SiderBar/Components/SettingDrawer/hook.ts diff --git a/src/layout/components/SiderBar/Components/SettingDrawer/index.scss b/src/layout/components/SiderBar/Components/SettingDrawer/index.scss new file mode 100644 index 00000000..760db0c1 --- /dev/null +++ b/src/layout/components/SiderBar/Components/SettingDrawer/index.scss @@ -0,0 +1,8 @@ +.setting-drawer__space { + width: 100%; + + & .n-descriptions-table-content { + display: flex !important; + justify-content: space-between; + } +} diff --git a/src/layout/SiderBar/Components/SettingDrawer/index.tsx b/src/layout/components/SiderBar/Components/SettingDrawer/index.tsx similarity index 83% rename from src/layout/SiderBar/Components/SettingDrawer/index.tsx rename to src/layout/components/SiderBar/Components/SettingDrawer/index.tsx index a9bfcaf6..6ef7e098 100644 --- a/src/layout/SiderBar/Components/SettingDrawer/index.tsx +++ b/src/layout/components/SiderBar/Components/SettingDrawer/index.tsx @@ -7,6 +7,8 @@ import { NSwitch, NColorPicker, NTooltip, + NDescriptions, + NDescriptionsItem, } from 'naive-ui' import RayIcon from '@/components/RayIcon/index' import { useSwatchesColorOptions } from './hook' @@ -35,8 +37,9 @@ const SettingDrawer = defineComponent({ const { t } = useI18n() const settingStore = useSetting() - const { changeTheme, changePrimaryColor } = settingStore - const { themeValue, primaryColorOverride } = storeToRefs(settingStore) + const { changeTheme, changePrimaryColor, changeMenuTagLog } = settingStore + const { themeValue, primaryColorOverride, menuTagLog } = + storeToRefs(settingStore) const modelShow = computed({ get: () => props.show, @@ -57,6 +60,8 @@ const SettingDrawer = defineComponent({ changeTheme, themeValue, primaryColorOverride, + changeMenuTagLog, + menuTagLog, } }, render() { @@ -119,6 +124,15 @@ const SettingDrawer = defineComponent({ v-model:value={this.primaryColorOverride.common.primaryColor} onUpdateValue={this.changePrimaryColor.bind(this)} /> + 界面显示 + + + + + diff --git a/src/layout/SiderBar/hook.ts b/src/layout/components/SiderBar/hook.ts similarity index 100% rename from src/layout/SiderBar/hook.ts rename to src/layout/components/SiderBar/hook.ts diff --git a/src/layout/SiderBar/index.scss b/src/layout/components/SiderBar/index.scss similarity index 100% rename from src/layout/SiderBar/index.scss rename to src/layout/components/SiderBar/index.scss diff --git a/src/layout/SiderBar/index.tsx b/src/layout/components/SiderBar/index.tsx similarity index 96% rename from src/layout/SiderBar/index.tsx rename to src/layout/components/SiderBar/index.tsx index c7b0eb3b..93e0a016 100644 --- a/src/layout/SiderBar/index.tsx +++ b/src/layout/components/SiderBar/index.tsx @@ -1,7 +1,7 @@ import './index.scss' import { NLayoutHeader, NSpace, NTooltip, NDropdown } from 'naive-ui' import RayIcon from '@/components/RayIcon/index' -import { useMenu, useSetting } from '@/store' +import { useSetting } from '@/store' import { useLanguageOptions } from '@/language/index' import SettingDrawer from './Components/SettingDrawer/index' import { useAvatarOptions } from './hook' @@ -12,12 +12,10 @@ import type { IconEventMapOptions, IconEventMap } from './type' const SiderBar = defineComponent({ name: 'SiderBar', setup() { - const menuStore = useMenu() const settingStore = useSetting() const { t } = useI18n() - const { changeReloadLog } = menuStore - const { updateLocale } = settingStore + const { updateLocale, changeReloadLog } = settingStore const modelDrawerPlacement = ref(settingStore.drawerPlacement) const showSettings = ref(false) diff --git a/src/layout/SiderBar/type.ts b/src/layout/components/SiderBar/type.ts similarity index 100% rename from src/layout/SiderBar/type.ts rename to src/layout/components/SiderBar/type.ts diff --git a/src/layout/index.scss b/src/layout/index.scss index c2dd7908..41c12ada 100644 --- a/src/layout/index.scss +++ b/src/layout/index.scss @@ -6,7 +6,8 @@ } & .layout-content__router-view { - height: calc(100% - $layoutHeaderHeight); - padding: $layoutRouterViewContainer; + // height: calc(100% - $layoutHeaderHeight - $layoutMenuHeight); + height: var(--layout-content-height); + padding: $layoutRouterViewContainer / 2; } } diff --git a/src/layout/index.tsx b/src/layout/index.tsx index 903ebd41..8160bed6 100644 --- a/src/layout/index.tsx +++ b/src/layout/index.tsx @@ -1,31 +1,53 @@ import './index.scss' -import { Transition } from 'vue' import { NLayout, NLayoutContent } from 'naive-ui' import RayTransitionComponent from '@/components/RayTransitionComponent/index.vue' -import LayoutMenu from './Menu/index' -import SiderBar from './SiderBar/index' -import { useMenu } from '@/store' +import LayoutMenu from './components/Menu/index' +import SiderBar from './components/SiderBar/index' +import MenuTag from './components/MenuTag/index' +import { useSetting } from '@/store' const Layout = defineComponent({ name: 'Layout', props: {}, setup() { - const menuStore = useMenu() + const menuStore = useSetting() const { height: windowHeight } = useWindowSize() const modelReloadRoute = computed(() => menuStore.reloadRouteLog) + const modelMenuTagLog = computed(() => menuStore.menuTagLog) + const cssVarsRef = computed(() => { + let cssVar = {} + + if (menuStore.menuTagLog) { + cssVar = { + '--layout-content-height': 'calc(100% - 110px)', + } + } else { + cssVar = { + '--layout-content-height': 'calc(100% - 64px)', + } + } + + return cssVar + }) return { windowHeight, modelReloadRoute, + modelMenuTagLog, + cssVarsRef, } }, render() { return ( -
+
+ {this.modelMenuTagLog ? : ''} import('@/views/error/index'), + meta: { + i18nKey: 'Error', + icon: 'error', + }, +} diff --git a/src/router/modules/index.ts b/src/router/modules/index.ts index 4ad7e60b..389eb8a9 100644 --- a/src/router/modules/index.ts +++ b/src/router/modules/index.ts @@ -1,7 +1,8 @@ import dashboard from './dashboard' import reyl from './rely' +import error from './error' -const routes = [dashboard, reyl] +const routes = [dashboard, error, reyl] export default routes diff --git a/src/store/index.ts b/src/store/index.ts index 6f394921..64e51525 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,3 +1,5 @@ +import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' + import type { App } from 'vue' export { useSetting } from './modules/setting' // import { useSetting } from '@/store' 即可使用 @@ -7,4 +9,6 @@ const store = createPinia() export const setupStore = (app: App) => { app.use(store) + + store.use(piniaPluginPersistedstate) } diff --git a/src/store/modules/menu.ts b/src/store/modules/menu.ts index 9850bead..a1b10365 100644 --- a/src/store/modules/menu.ts +++ b/src/store/modules/menu.ts @@ -9,15 +9,28 @@ export const useMenu = defineStore('menu', () => { const router = useRouter() const { t } = useI18n() - const cacheMenuKey = getCache('menuKey') === 'no' ? '' : getCache('menuKey') + const cacheMenuKey = + getCache('menuKey') === 'no' ? '/dashboard' : getCache('menuKey') const menuState = reactive({ menuKey: cacheMenuKey as string | null, // 当前菜单 `key` options: [] as RouteRecordRaw[], // 菜单列表 collapsed: false, // 是否折叠菜单 - reloadRouteLog: true, // 刷新路由开关 + menuTagOptions: [] as RouteRecordRaw[], }) + const handleMenuTagOptions = (item: RouteRecordRaw) => { + if (item.path !== menuState.menuKey) { + const tag = menuState.menuTagOptions.find( + (curr) => curr.path === item.path, + ) + + if (!tag) { + menuState.menuTagOptions.push(item) + } + } + } + /** * * @param key 菜单更新后的 `key` @@ -26,6 +39,8 @@ export const useMenu = defineStore('menu', () => { * 修改 `menu key` 后的回调函数 */ const menuModelValueChange = (key: string, item: MenuOption) => { + handleMenuTagOptions(item as unknown as RouteRecordRaw) + menuState.menuKey = key router.push(`${item.path}`) @@ -69,7 +84,14 @@ export const useMenu = defineStore('menu', () => { ), } - return curr.meta?.icon ? Object.assign(route, expandIcon) : route + const attr = curr.meta?.icon ? Object.assign(route, expandIcon) : route + + // 初始化 `menu tag` + if (curr.path === cacheMenuKey) { + menuState.menuTagOptions.push(attr) + } + + return attr }) } @@ -83,17 +105,14 @@ export const useMenu = defineStore('menu', () => { const collapsedMenu = (collapsed: boolean) => (menuState.collapsed = collapsed) - /** - * - * @param bool 刷新页面开关 - */ - const changeReloadLog = (bool: boolean) => (menuState.reloadRouteLog = bool) + const spliceMenTagOptions = (idx: number) => + menuState.menuTagOptions.splice(idx, 1) return { ...toRefs(menuState), menuModelValueChange, setupAppRoutes, collapsedMenu, - changeReloadLog, + spliceMenTagOptions, } }) diff --git a/src/store/modules/setting.ts b/src/store/modules/setting.ts index f8b91f20..1e9baac7 100644 --- a/src/store/modules/setting.ts +++ b/src/store/modules/setting.ts @@ -1,49 +1,57 @@ -import { setCache, getCache } from '@/utils/cache' - -export const useSetting = defineStore('setting', () => { - const cachePrimaryColor: string = - getCache('primaryColor', 'localStorage') === 'no' - ? '#18A058' - : getCache('primaryColor', 'localStorage') - const cacheTheme = - getCache('theme', 'localStorage') === 'no' - ? false - : getCache('theme', 'localStorage') - - const settingState = reactive({ - drawerPlacement: 'right' as NaiveDrawerPlacement, - primaryColorOverride: { - common: { - primaryColor: cachePrimaryColor, // 主题色 +export const useSetting = defineStore( + 'setting', + () => { + const settingState = reactive({ + drawerPlacement: 'right' as NaiveDrawerPlacement, + primaryColorOverride: { + common: { + primaryColor: '#18A058', // 主题色 + }, }, + themeValue: false, // `true` 为黑夜主题, `false` 为白色主题 + reloadRouteLog: true, // 刷新路由开关 + menuTagLog: true, // 多标签页开关 + }) + const { locale } = useI18n() + + const updateLocale = (key: string) => { + // TODO: 修改语言 + locale.value = key + } + + const changeTheme = (bool: boolean) => { + settingState.themeValue = bool + } + + const changePrimaryColor = (value: string) => { + settingState.primaryColorOverride.common.primaryColor = value + } + + /** + * + * @param bool 刷新页面开关 + */ + const changeReloadLog = (bool: boolean) => + (settingState.reloadRouteLog = bool) + + /** + * + * @param bool 刷新页面开关 + */ + const changeMenuTagLog = (bool: boolean) => (settingState.menuTagLog = bool) + + return { + ...toRefs(settingState), + updateLocale, + changeTheme, + changePrimaryColor, + changeReloadLog, + changeMenuTagLog, + } + }, + { + persist: { + key: 'piniaSettingStore', }, - themeValue: cacheTheme, // `true` 为黑夜主题, `false` 为白色主题 - }) - const { locale } = useI18n() - - const updateLocale = (key: string) => { - // TODO: 修改语言 - locale.value = key - - setCache('localeLanguage', key, 'localStorage') - } - - const changeTheme = (bool: boolean) => { - settingState.themeValue = bool - - setCache('theme', bool, 'localStorage') - } - - const changePrimaryColor = (value: string) => { - settingState.primaryColorOverride.common.primaryColor = value - - setCache('primaryColor', value, 'localStorage') - } - - return { - ...toRefs(settingState), - updateLocale, - changeTheme, - changePrimaryColor, - } -}) + }, +) diff --git a/src/styles/setting.scss b/src/styles/setting.scss index d7aeb144..e6146e3d 100644 --- a/src/styles/setting.scss +++ b/src/styles/setting.scss @@ -1,2 +1,3 @@ $layoutRouterViewContainer: 18px; $layoutHeaderHeight: 64px; +$layoutMenuHeight: 46px; diff --git a/src/views/login/index.tsx b/src/views/login/index.tsx index 98357ee8..9e112fb3 100644 --- a/src/views/login/index.tsx +++ b/src/views/login/index.tsx @@ -22,13 +22,11 @@ const Login = defineComponent({ const { t } = useI18n() const { height: windowHeight } = useWindowSize() const settingStore = useSetting() - const { themeValue } = storeToRefs(settingStore) const { updateLocale } = settingStore return { ...toRefs(state), windowHeight, - themeValue, updateLocale, ray: t, } diff --git a/yarn.lock b/yarn.lock index f3dbd889..60ea79df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3489,6 +3489,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pinia-plugin-persistedstate@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-2.4.0.tgz#fda569b3c397517a0cf8aba83a628283767da620" + integrity sha512-bQcpv47jk3ISl+InuJWsFaS/K7pRZ97kfoD2WCf/suhnlLy48k3BnFM2tI6YZ1xMsDaPv4yOsaPuPAUuSmEO2Q== + pinia@^2.0.17: version "2.0.17" resolved "https://registry.yarnpkg.com/pinia/-/pinia-2.0.17.tgz#f925e5e4f73c15e16dfb4838176a9ca50752f26b"