Procházet zdrojové kódy

首页不见了的bug修复

ZGC4846 před 1 týdnem
rodič
revize
f374630b78

+ 182 - 0
src/config/menus.js

@@ -0,0 +1,182 @@
+/**
+ * 本地默认菜单(接口未配置 /page-hr 时兜底,保持改造前侧栏效果)
+ * component 相对 src/views/
+ */
+export const DEFAULT_MENUS = [
+  {
+    path: '/home',
+    component: 'homeView/index',
+    meta: { title: '首页', icon: 'el-icon-house' },
+  },
+  {
+    path: '/position',
+    redirect: '/position-management',
+    meta: { title: '岗位管理', icon: 'el-icon-suitcase' },
+    children: [
+      {
+        path: '/position-management',
+        component: 'position/positionManagement/index',
+        meta: { title: '岗位设定' },
+      },
+      {
+        path: '/position-sequence',
+        component: 'position/positionSequence/index',
+        meta: { title: '岗位序列' },
+      },
+      {
+        path: '/position-application',
+        component: 'position/positionApplication/index',
+        meta: { title: '岗位申请' },
+      },
+    ],
+  },
+  {
+    path: '/hiring',
+    redirect: '/hiring-approval',
+    meta: { title: '录用管理', icon: 'el-icon-s-check' },
+    children: [
+      {
+        path: '/hiring-approval',
+        component: 'hiring/hiringApproval/index',
+        meta: { title: '录用审批' },
+      },
+      {
+        path: '/offer-notify',
+        component: 'hiring/offerNotify/index',
+        meta: { title: 'Offer 通知' },
+      },
+    ],
+  },
+  {
+    path: '/hr-management',
+    redirect: '/employee-archive',
+    meta: { title: '人事管理', icon: 'el-icon-user' },
+    children: [
+      {
+        path: '/employee-archive',
+        component: 'hrManagement/employeeArchive/index',
+        meta: { title: '员工档案' },
+      },
+      {
+        path: '/onboarding-handling',
+        component: 'hrManagement/onboardingHandling/index',
+        meta: { title: '办理入职' },
+      },
+      {
+        path: '/regularization-handling',
+        component: 'hrManagement/regularizationHandling/index',
+        meta: { title: '转正办理' },
+      },
+      {
+        path: '/resignation-application',
+        component: 'hrManagement/resignationApplication/index',
+        meta: { title: '离职申请' },
+      },
+      {
+        path: '/resignation-handover-order',
+        component: 'hrManagement/resignationHandoverOrder/index',
+        meta: { title: '离职交接单' },
+      },
+      {
+        path: '/position-transfer',
+        component: 'hrManagement/positionTransfer/index',
+        meta: { title: '岗位调动办理' },
+      },
+      {
+        path: '/qualification-management',
+        component: 'hrManagement/qualificationManagement/index',
+        meta: { title: '资质管理' },
+      },
+      {
+        path: '/term-management',
+        component: 'hrManagement/termManagement/index',
+        meta: { title: '期限管理' },
+      },
+      {
+        path: '/employee-template',
+        redirect: '/offer-letter-template',
+        meta: { title: '员工模板' },
+        children: [
+          {
+            path: '/offer-letter-template',
+            component: 'hrManagement/employeeTemplate/offerLetterTemplate/index',
+            meta: { title: '录用通知书' },
+          },
+          {
+            path: '/onboarding-contract-template',
+            component:
+              'hrManagement/employeeTemplate/onboardingContractTemplate/index',
+            meta: { title: '合同模版' },
+          },
+          {
+            path: '/regularization-template',
+            component: 'hrManagement/employeeTemplate/regularizationTemplate/index',
+            meta: { title: '转正申请模板' },
+          },
+          {
+            path: '/resignation-letter',
+            component: 'hrManagement/employeeTemplate/resignationLetter/index',
+            meta: { title: '辞职申请书' },
+          },
+          {
+            path: '/resignation-handover-table',
+            component:
+              'hrManagement/employeeTemplate/resignationHandoverTable/index',
+            meta: { title: '离职交接表' },
+          },
+          {
+            path: '/resignation-certificate',
+            component:
+              'hrManagement/employeeTemplate/resignationCertificate/index',
+            meta: { title: '离职证明' },
+          },
+          {
+            path: '/position-transfer-template',
+            component:
+              'hrManagement/employeeTemplate/positionTransferTemplate/index',
+            meta: { title: '岗位调动' },
+          },
+        ],
+      },
+    ],
+  },
+  {
+    path: '/contract-management',
+    component: 'contractManagement/index',
+    meta: { title: '合同管理', icon: 'el-icon-document' },
+  },
+  {
+    path: '/attendance',
+    redirect: '/attendance/rule',
+    meta: { title: '考勤管理', icon: 'el-icon-time' },
+    children: [
+      {
+        path: '/attendance/rule',
+        component: 'attendance/rule/index',
+        meta: { title: '打卡规则设定' },
+      },
+      {
+        path: '/attendance/report',
+        component: 'attendance/report/index',
+        meta: { title: '考勤统计' },
+      },
+    ],
+  },
+  {
+    path: '/hr-analysis',
+    redirect: '/personnel-structure-dashboard',
+    meta: { title: '人力资源分析报表', icon: 'el-icon-data-analysis' },
+    children: [
+      {
+        path: '/personnel-structure-dashboard',
+        component: 'hrAnalysis/personnelStructureDashboard/index',
+        meta: { title: '人员结构看板' },
+      },
+      {
+        path: '/personnel-structure-analysis-board',
+        component: 'hrAnalysis/personnelStructureAnalysisBoard/index',
+        meta: { title: '人员结构分析看板' },
+      },
+    ],
+  },
+];

