文章目录

  • Amis
    • Amis 安装
    • 新增页面
    • 关于amis样式
    • 最终呈现界面
  • 自由的左边栏菜单,本地Json保存菜单数据
    • 从服务器加载 menu
    • 菜单项保存在本地Json
    • 进一步优化
    • 修改app.tsx
    • 添加版本号
    • 菜单图标
  • umi request 的简单修改
    • 修改service里面的api.ts
  • 设计React里面使用的Amis组件
    • 创建amis组件
    • Hook 方式的挂载事件
      • useState
      • useEffect
    • useEffect与class时代的生命周期
    • 组件加载json文件
    • 修改路由
  • Amis 组件再分析
    • 消息提醒
    • renderAmis
    • fetcher
  • 使用fontawesome字体
    • 字体图标问题
    • 导入font-awesome字体图标

Amis

amis 是一个低代码前端框架,它使用 JSON 配置来生成页面,可以减少页面开发工作量,极大提升效率。amis 可以高效的实现数据的增删改查CRUD,对于大部分简单的数据表,我们能够不用写代码自然是最好的。它最大的优势是,不需要代码就可以实现许多我们需要实现的功能。具体见:https://aisuda.bce.baidu.com/amis/zh-CN/docs/index

做这个项目的初衷就是要用Amis实现简单的操作,用Ant design 组件实现更加自由的操作。前面我们已经用Ant design pro 实现了基础框架。现在是时候把Amis集成进来了。

Amis 安装

因为amis现在更新还非常频繁,为了能够确保我们安装的版本更加稳定一下,我选择了指定版本安装。

npm install amis@2.6.0 --save

安装成功之后,可以开始测试是不是能够把amis集成进来。为此我们需要增加一个测试页面。

新增页面

1、在 src / pages 下创建一个新的文件夹mytest,并且在改文件夹里面添加index.tsx 文件。

config
srcmodelspages
+   mytest+ index.tsx...
...
package.json

2、页面代码如下

const testPage: React.FC = () => {return <div>my test page</div>;
};
export default testPage;

3、添加新路由
在config/config.ts里面的routes,添加新的路由。

{path: '/mytest',name: 'mytest',icon: 'smile',component: './mytest',
},


4、热更新之后,我们可以看到如下画面

5、参考amis官网,在新页面里面提交amis代码,实现用json代码展现功能页面,只是为了测试,我们随意加载amis官方一段代码,修改后的index.tsx代码如下:

import axios from 'axios';
import copy from 'copy-to-clipboard';
import 'amis/lib/themes/cxd.css';import { render as renderAmis } from 'amis';
import { ToastComponent, AlertComponent, toast } from 'amis-ui';
const AmisComponent: React.FC = () => {const theme = 'cxd';const locale = 'zh-CN';// 请勿使用 React.StrictMode,目前还不支持return (<div><p>通过 amis 渲染页面</p><ToastComponent theme={theme} key="toast" position={'top-right'} locale={locale} /><AlertComponent theme={theme} key="alert" locale={locale} />{renderAmis({type: 'page',body: {type: 'form',debug: true,api: '/amis/api/mock2/form/saveForm',body: [{name: 'city',type: 'input-city',label: '城市',searchable: true,},],},},{// props...// locale: 'en-US' // 请参考「多语言」的文档// scopeRef: (ref: any) => (amisScoped = ref)  // 功能和前面 SDK 的 amisScoped 一样},{// 下面三个接口必须实现fetcher: ({url, // 接口地址method, // 请求方法 get、post、put、deletedata, // 请求数据responseType,config, // 其他配置headers, // 请求头}: any) => {config = config || {};config.withCredentials = true;responseType && (config.responseType = responseType);if (config.cancelExecutor) {config.cancelToken = new (axios as any).CancelToken(config.cancelExecutor);}config.headers = headers || {};if (method !== 'post' && method !== 'put' && method !== 'patch') {if (data) {config.params = data;}return (axios as any)[method](url, config);} else if (data && data instanceof FormData) {config.headers = config.headers || {};config.headers['Content-Type'] = 'multipart/form-data';} else if (data &&typeof data !== 'string' &&!(data instanceof Blob) &&!(data instanceof ArrayBuffer)) {data = JSON.stringify(data);config.headers = config.headers || {};config.headers['Content-Type'] = 'application/json';}return (axios as any)[method](url, data, config);},isCancel: (value: any) => (axios as any).isCancel(value),copy: (content) => {copy(content);toast.success('内容已复制到粘贴板');},theme,// 后面这些接口可以不用实现// 默认是地址跳转// jumpTo: (//   location: string /*目标地址*/,//   action: any /* action对象*/// ) => {//   // 用来实现页面跳转, actionType:link、url 都会进来。// },// updateLocation: (//   location: string /*目标地址*/,//   replace: boolean /*是replace,还是push?*/// ) => {//   // 地址替换,跟 jumpTo 类似// },// isCurrentUrl: (//   url: string /*url地址*/,// ) => {//   // 用来判断是否目标地址当前地址// },// notify: (//   type: 'error' | 'success' /**/,//   msg: string /*提示内容*/// ) => {//   toast[type]//     ? toast[type](msg, type === 'error' ? '系统错误' : '系统消息')//     : console.warn('[Notify]', type, msg);// },// alert,// confirm,// tracker: (eventTracke) => {}},)}</div>);
};
export default AmisComponent;

其中,特别提醒,要导入css样式,否则的话呈现出来的界面会很混乱。代码如下:

import 'amis/lib/themes/cxd.css';

这行代码在amis的官网里面是没有的,这个坑我踩过。考虑到后面有可能会直接修改amis的样式,建议把依赖库里面的css文件copy到代码文件夹里面,这样可以不受amis升级影响,避免直接修改amis样式的时候,会被amis升级信息覆盖掉。

config
srcmodelspages
+   mytest+ index.tsx+ cdx.css...
...
package.json

index.ts的页面代码,修改如下:

import './cxd.css';

以上仅供参考,也可以考虑采用amis官网上面关于在amis中自定义样式的其它方式。

关于amis样式

amis官网上面关于amis自定义样式有四种方式
1、使用 CSS 变量动态修改,通过这种方式修改大部分 amis 组件的样式,所有组件都会生效,注意这种方法不支持 IE11。
2、使用辅助 class,可以对单个组件做定制修改。
3、自己生成主题 CSS,可以修改所有配置,目前只能通过源码方式,请参考 scss\themes\cxd.scss 文件,修改变量后重新编译一个 css,需要注意这种方式在更新 amis 版本的时候最好重新编译,否则就会出现使用旧版 css 的情况,可能导致出错,因此不推荐使用。
4、wrapper 组件可以直接写内嵌 style。

另外amis官网提供的主题编辑器,点击链接可以直接进入。通过主题编辑器,可以比较直观操作。

最终呈现界面

自由的左边栏菜单,本地Json保存菜单数据

因为我们使用amis,我们可以大胆的考虑我们日后可能实现整个系统交给不懂编程的人去维护甚至修改。那么我们对左边栏的菜单项目就要求更加自由的控制,如果这个菜单需要修改,我们不能够重新编译系统,我们要实现简单的修改某个json文档就可以了。
那么我们就需要修改左边栏菜单的工作方式。

从服务器加载 menu

先看看ProComponents官网上的说法

ProLayout 提供了强大的菜单功能,但是这样必然会封装很多行为,导致需要一些特殊逻辑的用户感到不满。所以我们提供了很多的 API,期望可以满足绝大部分客户的方式。
从服务器加载 menu 主要使用的 API 是 menuDataRender 和 menuRender,menuDataRender可以控制当前的菜单数据,menuRender可以控制菜单的 dom 节点。

根据官网所说,接下来我们可以这样修改
1、在src/service里面添加menu子目录,再添加customMenu.ts用来返回自定义菜单内容

config
srcmodelsservice+ 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;// 如果没有登录,重定向到 loginif (!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/json下面再建一个menu子文件夹,把原来的menuData.json改成zh-CN.json并移动到menu子文件夹。
在src/services/ant-design-pro/api.ts里面添加函数getLocalMenu,代码如下:

import { MenuDataItem } from '@ant-design/pro-layout';
/**根据locale的key读取菜单的Json文档 */
export async function getLocalMenu(locale: string) {return request<MenuDataItem[]>(`/json/menu/${locale}.json`, {method: 'get',headers: {'Content-Type': 'application/json',},});
}

修改app.tsx

修改app.tsx我们前面添加的menu项。
1、因为我们需要自己根据当前用户选择的语言加载不同的菜单文件,我们不需要框架自己到源代码src/locales里面去匹配,所以我们设置locale选项为false。
2、修改menu里面的request

/// src/app.tsx
import { getLocalJson, getLocalMenu } 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;// 如果没有登录,重定向到 loginif (!initialState?.currentUser && location.pathname !== loginPath) {history.push(loginPath);}},menu: {locale: false,//我们自己匹配语言request: async () => {//通过getLocale()获取当前用户选择的语言,据此决定加载的菜单文件const res = await getLocalMenu(getLocale());        return res;},},//.......后续代码略

添加版本号

