menu.js 5.9 KB
Newer Older
sin's avatar
sin committed
1 2 3 4 5
import memoizeOne from 'memoize-one';
import isEqual from 'lodash/isEqual';
import { formatMessage } from 'umi/locale';
import Authorized from '@/utils/Authorized';
import { menu } from '../defaultSettings';
sin's avatar
sin committed
6
import { getAdminMenus, getAdminUrls } from '../services/admin';
sin's avatar
sin committed
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73

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);
};
sin's avatar
sin committed
74

sin's avatar
sin committed
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
// 用于生成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,
sin's avatar
sin committed
148
    };
sin's avatar
sin committed
149 150 151 152 153 154 155

    if (antDataMenu.children) {
      const childrenMenus = moveChildrenMenus(antDataMenu.children);
      res = {
        ...res,
        ...childrenMenus,
      };
sin's avatar
sin committed
156 157 158 159 160
    }
  }
  return res;
};

sin's avatar
sin committed
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
/**
 * 获取面包屑映射
 * @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: [],
sin's avatar
sin committed
188
    urlsData: {},
sin's avatar
sin committed
189 190 191 192
    breadcrumbNameMap: {},
  },

  effects: {
sin's avatar
sin committed
193 194
    *getMenuData({ payload }, { put, call }) {
      const { data } = yield call(getAdminMenus);
sin's avatar
sin committed
195
      const { routes, authority } = payload;
sin's avatar
sin committed
196

sin's avatar
sin committed
197 198 199
      // authority 已经不适用
      const antMenuData = filterMenuData(memoizeOneFormatter(routes, authority));
      let menuData = antMenuData;
sin's avatar
sin committed
200
      // const resultMenuData = data;
sin's avatar
sin committed
201
      if (data !== 'all') {
sin's avatar
sin committed
202 203 204
        const moveChildrenMenusData = moveChildrenMenus(antMenuData);
        const buildTreeMenuData = buildTreeMenu(antMenuData, moveChildrenMenusData, data);
        menuData = buildTreeMenuData;
sin's avatar
sin committed
205 206 207
      }

      // 生成 menu 和 router mapping
sin's avatar
sin committed
208 209 210 211 212 213
      const breadcrumbNameMap = memoizeOneGetBreadcrumbNameMap(menuData);
      yield put({
        type: 'save',
        payload: { menuData, breadcrumbNameMap },
      });
    },
sin's avatar
sin committed
214 215 216 217 218 219 220 221 222 223 224 225 226 227
    *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 },
      });
    },
sin's avatar
sin committed
228 229 230
  },

  reducers: {
sin's avatar
sin committed
231
    save(state, { payload }) {
sin's avatar
sin committed
232 233
      return {
        ...state,
sin's avatar
sin committed
234
        ...payload,
sin's avatar
sin committed
235 236 237 238
      };
    },
  },
};