+ 10 - 5
src/config/setting.js

@@ -1,3 +1,5 @@
+import { DEFAULT_MENUS } from './menus';
+
 /** 接口基础地址 */
 export const API_BASE_URL = process.env.VUE_APP_API_BASE_URL;
 
@@ -26,12 +28,15 @@ export const WHITE_LIST = ['/login'];
 /** KeepAlive 排除的路由 */
 export const KEEP_ALIVE_EXCLUDES = [];
 
-/** 直接指定菜单数据(undefined 则走接口) */
-export const USER_MENUS = undefined;
+/**
+ * 菜单数据:优先接口 /page-hr;接口未配置时用本地 DEFAULT_MENUS 兜底
+ * 门户菜单配置好后,可将此处改回 undefined,仅走接口
+ */
+export const USER_MENUS = DEFAULT_MENUS;
 
-/** 首页名称 / 路径,为空则取菜单第一个 */
-export const HOME_TITLE = undefined;
-export const HOME_PATH = undefined;
+/** 首页名称 / 路径 */
+export const HOME_TITLE = '首页';
+export const HOME_PATH = '/home';
 
 /** 外层布局路由地址 */
 export const LAYOUT_PATH = '/';

+ 136 - 0
src/layout/components/header-tools.vue

@@ -0,0 +1,136 @@
+<!-- 顶栏右侧:用户信息 -->
+<template>
+  <div class="ele-admin-header-tool">
+    <div v-if="groupOptions.length > 1" class="ele-admin-header-tool-item">
+      <el-select
+        v-model="groupId"
+        size="small"
+        style="width: 140px"
+        @change="onGroupChange"
+      >
+        <el-option
+          v-for="item in groupOptions"
+          :key="item.groupId"
+          :label="item.groupName"
+          :value="item.groupId"
+        />
+      </el-select>
+    </div>
+    <div v-if="roleOptions.length > 1" class="ele-admin-header-tool-item">
+      <el-select
+        v-model="roleId"
+        size="small"
+        style="width: 140px"
+        @change="onRoleChange"
+      >
+        <el-option
+          v-for="item in roleOptions"
+          :key="item.roleId"
+          :label="item.roleName"
+          :value="item.roleId"
+        />
+      </el-select>
+    </div>
+    <div class="ele-admin-header-tool-item">
+      <el-dropdown @command="onCommand">
+        <div class="ele-admin-header-avatar">
+          <el-avatar :size="28" icon="el-icon-user-solid" />
+          <span class="hidden-xs-only user-name">{{ displayName }}</span>
+          <i class="el-icon-arrow-down"></i>
+        </div>
+        <el-dropdown-menu slot="dropdown">
+          <el-dropdown-item command="logout" icon="el-icon-switch-button">
+            退出登录
+          </el-dropdown-item>
+        </el-dropdown-menu>
+      </el-dropdown>
+    </div>
+  </div>
+</template>
+
+<script>
+import { getCurrentUser, setCurrentUser } from '@/utils/token-util';
+import { logout } from '@/utils/page-tab-util';
+import router from '@/router';
+
+export default {
+  name: 'HeaderTools',
+  data() {
+    const current = getCurrentUser() || {};
+    return {
+      groupId: current.currentGroupId || '',
+      roleId: current.currentRoleId || '',
+    };
+  },
+  computed: {
+    loginUser() {
+      return this.$store.state.user.info || {};
+    },
+    displayName() {
+      const user = this.loginUser;
+      return (
+        user.nickname ||
+        user.userName ||
+        user.loginName ||
+        user.realName ||
+        '用户'
+      );
+    },
+    groupOptions() {
+      return this.loginUser.loginChangeGroupVOList || [];
+    },
+    roleOptions() {
+      const group = this.groupOptions.find(
+        (item) => String(item.groupId) === String(this.groupId),
+      );
+      return group?.loginChangeRoleVOList || [];
+    },
+  },
+  methods: {
+    onGroupChange() {
+      const firstRole = this.roleOptions[0];
+      if (firstRole) this.onRoleChange(firstRole.roleId);
+    },
+    onRoleChange(roleId) {
+      this.roleId = roleId;
+      setCurrentUser({
+        currentGroupId: this.groupId,
+        currentRoleId: roleId,
+      });
+      this.$store.commit('user/setMenus', null);
+      this.$store.dispatch('user/fetchUserInfo').then((payload) => {
+        if (payload?.menus?.length && router.roleChange) {
+          router.roleChange(payload);
+        } else {
+          window.location.reload();
+        }
+      });
+    },
+    onCommand(command) {
+      if (command !== 'logout') return;
+      this.$confirm('确定要退出登录吗?', '提示', { type: 'warning' })
+        .then(() => {
+          this.$store.commit('user/setMenus', null);
+          logout(true);
+        })
+        .catch(() => {});
+    },
+  },
+};
+</script>
+
+<style scoped>
+.user-name {
+  margin: 0 4px 0 8px;
+  max-width: 100px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  vertical-align: middle;
+}
+.ele-admin-header-avatar {
+  display: inline-flex;
+  align-items: center;
+  cursor: pointer;
+}
+</style>