大部分浏览器,比如Chrome都会对用户加载的文件采用缓存在本地的机制,这样我们在日后如果需要更新我们的菜单,那就有可能出现我们已经更新好了,但是客户端看起来还是原来的样子。为了避免日后更新菜单json的时候,浏览器由于文件缓存的原因没有自动刷新,我们需要在加载本地json文档的时候添加版本号,这样浏览器会比对不同版本号,从而判断出是否是新的文档。
1、public\json里面添加ver.json并且设置内容为 1.0.0.1
2、修改api.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 getLocalMenu(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 getLocalMenu(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.tsx

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": "例子"}]

最后呈现

umi request 的简单修改

design pro 的api访问采用request,在本项目的/src/service/ant-design-pro里面有访问mock模拟api的代码,由于我们的是要做前后端分离的,后端的api和前端不一定放在同一个服务器。而我们前面对菜单的改造是希望我们可以访问本地的json文件,这样我们就需要把这两种访问方式区分开来,并且amis的schema文档内部也有需要访问后端api的时候,为此我们需要简单的封装一下umi 的request。当然后面我们如果有需要,还可以进一步对请求进行拦截。对实例request进行请求拦截(interceptors)。
在src/utils/文件夹里面添加request.ts,代码如下

/* eslint-disable */
import { extend } from 'umi-request';
//api 网址前缀,为了方便日后更换服务器,直接用一个常量定义
const urlPrefix = '';//http://localhost:1898';//开发的时候如果都是本地服务器,把这个常量设置为空字符就可以
const remoteRequest = extend({// 路径前缀(基础路径)prefix: urlPrefix,timeout: 5000,
});
/*** 读取本地文件*/
export const localRequest = extend({prefix: '',timeout: 5000,
});/** amis的schema文件专用request*/
export async function amisRequest(url: string, method?: string, options?: { [key: string]: any }) {let newMethod = '';if (!method) newMethod = 'GET';else newMethod = method.toUpperCase();switch (newMethod) {case 'GET':return remoteRequest.get(url, options);case 'POST':return remoteRequest.post(url, options);case 'DELETE':return remoteRequest.delete(url, options);case 'PUT':return remoteRequest.put(url, options);}}
export default remoteRequest;

我们封装了request,项目里面需要访问api的地方都需要改代码,目前有两个地方有Api访问,一个是service里面,一个是amis 的schema里面,这里我们先解决service里面,关于amis访问api见我们下一篇文章设计amis组件。

修改service里面的api.ts

这比较简单,import头改一下,读取本地json的地方改一下。代码分别如下:

//import { request } from 'umi';
import request, { localRequest } from '@/utils/request';

读取本地json的地方,全部改成用localRequest就可以了

/**读取本地Json文档 */
export async function getLocalSchema(fileName: string, ver?: string) {return localRequest<Schema>(`/json/${fileName}.json?v=${ver}`, {method: 'get',headers: {'Content-Type': 'application/json',},});
}
import { MenuDataItem } from '@ant-design/pro-layout';
import { Schema } from 'amis';/**根据locale的key读取菜单的Json文档 */
export async function getLocalMenu(locale: string, ver: string) {return localRequest<MenuDataItem[]>(`/json/menu/${locale}.json?v=${ver}`, {method: 'get',headers: {'Content-Type': 'application/json',},});
}
/**获取当前版本号 */
export async function getCurVer() {return localRequest<string>(`/json/ver.json?v=/${new Date().getTime()}`, {method: 'get',headers: {'Content-Type': 'application/json',},});
}

设计React里面使用的Amis组件

创建amis组件

我们前面已经安装好了amis,并且copy官方Demo代码生成了一个react中使用amis的页面。但是这样是不方便的,我们每一个amis schema都需要重复做这样的页面是不合适的。考虑我们可以读取本地json文档,应该设计一个amis组件,这个组件可以获取当前url的path最后一层为文件名,根据文件名加载本地json文档做为组件的schema。
这样做的好处是,运营的时候可以交给不懂编程的运营人员直接修改,修改后的json格式schema上传后就可以了。这样,程序的修改,不需要重新编译程序。

Hook 方式的挂载事件

创建组件,我们采用函数方式:

const AmisComponent: React.FC = () => {}

这种方式,因为没有采用class继承,没有this,没有生命周期函数比如:componentDidMount(),componentDidUpdate(),componentWillUnmount()等等。取而代之的是useEffect和useState。

useState

在使用class的时候,组件的数据通过this.state = {},来实现,但是在hook中我们采用的方式就是使用useState这个hook。示例如下:

import { useEffect, useState } from 'react';
const [count, setValue] = useState<number>(0);
setValue(3);

这个代码翻译成class方式如下:

this.state = {count:0};
this.setState({count:3});

useEffect

useEffect的原型如下:

function useEffect(effect: EffectCallback, deps?: DependencyList): void;

总共两个参数,一个是Effect影响函数,一个是依赖列表。影响函数好理解,就是我们希望对本组件进行影响的操作。
依赖列表一个数组,一个变量名称组成的数组,是触发影响函数的依赖变量。影响函数外面的任何可能改变函数运行结果的变量都可以写入依赖列表里面去。例如:

  //代码Alet a=0,b=0,c=0;useEffect(() => {console.log(a,b,c);},[])

上面这段代码里面,a,b,c三个变量都可能改变函数的运行,所以都可以写到依赖列表里面去:

  //代码Blet a=0,b=0,c=0;useEffect(() => {console.log(a,b,c);},[a,b,c])

上面两段代码A和B,由于A里面的代码依赖列表是空白的,所以useEffect里面的影响函数指执行一次。代码B里面的依赖列表是a,b,c只要a,b,c有变化,影响函数就会执行。

useEffect与class时代的生命周期

基于上面对useEffect的理解,我们可以整理出它和class时代的生命周期函数的对应关系:
1、componentDidMount() 对应如下代码:

useEffect(()=>{},[]);//组件渲染后,执行一次。因为依赖列表里面是空白的

2、componentWillUnmount(),用hook来代替,需要去return一个callback(回调函数),如下面的形式所示:

 useEffect(() => {return () => {//todo:clear something};}, []);

3、componentDidUpdate(),用一个特殊变量的去触发hook,如下面所示,count指的就是这个特殊的变量,该hook触发,只会是count的值改变时,这个也相当vue里面的watch,可以监控某个变量的变化:

useEffect(() => { },[count])

组件加载json文件

有了以上的知识基础,我们可以开始实现我们的功能。由于我们设计组件的初衷就要组件可以根据location中的path来自动加载,对应的json,我们需要解析location的最后一层,并且初始化myschema(用来保存json内容的变量)变量,由于我们需要把该变量丢给render渲染函数,根据amis里面schema类型的定义,我们发现有一个type属性是非空的,所有我们useState的时候,初始变量必须是{type:‘page’},实在懒得写,也可以给一个{}空白对象作为初始值,否则初次渲染的时候就会报错:TypeError: Cannot read property ‘mobile’ of undefined。这个是amis渲染器没有undefined判断的原因。

 //获取path路径中的json文件名称const location = useLocation();const schemaFile = location.pathname.substring(location.pathname.lastIndexOf('/') + 1);//初始化Schemaconst [myschema, setValue] = useState<Schema>({ type: 'page', title: '', body: '' });

加载schema文件时,我们采用umi 的request库,该库返回的是一个Promise,在useEffect也可以用.then来实现赋值,但是这种很破坏结构新的回调函数方式我比较不喜欢。通常解决办法是用async/await,由于我们采用函数式,React.FC如果前面加async,会有类型错误的提醒(typescript问题),为了确保函数类型一致,我们采用立即执行函数IIFE。因此useEffect被设计成这样:

useEffect(() => {   (async () => {const schema = await getLocalSchema(schemaFile, initialState?.ver);if (isMounted) setValue(schema);})();//IIFE}, [schemaFile]);//我们希望组件会根据当前location里面的path的变化重新加载不同的json文件,所以这个地方的依赖列表不能空

提醒注意,由于我们需要在用户点击不同菜单项的时候加载不一样的json文件,所以我们需要监控schemaFile的变化,必须要把它列入依赖列表。

综上,组件完整代码如下:

/* eslint-disable react-hooks/exhaustive-deps */
// import axios from 'axios';
import copy from 'copy-to-clipboard';
import './cxd.css';
import { render as renderAmis } from 'amis';
import { ToastComponent, AlertComponent, toast } from 'amis-ui';
import { useLocation } from 'react-router';
import { useModel } from 'umi';
import { getLocalSchema } from '@/services/ant-design-pro/api';
import { useEffect, useState } from 'react';
import type { Schema } from 'amis';
import request, { amisRequest } from '@/utils/request';
import remoteRequest from '@/utils/request';
//import request, { localRequest } from '@/utils/request';
const AmisComponent: React.FC = () => {//获取path路径中的json文件名称const location = useLocation();const schemaFile = location.pathname.substring(location.pathname.lastIndexOf('/') + 1);//获取版本号const { initialState } = useModel('@@initialState');//初始化Schemaconst [myschema, setValue] = useState<Schema>({ type: 'page', title: '', body: '' });//读取本地json文件到mySchema变量useEffect(() => {//https://zhuanlan.zhihu.com/p/454841748//设置isMounted是为了解决react内存泄露警告问题let isMounted = true;(async () => {const schema = await getLocalSchema(schemaFile, initialState?.ver);if (isMounted) setValue(schema);})();return () => {isMounted = false;};}, [schemaFile]);const theme = 'cxd';const locale = 'zh-CN';// 请勿使用 React.StrictMode,目前还不支持return (<div><ToastComponent theme={theme} key="toast" position={'top-right'} locale={locale} /><AlertComponent theme={theme} key="alert" locale={locale} />{renderAmis(myschema,//这个就是json文件内容 {//第二个参数可以为空白},    {// 下面三个接口必须实现fetcher: ({url, // 接口地址method, // 请求方法 get、post、put、deletedata, // 请求数据responseType,config, // 其他配置headers, // 请求头}: any) => {// eslint-disable-next-line no-param-reassignconfig = config || {};config.withCredentials = true;// eslint-disable-next-line @typescript-eslint/no-unused-expressionsresponseType && (config.responseType = responseType);if (config.cancelExecutor) {request.CancelToken = config.cancelExecutor;//config.cancelToken = new (axios as any).CancelToken(config.cancelExecutor);}config.headers = headers || {};if (method !== 'post' && method !== 'put' && method !== 'patch') {if (data) {config.params = data;}return amisRequest(url, method, { ...data, ...config }); // (axios as any)[method](url, config);} else if (data && data instanceof FormData) {config.headers = config.headers || {};config.headers['Content-Type'] = 'multipart/form-data';} else if (data &&typeof data !== 'string' &&!(data instanceof Blob) &&!(data instanceof ArrayBuffer)) {// eslint-disable-next-line no-param-reassigndata = JSON.stringify(data);config.headers = config.headers || {};config.headers['Content-Type'] = 'application/json';}//return (axios as any)[method](url, data, config);return amisRequest(url, method, { ...data, ...config });},isCancel: (value: any) => remoteRequest.isCancel(value), //isCancel(value),copy: (content) => {copy(content);toast.success('内容已复制到粘贴板');},theme,},)}</div>);
};
export default AmisComponent;

修改路由

有了组件,我们可以添加页面了:src/pages里面添加amispage,并完成代码如下:

import AmisComponent from '@/components/Amis';
export default AmisComponent;

这个页面承载了我们的设计思想,就是根据不同的path加载不同的json文件,那么这个页面就会频繁的被调用,它在路由里面是需要设置参数的,这个参数就是我们的json文件名。在config/config.ts的路由数组里面添加如下代码

{path: '/amispage/:f',name: 'amispage',icon: 'smile',component: './amispage',
},

我们在前面从服务器加载菜单的时候,为了实现菜单的图标,有做过一个组件LocalMenu实现对菜单数据的遍历。我们可以改造一下,对菜单数据里面的每个菜单项增加一个指向amispage的前缀,这样运营人员设计菜单的json文档的时候,就可以在path里面直接书写文件名了。src/components/LocalMenu/index.tsx代码修改如下:

const loopMenuItem = (menus: any[]): MenuDataItem[] =>menus.map(({ icon, routes, filename, path, ...item }) => {let newPath = path;if (path) {newPath = `/amispage/${path}`;}return {...item,path: newPath,icon: icon && IconMap[icon as string],children: routes && loopMenuItem(routes),};});

这样,我们的运营人员就可以自由的设计如下的菜单json,其中每个path都对应一个在public/json文件夹里面的json文件,比如path:index对应public/json/index.json,菜单json示例如下:

[{"path": "/","name": "表的crud","locale":false,"icon": "smile",  "routes": [      {"path": "index","name": "crud基本操作"        },{"path": "crudNew","name": "crud新增操作"},{"path": "crudDelete","name": "crud删除操作"}      ]},{"path": "echart","locale":false,"icon": "heart","name": "其它示例"}
]

最终界面呈现如下:

Amis 组件再分析

消息提醒

这是两个UI组件,用来对用户操作进行消息提示的。
ToastComponent组件是广播式的,是一种非阻塞式的提醒,会自动关闭。可以设置展示位置和持续时间。提示显示位置,可用’top-right’、‘top-center’、‘top-left’、‘bottom-center’、‘bottom-left’、‘bottom-right’、‘center’。持续时间默认5秒可以通过timeout修改。
AlertComponent 用来做文字特殊提示,分为四类:提示类、成功类、警告类和危险类。

renderAmis

组件的核心是renderAmis,最基础的用法如下:

renderAmis(schema, {data: {username: 'amis'}});

其中schema就是我们设计的json文件,data是用来数据给渲染器内部使用的,大多数时候是用不上的。这个看起来是非常简单的。但是如果我们的组件需要访问Api,那我们还需要实现几个重要的接口

fetcher

fetcher是接口请求器,实现该函数才可以实现 ajax 发送,函数签名如下:

(config: {url; // 接口地址method; // 请求方法 get、post、put、deletedata; // 请求数据responseType;config; // 其他配置headers; // 请求头
}) => Promise<fetcherResult>;

amis默认用axios作为ajax库来实现api的请求,我们项目的主框架式ant design它采用umi的request。我们已经对他做了二次封装了,为了统一使用Ajax库,在amis中我们也应该用自己封装好了的库。
为了专门给amis的api请求使用,我们再已经封装好的src/utils/request.ts里面做一些改进,代码如下

/** amis的schema文件专用request*/
export async function amisRequest(url: string, method?: string, options?: { [key: string]: any }) {let newMethod = '';if (!method) newMethod = 'GET';else newMethod = method.toUpperCase();switch (newMethod) {case 'GET':return remoteRequest.get(url, options);case 'POST':return remoteRequest.post(url, options);case 'DELETE':return remoteRequest.delete(url, options);case 'PUT':return remoteRequest.put(url, options);}

这样在renderAmis中就可以直接使用

 fetcher: ({url, // 接口地址method, // 请求方法 get、post、put、deletedata, // 请求数据responseType,config, // 其他配置headers, // 请求头}: any) => {// eslint-disable-next-line no-param-reassignconfig = config || {};config.withCredentials = true;// eslint-disable-next-line @typescript-eslint/no-unused-expressionsresponseType && (config.responseType = responseType);if (config.cancelExecutor) {//这个必须传递request.CancelToken = config.cancelExecutor;             }config.headers = headers || {};if (method !== 'post' && method !== 'put' && method !== 'patch') {if (data) {config.params = data;}return amisRequest(url, method, { ...data, ...config }); } else if (data && data instanceof FormData) {config.headers = config.headers || {};config.headers['Content-Type'] = 'multipart/form-data';} else if (data &&typeof data !== 'string' &&!(data instanceof Blob) &&!(data instanceof ArrayBuffer)) {// eslint-disable-next-line no-param-reassigndata = JSON.stringify(data);config.headers = config.headers || {};config.headers['Content-Type'] = 'application/json';}            return amisRequest(url, method, { ...data, ...config });},

踩坑记:axios改用umi的request之后,相同的mock给出的响应,用axios没问题,用request之后后显示Response is empty错误,跟踪数据后发现axios的响应会比umi多一个data,把mock的返回数据加上一层data包裹,就好了。修改后代码如下:

const resData: AmisData.ResponseData = {status: 0,msg: '新增成功',data: null,};res.send({data:resData});

使用fontawesome字体

ant design pro是摈弃font字体图标的,取而代之的是svg。这是一种好的做法,可以更加自由并且系统体积也更小了。但是amis采用font-awesome字体图标,这就有点问题,我们必须兼顾。

字体图标问题

最开始我们使用amis的时候,引入的样式表是amis精简后的css,这个文件比较小不到1MB,但是遇到一些带图标的按钮有可能显示不出来,比如以下的schema,我们再使用crud组件的时候希望在工具栏上面显示一个刷新按钮,代码如下:

"headerToolbar": [  "reload"
]

这个时候我们会发现,刷新按钮的图标显示不出来。

进入浏览器调试可以跟踪到,这个是一个使用fontawesom字体的图标。amis内部是使用fontawesome字体的,如果我们把amis全部css和字体库都导入到我们系统里面,就可以使用全部的样式库了。这样这个刷新按钮的图标也就可以显示出来了。
代码如下:

import 'amis/sdk/iconfont.css';
import 'amis/lib/themes/cxd.css';
import 'amis/lib/helper.css';
import 'amis/sdk/cxd.css';

但是这样做我们需要多导入伊特sdk/cxd.css 文件,这个文件接近3MB。并且fontawesome在其他React组件中并不能共享。为此,全局导入fontawesome要划算许多,整个awesome才1.25MB,如果全局导入我们可以在更多地方用到。

导入font-awesome字体图标

1、下载并解压到src/font=awesome
2、在global.tsx里面执行导入

import "./font-awesome/less/font-awesome.less";

问题解决

一、Ant Design Pro 与 Amis 结合相关推荐

  1. ant Design Pro 登录状态管理

    未登录自动跳转到登录页面,登录成功不跳转回登录页面的实现代码调用流程. ant Design Pro 是一个企业中后台管理框架,开始做他,第一个肯定是要做登录,下面来看一下它是怎么登录的. 先看路由配 ...

  2. Ant Design Pro 网络请求流程

    微信小程序开发交流qq群   173683895    承接微信小程序开发.扫码加微信. 在 Ant Design Pro 中,一个完整的前端 UI 交互到服务端处理流程是这样的: UI 组件交互操作 ...

  3. 【Ant Design Pro 一】 环境搭建,创建一个demo

    技术交流qq群   173683895 搭建 Ant Design Pro 的前期准备:你的本地环境需要安装 cnpm.node. 注:代码块中的 $  代表: $后面是在命令行输入的命令,举例 $ ...

  4. ant design pro (八)构建和发布

    一.概述 原文地址:https://pro.ant.design/docs/deploy-cn 二.详细 2.1.构建 当项目开发完毕,只需要运行一行命令就可以打包你的应用: npm run buil ...

  5. ant design pro(一)安装、目录结构、项目加载启动【原始、以及idea开发】

    一.概述 1.1.脚手架概念 编程领域中的"脚手架(Scaffolding)"指的是能够快速搭建项目"骨架"的一类工具.例如大多数的React项目都有src,p ...

  6. Ant Design Pro开发后台管理系统(新增页面)

    通过实际demo演示一个管理后台的开发过程 知识点: 1.新增router,新增models 新增菜单配置 1.如上图所示,打开/src/common/menu.js可以看到菜单列表 其中 menuD ...

  7. ant design pro 页面加载原理及过程,@connect 装饰器

    一.概述 以列表页中的标准列表为主 Ant Design Pro 默认通过只需浏览器单方面就可处理的 HashHistory 来完成路由.如果要切换为 BrowserHistory,那在 src/in ...

  8. ant design pro 加载慢_ant design pro项目打包后页面加载缓慢

    用ant design pro框架做的项目,打包之后每一个页面加载的速度都特别慢,在.webpackrc里面配置了ignoreMomentLocale: true, disableDynamicImp ...

  9. Ant Design Pro+Electron+electron-builder实现React应用脱离浏览器,桌面安装运行

    学习参考: electron-api-demos 我以下的方式,其实并把Ant Design Pro文件打包到生成的安装包里,所以,安装包=Ant Design Pro+Electron环境 ant- ...

最新文章

  1. YOLOvi(i=1,2,3,4)系列
  2. 基于HttpClient的HttpUtils(后台访问URL)
  3. 2017.4.7 e.toString() 与 e.getMessage()的区别
  4. 二分+01分数规划+最大化平均值 Dropping tests POJ - 2976
  5. u盘在磁盘管理可以显示 但是电脑中找不到_U盘无法识别怎么办?试试这种方法,没准还有救!...
  6. 【转】QTableView 小结
  7. 【图像压缩】基于matlab GUI FFT图像压缩【含Matlab源码 843期】
  8. 手撕包菜BT搜索引擎带爬虫自动抓取安装
  9. MySQL数据库知识点大全
  10. linux里php如何执行文件,linux如何执行文件
  11. 自制药枕:边做梦边养生
  12. 干货 | 4步带你完成私有云盘搭建
  13. Proxmox(PVE) Web 控制面板添加登录二步验证(TFA)
  14. jQuery动画slideUp()不正常位移原因
  15. ipad触摸测试软件,平板电脑屏幕灵敏度测试:iPad系列最出色
  16. excel怎么拆分表格
  17. 视频教程-Python疫情监控完整项目实战-Python
  18. 计算机操作系统 - 概述
  19. 设计模式 -- 状态模式
  20. 如何画一个精致的思维导图

热门文章

  1. [补档]从OI学麻将
  2. 爬虫Xpath语法详解
  3. android 飞入购物车效果,大佬留步,小程序中如何实现购物车商品曲线飞入效果(类似饿了么)...
  4. ios内购php验证码,PHP (Laravel) 实现 iOS 内购服务端验证
  5. ANSYS中BEAM188单元的使用
  6. Gamma校正原理及实现(一)
  7. 智能合约安全漏洞之区块链的时间戳依赖漏洞与解决方法
  8. 在瀑布下用火焰烤饼:三步法助你快速定位网站性能问题(超详细)
  9. JPEG文件数据结构以及将位图保存为JPG的代码
  10. 18.03.06 vijos1006 晴天小猪历险记之Hill