menu.js 5.9 KB
import memoizeOne from 'memoize-one';
import isEqual from 'lodash/isEqual';
import { formatMessage } from 'umi/locale';
import Authorized from '@/utils/Authorized';
import { menu } from '../defaultSettings';
import { getAdminMenus, getAdminUrls } from '../services/admin';

const { check } = Authorized;

// Conversion router to menu.
function formatter(data, parentAuthority, parentName) {
  return data
    .map(item => {
      if (!item.name || !item.path) {
        return null;
      }

      let locale = 'menu';
      if (parentName) {
        locale = `${parentName}.${item.name}`;
      } else {
        locale = `menu.${item.name}`;
      }
      // if enableMenuLocale use item.name,
      // close menu international
      const name = menu.disableLocal
        ? item.name
        : formatMessage({ id: locale, defaultMessage: item.name });
      const result = {
        ...item,
        name,
        locale,
        authority: item.authority || parentAuthority,
      };
      if (item.routes) {
        const children = formatter(item.routes, item.authority, locale);
        // Reduce memory usage
        result.children = children;
      }
      delete result.routes;
      return result;
    })
    .filter(item => item);
}

const memoizeOneFormatter = memoizeOne(formatter, isEqual);

/**
 * get SubMenu or Item
 */
const getSubMenu = item => {
  // doc: add hideChildrenInMenu
  if (item.children && !item.hideChildrenInMenu && item.children.some(child => child.name)) {
    return {
      ...item,
      children: filterMenuData(item.children), // eslint-disable-line
    };
  }
  return item;
};

/**
 * filter menuData
 */
const filterMenuData = menuData => {
  if (!menuData) {
    return [];
  }
  return menuData
    .filter(item => item.name && !item.hideInMenu)
    .map(item => check(item.authority, getSubMenu(item)))
    .filter(item => item);
};

// 用于生成uuid
function S4() {
  return ((1 + Math.random()) * 0x10000 || 0).toString(16).substring(1);
}
function guid() {
  return S4() + S4() + S4() + S4() + S4() + S4() + S4() + S4();
}

const findRootMenu = (antDataMenus, rootAntDataMenu, requestDataMenu) => {
  let res;
  for (let i = 0; i < antDataMenus.length; i += 1) {
    const antDataMenu = antDataMenus[i];
    if (antDataMenu.path === requestDataMenu.handler) {
      res = rootAntDataMenu;
      break;
    }
    if (antDataMenu.children) {
      res = findRootMenu(antDataMenu.children, antDataMenu, requestDataMenu);
      break;
    }
  }
  return res;
};

const buildTreeMenu = (antMenuData, moveChildrenMenusData, requestDataMenus) => {
  return requestDataMenus.map(item => {
    if (!item.handler) {
      // root 节点
      const uuid = `sms${guid()}`;
      const res = {
        icon: 'user',
        name: item.displayName,
        path: uuid,
      };

      // 子节点
      if (item.children) {
        // 通过子节点找到对于的父节点,设置 path,没有则是 uuid
        const rootMenu = findRootMenu(antMenuData, {}, item.children[0]);
        if (rootMenu) {
          res.path = rootMenu.path;
        }

        // 开始递归构建数据结构
        const childrenMenus = buildTreeMenu(antMenuData, moveChildrenMenusData, item.children);
        res.children = childrenMenus;
      }
      return res;
    }

    // moveChildrenMenusData 是一个 map,对比 url 地址是否存在,不存在就给一个 404 的页面
    const handleMapperData = moveChildrenMenusData[item.handler];
    if (handleMapperData) {
      return {
        ...handleMapperData,
        icon: 'user',
        name: item.displayName,
        path: item.handler,
      };
    }

    // 没有就返回404页面
    return moveChildrenMenusData['/exception/404'];
  });
};

const moveChildrenMenus = antDataMenus => {
  let res = {};
  for (let i = 0; i < antDataMenus.length; i += 1) {
    const antDataMenu = antDataMenus[i];
    res[antDataMenu.path] = {
      ...res,
      ...antDataMenu,
    };

    if (antDataMenu.children) {
      const childrenMenus = moveChildrenMenus(antDataMenu.children);
      res = {
        ...res,
        ...childrenMenus,
      };
    }
  }
  return res;
};

/**
 * 获取面包屑映射
 * @param {Object} menuData 菜单配置
 */
const getBreadcrumbNameMap = menuData => {
  const routerMap = {};

  const flattenMenuData = data => {
    data.forEach(menuItem => {
      if (menuItem.children) {
        flattenMenuData(menuItem.children);
      }
      // Reduce memory usage
      routerMap[menuItem.path] = menuItem;
    });
  };
  flattenMenuData(menuData);
  return routerMap;
};

const memoizeOneGetBreadcrumbNameMap = memoizeOne(getBreadcrumbNameMap, isEqual);

export default {
  namespace: 'menu',

  state: {
    menuData: [],
    urlsData: {},
    breadcrumbNameMap: {},
  },

  effects: {
    *getMenuData({ payload }, { put, call }) {
      const { data } = yield call(getAdminMenus);
      const { routes, authority } = payload;

      // authority 已经不适用
      const antMenuData = filterMenuData(memoizeOneFormatter(routes, authority));
      let menuData = antMenuData;
      // const resultMenuData = data;
      if (data !== 'all') {
        const moveChildrenMenusData = moveChildrenMenus(antMenuData);
        const buildTreeMenuData = buildTreeMenu(antMenuData, moveChildrenMenusData, data);
        menuData = buildTreeMenuData;
      }

      // 生成 menu 和 router mapping
      const breadcrumbNameMap = memoizeOneGetBreadcrumbNameMap(menuData);
      yield put({
        type: 'save',
        payload: { menuData, breadcrumbNameMap },
      });
    },
    *getUrlsData(state, { put, call }) {
      const { data } = yield call(getAdminUrls);

      // 构建 {'/user': true} 这种 map 结构方便取数据、
      const urlsData = {};
      data.forEach(item => {
        urlsData[item] = true;
      });

      yield put({
        type: 'save',
        payload: { urlsData },
      });
    },
  },

  reducers: {
    save(state, { payload }) {
      return {
        ...state,
        ...payload,
      };
    },
  },
};