因为我们使用amis,我们可以大胆的考虑我们日后可能实现整个系统交给不懂编程的人去维护甚至修改。那么我们对左边栏的菜单项目就要求更加自由的控制,如果这个菜单需要修改,我们不能够重新编译系统,我们要实现简单的修改某个json文档就可以了。
那么我们就需要修改左边栏菜单的工作方式。
从服务器加载 menu
先看看ProComponents官网上的说法
ProLayout 提供了强大的菜单功能,但是这样必然会封装很多行为,导致需要一些特殊逻辑的用户感到不满。所以我们提供了很多的 API,期望可以满足绝大部分客户的方式。
从服务器加载 menu 主要使用的 API 是 menuDataRender 和 menuRender,menuDataRender可以控制当前的菜单数据,menuRender可以控制菜单的 dom 节点。
根据官网所说,接下来我们可以这样修改
1、在src/service里面添加menu子目录,再添加customMenu.ts用来返回自定义菜单内容
config
src
models
service
+ menu
+ customMenu.ts
...
...
package.json
customenu.ts代码如下
export default [
{
path: '/',
name: '欢迎',
routes: [
{
path: '/welcome',
name: 'one',
routes: [
{
path: '/welcome/welcome',
name: 'two',
exact: true,
},
],
},
],
},
{
path: '/demo',
name: '例子',
},
];
2、修改app.tsx里面的layout布局配置,增加menu:项目
增加相关导入和辅助函数
import customMenuDate from '@/services/menu/customMenu';
const waitTime = (time: number = 100) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
修改layout配置,增加menu项
export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) => {
return {
rightContentRender: () => <RightContent />,
disableContentMargin: false,
waterMarkProps: {
content: initialState?.currentUser?.name,
},
footerRender: () => <Footer />,
onPageChange: () => {
const { location } = history;
// 如果没有登录,重定向到 login
if (!initialState?.currentUser && location.pathname !== loginPath) {
history.push(loginPath);
}
},
menu: {
request: async () => {
await waitTime(2000);
return customMenuDate;
},
},
//.......后续代码略
保存热更新后
菜单项保存在本地Json
从服务端加载menu数据还是不够完美,我们还需要做一个菜单管理模块,实现不懂编程的人来修改菜单。但是考虑日后维护本系统的人还是需要学习amis的,如果我们把菜单数据保存在前端的json文件,只需要简单的说明,维护人员就可以轻松的修改json文档。
那么这就需要本项目可以读取本地json文档
1、json文档保存在项目哪里
因为项目的public文件夹是会被当做静态资源copy到编译后的目标文件夹里面的,我们的菜单json就是一个静态资源,放着public文件夹里面最合适,我们在public里面创建子文件夹json,并把菜单数据保存为menuData.json
2、读取本地json
为了能够实现本地json,我们根据上面第一点的设置,在src/services/ant-design-pro/api.ts里面添加函数getLocalJson,实现传递文件名就可以读取public/json里面的文件,并把读取文件以string的新式返回,代码如下:
/**读取本地Json文档 */
export async function getLocalJson(fileName: string) {
return request<string>(`/json/${fileName}`, {
method: 'get',
headers: {
'Content-Type': 'application/json',
},
});
}
3、菜单json文件示例
[
{
"path": "/",
"name": "欢迎",
"locale":false,
"routes": [
{
"path": "/welcome",
"name": "one",
"routes": [
{
"path": "/welcome/welcome",
"name": "two",
"exact": true
}
]
}
]
},
{
"path": "/demo",
"locale":false,
"name": "例子"
}
]
进一步优化
1、读取本地json文件之后 我们只是简单的返回string类型,这个不利于获取到的数据进行进一步的操作,因为pro-layout的菜单采用的数据类型typing是已经定义好的,我们可以直接引用。
2、加载菜单文件的时候,应该考虑当前用户选择的语言,然后根据用户当前的语言来加载不同的菜单文件,为了实现此功能,我们的函数参数应该直接修改成用户当前的语言,然后我们本地文档名称也应该直接用用户locale的key作为文件名。为此,我们public下面再建一个menu子文件夹,把原来的menuData.json改成zh-CN.json并移动到menu子文件夹。
在src/services/ant-design-pro/api.ts里面添加函数getLocalMemu,代码如下:
/**根据locale的key读取菜单的Json文档 */
export async function getLocalMemu(locale: string) {
return request<MenuDataItem[]>(`/json/menu/${locale}.json`, {
method: 'get',
headers: {
'Content-Type': 'application/json',
},
});
}
修改app.ts
修改app.ts我们前面添加的menu项。
1、因为我们需要自己根据当前用户选择的语言加载不同的菜单文件,我们不需要框架自己到源代码src/locales里面去匹配,所以我们设置locale选项为false。
2、修改menu里面的request
/// src/app.tsx
import { getLocalJson, getLocalMemu } from './services/ant-design-pro/api';
import { getLocale } from 'umi';
export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) => {
return {
rightContentRender: () => <RightContent />,
disableContentMargin: false,
waterMarkProps: {
content: initialState?.currentUser?.name,
},
footerRender: () => <Footer />,
onPageChange: () => {
const { location } = history;
// 如果没有登录,重定向到 login
if (!initialState?.currentUser && location.pathname !== loginPath) {
history.push(loginPath);
}
},
menu: {
locale: false,//我们自己匹配语言
request: async () => {
//通过getLocale()获取当前用户选择的语言,据此决定加载的菜单文件
const res = await getLocalMemu(getLocale());
return res;
},
},
//.......后续代码略
添加版本号
大部分浏览器,比如Chrome都会对用户加载的文件采用缓存在本地的机制,这样我们在日后如果需要更新我们的菜单,那就有可能出现我们已经更新好了,但是客户端看起来还是原来的样子。为了避免日后更新菜单json的时候,浏览器由于文件缓存的原因没有自动刷新,我们需要在加载本地json文档的时候添加版本号,这样浏览器会比对不同版本号,从而判断出是否是新的文档。
1、public\json里面添加ver.json并且设置内容为 1.0.0.1
2、修改apt.ts,添加获取版本号的函数
/**获取当前版本号 */
export async function getCurVer() {
//ver.json后面跟一个v=v=/${new Date().getTime()}确保不会被缓存
return request<string>(`/json/ver.json?v=${new Date().getTime()}`, {
method: 'get',
headers: {
'Content-Type': 'application/json',
},
});
}
/**根据locale的key读取菜单的Json文档 */
export async function getLocalMemu(locale: string, ver: string) {
return request<MenuDataItem[]>(`/json/menu/${locale}.json?v=${ver}`, {
method: 'get',
headers: {
'Content-Type': 'application/json',
},
});
}
3、修改app.tsx里面getInitialState部分,增加初始化的时候获取当前版本号的功能,代码如下
/// src/app.tsx
import { getCurVer } from './services/ant-design-pro/api';
export async function getInitialState(): Promise<{
ver?: string;//新增版本号项目
settings?: Partial<LayoutSettings>;
currentUser?: API.CurrentUser;
loading?: boolean;
fetchUserInfo?: () => Promise<API.CurrentUser | undefined>;
}> {
const fetchUserInfo = async () => {
try {
const msg = await queryCurrentUser();
return msg.data;
} catch (error) {
history.push(loginPath);
}
return undefined;
};
const curVer = await getCurVer();//读取版本号
// 如果不是登录页面,执行
if (history.location.pathname !== loginPath) {
const currentUser = await fetchUserInfo();
return {
ver: curVer,
fetchUserInfo,
currentUser,
settings: defaultSettings,
};
}
return {
fetchUserInfo,
settings: defaultSettings,
};
}
// ......其它代码忽略
// 获取菜单数据的部分代码
menu: {
locale: false, //我们自己匹配语言
request: async () => {
const ver = initialState?.ver ? initialState.ver : ' ';
const res = await getLocalMemu(getLocale(), ver);
return res;
},
},
// ......其它代码忽略
菜单图标
至此我们已经基本实现自由的左边栏菜单功能了,还差了一个小东西,就是菜单项的图标,因为menu的request返回的只是图标的string类型的名称,我们还需要根据名称进行对应组件的渲染,因此还需要一下小的动作。
1、因为代码开始有点多了,不再时候直接写在app.ts里面,我们开始设计一个components,在src/compontents里面添加LocalMenu子文件夹,并创建index.tsx
2、开始代码
需要导入 @ant-design/icons,把string和相关组件导入
import { getLocalMenu } from '@/services/ant-design-pro/api';
import { getLocale } from 'umi';
import { HeartOutlined, SmileOutlined } from '@ant-design/icons';
import type { MenuDataItem } from '@ant-design/pro-layout';
const IconMap = {
smile: <SmileOutlined />,
heart: <HeartOutlined />,
};
const loopMenuItem = (menus: any[]): MenuDataItem[] =>
menus.map(({ icon, routes, ...item }) => ({
...item,
icon: icon && IconMap[icon as string],
children: routes && loopMenuItem(routes),
}));
export default async (ver: string) => {
const res = await getLocalMenu(getLocale(), ver);
return loopMenuItem(res);
};
3、修改app.ts
import LocalMenu from './components/LocalMenu';
menu: {
locale: false, //我们自己匹配语言
request: async () => {
const ver = initialState?.ver ? initialState.ver : ' ';
const res = await LocalMenu(ver);
return res;
},
},
4、修改public/json/menu/zh-CN.json
{
"path": "/demo",
"locale":false,
"icon": "smile",
"name": "例子"
}
最后呈现