1 什么是 Ant Design?
Ant Design 是阿里蚂蚁金服团队基于 React 开发的 ui 组件,主要用于后台系统的使用。
官网:https://ant.design/index-cn
设计语言:
随着商业化的趋势,越来越多的企业级产品对更好的用户体验有了进一步的要求。 带着这样的一个终极目标,我们(蚂蚁金服体验技术部)经过大量的项目实践和总结,逐步打磨出一个服务于企业级产品的设计体系 Ant Design。基于『确定』和 『自然』的设计价值观,通过模块化的解决方案,降低冗余的生产成本,让设计者专注于更好的用户体验。
特性:
- 提炼自企业级中后台产品的交互语言和视觉风格。
- 开箱即用的高质量 React 组件。
- 使用 TypeScript 构建,提供完整的类型定义文件。
- 全链路开发和设计工具体系。
2 开始使用
2.1 引入 Ant Design
Ant Design 是一个服务于企业级产品的设计体系,组件库是它的 React 实现,antd 被发布为一个 npm 包方便开发者安装并使用。
在 umi 中,你可以通过在插件集 umi-plugin-react
中配置 antd 打开 antd 插件,antd 插件会帮你引入 antd 并实现按需编译。
在 config.js
文件中进行配置:
export default {
plugins: [
['umi-plugin-react', {
dva: true, // 开启dva功能
antd: true // 开启Ant Design功能
}]
]
};
2.2 小试牛刀
接下来,我们开始使用 antd 的组件,以 tabs 组件为例,地址:https://ant.design/components/tabs-cn/
效果:
看下官方给出的使用示例:
import { Tabs } from 'antd';
const { TabPane } = Tabs;
function callback(key) {
console.log(key);
}
ReactDOM.render(
<Tabs defaultActiveKey="1" onChange={callback}>
<TabPane tab="Tab 1" key="1">
Content of Tab Pane 1
</TabPane>
<TabPane tab="Tab 2" key="2">
Content of Tab Pane 2
</TabPane>
<TabPane tab="Tab 3" key="3">
Content of Tab Pane 3
</TabPane>
</Tabs>,
mountNode,
);
下面我们参考官方给出的示例,进行使用:
创建 MyTabs.js
文件:
import React from 'react';
import { Tabs } from 'antd'; // 第一步:导入需要使用的组建
const { TabPane } = Tabs;
function callback(key) {
console.log(key);
}
class MyTabs extends React.Component {
render() {
return (
<Tabs defaultActiveKey="1" onChange={callback}>
<TabPane tab="Tab 1" key="1">
Content of Tab Pane 1
</TabPane>
<TabPane tab="Tab 2" key="2">
Content of Tab Pane 2
</TabPane>
<TabPane tab="Tab 3" key="3">
Content of Tab Pane 3
</TabPane>
</Tabs>
);
}
}
export default MyTabs;
效果:
到此,我们已经掌握了 antd 组件的基本使用。
2.3 布局
antd 布局:https://ant.design/components/layout-cn/ 在后台系统页面布局中,往往是经典的三部分布局,像这样:
下面,我们通过 antd 组件来完成这个布局。
2.3.1 组件概述
-
Layout
: 布局容器,其下可嵌套Header
Sider
Content
Footer
或Layout
本身,可以放在任何父容器中。 -
Header
: 顶部布局,自带默认样式,其下可嵌套任何元素,只能放在Layout
中。 -
Sider
: 侧边栏,自带默认样式及基本功能,其下可嵌套任何元素,只能放在Layout
中。 -
Content
: 内容部分,自带默认样式,其下可嵌套任何元素,只能放在Layout
中。 -
Footer
: 底部布局,自带默认样式,其下可嵌套任何元素,只能放在Layout
中。
2.3.2 搭建整体框架
该
index.js
是整个项目的全局布局文件。
在 src
目录下创建 layouts
目录,并且在 layouts
目录下创建 index.js
文件,写入内容:
import React from 'react'
import { Layout } from 'antd';
const { Header, Footer, Sider, Content } = Layout;
class BasicLayout extends React.Component{
render(){
return (
<Layout>
<Sider>Sider</Sider>
<Layout>
<Header>Header</Header>
<Content>Content</Content>
<Footer>Footer</Footer>
</Layout>
</Layout>
);
}
}
export default BasicLayout;
需要特别说明的是,在umi中约定的目录结构中,
layouts/index.js
文件将被作为全局的布局文件。
接下来,配置路由: (非必须)
config.js
文件:
export default {
plugins: [
['umi-plugin-react', {
dva: true, // 开启dva功能
antd: true // 开启Ant Design功能
}]
],
routes: [{
path: '/',
component: '../layouts' //配置布局路由
}]
};
进行页面访问:
可以看到,布局已经生成,只是样式优点丑。
2.3.3 子页面使用布局
前面所定义的布局是全局布局,那么,在子页面中如何使用这个全局布局呢?
首先,需要在布局文件中,将 Content
内容替换成 {this.props.children}
,意思是引入传递的内容。
接下来配置路由(注意,在布局路由下面进行配置):
说明: 下面的路由配置,是表明你需要通过手动配置的方式上进行访问页面,而不采用
umi
默认的路由方式。下面作为了解,以后学习:
export default { plugins: [ ['umi-plugin-react', { dva: true, // 开启dva功能 antd: true // 开启Ant Design功能 }] ], routes: [{ path: '/', component: '../layouts', // 配置布局路由 routes: [ // 在这里进行配置子页面 { path: '/myTabs', component: './myTabs' } ] }] };
进行访问测试:
可以看到,在 MyTabs 组件中已经应用了全局的布局。其他子页面也就同理了。
2.3.4 美化页面
接下来,对页面做一些美化的工作:
import React from 'react'
import { Layout } from 'antd';
const { Header, Footer, Sider, Content } = Layout;
class BasicLayout extends React.Component{
render(){
return (
<Layout>
<Sider width={256} style={{ minHeight: '100vh', color: 'white' }}>
Sider
</Sider>
<Layout>
<Header style={{ background: '#fff', textAlign: 'center', padding: 0 }}>Header</Header>
<Content style={{ margin: '24px 16px 0' }}>
<div style={{ padding: 24, background: '#fff', minHeight: 360 }}>
{
this.props.children
}
</div>
</Content>
<Footer style={{ textAlign: 'center' }}>后台系统 ©2018 Created by XXX</Footer>
</Layout>
</Layout>
);
}
}
export default BasicLayout;
效果:
2.3.5 引入导航条
使用Menu组件作为导航条:https://ant.design/components/menu-cn/
import React from 'react'
import { Layout, Menu, Icon } from 'antd';
const { Header, Footer, Sider, Content } = Layout;
const SubMenu = Menu.SubMenu;
class BasicLayout extends React.Component{
constructor(props){
super(props);
this.state = {
collapsed: false,
}
}
render(){
return (
<Layout>
<Sider width={256} style={{minHeight: '100vh', color: 'white'}}>
<div style={{ height: '32px', background: 'rgba(255,255,255,.2)', margin: '16px'}}/>
<Menu
defaultSelectedKeys={['2']}
defaultOpenKeys={['sub1']}
mode="inline"
theme="dark"
inlineCollapsed={this.state.collapsed}
>
<Menu.Item key="1">
<Icon type="pie‐chart"/>
<span>Option 1</span>
</Menu.Item>
<Menu.Item key="2">
<Icon type="desktop"/>
<span>Option 2</span>
</Menu.Item>
<Menu.Item key="3">
<Icon type="inbox"/>
<span>Option 3</span>
</Menu.Item>
<SubMenu key="sub1" title={<span><Icon type="mail"/><span>Navigation One</span></span>}>
<Menu.Item key="5">Option 5</Menu.Item>
<Menu.Item key="6">Option 6</Menu.Item>
<Menu.Item key="7">Option 7</Menu.Item>
<Menu.Item key="8">Option 8</Menu.Item>
</SubMenu>
<SubMenu key="sub2" title={<span><Icon type="appstore"/><span>Navigation Two</span></span>}>
<Menu.Item key="9">Option 9</Menu.Item>
<Menu.Item key="10">Option 10</Menu.Item>
<SubMenu key="sub3" title="Submenu">
<Menu.Item key="11">Option 11</Menu.Item>
<Menu.Item key="12">Option 12</Menu.Item>
</SubMenu>
</SubMenu>
</Menu>
</Sider>
<Layout>
<Header style={{background: '#fff', textAlign: 'center', padding: 0}}>Header</Header>
<Content style={{margin: '24px 16px 0'}}>
<div style={{padding: 24, background: '#fff', minHeight: 360}}>
{
this.props.children
}
</div>
</Content>
<Footer style={{textAlign: 'center'}}>后台系统 ©2018 Created by XXX</Footer>
</Layout>
</Layout>
);
}
}
export default BasicLayout;
效果:
2.3.6 为导航添加链接
下面,我们对左侧的导航条添加链接,点击相应的链接在右侧进行相应页面的显示。
在 src
目录下创建 user
目录,并且在 user
目录下创建 UserAdd.js
和 UserList.js
文件,用于模拟实现新增用户和查询用户列表功能。
-
先删除页面布局,只保留一个菜单
-
创建新增用户和查询用户列表页面:
UserAdd.js
:import React from 'react' class UserAdd extends React.Component{ render(){ return ( <div>新增用户</div> ); } } export default UserAdd;
UserList.js
:import React from 'react' class UserList extends React.Component{ render(){ return ( <div>用户列表</div> ); } } export default UserList;
-
为菜单添加链接:
方式一:使用<a>
的href
方式二:使用
umi
的link
,先引入 link
注意: 这里使用了umi的link标签,目的是出现记录点击的菜单。
2.4 表格
2.4.1 基本用法
参考官方文档进行实现:https://ant.design/components/table-cn/
改造 UserList.js
页面:
import React from 'react'
import {Table, Divider, Tag, Pagination } from 'antd';
const { Column } = Table;
const data = [{
key: '1',
name: '张三',
age: 32,
address: '上海市',
tags: ['程序员', '帅气'],
}, {
key: '2',
name: '李四', age: 42,
address: '北京市', tags: ['屌丝'],
}, {
key: '3',
name: '王五',
age: 32,
address: '杭州市',
tags: ['高富帅', '富二代'],
}];
class UserList extends React.Component {
render() {
return (
<div>
<Table dataSource={data} pagination={{position:"bottom",total:500,pageSize:10, defaultCurrent:3}}>
<Column title="姓名" dataIndex="name" key="name"/>
<Column title="年龄" dataIndex="age" key="age"/>
<Column title="地址" dataIndex="address" key="address"/>
<Column title="标签" dataIndex="tags" key="tags"
render={tags => (
<span>
{tags.map(tag => <Tag color="blue" key={tag}>{tag}</Tag>)}
</span>
)}
/>
<Column title="操作"
key="action"
render={(text, record) => (
<span>
<a href="javascript:;">编辑</a>
<Divider type="vertical"/>
<a href="javascript:;">删除</a>
</span>
)}
/>
</Table>
</div>
);
}
}
export default UserList;
2.4.2 将数据分离到 model 中
model 的实现:
UserListData.js
import request from "../util/request";
export default {
namespace: 'userList',
state: {
list: []
},
effects: {
*initData(params, sagaEffects) {
const {call, put} = sagaEffects;
const url = "/ds/user/list";
let data = yield call(request, url);
yield put({
type : "queryList",
data : data
});
} },
reducers: {
queryList(state, result) {
let data = [
...result.data];
return { // 更新状态值
list: data
}
}
}
}
修改 UserList.js
中的逻辑:
import React from 'react'
import {Table, Divider, Tag, Pagination } from 'antd';
import { connect } from 'dva';
const { Column } = Table;
const namespace = 'userList';
@connect((state)=>{
return {
data : state[namespace].list
}
}, (dispatch) => {
return {
initData : () => {
dispatch({
type: namespace + "/initData"
});
}
}
})
class UserList extends React.Component {
componentDidMount(){
this.props.initData();
}
render() {
return (
<div>
<Table dataSource={this.props.data} pagination={{position:"bottom",total:500,pageSize:10, defaultCurrent:3}}>
<Column title="姓名" dataIndex="name" key="name"/>
<Column title="年龄" dataIndex="age" key="age"/>
<Column title="地址" dataIndex="address" key="address"/>
<Column title="标签" dataIndex="tags" key="tags"
render={tags => (
<span>
{tags.map(tag => <Tag color="blue" key={tag}>{tag}</Tag>)}
</span>
)}
/>
<Column title="操作"
key="action"
render={(text, record) => (
<span>
<a href="javascript:;">编辑</a>
<Divider type="vertical"/>
<a href="javascript:;">删除</a>
</span>
)}
/>
</Table>
</div>
);
}
}
export default UserList;
mock 数据:MockListData.js
export default {
'get /ds/list': function (req, res) {
res.json({
data: [1, 2, 3, 4],
maxNum: 4
}); },
'get /ds/user/list': function (req, res) {
res.json([{
key: '1',
name: '张三1',
age: 32,
address: '上海市',
tags: ['程序员', '帅气'],
}, {
key: '2',
name: '李四', age: 42,
address: '北京市', tags: ['屌丝'],
}, {
key: '3',
name: '王五',
age: 32,
address: '杭州市',
tags: ['高富帅', '富二代'],
}]);
}
}
测试结果: