阿里粑粑开源的管理框架Ant Design Pro使用记录之列表的灵活使用。
在table的行render时常用的操作
1、时间格式化,指定时区
{
title: '更新时间',
dataIndex: 'updateTime',
sorter: true,
render: val => <span>{moment(val).utc().format('YYYY-MM-DD HH:mm:ss')}</span>,
},
2、列表上显示图片
{
title: '肺功能报告',
dataIndex: 'url',
render: text => <img alt="商品图片" style={{ width: 100, height: 50 }} src={text} />,
},
这个图片不能在当前页放大,或者直接在浏览器访问。
如果需要点击后在当前页面放大,这样的效果
可以这么干,先撸一个imageView组件放在src/pages/Common下
import React, { PureComponent } from 'react';
import { Spin, Modal } from 'antd';
const defaultStyle = {
borderRadius: 5,
borderWidth: 1,
borderStyle: 'solid',
borderColor: '#d9d9d9',
width: 70,
height: 70,
padding: 5,
cursor: 'pointer',
display: 'inline-flex',
justifyContent: 'center',
alignItems: 'center',
};
class ImageView extends PureComponent {
state = {
loading: true,
visible: false,
imgWidth: 0,
imgHeight: 0,
};
componentDidMount() {
this.init();
}
componentWillReceiveProps(nextProps) {
const { url } = this.props;
if (url !== nextProps.url) {
this.init();
}
}
init = () => {
const { url } = this.props;
if (url) {
this.setState({ loading: true });
const image = new Image();
image.src = url;
if (image.complete) {
const size = this.getImageSize(image.width, image.height);
this.setState({ loading: false, imgWidth: size.width, imgHeight: size.height });
} else {
image.onload = () => {
const size = this.getImageSize(image.width, image.height);
this.setState({ loading: false, imgWidth: size.width, imgHeight: size.height });
};
}
}
};
getImageSize = (originImgWidth, originImgHeight) => {
const { width, height } = this.props;
const divWidth = (width || defaultStyle.width) - 2 * defaultStyle.padding;
const divHeight = (height || defaultStyle.height) - 2 * defaultStyle.padding;
const ratio = this.getRatio(originImgWidth, originImgHeight);
const imgWidth = originImgWidth > originImgHeight ? divWidth : divWidth * ratio;
const imgHeight = originImgHeight > originImgWidth ? divHeight : divHeight * ratio;
return { width: imgWidth, height: imgHeight };
};
getRatio = (width, height) => (width < height ? width / height : height / width);
render() {
const { url, style } = this.props;
const { loading, visible, imgWidth, imgHeight } = this.state;
return (
<span>
{url ? (
<span style={{ ...defaultStyle, ...style }}>
{loading ? (
<Spin />
) : (
<img
style={{ width: imgWidth, height: imgHeight }}
alt="点击预览图片"
src={url}
onClick={() => {
this.setState({ visible: true });
}}
/>
)}
<Modal
style={{ top: 20 }}
visible={visible}
footer={null}
onCancel={() => {
this.setState({ visible: false });
}}
>
<img alt="" style={{ width: '100%' }} src={url} />
</Modal>
</span>
) : (
'无'
)}
</span>
);
}
}
export default ImageView;
然后table上这么用
{
title: '图片',
dataIndex: 'itemImage',
key: 'itemImage',
render: (text, record) => <ImageView alt={record.title} url={text} />,
},
3、长文本使用省略号
{
title: '接收详情',
dataIndex: 'receiveDesc',
render: value => (
<Ellipsis length={5} tooltip>
{value}
</Ellipsis>
),
},
用到的Ellipsis是官网在components提供的通用组件。
4、可折叠table
handleTableExpand = record => {
const toTags = items => items.map((value, index) => <Tag key={index}>{value}</Tag>);
return (
<div>
<DescriptionList size="small" col={1}>
<Description term="资源IDS">{toTags(getResources(record.resourceIds))}</Description>
</DescriptionList>
<DescriptionList style={{ marginTop: 15 }} size="small">
<Description term="短信验证码长度">{record.smsCodeLength}</Description>
<Description term="短信验证码有效期">
{record.smsCodeLength ? `${record.smsCodeExpire}分钟` : ''}
</Description>
<Description term="短信验证码签名">{record.smsCodeSign}</Description>
</DescriptionList>
</div>
);
};
然后在table上指定expandedRowRender={this.handleTableExpand}
即可。
5、table显示总条数
state: {
query: { ...defaultQuery },
list: {
data: [],
pagination: {},
},
exporting: false,
},
effects: {
*fetch({ payload }, { call, put, select }) {
yield put({ type: 'query', payload });
const query = yield select(state => state.smsLog.query);
const { data } = yield call(page, query);
yield put({
type: 'list',
payload: {
data: data.content,
pagination: {
current: data.number + 1,
pageSize: data.size,
total: Number(data.totalElements),
},
},
});
},
}
渲染时指定pagination的包括了total字段,所以就会显示出总条数。
<PageTable
columns={this.columns}
loading={loading}
dataSource={list.data}
pagination={list.pagination}
onChange={this.handleTableChange}
/>
list就是从接口获取的数据,
6、表头支持筛选
{
title: '人员状态',
dataIndex: 'jobStatus',
render:(text)=>{
const filters = options.jobStatus.filter(s => s.value === text);
return filters.length ? filters[0].text : '';
},
filters: options.jobStatus,
}
页面加载时加载个数据字典的值
componentDidMount() {
this.props.dispatch({type: 'rwUserOption/init'}).then(() => {
this.props.dispatch({type: 'rwUserList/reload'});
});
}
请求字典数据:
import dict from '../../services/api-dict'
const optionKeys = [
"RWGH-USER-XIAOWEI",
"RWGH-USER-STATUS",
'RWGH-TRAIN-ATTENDANCE'
];
export default {
namespace: 'rwUserOption',
state: {
sex:[
{text: '男', value: '0'},
{text: '女', value: '1'}
],
//小微
xiaowei: [],
//工作状态
jobStatus: [],
attendances: []
},
effects: {
* init({payload}, {put, call}) {
const [xiaowei, jobStatus, attendances] = yield call(dict.many, optionKeys);
const mapper = (items) => {
return items.map(item => {
const {dictValueName, dictValueCode} = item;
return {text: dictValueName, value: dictValueCode};
})
};
yield put({type: 'changeXiaoWei', payload: mapper(xiaowei)});
yield put({type: 'changeJobStatus', payload: mapper(jobStatus)});
yield put({type: 'changeAttendances', payload: mapper(attendances)});
}
},
reducers: {
changeXiaoWei(state, {payload: xiaowei}) {
return {...state, xiaowei}
},
changeJobStatus(state, {payload: jobStatus}) {
return {...state, jobStatus}
},
changeAttendances(state, {payload: attendances}){
return {...state, attendances}
}
}
}
dict.many是这样定义的:
import req from './api-base';
const dict = {
one: (indexCode, cache = true) => {
return new Promise(resolve => {
const dict = sessionStorage.getItem(indexCode);
if (cache && dict) {
resolve(dict);
} else {
req.get("/questionnaire/admin/sys/dict/values", {indexCode}).then(data => {
sessionStorage.setItem(dict, data.data);
resolve(data.data)
})
}
});
},
many: (indexCodes, cache = true) => {
return Promise.all(indexCodes.map(code => dict.one(code, cache)));
}
}
export default dict;
其中人员状态的数据结构如下:
{
"code": 0,
"message": "操作成功",
"data": [
{
"dictValueId": 162,
"dictIndexId": 148,
"dictValueCode": "0",
"dictValueName": "在职",
"deleteFlag": "0",
"dictValueSort": 0
},
{
"dictValueId": 163,
"dictIndexId": 148,
"dictValueCode": "1",
"dictValueName": "产假",
"deleteFlag": "0",
"dictValueSort": 1
},
{
"dictValueId": 164,
"dictIndexId": 148,
"dictValueCode": "2",
"dictValueName": "离职",
"deleteFlag": "0",
"dictValueSort": 2
},
{
"dictValueId": 165,
"dictIndexId": 148,
"dictValueCode": "3",
"dictValueName": "退休",
"deleteFlag": "0",
"dictValueSort": 3
},
{
"dictValueId": 167,
"dictIndexId": 148,
"dictValueCode": "4",
"dictValueName": "转岗",
"deleteFlag": "0",
"dictValueSort": 4
},
{
"dictValueId": 168,
"dictIndexId": 148,
"dictValueCode": "5",
"dictValueName": "病假",
"deleteFlag": "0",
"dictValueSort": 5
}
]
}
这个筛选是多选的,选中后点击确定会触发查询,参数会xiaoweis=XSJDXW%2CXHYXW像这样拼接到url里。
7、结果转enum显示文本,如订单状态等。
const sendResultDom = {
success: <Badge status="success" text="成功" />,
error: <Badge status="error" text="失败" />,
};
{
title: '发送结果',
dataIndex: 'sendResult',
render: test => (test === '0' ? sendResultDom.success : sendResultDom.error),
},
这种适合于这个对应值比较稳定变动较小的情况,如果是经常修改或者适配新增,又不改代码的话,就需要以数据字典的形式提供给页面字典数据,然后用组件去适配显示内容。
比如数据字典的sysDict/value/code/list?indexCode=sms_receive_result数据结构如下:
{
"code": 0,
"message": "操作成功",
"data": [
{
"id": "88685502718279680",
"isDelete": false,
"createTime": 1553756821000,
"updateTime": 1553756821000,
"parentId": "88685399781670912",
"name": "成功",
"code": "0",
"sort": 0,
"type": 1,
"remark": ""
},
{
"id": "88685542446727168",
"isDelete": false,
"createTime": 1553756830000,
"updateTime": 1553756830000,
"parentId": "88685399781670912",
"name": "失败",
"code": "1",
"sort": 1,
"type": 1,
"remark": ""
},
{
"id": "88685595827634176",
"isDelete": false,
"createTime": 1553756843000,
"updateTime": 1553756843000,
"parentId": "88685399781670912",
"name": "等待",
"code": "-1",
"sort": 2,
"type": 1,
"remark": ""
}
]
}
然后写一个DictValue的组件:
/* eslint-disable */
import React, { PureComponent } from 'react';
import { Spin, Select } from 'antd';
import _ from 'lodash';
import memoizeOne from 'memoize-one';
import { valueListByIndexCode } from '@/services/dict';
const dictCache = {};
const filterValue = (dicts, value) => _.find(dicts, d => d.code === value);
const memoizeGetValue = memoizeOne(filterValue);
async function getIndexValues(index) {
const values = dictCache[index];
if (values) return values;
const { data } = await valueListByIndexCode({ indexCode: index });
const newValues = data.map(d => ({ name: d.name, code: d.code, remark: d.remark }));
dictCache[index] = newValues;
return newValues;
}
export async function getValue(index, value) {
const indexValues = await getIndexValues(index);
return memoizeGetValue(indexValues, value);
}
async function getValueName(index, value) {
const indexValue = await getValue(index, value);
return indexValue ? indexValue.name : value;
}
export class DictValue extends PureComponent {
state = {
loading: false,
text: '',
};
componentDidMount() {
const { index, value } = this.props;
this.loadDate(index, value);
}
componentWillReceiveProps(nextProps) {
const { index, value } = nextProps;
const { props } = this;
if (props.index !== index || props.value !== value) {
this.loadDate(index, value);
}
}
loadDate = (index, value) => {
if (!value) {
this.setState({ text: '', loading: false });
return;
}
this.setState({ loading: true });
getValueName(index, value).then(text => {
this.setState({ text, loading: false });
});
};
render() {
const { loading, text } = this.state;
return <span>{loading ? <Spin size="small" /> : `${text}`}</span>;
}
}
const { Option } = Select;
export class DictSelect extends PureComponent {
state = {
data: [],
};
componentDidMount() {
const { index } = this.props;
getIndexValues(index).then(data => {
this.setState({ data });
});
}
render() {
const { props } = this;
const { data } = this.state;
return (
<Select placeholder="请选择" {...props}>
{data.map(item => (
<Option key={item.code} value={item.code}>
{item.name}
</Option>
))}
</Select>
);
}
}
其中valueListByIndexCode
就是获取到上面结构数据的api,不用特殊说明了。
这个类包含了两个方法,一个是DictValue,用来从key获取value,一个是DictSelect提供一下该字典值得下拉选项。
DictValue的用法:
{
title: '接收结果',
dataIndex: 'receiveResult',
render: value => <DictValue index="sms_receive_result" value={value} />,
},
DictSelect的用法:
<FormItem label="接收结果">
<DictSelect
allowClear
index="sms_receive_result"
value={query.receiveResult}
onChange={receiveResult => this.handleSearch({ receiveResult })}
/>
</FormItem>
8、等待。。。