假設有一個這樣的需求:table表頭排序,用戶可以將關心的列頭排在前面。
我們都知道,使用第三方組件庫時,一般需要設置參數 dataSource(table數據源,是一個數組,指定每一行字段的值) 和 columns(表頭,是一個數組,各個列的屬性),dataSource里面的值會自動賦值到與之字段相同的列上去。
所以dataSource還是dataSource,不用改,按正常邏輯請求接口獲取列表數據即可,接下來要動的就是columns,既然要實現用戶可以自己調整列,那么表頭columns就不能寫死了,而是需要跟后端交互動態存儲和獲取表頭,改變完成后調接口取新的值賦值給columns就可以,具體接口如何調用,參數,返回值什么的跟你的后端商量好就行。
接下來就要封裝一個用於讓用戶設置表頭顯示順序的UI界面以及交互邏輯組件了,畢竟可能會有很多頁面的table需要此功能。
第一種方法:列表上下排序(用戶可以通過點擊相應的移動按鈕來調整順序)
import React, { Component } from 'react';
import { connect } from 'dva';
import { Button, Card, Checkbox, Col, Row } from 'antd';
import styles from './index.less';
import { TableListItem } from '@/utils/TableData';
interface TableFieldsOrderProps {
initialTags: TableListItem<any>[],
renderOnChange: (value: any) => void;
renderIsVisible: (value: any) => void;
}
interface TableFieldsOrderState {
initialTags?: TableListItem<any>[],
currentSelectedTag?: TableListItem<any>,
}
@connect()
class TableFieldsOrder
extends Component<TableFieldsOrderProps> {
state: TableFieldsOrderState = {
initialTags: [],
currentSelectedTag: {},
};
static getDerivedStateFromProps(props: any, state: any) {
const {
initialTags = [],
} = props;
return {
initialTags,
}
}
private onChange = (value: any) => {
const { renderOnChange } = this.props;
renderOnChange(value);
};
private isVisible = (value: any) => {
const { renderIsVisible } = this.props;
renderIsVisible(value);
};
// 設置顯示列順序——記錄當前選中的表頭字段
private handleBlockSelected = (value: any) => {
this.setState({ currentSelectedTag: value })
}
// 設置顯示列順序——字段移到最上
private tableFieldMoveTop = () => {
const { initialTags = [], currentSelectedTag = {} } = this.state;
const currentIndex = initialTags.findIndex(tag => tag.id === currentSelectedTag.id)
initialTags.splice(currentIndex, 1)
initialTags.splice(0, 0, currentSelectedTag)
this.onChange(initialTags)
}
// 設置顯示列順序——字段上移n條
private tableFieldMoveUp = (num: number) => {
const { initialTags = [], currentSelectedTag = {} } = this.state;
const currentIndex = initialTags.findIndex(tag => tag.id === currentSelectedTag.id)
initialTags.splice(currentIndex, 1)
if (currentIndex - num < 0) {
initialTags.splice(0, 0, currentSelectedTag)
} else {
initialTags.splice(currentIndex - num, 0, currentSelectedTag)
}
this.onChange(initialTags)
}
// 設置顯示列順序——字段下移n條
private tableFieldMoveDown = (num: number) => {
const { initialTags = [], currentSelectedTag = {} } = this.state;
const maxIndex = initialTags.length - 1
const currentIndex = initialTags.findIndex(tag => tag.id === currentSelectedTag.id)
initialTags.splice(currentIndex, 1)
if (currentIndex + num > maxIndex) {
initialTags.splice(maxIndex, 0, currentSelectedTag)
} else {
initialTags.splice(currentIndex + num, 0, currentSelectedTag)
}
this.onChange(initialTags)
}
// 設置顯示列順序——字段移到最下
private tableFieldMoveBottom = () => {
const { initialTags = [], currentSelectedTag = {} } = this.state;
const maxIndex = initialTags.length - 1
const currentIndex = initialTags.findIndex(tag => tag.id === currentSelectedTag.id)
initialTags.splice(currentIndex, 1)
initialTags.splice(maxIndex, 0, currentSelectedTag)
this.onChange(initialTags)
}
render() {
const {
initialTags = [],
currentSelectedTag = {},
} = this.state;
return (
<div>
<Card
className="content-scroll-bar"
bodyStyle={{ padding: '2px' }}
style={{ width: '80%', display: 'inline-block' }}>
{
initialTags.map(tag => (
<div
className={tag.id === currentSelectedTag.id ? styles.tagClick : styles.tag}
onClick={() => this.handleBlockSelected(tag)}
key={tag.id}>
<Checkbox
disabled={tag.disabled}
checked={tag.isVisible}
onChange={() => this.isVisible(tag)}>
<span>{tag.title}</span>
</Checkbox>
</div>
))
}
</Card>
<div style={{ width: '20%', display: 'inline-block' }}>
<Row className={styles.tagBlockFirst}>
<Col span={24} className="move-top">
<Button
key="moveTop"
type="primary"
onClick={this.tableFieldMoveTop}
>最上</Button>
</Col>
</Row>
<Row className={styles.tagBlock}>
<Col span={24} className="move-up-ten">
<Button
key="moveUpTen"
type="primary"
onClick={() => { this.tableFieldMoveUp(10) }}
>上移十條</Button>
</Col>
</Row>
<Row className={styles.tagBlock}>
<Col span={24} className="move-up-one">
<Button
key="moveUpOne"
type="primary"
onClick={() => { this.tableFieldMoveUp(1) }}
>上移一條</Button>
</Col>
</Row>
<Row className={styles.tagBlock}>
<Col span={24} className="move-down-one">
<Button
key="moveDownOne"
type="primary"
onClick={() => { this.tableFieldMoveDown(1) }}
>下移一條</Button>
</Col>
</Row>
<Row className={styles.tagBlock}>
<Col span={24} className="move-down-ten">
<Button
key="moveDownTen"
type="primary"
onClick={() => { this.tableFieldMoveDown(10) }}
>下移十條</Button>
</Col>
</Row>
<Row className={styles.tagBlock}>
<Col span={24} className="move-bottom">
<Button
key="moveBottom"
type="primary"
onClick={this.tableFieldMoveBottom}
>最下</Button>
</Col>
</Row>
</div>
</div>
)
}
}
export default TableFieldsOrder
.tag { margin: 3px; padding: 0 8px; color: #666; font-size: 13px; line-height: 28px; background: rgba(255, 255, 255, 0.7); border: 1px solid #ccc; border-radius: 4px; } .tagClick { margin: 3px; padding: 0 8px; color: #666; font-size: 13px; line-height: 28px; background-color: beige; border: 1px solid #ccc; border-radius: 4px; } .tagBlockFirst { text-align: center; } .tagBlock { margin-top: 8px; text-align: center; }
顯示的界面如下圖:

CheckBox用於讓用戶設置要不要顯示這一列。
第二種方法:拖拽排序(借助了一個基於react實現的庫:react-draggable-tags)
import React, { Component } from 'react';
import { connect } from 'dva';
// @ts-ignore DraggableAreasGroup
import { DraggableArea } from 'react-draggable-tags';
import { Checkbox } from 'antd';
import styles from './index.less';
import { TableListItem } from '@/utils/TableData';
interface DraggableAreaProps {
draggableTags: TableListItem<any>[],
renderOnChange: (value: any) => void;
renderIsVisible: (value: any) => void;
}
interface DraggableAreaState {
draggableTags?: TableListItem<any>[],
}
@connect()
class DraggableAreaView
extends Component<DraggableAreaProps> {
state: DraggableAreaState = {
draggableTags: [],
};
static getDerivedStateFromProps(props: any, state: any) {
const {
draggableTags = [],
} = props;
return {
draggableTags,
}
}
private onChange = (value: any) => {
const { renderOnChange } = this.props;
renderOnChange(value);
};
private isVisible = (value: any) => {
const { renderIsVisible } = this.props;
renderIsVisible(value);
};
render() {
const {
draggableTags = [],
} = this.state;
return (
// className={styles.crossArea}
<div>
<div className={styles.square}>
<DraggableArea
tags={draggableTags}
// @ts-ignore
render={({ tag, index }) => (
<div
className={styles.tag}
key={index}>
<Checkbox
disabled={tag.disabled}
checked={tag.isVisible}
onChange={() => this.isVisible(tag)}>
<span>{tag.title}</span>
</Checkbox>
</div>
)}
onChange={(tags: any) => this.onChange(tags)}
/>
</div>
</div>
)
}
}
export default DraggableAreaView
.square { width: 100%; height: 100%; padding: 5px; //border: 1px solid #E9E9E9; //border-radius: 4px; } .tag { margin: 3px; padding: 0 8px; color: #666; font-size: 13px; line-height: 30px; background: rgba(255, 255, 255, 0.7); border: 1px solid #ccc; border-radius: 4px; } .crossArea { display: flex; .square { width: 50%; //height: 300px; &:first-child { margin-right: 10px; } } }
顯示的界面如下:

這兩種方法都接收同樣的三個props參數,props1:initialTags(表頭數組),props2:renderOnChange(調整字段順序后的回調函數,返回一個新的順序的表頭數組),props3:renderIsVisible(checkbox變化后的回調函數,返回值為當前觸發check change的表頭對象)。
下面來看下父組件中是如何使用的:
<Modal maskClosable={false} width={800} style={{ top: '10px' }} title="字段顯示順序" visible={fieldOrderVisible} onCancel={this.handleCancel} destroyOnClose footer={ <div key="btn" style={{ textAlign: 'center' }}> <Button onClick={this.handleCancel} key="cancel">取消</Button> <Button key="ok" loading={filedOrderOkLoading || false} onClick={this.handleOk} type="primary">確定</Button> </div> } > <TableFieldsOrder initialTags={initialTags} renderOnChange={value => this.renderOnChange(value)} renderIsVisible={value => this.renderIsVisible(value)} /> </Modal>
/** * 打開字段順序設置彈框 */ fieldDisplayOrder = () => { const { dispatch, } = this.props; dispatch({ type: 'FieldOrder/getListHeader', payload: { pageId: 'RTDAuditDetailStat', }, callback: (data: any) => { this.setState({ fieldOrderVisible: true, initialTags: data, }) }, }); }; /** * 取消改動 */ private handleCancel = () => { this.getListHeader(); this.setState({ fieldOrderVisible: false }); }; /** * 確定改動 */ handleOk = () => { const { dispatch } = this.props; dispatch({ type: 'FieldOrder/updListHeader', payload: { listHeaders: this.state.initialTags, pageId: 'RTDAuditDetailStat', }, callback: (status: any) => { if (status) { this.setState({ fieldOrderVisible: false, }) } }, }); }; private renderOnChange = (value: any) => { this.setState({ initialTags: value, }) }; private renderIsVisible = (value: any) => { const { initialTags = [] } = this.state; const node = findListNode(initialTags, 'id', value.id); node.isVisible = !node.isVisible; this.setState({ initialTags, }) };
這個功能到此就結束了,其實應該把Model彈框也封裝在一起,后續再優化吧。