+ 6 - 15
src/layout/index.vue

@@ -13,7 +13,7 @@
     :side-style="theme.sideStyle"
     :layout-style="theme.layoutStyle"
     :side-menu-style="theme.sideMenuStyle"
-    :tab-style="theme.tabStyle"
+    :tab-style="'card'"
     :fixed-header="theme.fixedHeader"
     :fixed-sidebar="theme.fixedSidebar"
     :fixed-body="theme.fixedBody"
@@ -27,7 +27,7 @@
     :hide-sidebars="HIDE_SIDEBARS"
     :repeatable-tabs="REPEATABLE_TABS"
     :home-title="HOME_TITLE || '首页'"
-    :home-path="HOME_PATH"
+    :home-path="HOME_PATH || '/home'"
     :layout-path="LAYOUT_PATH"
     :redirect-path="REDIRECT_PATH"
     :tab-sortable="true"
@@ -48,12 +48,7 @@
   >
     <router-layout />
     <template v-slot:right>
-      <div class="ele-admin-header-tool">
-        <div class="ele-admin-header-tool-item" @click="logout">
-          <i class="el-icon-switch-button"></i>
-          <span style="margin-left: 4px">退出</span>
-        </div>
-      </div>
+      <header-tools />
     </template>
   </ele-pro-layout>
 </template>
