diff --git a/apps/admin/autoResolver/auto-imports.d.ts b/apps/admin/autoResolver/auto-imports.d.ts index 6b153e67..e3b37ba4 100644 --- a/apps/admin/autoResolver/auto-imports.d.ts +++ b/apps/admin/autoResolver/auto-imports.d.ts @@ -261,6 +261,7 @@ declare global { const useStyleTag: typeof import('@vueuse/core')['useStyleTag'] const useSupported: typeof import('@vueuse/core')['useSupported'] const useSwipe: typeof import('@vueuse/core')['useSwipe'] + const useTabs: typeof import('../src/composables/useTabs')['useTabs'] const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList'] const useTextDirection: typeof import('@vueuse/core')['useTextDirection'] const useTextSelection: typeof import('@vueuse/core')['useTextSelection'] @@ -572,6 +573,7 @@ declare module 'vue' { readonly useStyleTag: UnwrapRef readonly useSupported: UnwrapRef readonly useSwipe: UnwrapRef + readonly useTabs: UnwrapRef readonly useTemplateRefsList: UnwrapRef readonly useTextDirection: UnwrapRef readonly useTextSelection: UnwrapRef @@ -876,6 +878,7 @@ declare module '@vue/runtime-core' { readonly useStyleTag: UnwrapRef readonly useSupported: UnwrapRef readonly useSwipe: UnwrapRef + readonly useTabs: UnwrapRef readonly useTemplateRefsList: UnwrapRef readonly useTextDirection: UnwrapRef readonly useTextSelection: UnwrapRef diff --git a/apps/admin/src/composables/useTabs.ts b/apps/admin/src/composables/useTabs.ts new file mode 100644 index 00000000..3bf8ac32 --- /dev/null +++ b/apps/admin/src/composables/useTabs.ts @@ -0,0 +1,29 @@ +import type { RouteLocationNormalized, Router } from "vue-router"; + +import { useRouter } from "vue-router"; +import { unref } from "vue"; +import type { Tab } from "~/store/modules/tabs"; +import { useTabsStore } from "~/store/modules/tabs"; + +export function useTabs(_router?: Router) { + const tabStore = useTabsStore(); + const router = _router || useRouter(); + + const { currentRoute } = router; + + function getCurrentTab() { + const route = unref(currentRoute); + return tabStore.getTabsList.find(item => item.fullPath === route.fullPath)!; + } + + return { + getTabsList: () => tabStore.getTabsList, + getPinnedTabsList: () => tabStore.getPinnedTabsList, + getLimitTabsList: () => tabStore.getLimitTabsList, + closeTab: (tab: Tab) => tabStore.closeTab(tab), + closePinnedTab: (tab: Tab) => tabStore.closePinnedTab(tab), + addTab: (route: RouteLocationNormalized) => tabStore.addTab(route), + pinnedTab: (tab: Tab) => tabStore.pinnedTab(tab), + getCurrentTab, + }; +} diff --git a/apps/admin/src/layouts/header/index.vue b/apps/admin/src/layouts/header/index.vue index 95fae0e9..de48409d 100644 --- a/apps/admin/src/layouts/header/index.vue +++ b/apps/admin/src/layouts/header/index.vue @@ -6,6 +6,7 @@ import UserInfoButton from "~/layouts/header/components/UserInfoButton.vue"; import SettingButton from "~/layouts/setting/index.vue"; import Breadcrumb from "~/layouts/header/components/Breadcrumb.vue"; import SearchAnyWhere from "~/layouts/header/components/SearchAnyWhere.vue"; +import LayoutTabs from "~/layouts/tabs/index.vue"; defineOptions({ name: "HeaderLayout", @@ -17,9 +18,12 @@ defineOptions({
-
+
+
+ +
diff --git a/apps/admin/src/layouts/tabs/index.vue b/apps/admin/src/layouts/tabs/index.vue new file mode 100644 index 00000000..e5b984ab --- /dev/null +++ b/apps/admin/src/layouts/tabs/index.vue @@ -0,0 +1,125 @@ + + + + + diff --git a/apps/admin/src/router/guard/stateGuard.ts b/apps/admin/src/router/guard/stateGuard.ts index 78a3beaa..b2a5fe97 100644 --- a/apps/admin/src/router/guard/stateGuard.ts +++ b/apps/admin/src/router/guard/stateGuard.ts @@ -4,6 +4,7 @@ import { removeRouteChangeListener } from "~/router/mitt/routeChange"; import { useAppStore } from "~/store/modules/app"; import { usePermissionStore } from "~/store/modules/permission"; import { useUserStore } from "~/store/modules/user"; +import { useTabsStore } from "~/store/modules/tabs"; export function createStateGuard(router: Router) { router.afterEach((to) => { @@ -12,9 +13,11 @@ export function createStateGuard(router: Router) { const userStore = useUserStore(); const appStore = useAppStore(); const permissionStore = usePermissionStore(); + const tabsStore = useTabsStore(); appStore.resetAPPState(); permissionStore.resetPermissionState(); userStore.resetUserState(); + tabsStore.resetTabsState(); removeRouteChangeListener(); } }); diff --git a/apps/admin/src/router/routes/modules/dashboard.ts b/apps/admin/src/router/routes/modules/dashboard.ts index b645a8bc..98d628ae 100644 --- a/apps/admin/src/router/routes/modules/dashboard.ts +++ b/apps/admin/src/router/routes/modules/dashboard.ts @@ -21,6 +21,7 @@ const dashboard: RouteRecordRaw = { title: "routes.dashboard.dashboard", icon: "i-mdi-monitor-dashboard", shouldHideInMenu: true, + shouldAffixToNavBar: true, }, }, ], diff --git a/apps/admin/src/store/modules/tabs.ts b/apps/admin/src/store/modules/tabs.ts new file mode 100644 index 00000000..37e9ec5b --- /dev/null +++ b/apps/admin/src/store/modules/tabs.ts @@ -0,0 +1,84 @@ +import { defineStore } from "pinia"; +import { APP_TABS_STORE_ID, PageConstants } from "@celeris/constants"; +import type { RouteLocationNormalized, RouteRecordName } from "vue-router"; +import { takeRight, uniqBy } from "@celeris/utils"; +import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from "~/router/routes/basic"; + +interface AppTabsState { + tabs: Tab[]; + pinnedTabs: Tab[]; + maxVisibleTabs: number; +} +export interface Tab { + name: RouteRecordName; + fullPath: string; + title: string; +} +export const useTabsStore = defineStore({ + id: APP_TABS_STORE_ID, + persist: [{ + paths: ["pinnedTabs"], + storage: localStorage, + }, { + paths: ["tabs"], + storage: sessionStorage, + }], + state: (): AppTabsState => ({ + tabs: [], + pinnedTabs: [], + maxVisibleTabs: 5, + }), + getters: { + getTabsList(state): Tab[] { + return state.tabs; + }, + getLimitTabsList(state): Tab[] { + return takeRight( + state.tabs.filter(tab => state.pinnedTabs.findIndex(p => p.fullPath === tab.fullPath) === -1).reverse(), + this.maxVisibleTabs, + ); + }, + getPinnedTabsList(state): Tab[] { + return state.pinnedTabs; + }, + }, + actions: { + addTab(route: RouteLocationNormalized) { + const { path, name, meta } = route; + if (!name || path === PageConstants.ERROR_PAGE || path === PageConstants.BASE_LOGIN || [REDIRECT_ROUTE.name, PAGE_NOT_FOUND_ROUTE.name].includes(name)) { + return; + } + const title = meta?.title as string || name.toString().split("-").at(-1); + if (title) { + const newTab: Tab = { name, fullPath: route.fullPath, title }; + this.tabs = uniqBy([newTab, ...this.tabs], "fullPath"); + } + }, + close(isPinned: boolean, tab: Tab) { + const targetTabs = isPinned ? this.pinnedTabs : this.tabs; + this.tabs = targetTabs.filter(currentTab => currentTab.fullPath !== tab.fullPath); + }, + closeTab(tab: Tab) { + this.close(false, tab); + }, + closePinnedTab(tab: Tab) { + this.close(true, tab); + }, + pinnedTab(tab: Tab) { + const isPresent = this.pinnedTabs.some(pinnedTab => pinnedTab.fullPath === tab.fullPath); + if (!isPresent) { + this.pinnedTabs = [tab, ...this.pinnedTabs]; + } + return true; + }, + resetTabsState() { + this.tabs = []; + this.pinnedTabs = []; + }, + }, +}); + +// Need to be used outside the setup +export function useTabsStoreWithOut() { + return useTabsStore(store); +} diff --git a/packages/node/vite/src/plugins/unocss.ts b/packages/node/vite/src/plugins/unocss.ts index 297b34b8..7e8bb3ac 100644 --- a/packages/node/vite/src/plugins/unocss.ts +++ b/packages/node/vite/src/plugins/unocss.ts @@ -67,6 +67,7 @@ export function createUnoCSSPluginConfig(): PluginOption { primary_8: "var(--primary-color-8)", primary_9: "var(--primary-color-9)", primary_10: "var(--primary-color10)", + action: "var(--action-color)", info: "var(--info-color)", info_hover: "var(--info-color-hover)", info_suppl: "var(--info-color-suppl)", diff --git a/packages/web/constants/src/storageConstants.ts b/packages/web/constants/src/storageConstants.ts index 55213df7..6da54b7a 100644 --- a/packages/web/constants/src/storageConstants.ts +++ b/packages/web/constants/src/storageConstants.ts @@ -5,6 +5,8 @@ export const LOCALES_STORE_KEY = "LOCALES__STORE__KEY"; export const APP_STORE_ID = "APP_STORE"; +export const APP_TABS_STORE_ID = "APP_TABS_STORE"; + export const APP_PERMISSION_STORE_ID = "APP_PERMISSION_STORE"; export const APP_USER_STORE_ID = "APP_USER_STORE"; diff --git a/packages/web/utils/index.ts b/packages/web/utils/index.ts index dbab8916..d909851d 100644 --- a/packages/web/utils/index.ts +++ b/packages/web/utils/index.ts @@ -6,4 +6,6 @@ export { intersection, uniqBy, pick, + split, + takeRight, } from "lodash-es";