diff --git a/console/console-src/App.vue b/console/console-src/App.vue index 825aebcd7..3fda3d1e8 100644 --- a/console/console-src/App.vue +++ b/console/console-src/App.vue @@ -1,129 +1,7 @@ - + - - diff --git a/console/console-src/layouts/BasicLayout.vue b/console/console-src/layouts/BasicLayout.vue index 4fea4b2a0..9b5c659b4 100644 --- a/console/console-src/layouts/BasicLayout.vue +++ b/console/console-src/layouts/BasicLayout.vue @@ -3,29 +3,20 @@ import { IconMore, IconSearch, IconUserSettings, + IconLogoutCircleRLine, VTag, VAvatar, Dialog, - VDropdown, - VDropdownItem, + IconAccountCircleLine, } from "@halo-dev/components"; import { RoutesMenu } from "@/components/menu/RoutesMenu"; -import type { MenuGroupType, MenuItemType } from "@halo-dev/console-shared"; import IconLogo from "~icons/core/logo?width=5rem&height=2rem"; -import { - RouterView, - useRoute, - useRouter, - type RouteRecordRaw, -} from "vue-router"; +import { RouterView, useRoute, useRouter } from "vue-router"; import { onMounted, reactive, ref } from "vue"; import axios from "axios"; import GlobalSearchModal from "@/components/global-search/GlobalSearchModal.vue"; import LoginModal from "@/components/login/LoginModal.vue"; -import { coreMenuGroups } from "@console/router/routes.config"; -import sortBy from "lodash.sortby"; -import { useRoleStore } from "@/stores/role"; -import { hasPermission } from "@/utils/permission"; +import { coreMenuGroups } from "@console/router/constant"; import { useUserStore } from "@/stores/user"; import { rbacAnnotations } from "@/constants/annotations"; import { defineStore, storeToRefs } from "pinia"; @@ -36,6 +27,7 @@ import { } from "overlayscrollbars-vue"; import { isMac } from "@/utils/device"; import { useEventListener } from "@vueuse/core"; +import { useRouteMenuGenerator } from "@/composables/use-route-menu-generator"; const route = useRoute(); const router = useRouter(); @@ -79,105 +71,7 @@ useEventListener(document, "keydown", (e: KeyboardEvent) => { } }); -// Generate menus by routes -const menus = ref([] as MenuGroupType[]); -const minimenus = ref([] as MenuItemType[]); - -const roleStore = useRoleStore(); -const { uiPermissions } = roleStore.permissions; - -const generateMenus = () => { - // sort by menu.priority and meta.core - const currentRoutes = sortBy( - router.getRoutes().filter((route) => { - const { meta } = route; - if (!meta?.menu) { - return false; - } - if (meta.permissions) { - return hasPermission(uiPermissions, meta.permissions as string[], true); - } - return true; - }), - [ - (route: RouteRecordRaw) => !route.meta?.core, - (route: RouteRecordRaw) => route.meta?.menu?.priority || 0, - ] - ); - - // group by menu.group - menus.value = currentRoutes.reduce((acc, route) => { - const { menu } = route.meta; - if (!menu) { - return acc; - } - const group = acc.find((item) => item.id === menu.group); - const childRoute = route.children[0]; - const childMetaMenu = childRoute?.meta?.menu; - - // only support one level - const menuChildren = childMetaMenu - ? [ - { - name: childMetaMenu.name, - path: childRoute.path, - icon: childMetaMenu.icon, - }, - ] - : undefined; - if (group) { - group.items?.push({ - name: menu.name, - path: route.path, - icon: menu.icon, - mobile: menu.mobile, - children: menuChildren, - }); - } else { - const menuGroup = coreMenuGroups.find((item) => item.id === menu.group); - let name = ""; - if (!menuGroup) { - name = menu.group; - } else if (menuGroup.name) { - name = menuGroup.name; - } - acc.push({ - id: menuGroup?.id || menu.group, - name: name, - priority: menuGroup?.priority || 0, - items: [ - { - name: menu.name, - path: route.path, - icon: menu.icon, - mobile: menu.mobile, - children: menuChildren, - }, - ], - }); - } - return acc; - }, [] as MenuGroupType[]); - - // sort by menu.priority - menus.value = sortBy(menus.value, [ - (menu: MenuGroupType) => { - return coreMenuGroups.findIndex((item) => item.id === menu.id) < 0; - }, - (menu: MenuGroupType) => menu.priority || 0, - ]); - - minimenus.value = menus.value - .reduce((acc, group) => { - if (group?.items) { - acc.push(...group.items); - } - return acc; - }, [] as MenuItemType[]) - .filter((item) => item.mobile); -}; - -onMounted(generateMenus); +const { menus, minimenus } = useRouteMenuGenerator(coreMenuGroups); // aside scroll const navbarScroller = ref(); @@ -215,10 +109,6 @@ onMounted(() => { initialize({ target: navbarScroller.value }); } }); - -function handleRouteToUC() { - window.location.href = "/uc"; -} @@ -262,12 +152,15 @@ function handleRouteToUC() { - + {{ currentUser?.spec.displayName }} @@ -283,19 +176,26 @@ function handleRouteToUC() { - - - - - {{ $t("core.sidebar.operations.profile.button") }} - - - {{ $t("core.sidebar.operations.logout.button") }} - - - + + + + + + + + + @@ -418,11 +318,8 @@ function handleRouteToUC() { .profile-name { @apply flex-1 - self-center; - } - - .profile-control { - @apply self-center; + self-center + overflow-hidden; } } } diff --git a/console/console-src/modules/dashboard/widgets/QuickLinkWidget.vue b/console/console-src/modules/dashboard/widgets/QuickLinkWidget.vue index 7006ffec4..bf6f1106b 100644 --- a/console/console-src/modules/dashboard/widgets/QuickLinkWidget.vue +++ b/console/console-src/modules/dashboard/widgets/QuickLinkWidget.vue @@ -11,7 +11,7 @@ import { IconSearch, IconDatabase2Line, VCard, - IconUserLine, + IconAccountCircleLine, Dialog, Toast, } from "@halo-dev/components"; @@ -37,9 +37,9 @@ const { t } = useI18n(); const actions: Action[] = [ { - icon: markRaw(IconUserLine), + icon: markRaw(IconAccountCircleLine), title: t( - "core.dashboard.widgets.presets.quicklink.actions.user_profile.title" + "core.dashboard.widgets.presets.quicklink.actions.user_center.title" ), action: () => { window.location.href = "/uc/profile"; diff --git a/console/console-src/modules/system/users/UserDetail.vue b/console/console-src/modules/system/users/UserDetail.vue index 9c5047d4a..da3a3a6df 100644 --- a/console/console-src/modules/system/users/UserDetail.vue +++ b/console/console-src/modules/system/users/UserDetail.vue @@ -22,9 +22,11 @@ import type { Component } from "vue"; import { markRaw } from "vue"; import DetailTab from "./tabs/Detail.vue"; import { useRouteQuery } from "@vueuse/router"; +import { useUserStore } from "@/stores/user"; const { currentUserHasPermission } = usePermission(); const { t } = useI18n(); +const { currentUser } = useUserStore(); interface UserTab { id: string; @@ -84,6 +86,10 @@ provide>("activeTab", activeTab); const tabbarItems = computed(() => { return tabs.map((tab) => ({ id: tab.id, label: tab.label })); }); + +function handleRouteToUC() { + window.location.href = "/uc"; +} @@ -111,8 +117,15 @@ const tabbarItems = computed(() => { - - + + + 个人中心 + + {{ $t("core.common.buttons.edit") }} diff --git a/console/console-src/modules/system/users/tabs/Detail.vue b/console/console-src/modules/system/users/tabs/Detail.vue index 73b4b62b4..8fd1a2f9d 100644 --- a/console/console-src/modules/system/users/tabs/Detail.vue +++ b/console/console-src/modules/system/users/tabs/Detail.vue @@ -1,72 +1,17 @@ @@ -94,7 +39,7 @@ const handleBindAuth = (authProvider: ListedAuthProvider) => { v-for="(role, index) in user?.roles" :key="index" @click=" - router.push({ + $router.push({ name: 'RoleDetail', params: { name: role.metadata.name }, }) @@ -119,50 +64,6 @@ const handleBindAuth = (authProvider: ListedAuthProvider) => { :content="formatDatetime(user?.user.metadata?.creationTimestamp)" class="!px-2" /> - - - - - - - - - - - {{ authProvider.displayName }} - - - - - {{ $t("core.user.detail.operations.unbind.button") }} - - - {{ $t("core.user.detail.operations.bind.button") }} - - - - - - - diff --git a/console/console-src/router/constant.ts b/console/console-src/router/constant.ts new file mode 100644 index 000000000..d7c2580cc --- /dev/null +++ b/console/console-src/router/constant.ts @@ -0,0 +1,29 @@ +import type { MenuGroupType } from "@halo-dev/console-shared"; + +export const coreMenuGroups: MenuGroupType[] = [ + { + id: "dashboard", + name: undefined, + priority: 0, + }, + { + id: "content", + name: "core.sidebar.menu.groups.content", + priority: 1, + }, + { + id: "interface", + name: "core.sidebar.menu.groups.interface", + priority: 2, + }, + { + id: "system", + name: "core.sidebar.menu.groups.system", + priority: 3, + }, + { + id: "tool", + name: "core.sidebar.menu.groups.tool", + priority: 4, + }, +]; diff --git a/console/console-src/router/routes.config.ts b/console/console-src/router/routes.config.ts index 0be68f90d..0761ae961 100644 --- a/console/console-src/router/routes.config.ts +++ b/console/console-src/router/routes.config.ts @@ -4,7 +4,6 @@ import Forbidden from "@/views/exceptions/Forbidden.vue"; import BasicLayout from "@console/layouts/BasicLayout.vue"; import Setup from "@console/views/system/Setup.vue"; import Redirect from "@console/views/system/Redirect.vue"; -import type { MenuGroupType } from "@halo-dev/console-shared"; import SetupInitialData from "@console/views/system/SetupInitialData.vue"; export const routes: Array = [ @@ -47,32 +46,4 @@ export const routes: Array = [ }, ]; -export const coreMenuGroups: MenuGroupType[] = [ - { - id: "dashboard", - name: undefined, - priority: 0, - }, - { - id: "content", - name: "core.sidebar.menu.groups.content", - priority: 1, - }, - { - id: "interface", - name: "core.sidebar.menu.groups.interface", - priority: 2, - }, - { - id: "system", - name: "core.sidebar.menu.groups.system", - priority: 3, - }, - { - id: "tool", - name: "core.sidebar.menu.groups.tool", - priority: 4, - }, -]; - export default routes; diff --git a/console/package.json b/console/package.json index bf9c8383c..b49feeb4e 100644 --- a/console/package.json +++ b/console/package.json @@ -119,6 +119,7 @@ "@types/lodash.debounce": "^4.0.7", "@types/lodash.isequal": "^4.5.6", "@types/lodash.merge": "^4.6.7", + "@types/lodash.sortby": "^4.7.9", "@types/node": "^18.11.19", "@types/qs": "^6.9.7", "@types/randomstring": "^1.1.8", diff --git a/console/packages/components/src/icons/icons.ts b/console/packages/components/src/icons/icons.ts index d7ca48dd7..a89714bee 100644 --- a/console/packages/components/src/icons/icons.ts +++ b/console/packages/components/src/icons/icons.ts @@ -67,6 +67,9 @@ import IconArrowLeftRightLine from "~icons/ri/arrow-left-right-line"; import IconArrowUpDownLine from "~icons/ri/arrow-up-down-line"; import IconRiUpload2Fill from "~icons/ri/upload-2-fill"; import IconNotificationBadgeLine from "~icons/ri/notification-badge-line"; +import IconLogoutCircleRLine from "~icons/ri/logout-circle-r-line"; +import IconAccountCircleLine from "~icons/ri/account-circle-line"; +import IconSettings3Line from "~icons/ri/settings-3-line"; export { IconDashboard, @@ -138,4 +141,7 @@ export { IconArrowUpDownLine, IconRiUpload2Fill, IconNotificationBadgeLine, + IconLogoutCircleRLine, + IconAccountCircleLine, + IconSettings3Line, }; diff --git a/console/pnpm-lock.yaml b/console/pnpm-lock.yaml index 9053b3821..ed10cdb31 100644 --- a/console/pnpm-lock.yaml +++ b/console/pnpm-lock.yaml @@ -243,6 +243,9 @@ importers: '@types/lodash.merge': specifier: ^4.6.7 version: 4.6.7 + '@types/lodash.sortby': + specifier: ^4.7.9 + version: 4.7.9 '@types/node': specifier: ^18.11.19 version: 18.13.0 @@ -3952,6 +3955,12 @@ packages: '@types/lodash': 4.14.186 dev: true + /@types/lodash.sortby@4.7.9: + resolution: {integrity: sha512-PDmjHnOlndLS59GofH0pnxIs+n9i4CWeXGErSB5JyNFHu2cmvW6mQOaUKjG8EDPkni14IgF8NsRW8bKvFzTm9A==} + dependencies: + '@types/lodash': 4.14.186 + dev: true + /@types/lodash@4.14.186: resolution: {integrity: sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw==} dev: true diff --git a/console/src/components/base-app/BaseApp.vue b/console/src/components/base-app/BaseApp.vue new file mode 100644 index 000000000..be6087ef1 --- /dev/null +++ b/console/src/components/base-app/BaseApp.vue @@ -0,0 +1,113 @@ + + + + + + + diff --git a/console/src/composables/use-route-menu-generator.ts b/console/src/composables/use-route-menu-generator.ts new file mode 100644 index 000000000..dbaaa0649 --- /dev/null +++ b/console/src/composables/use-route-menu-generator.ts @@ -0,0 +1,129 @@ +import { useRoleStore } from "@/stores/role"; +import type { MenuGroupType, MenuItemType } from "@halo-dev/console-shared"; +import { onMounted, ref, type Ref } from "vue"; +import sortBy from "lodash.sortby"; +import { hasPermission } from "@/utils/permission"; +import { + useRouter, + type RouteRecordRaw, + type RouteRecordNormalized, +} from "vue-router"; + +interface useRouteMenuGeneratorReturn { + menus: Ref; + minimenus: Ref; +} + +export function useRouteMenuGenerator( + menuGroups: MenuGroupType[] +): useRouteMenuGeneratorReturn { + const router = useRouter(); + + const menus = ref([] as MenuGroupType[]); + const minimenus = ref([] as MenuItemType[]); + + const roleStore = useRoleStore(); + const { uiPermissions } = roleStore.permissions; + + const generateMenus = () => { + // sort by menu.priority and meta.core + const currentRoutes = sortBy( + router.getRoutes().filter((route) => { + const { meta } = route; + if (!meta?.menu) { + return false; + } + if (meta.permissions) { + return hasPermission( + uiPermissions, + meta.permissions as string[], + true + ); + } + return true; + }), + [ + (route: RouteRecordRaw) => !route.meta?.core, + (route: RouteRecordRaw) => route.meta?.menu?.priority || 0, + ] + ); + + // group by menu.group + menus.value = currentRoutes.reduce((acc, route) => { + const { menu } = route.meta; + if (!menu) { + return acc; + } + const group = acc.find((item) => item.id === menu.group); + const childRoute = route.children[0]; + const childMetaMenu = childRoute?.meta?.menu; + + // only support one level + const menuChildren = childMetaMenu + ? [ + { + name: childMetaMenu.name, + path: childRoute.path, + icon: childMetaMenu.icon, + }, + ] + : undefined; + if (group) { + group.items?.push({ + name: menu.name, + path: route.path, + icon: menu.icon, + mobile: menu.mobile, + children: menuChildren, + }); + } else { + const menuGroup = menuGroups.find((item) => item.id === menu.group); + let name = ""; + if (!menuGroup) { + name = menu.group || ""; + } else if (menuGroup.name) { + name = menuGroup.name; + } + acc.push({ + id: menuGroup?.id || menu.group || "", + name: name, + priority: menuGroup?.priority || 0, + items: [ + { + name: menu.name, + path: route.path, + icon: menu.icon, + mobile: menu.mobile, + children: menuChildren, + }, + ], + }); + } + return acc; + }, [] as MenuGroupType[]); + + // sort by menu.priority + menus.value = sortBy(menus.value, [ + (menu: MenuGroupType) => { + return menuGroups.findIndex((item) => item.id === menu.id) < 0; + }, + (menu: MenuGroupType) => menu.priority || 0, + ]); + + minimenus.value = menus.value + .reduce((acc, group) => { + if (group?.items) { + acc.push(...group.items); + } + return acc; + }, [] as MenuItemType[]) + .filter((item) => item.mobile); + }; + + onMounted(generateMenus); + + return { + menus, + minimenus, + }; +} diff --git a/console/src/locales/en.yaml b/console/src/locales/en.yaml index f9a858f1e..ea30f344b 100644 --- a/console/src/locales/en.yaml +++ b/console/src/locales/en.yaml @@ -106,8 +106,8 @@ core: quicklink: title: Quick Link actions: - user_profile: - title: User Profile + user_center: + title: User Center view_site: title: View Site new_post: diff --git a/console/src/locales/es.yaml b/console/src/locales/es.yaml index 8ef01a0a0..9f83920de 100644 --- a/console/src/locales/es.yaml +++ b/console/src/locales/es.yaml @@ -105,7 +105,7 @@ core: quicklink: title: Enlace Rápido actions: - user_profile: + user_center: title: Perfil de Usuario view_site: title: Ver Sitio diff --git a/console/src/locales/zh-CN.yaml b/console/src/locales/zh-CN.yaml index 7901a5f7d..15493b293 100644 --- a/console/src/locales/zh-CN.yaml +++ b/console/src/locales/zh-CN.yaml @@ -106,8 +106,8 @@ core: quicklink: title: 快捷访问 actions: - user_profile: - title: 个人资料 + user_center: + title: 个人中心 view_site: title: 查看站点 new_post: diff --git a/console/src/locales/zh-TW.yaml b/console/src/locales/zh-TW.yaml index 621f576aa..e213195ff 100644 --- a/console/src/locales/zh-TW.yaml +++ b/console/src/locales/zh-TW.yaml @@ -106,8 +106,8 @@ core: quicklink: title: 快捷訪問 actions: - user_profile: - title: 個人資料 + user_center: + title: 個人中心 view_site: title: 查看站點 new_post: diff --git a/console/uc-src/App.vue b/console/uc-src/App.vue index af9c7ac5d..3fda3d1e8 100644 --- a/console/uc-src/App.vue +++ b/console/uc-src/App.vue @@ -1,7 +1,7 @@ - + diff --git a/console/uc-src/layouts/BasicLayout.vue b/console/uc-src/layouts/BasicLayout.vue index 2ac0f54ea..995ff4dde 100644 --- a/console/uc-src/layouts/BasicLayout.vue +++ b/console/uc-src/layouts/BasicLayout.vue @@ -5,25 +5,16 @@ import { VTag, VAvatar, Dialog, - VDropdown, - VDropdownItem, + IconLogoutCircleRLine, + IconSettings3Line, } from "@halo-dev/components"; import { RoutesMenu } from "@/components/menu/RoutesMenu"; -import type { MenuGroupType, MenuItemType } from "@halo-dev/console-shared"; import IconLogo from "~icons/core/logo?width=5rem&height=2rem"; -import { - RouterView, - useRoute, - useRouter, - type RouteRecordRaw, -} from "vue-router"; +import { RouterView, useRoute, useRouter } from "vue-router"; import { onMounted, reactive, ref } from "vue"; import axios from "axios"; import LoginModal from "@/components/login/LoginModal.vue"; -import { coreMenuGroups } from "@console/router/routes.config"; -import sortBy from "lodash.sortby"; -import { useRoleStore } from "@/stores/role"; -import { hasPermission } from "@/utils/permission"; +import { coreMenuGroups } from "@console/router/constant"; import { useUserStore } from "@/stores/user"; import { rbacAnnotations } from "@/constants/annotations"; import { defineStore, storeToRefs } from "pinia"; @@ -32,6 +23,7 @@ import { useOverlayScrollbars, type UseOverlayScrollbarsParams, } from "overlayscrollbars-vue"; +import { useRouteMenuGenerator } from "@/composables/use-route-menu-generator"; const route = useRoute(); const router = useRouter(); @@ -65,105 +57,7 @@ const handleLogout = () => { }); }; -// Generate menus by routes -const menus = ref([] as MenuGroupType[]); -const minimenus = ref([] as MenuItemType[]); - -const roleStore = useRoleStore(); -const { uiPermissions } = roleStore.permissions; - -const generateMenus = () => { - // sort by menu.priority and meta.core - const currentRoutes = sortBy( - router.getRoutes().filter((route) => { - const { meta } = route; - if (!meta?.menu) { - return false; - } - if (meta.permissions) { - return hasPermission(uiPermissions, meta.permissions as string[], true); - } - return true; - }), - [ - (route: RouteRecordRaw) => !route.meta?.core, - (route: RouteRecordRaw) => route.meta?.menu?.priority || 0, - ] - ); - - // group by menu.group - menus.value = currentRoutes.reduce((acc, route) => { - const { menu } = route.meta; - if (!menu) { - return acc; - } - const group = acc.find((item) => item.id === menu.group); - const childRoute = route.children[0]; - const childMetaMenu = childRoute?.meta?.menu; - - // only support one level - const menuChildren = childMetaMenu - ? [ - { - name: childMetaMenu.name, - path: childRoute.path, - icon: childMetaMenu.icon, - }, - ] - : undefined; - if (group) { - group.items?.push({ - name: menu.name, - path: route.path, - icon: menu.icon, - mobile: menu.mobile, - children: menuChildren, - }); - } else { - const menuGroup = coreMenuGroups.find((item) => item.id === menu.group); - let name = ""; - if (!menuGroup) { - name = menu.group; - } else if (menuGroup.name) { - name = menuGroup.name; - } - acc.push({ - id: menuGroup?.id || menu.group, - name: name, - priority: menuGroup?.priority || 0, - items: [ - { - name: menu.name, - path: route.path, - icon: menu.icon, - mobile: menu.mobile, - children: menuChildren, - }, - ], - }); - } - return acc; - }, [] as MenuGroupType[]); - - // sort by menu.priority - menus.value = sortBy(menus.value, [ - (menu: MenuGroupType) => { - return coreMenuGroups.findIndex((item) => item.id === menu.id) < 0; - }, - (menu: MenuGroupType) => menu.priority || 0, - ]); - - minimenus.value = menus.value - .reduce((acc, group) => { - if (group?.items) { - acc.push(...group.items); - } - return acc; - }, [] as MenuItemType[]) - .filter((item) => item.mobile); -}; - -onMounted(generateMenus); +const { menus, minimenus } = useRouteMenuGenerator(coreMenuGroups); // aside scroll const navbarScroller = ref(); @@ -201,10 +95,6 @@ onMounted(() => { initialize({ target: navbarScroller.value }); } }); - -function handleRouteToConsole() { - window.location.href = "/console"; -} @@ -253,19 +143,25 @@ function handleRouteToConsole() { - - - - - 管理控制台 - - - {{ $t("core.sidebar.operations.logout.button") }} - - - + + + + + + + + @@ -387,11 +283,8 @@ function handleRouteToConsole() { .profile-name { @apply flex-1 - self-center; - } - - .profile-control { - @apply self-center; + self-center + overflow-hidden; } } } diff --git a/console/uc-src/main.ts b/console/uc-src/main.ts index b2274cd20..449078628 100644 --- a/console/uc-src/main.ts +++ b/console/uc-src/main.ts @@ -3,7 +3,7 @@ import { createApp, type DirectiveBinding } from "vue"; import App from "./App.vue"; import { setupVueQuery } from "@/setup/setupVueQuery"; import { setupComponents } from "@/setup/setupComponents"; -import { setupI18n } from "@/locales"; +import { getBrowserLanguage, i18n, setupI18n } from "@/locales"; import { createPinia } from "pinia"; import { setupCoreModules, setupPluginModules } from "@uc/setup/setupModules"; import router from "@uc/router"; @@ -11,6 +11,7 @@ import { useUserStore } from "@/stores/user"; import { apiClient } from "@/utils/api-client"; import { useRoleStore } from "@/stores/role"; import { hasPermission } from "@/utils/permission"; +import { useGlobalInfoStore } from "@/stores/global-info"; const app = createApp(App); @@ -55,19 +56,34 @@ async function loadUserPermissions() { })(); async function initApp() { - setupCoreModules(app); - - const userStore = useUserStore(); - await userStore.fetchCurrentUser(); - - loadUserPermissions(); - try { - await setupPluginModules(app); - } catch (e) { - console.error("Failed to load plugins", e); - } + setupCoreModules(app); - app.use(router); - app.mount("#app"); + const userStore = useUserStore(); + await userStore.fetchCurrentUser(); + + // set locale + i18n.global.locale.value = + localStorage.getItem("locale") || getBrowserLanguage(); + + const globalInfoStore = useGlobalInfoStore(); + await globalInfoStore.fetchGlobalInfo(); + + if (userStore.isAnonymous) { + return; + } + + await loadUserPermissions(); + + try { + await setupPluginModules(app); + } catch (e) { + console.error("Failed to load plugins", e); + } + } catch (error) { + console.error("Failed to init app", error); + } finally { + app.use(router); + app.mount("#app"); + } } diff --git a/console/uc-src/modules/profile/components/ProfileEditingModal.vue b/console/uc-src/modules/profile/components/ProfileEditingModal.vue index db0f9b952..2a49b6a25 100644 --- a/console/uc-src/modules/profile/components/ProfileEditingModal.vue +++ b/console/uc-src/modules/profile/components/ProfileEditingModal.vue @@ -90,7 +90,7 @@ const handleUpdateUser = async () => { onVisibleChange(false); - queryClient.invalidateQueries({ queryKey: ["profile"] }); + queryClient.invalidateQueries({ queryKey: ["user-detail"] }); Toast.success(t("core.common.toast.save_success")); } catch (e) { diff --git a/console/uc-src/modules/profile/module.ts b/console/uc-src/modules/profile/module.ts index dbb60fa5f..4c112b18f 100644 --- a/console/uc-src/modules/profile/module.ts +++ b/console/uc-src/modules/profile/module.ts @@ -1,6 +1,6 @@ import { definePlugin } from "@halo-dev/console-shared"; import BasicLayout from "@uc/layouts/BasicLayout.vue"; -import { IconUserLine } from "@halo-dev/components"; +import { IconAccountCircleLine } from "@halo-dev/components"; import { markRaw } from "vue"; import Profile from "./Profile.vue"; @@ -21,7 +21,7 @@ export default definePlugin({ searchable: true, menu: { name: "我的", - icon: markRaw(IconUserLine), + icon: markRaw(IconAccountCircleLine), priority: 0, mobile: true, }, diff --git a/console/uc-src/modules/profile/tabs/Detail.vue b/console/uc-src/modules/profile/tabs/Detail.vue index e10a2cc71..9a502747a 100644 --- a/console/uc-src/modules/profile/tabs/Detail.vue +++ b/console/uc-src/modules/profile/tabs/Detail.vue @@ -9,7 +9,6 @@ import { } from "@halo-dev/components"; import type { Ref } from "vue"; import { inject, computed } from "vue"; -import { useRouter } from "vue-router"; import type { DetailedUser, ListedAuthProvider } from "@halo-dev/api-client"; import { rbacAnnotations } from "@/constants/annotations"; import { formatDatetime } from "@/utils/date"; @@ -20,7 +19,6 @@ import { useI18n } from "vue-i18n"; const user = inject>("user"); -const router = useRouter(); const { t } = useI18n(); const { data: authProviders, isFetching } = useQuery({ @@ -88,16 +86,7 @@ const handleBindAuth = (authProvider: ListedAuthProvider) => { :label="$t('core.user.detail.fields.roles')" class="!px-2" > - + @@ -118,7 +107,7 @@ const handleBindAuth = (authProvider: ListedAuthProvider) => { class="!px-2" /> diff --git a/console/uc-src/router/constant.ts b/console/uc-src/router/constant.ts new file mode 100644 index 000000000..d7c2580cc --- /dev/null +++ b/console/uc-src/router/constant.ts @@ -0,0 +1,29 @@ +import type { MenuGroupType } from "@halo-dev/console-shared"; + +export const coreMenuGroups: MenuGroupType[] = [ + { + id: "dashboard", + name: undefined, + priority: 0, + }, + { + id: "content", + name: "core.sidebar.menu.groups.content", + priority: 1, + }, + { + id: "interface", + name: "core.sidebar.menu.groups.interface", + priority: 2, + }, + { + id: "system", + name: "core.sidebar.menu.groups.system", + priority: 3, + }, + { + id: "tool", + name: "core.sidebar.menu.groups.tool", + priority: 4, + }, +]; diff --git a/console/uc-src/router/guards/auth-check.ts b/console/uc-src/router/guards/auth-check.ts new file mode 100644 index 000000000..4fc4440ed --- /dev/null +++ b/console/uc-src/router/guards/auth-check.ts @@ -0,0 +1,17 @@ +import { useUserStore } from "@/stores/user"; +import type { Router } from "vue-router"; + +export function setupAuthCheckGuard(router: Router) { + router.beforeEach((to, from, next) => { + const userStore = useUserStore(); + + if (userStore.isAnonymous) { + window.location.href = `/console/login?redirect_uri=${encodeURIComponent( + window.location.href + )}`; + return; + } + + next(); + }); +} diff --git a/console/uc-src/router/guards/permission.ts b/console/uc-src/router/guards/permission.ts new file mode 100644 index 000000000..0094f9cf8 --- /dev/null +++ b/console/uc-src/router/guards/permission.ts @@ -0,0 +1,22 @@ +import { useRoleStore } from "@/stores/role"; +import { hasPermission } from "@/utils/permission"; +import type { Router } from "vue-router"; + +export function setupPermissionGuard(router: Router) { + router.beforeEach((to, from, next) => { + const roleStore = useRoleStore(); + const { uiPermissions } = roleStore.permissions; + const { meta } = to; + if (meta && meta.permissions) { + const flag = hasPermission( + Array.from(uiPermissions), + meta.permissions as string[], + true + ); + if (!flag) { + next({ name: "Forbidden" }); + } + } + next(); + }); +} diff --git a/console/uc-src/router/index.ts b/console/uc-src/router/index.ts index 651918aa7..98df46a39 100644 --- a/console/uc-src/router/index.ts +++ b/console/uc-src/router/index.ts @@ -5,6 +5,8 @@ import { type RouteLocationNormalizedLoaded, } from "vue-router"; import routesConfig from "@uc/router/routes.config"; +import { setupAuthCheckGuard } from "./guards/auth-check"; +import { setupPermissionGuard } from "./guards/permission"; const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), @@ -19,4 +21,7 @@ const router = createRouter({ }, }); +setupAuthCheckGuard(router); +setupPermissionGuard(router); + export default router; diff --git a/console/uc-src/router/routes.config.ts b/console/uc-src/router/routes.config.ts index cfd4e0f82..48fd30517 100644 --- a/console/uc-src/router/routes.config.ts +++ b/console/uc-src/router/routes.config.ts @@ -2,7 +2,6 @@ import type { RouteRecordRaw } from "vue-router"; import NotFound from "@/views/exceptions/NotFound.vue"; import Forbidden from "@/views/exceptions/Forbidden.vue"; import BasicLayout from "@uc/layouts/BasicLayout.vue"; -import type { MenuGroupType } from "@halo-dev/console-shared"; export const routes: Array = [ { @@ -23,32 +22,4 @@ export const routes: Array = [ }, ]; -export const coreMenuGroups: MenuGroupType[] = [ - { - id: "dashboard", - name: undefined, - priority: 0, - }, - { - id: "content", - name: "core.sidebar.menu.groups.content", - priority: 1, - }, - { - id: "interface", - name: "core.sidebar.menu.groups.interface", - priority: 2, - }, - { - id: "system", - name: "core.sidebar.menu.groups.system", - priority: 3, - }, - { - id: "tool", - name: "core.sidebar.menu.groups.tool", - priority: 4, - }, -]; - export default routes; diff --git a/console/uc-src/setup/setupModules.ts b/console/uc-src/setup/setupModules.ts index 1721d0acb..dbf739820 100644 --- a/console/uc-src/setup/setupModules.ts +++ b/console/uc-src/setup/setupModules.ts @@ -21,7 +21,7 @@ export async function setupPluginModules(app: App) { const { load } = useScriptTag( `${ import.meta.env.VITE_API_URL - }/apis/api.console.halo.run/v1alpha1/plugins/-/bundle.js` + }/apis/api.console.halo.run/v1alpha1/plugins/-/bundle.js?t=${Date.now()}` ); await load(); @@ -45,7 +45,7 @@ export async function setupPluginModules(app: App) { await loadStyle( `${ import.meta.env.VITE_API_URL - }/apis/api.console.halo.run/v1alpha1/plugins/-/bundle.css` + }/apis/api.console.halo.run/v1alpha1/plugins/-/bundle.css?t=${Date.now()}` ); } catch (e) { const message = i18n.global.t("core.plugin.loader.toast.style_load_failed");