@@ -61,6 +56,7 @@
 <script>
 import { mapGetters } from 'vuex';
 import RouterLayout from '@/components/RouterLayout/index.vue';
+import HeaderTools from './components/header-tools.vue';
 import {
   PROJECT_NAME,
   HIDE_SIDEBARS,
@@ -81,12 +77,11 @@ import {
   reloadPageTab,
   setHomeComponents,
   setPageTabs,
-  logout as doLogout,
 } from '@/utils/page-tab-util';
 
 export default {
   name: 'EleLayout',
-  components: { RouterLayout },
+  components: { RouterLayout, HeaderTools },
   data() {
     return {
       PROJECT_NAME,
@@ -119,15 +114,11 @@ export default {
       this.$store.dispatch('theme/setBodyFullscreen', value);
     },
     onLogoClick(isHome) {
-      if (!isHome) this.$router.push(LAYOUT_PATH);
+      if (!isHome) this.$router.push(HOME_PATH || '/home');
     },
     screenSizeChange() {
       this.$store.dispatch('theme/updateScreenSize');
     },
-    logout() {
-      this.$store.commit('user/setMenus', null);
-      doLogout(true);
-    },
     addPageTab,
     removePageTab,
     removeAllPageTab,

+ 18 - 14
src/router/index.js

@@ -6,6 +6,7 @@ import {
   REDIRECT_PATH,
   LAYOUT_PATH,
   SYSTEM_NAME,
+  HOME_PATH,
 } from '@/config/setting';
 import store from '@/store';
 import { getToken, setToken, getCurrentUser } from '@/utils/token-util';
@@ -33,32 +34,35 @@ router.beforeEach((to, from, next) => {
   }
 
   if (getToken()) {
-    if (!store.state.user.menus) {
+    if (!store.state.user.menus?.length) {
       store
         .dispatch('user/fetchUserInfo')
         .then(({ menus, homePath, authoritiesRouter }) => {
-          if (menus) {
-            router.addRoute(
-              getMenuRoutes([...(menus || []), ...(authoritiesRouter || [])], homePath),
-            );
-            next({ ...to, replace: true });
-            return;
-          }
-          next();
+          const menuList = menus || [];
+          router.addRoute(
+            getMenuRoutes(
+              [...menuList, ...(authoritiesRouter || [])],
+              homePath || HOME_PATH || '/home',
+            ),
+          );
+          next({ ...to, replace: true });
         })
         .catch((e) => {
           console.error(e);
-          next();
+          next({ path: '/login', replace: true });
         });
       return;
     }
 
     if (routes.length >= router.getRoutes()?.length) {
       router.addRoute(
-        getMenuRoutes([
-          ...store.state.user.menus,
-          ...store.state.user.authoritiesRouter,
-        ]),
+        getMenuRoutes(
+          [
+            ...store.state.user.menus,
+            ...(store.state.user.authoritiesRouter || []),
+          ],
+          HOME_PATH || '/home',
+        ),
       );
       next({ ...to, replace: true });
       return;

+ 49 - 29
src/store/modules/user.js

@@ -3,6 +3,7 @@
  */
 import { formatMenus, toTreeData, formatTreeData } from 'ele-admin';
 import { USER_MENUS, SYSTEM_NAME } from '@/config/setting';
+import { DEFAULT_MENUS } from '@/config/menus';
 import { getResourcesTree } from '@/api/layout';
 import { getCurrentUser } from '@/utils/token-util';
 import Vue from 'vue';
@@ -31,6 +32,11 @@ const formatRouter = (list = []) => {
   return { menuList: walk(list), authorities };
 };
 
+function buildLocalMenus() {
+  const source = USER_MENUS || DEFAULT_MENUS;
+  return formatMenus(JSON.parse(JSON.stringify(source)));
+}
+
 export default {
   namespaced: true,
   state: {
@@ -60,37 +66,51 @@ export default {
   actions: {
     async fetchUserInfo({ commit }) {
       const currentUser = getCurrentUser();
-      if (!currentUser?.currentGroupId || !currentUser?.currentRoleId) {
-        return {};
-      }
-      const result = await getResourcesTree({
-        groupId: currentUser.currentGroupId,
-        roleId: currentUser.currentRoleId,
-      }).catch(() => null);
-      const list = result?.filter((item) => item.path === APP_MENU_PATH);
-      if (!list?.length) {
-        return {};
-      }
+      let menus;
+      let homePath;
+      let authoritiesRouter = [];
 
-      const { menuList, authorities } = formatRouter(list[0].children);
-      commit('setAuthorities', authorities);
+      try {
+        if (currentUser?.currentGroupId && currentUser?.currentRoleId) {
+          const result = await getResourcesTree({
+            groupId: currentUser.currentGroupId,
+            roleId: currentUser.currentRoleId,
+          });
+          const list = result?.filter((item) => item.path === APP_MENU_PATH);
+          if (list?.length) {
+            const { menuList, authorities } = formatRouter(list[0].children || []);
+            commit('setAuthorities', authorities);
+            const formatted = formatMenus(
+              toTreeData({
+                data: menuList,
+                idField: 'id',
+                parentIdField: 'parentId',
+              }),
+            );
+            menus = formatted.menus;
+            homePath = formatted.homePath;
+            const authFormatted = formatMenus(
+              toTreeData({
+                data: authorities.filter((item) => item.path),
+                idField: 'id',
+                parentIdField: 'parentId',
+              }),
+            );
+            authoritiesRouter = authFormatted.menus || [];
+          }
+        }
+      } catch (e) {
+        console.warn('[hr] 菜单接口不可用,使用本地菜单', e);
+      }
 
-      const { menus, homePath } = formatMenus(
-        USER_MENUS ??
-          toTreeData({
-            data: menuList,
-            idField: 'id',
-            parentIdField: 'parentId',
-          }),
-      );
-      const { menus: authoritiesRouter } = formatMenus(
-        USER_MENUS ??
-          toTreeData({
-            data: authorities.filter((item) => item.path),
-            idField: 'id',
-            parentIdField: 'parentId',
-          }),
-      );
+      // 接口无 /page-hr 或失败时,回退本地菜单(恢复改造前侧栏)
+      if (!menus?.length) {
+        const local = buildLocalMenus();
+        menus = local.menus;
+        homePath = local.homePath || '/home';
+        authoritiesRouter = [];
+        commit('setAuthorities', []);
+      }
 
       commit('setMenus', menus);
       commit('setAuthoritiesRouter', authoritiesRouter);

+ 2 - 1
src/views/login/index.vue

@@ -99,6 +99,7 @@
     created() {
       // 重置菜单,登录后由路由守卫重新拉取并注册动态路由
       this.$store.commit('user/setMenus', null);
+      this.$store.commit('user/setAuthoritiesRouter', []);
     },
     methods: {
       submit() {
@@ -126,7 +127,7 @@
             cleanPageTabs();
             this.$message.success(result.message || '登录成功');
             await this.$router
-              .replace(this.$route.query.from || '/')
+              .replace(this.$route.query.from || '/home')
               .catch(() => {});
           } catch (error) {
             if (!error.response && !error.request) {