Ant Design 常用命令汇总 一、安装 1. 安装脚手架工具 antd-init 是一个用于演示 antd 如何使用的脚手架工具,真实项目建议使用 dva-cli 。
更多功能请参考 脚手架工具 和 开发工具文档 。
除了官方提供的脚手架,您也可以使用社区提供的脚手架和范例:
更多脚手架可以查看 脚手架市场
创建一个项目 使用命令行进行初始化。
1 2 3 4 5 $ mkdir antd-demo && cd antd-demo $ antd-init antd-init 会自动安装 npm 依赖,若有问题则可自行安装。 若安装缓慢报错,可尝试用 cnpm 或别的镜像源自行安装:rm -rf node_modules && cnpm install。
3. 使用组件 脚手架会生成一个 Todo 应用实例(一个很有参考价值的 React 上手示例),先不管它,我们用来测试组件。
直接用下面的代码替换 index.js 的内容,用 React 的方式直接使用 antd 组件。
1 2 3 4 5 6 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 import React from 'react'; import ReactDOM from 'react-dom'; import { LocaleProvider, DatePicker, message } from 'antd'; // 由于 antd 组件的默认文案是英文,所以需要修改为中文 import zhCN from 'antd/lib/locale-provider/zh_CN'; import moment from 'moment'; import 'moment/locale/zh-cn'; moment.locale('zh-cn'); class App extends React.Component { constructor(props) { super(props); this.state = { date: '', }; } handleChange(date) { message.info('您选择的日期是: ' + date.toString()); this.setState({ date }); } render() { return ( <LocaleProvider locale={zhCN}> <div style={{ width: 400, margin: '100px auto' }}> <DatePicker onChange={value => this.handleChange(value)} /> <div style={{ marginTop: 20 }}>当前日期:{this.state.date.toString()}</div> </div> </LocaleProvider> ); } } ReactDOM.render(<App />, document.getElementById('root'));
4. 开发调试 一键启动调试,访问 http://127.0.0.1:8000 查看效果。
5. 构建和部署
入口文件会构建到 dist 目录中,你可以自由部署到不同环境中进行引用。
二、Ant Design Pro 安装 有两种方式进行安装:
1. 直接 clone git 仓库 1 2 $ git clone --depth=1 https://github.com/ant-design/ant-design-pro.git my-project $ cd my-project
2. 使用命令行工具 你可以使用集成化的命令行工具 ant-design-pro-cli 。
1 2 3 $ npm install ant-design-pro-cli -g $ mkdir my-project && cd my-project $ pro new # 安装脚手架
三、目录结构 我们已经为你生成了一个完整的开发框架,提供了涵盖中后台开发的各类功能和坑位,下面是整个项目的目录结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ├── mock # 本地模拟数据 ├── public │ └── favicon.ico # Favicon ├── src │ ├── assets # 本地静态资源 │ ├── common # 应用公用配置,如导航信息 │ ├── components # 业务通用组件 │ ├── e2e # 集成测试用例 │ ├── layouts # 通用布局 │ ├── models # dva model │ ├── routes # 业务页面入口和常用模板 │ ├── services # 后台接口服务 │ ├── utils # 工具库 │ ├── g2.js # 可视化图形配置 │ ├── theme.js # 主题配置 │ ├── index.ejs # HTML 入口模板 │ ├── index.js # 应用入口 │ ├── index.less # 全局样式 │ └── router.js # 路由入口 ├── tests # 测试工具 ├── README.md └── package.json
四、本地开发 安装依赖。
如果网络状况不佳,可以使用 cnpm 进行加速。
启动完成后会自动打开浏览器访问 http://localhost:8000,你看到下面的页面就代表成功了。
1. 创建一个项目 可以是已有项目,或者是使用 dva / create-react(-native)-app 新创建的空项目,你也可以从 官方示例 脚手架里拷贝并修改
参考更多官方示例集 或者你可以利用 React 生态圈中的 各种脚手架
2. 安装 1 $ npm install antd-mobile --save
3. 使用 Web 使用方式 React Native 使用方式 入口页面 (html 或 模板) 相关设置:
引入 FastClick 并且设置 html meta
引入 Promise 的 fallback 支持 (部分安卓手机不支持 Promise)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <!DOCTYPE html> <html> <head> <!-- set `maximum-scale` for some compatibility issues --> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /> <script src="https://as.alipayobjects.com/g/component/fastclick/1.0.6/fastclick.js"></script> <script> if ('addEventListener' in document) { document.addEventListener('DOMContentLoaded', function() { FastClick.attach(document.body); }, false); } if(!window.Promise) { document.writeln('<script src="https://as.alipayobjects.com/g/component/es6-promise/3.2.2/es6-promise.min.js"'+'>'+'<'+'/'+'script>'); } </script> </head> <body></body> </html>
组件使用实例:
1 2 import { Button } from 'antd-mobile'; ReactDOM.render(<Button>Start</Button>, mountNode);
引入样式:
1 import 'antd-mobile/dist/antd-mobile.css'; // or 'antd-mobile/dist/antd-mobile.less'
4. 按需加载 注意:强烈推荐使用。
下面两种方式都可以只加载用到的组件,选择其中一种方式即可。
1 2 3 4 5 6 7 8 9 10 // .babelrc or babel-loader option { "plugins": [ ["import", { libraryName: "antd-mobile", style: "css" }] // `style: true` 会加载 less 文件 ] } 然后只需从 antd-mobile 引入模块即可,无需单独引入样式。 // babel-plugin-import 会帮助你加载 JS 和 CSS import { DatePicker } from 'antd-mobile';
1 2 3 import DatePicker from 'antd-mobile/lib/date-picker'; // 加载 JS import 'antd-mobile/lib/date-picker/style/css'; // 加载 CSS // import 'antd-mobile/lib/date-picker/style'; // 加载 LESS
更多增强 (非必须):
如何自定义主题?见此文档 , 基于 antd-mobile 的自定义 UI 库:web-custom-ui / web-custom-ui-pro
antd 常用组件的问题及实现 在基于 react 的 web 后台开发中,常常会使用到 antd 组件,可以使用现有所提供的,也可以自己再次封装使用。基于平时开发中遇到较多关于 antd 组件问题的情况,记录一下平时常用的代码实现。
1.Radio 单选框
1 2 3 4 5 6 7 8 9 10 11 <FormItem {...formItemLayout} label="是否启用" required> {getFieldDecorator('enabled',{ initialValue: data.enabled, rules: [{ required: true, message: '请选择是否启用' }], })( <RadioGroup> <Radio value={true}>启用</Radio> <Radio value={false}>禁用</Radio> </RadioGroup> )} </FormItem>
2.Select 下拉选择,数据源根据接口获取
1 2 3 4 5 6 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 <FormItem {...formItemLayout} label='xxx'> {getFieldDecorator('feedHabit', { initialValue: (detail && detail.feedHabit) || undefined, rules: [ { required: false, message: 'xxx' } ] })( <Select showSearch allowClear placeholder='xxx' filterOption={(inputValue, option) => option.props.children.indexOf(inputValue) > -1} notFoundContent={ <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} style={{ margin: 8 }} > <Button type='primary' onClick={() => dispatch({ type: `${namespace}/getFeedHabitList` })} >刷新 </Button> </Empty> } > { feedHabitList && feedHabitList.map((item, index) => ( <Select.Option key={item.code} value={item.code}>{item.name}</Select.Option> )) } </Select> )} </FormItem>
3.Switch 开发的切换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <Form.Item {...formItemLayout} label='xxx'> {getFieldDecorator('enabledAutoFeed', { initialValue: (detail && detail.enabledAutoFeed) || undefined, valuePropName: 'checked', // 记得加上这个属性 rules: [ {required: false, message: 'xxx'} ] })( <Switch checkedChildren='是' unCheckedChildren='否' onChange={(val, event) => { event.preventDefault(); event.stopPropagation(); this.setState({ enabledAutoFeed: val }) // 将当前改变的值存至state中,提交时直接用state中存储的值就好了 }} /> )} </Form.Item>
4.Cascader 情况一:只有两个层级数 此情况组件用例是在一个新增/编辑的弹出框中使用,新增时不带数据,可选择;编辑时带出默认数据,也可选择;层级是 2 级。 开发思路:
(1)新增时,不带默认数据出来,但是已经请求到第一层级的数据,点击第一层级中的某一项 item 时,加载请求第二层级的数据给到 item 的 children 中,这样就可以显示第二层级的数据了;
(2)编辑时,带出默认数据,此时 defaultMaterialIds 应该是有两个 id 值的,如:[parentId, sonId],但是在点编辑弹出弹框而还没有点击级联组件时,只显示了第一层级的数据,因为此时页面起始已经有第一层级的数据源,而不知道第二层级的数据源,只单纯的有第二层级项的 id(也就是 sonId),是不知道它的 label(或 name)的,思路是:在点击编辑的时候,拿到它的第一层级 id(也就是 parentId),请求第二层级的数据就好了(其实就是点击编辑时再次调用 loadData 方法~~~)
附上实现逻辑代码:
1.弹框中(editModal.jsx):
render 中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <Form.Item {...formItemLayout} label='物料名称'> {getFieldDecorator(`materialId`, { // defaultMaterialIds 是默认的数据(一个数组形式),如:[parentId, sonId] initialValue: defaultMaterialIds || [] })( <Cascader fieldNames={fieldNames} options={materialLabel} loadData={this.loadData} onChange={this.onChange} placeholder='请选择物料名称' changeOnSelect /> )} </Form.Item>
相应方法:
1 2 3 4 5 6 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 // 级联-选择后的回调 onChange = (value, selectedOptions) => { const { dispatch, namespace } = this.props; const name = []; const id = []; const code = []; // console.log(value, selectedOptions); selectedOptions.map((v) => { name.push(v.name); id.push(v.id); code.push(v.code) }); this.setState({ currentId: value }) }; // 动态加载选项 loadData = (selectedOptions) => { const { dispatch, namespace, materialLabel, form: { setFieldsValue }, } = this.props; const targetOption = selectedOptions[selectedOptions.length - 1]; targetOption.loading = true; // 异步加载数据,targetOption 是选中的那一项数据 dispatch({ type:`${namespace}/getMaterialList`, payload:{ targetOption }, callback:(res)=>{ targetOption.loading = false; } }) }
2.view.jsx 中(editModal 在 view 中被引用):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // 点击编辑时,传第一级id,请求第二级数据显示 loadData = (parentId) => { const { dispatch, pigfarmConfListIndexMod: { materialLabel } }= this.props; // materialLabel为第一层级数据源 const targetOption = materialLabel.find(item => item.id === parentId) // 异步加载数据 dispatch({ type:`${namespace}/getMaterialList`, payload:{ targetOption }, }) } // 点击编辑时调用的方法 showModal = ({data = null, type = 'add' }) => { const { dispatch } = this.props; if(type === 'add') { dispatch({ type: `${namespace}/updateStore`, payload: { showModal: true, detail: data, modalType: type, } }); }else { // 编辑时拿到parentId请求第二层级数据 this.loadData(data.parentId); dispatch({ type: `${namespace}/updateStore`, payload: { showModal: true, detail: data, defaultMaterialIds: [data.parentId, data.materialId], modalType: type, } }); } }
3.mod.js 中:
1 2 3 4 5 6 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 // 获取物料列表---这是第二层级 *getMaterialList({ payload, callback }, { put, call, select }) { // ... // targetOption 是点击的第一层级的项 let targetOption = payload.targetOption; const children = []; // 第一层级数据源materialLabel是一个对象数组,数组中的每一项都是一个对象,带有id,name等属性 const _materialLabel = [...materialLabel] // payload.targetOption.id是点击的第一层级的项,用来获取第二层级的数据源 const result = yield call(urlMaterialList, {id: payload.targetOption.id}); // idx是在第一层级中点击的项的位置 const idx = _materialLabel.findIndex(item=>item.id===targetOption.id) // 深拷贝第一层级中选中的项,后面会加上一个children属性,而children就是第二层级的数据源 // 把这个第二层级数据源 替换掉 第一层级中选中的那个项 // 然后赋值给整个第一层级的数据源,因为第一层级选中的那个项已经加上了children属性,可以显示第二层级的数据了 const materialItem = cloneDeep(targetOption); // ... if(result.data.length > 0) { result.data.map(item => { // 因为第2级就是末级了,所以isLeaf为true let obj = { ...item, isLeaf: true }; children.push(obj); }) // 当前项加上children ,loading 属性 materialItem.children = children; materialItem.loading = false; // 然后替换第一层级中选中的那一项 _materialLabel.splice(idx,1,materialItem) yield put({ type: 'updateStore', payload: { materialList: result.data, materialLabel:_materialLabel } }); callback && callback(); } // ... },
情况二:多个层级(以下例子为 4 级:地址的省-市-区-镇) 此情况是用于获取一个中国地区各省市级地址信息的获取及展示/显示; 在平时开发项目中,一般会封装好一个组件,然后多个地方使用,提高开发效率。
开发思路 :一开始获得第一层级的地址 a,将 a 的 id 传至后端请求 a 的第二层级 b,b 的 id 又请求第三层级 c…以此类推,将请求到的数据 b 每一项加上 isLeaf 属性(末级为 true,非末级为 false),然后将这个新的 b 赋值给 a 的 children,就可以显示第二层级 b 的数据了,依此类推,第三第四级也是…
以下是有关子组件及父组件的逻辑及实现:(主要看注释吧~)
1.子组件中(封装 export 名为 CascaderAddress ): 1 2 3 4 5 6 7 8 9 <Cascader // 在实际编辑器中,应该不能这样注释的,这里是为理解外加的注释 {...options} // 可选项配置,如placeHolder,changeOnSelect等,当然也可直接分开写 value={defaultAddress} // 指定的或默认的数据 options={addressOptions} // 级联地址的数据源 loadData={this.loadAddressData} // 动态加载数据(如点击第一层,加载第二层...) onChange={this.onChangeAddress} // 点击/选择完成后的回调 fieldNames={fieldNames} // 自定义数据源options的label,value,children />
1 2 3 4 5 6 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 // 地址选择 onChangeAddress = (value, selectedOptions) => { const { changeCallback } = this.props; const dataString = []; const ids = []; selectedOptions.map((v) => { dataString.push(v.areaName); ids.push(v.id); }); // 选择完成后的回调,将选择地址的id,name,以及其他信息存起来备用 changeCallback({data: selectedOptions, ids, dataString}); } // 获取地址 loadAddressData = selectedOptions => { const { loadData, addressOptions } = this.props; const targetOption = selectedOptions[selectedOptions.length - 1]; targetOption.loading = true; // 将当前选择项的id请求后端拿到下一层级数据(加上isLeaf属性),赋给当前选择项的children,就可以显示出下一层级的数据了 this.getAddressOptions(targetOption.id, (list) => { targetOption.loading = false; targetOption.children = list; loadData(addressOptions); }) }; // 请求地址数据 getAddressOptions = (parentId, callback) => { const { max } = this.state; const { request, url } = this.props; const params = { 'id': parentId || '0', // '0'表示请求第一层级,看后端需要传什么 } request({ // 拿点击的那一项的id请求下一层级的数据(也就是children) url: `${url}/${params.id}`, method: 'get', data: params, }) .then((res) => { if(res.code === '000000') { let list = [] if(res.data && res.data.length) { // 判断是否为末级,为每一项加上isLeaf属性 list = res.data.map(item => ({ ...item, isLeaf: item.areaLevel > max })); } // 回调显示当前list数据 callback(list); } else { message.error(res.msg); } }) }
2.父组件中引用: (1)view.jsx
1 2 3 4 5 6 7 8 9 10 11 // 注意farmAddress为当前级联控件的唯一标识 <Form.Item {...formItemLayout} label='地址'> {getFieldDecorator('farmAddress', { initialValue: defaultAddressIds || undefined, rules: [ { required: true, message: '请输入地址' }, ], })( <CascaderAddress {...cascaderProps}/> )} </Form.Item>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 // 级联数据值名称配置 const cascaderOptionLabel = { label: 'areaName', // 显示的是label,自定义为areaName value: 'id', // 传值的是value,自定义为id children: 'children' } // 级联地址选择配置项目 const cascaderProps = { request, url: `xxx`, addressOptions, // 数据源 defaultAddress: defaultAddressIds, // 默认数据, fieldNames: cascaderOptionLabel, // 自定义配置项 loadData: (data) => { // 动态加载数据 dispatch({ type: `${namespace}/updateStore`, payload: { addressOptions: [...data] } }) }, // 选择之后的回调,farmAddress为表单控件中存储的数据,可直接/处理之后提交~ changeCallback: ({data, dataString, ids}) => { setFieldsValue({ farmAddress: ids }); // 设置form表单数据 dispatch({ type: `${namespace}/updateStore`, payload: { defaultAddressIds: ids } }); // 存储至mod备用 }, options: { placeholder: '请选择地址', changeOnSelect: true, } } }
(2)mod.js
1 2 3 4 5 6 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 // 获取地址列表 *getAddressOptions({ payload = { id: '' }, callback }, { put, call, select }) { ... // ...假设调用接口请求到数据存至res.data let addressOptions = [] if(res.data && res.data.length) { addressOptions = res.data.map(item => ({ id: item.id, value: item.id, areaName: item.areaName, isLeaf: false })) } yield put({ type: 'updateStore', payload: { addressOptions, } }); }, // 根据ID获取该地址信息包含各级数据 * getAreaInfo({ payload, callback }, { call, put, select }) { ... // ... let addressOptions = []; if(result.data && result.data.length) { addressOptions = result.data[0].list; addressOptions.map( v => { // 省 v.isLeaf = false; if(v.id === result.data[0].selected.id) { v.children = result.data[1].list; v.children.map( v1 => { // 市 v1.isLeaf = false; if(v1.id === result.data[1].selected.id) { v1.children = result.data[2].list; v1.children.map( v2 => { // 区 v2.isLeaf = false; if(v2.id === result.data[2].selected.id) { v2.children = result.data[3].list; } }) } }) } }) } yield put({ type: 'updateStore', payload: { addressOptions, detailAddress: result.data, // 后端返回的默认地址id,组成一个数组展示 defaultAddressIds: [detail.addressProvince, detail.addressCity, detail.addressZone, detail.addressTown] } }); callback && callback(); // ... }, }
其实两个情况很类似的,都是将取到的下一级的数据,每一项都加上 isLeaf 属性,然后将这个新的数据加上 children,loading 属性,替换上一级中被选择的那一项,就可以显示下一级的数据了~~~
5.Table 某些列表,后端不做分页,需要前端分页时,table 的一般配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 <Table bordered // 给整个table加上边框 dataSource={tableList} columns={tableColumns} rowkey={record => record.id} pagination={{ showQuickJumper: true, showSizeChanger: true, showTotal: total => `共${total}条`, pageSizeOptions:['10','20','50','100'], // pageSize: 5, // 固定5条一页,有pageSize就不用pageSizeOptions了 }} />