提交 0a3e91b1 authored 作者: vipcxj's avatar vipcxj

增强DsTable,支持外部页码传入,支持自定义单元格

上级 aacf7a82
import React from 'react';
import PropTypes from 'prop-types';
import { Divider } from 'antd';
import modelCreator from './model';
import connect from '../../hoc/stateful';
import TableEx from '../../../components/table/index';
import { shallowEqual } from '../../../utils/helper';
import { push } from '../../../services/route';
import { shallowEqual, arrayJoin } from '../../../utils/helper';
import styles from './index.less';
const createUniqueKey = (columns, key) => {
if (columns.findIndex(v => v.k === key) === -1) {
return key;
} else {
return createUniqueKey(columns, `_${key}`);
}
};
const renderButton = (meta) => {
if (meta.path) {
const onClick = (e) => {
e.preventDefault();
push(meta.path, { ...meta });
};
const onKeyDown = (e) => {
if (e.keyCode === 13) {
e.preventDefault();
push(meta.path, { ...meta });
}
};
// noinspection JSUnresolvedVariable
return (
<a onClick={onClick} onKeyDown={onKeyDown}> {meta.buttonName || 'link'} </a>
);
}
throw new Error(`Unsupported button meta: ${JSON.stringify(meta)}.`);
};
class DsTable extends React.Component {
componentDidMount() {
const { coordinate, params, dispatchLocal } = this.props;
dispatchLocal({ type: 'doInit', payload: { coordinate, params } });
const { coordinate, params, dispatchLocal, current = 1, start, end } = this.props;
dispatchLocal({ type: 'doInit', payload: { coordinate, params, current, start, end } });
}
componentWillReceiveProps(nextProps) {
const { coordinate, params } = nextProps;
const { coordinate, params, current, start, end } = nextProps;
const { dispatchLocal } = this.props;
if (!shallowEqual(coordinate, this.props.coordinate) || !shallowEqual(params, this.props.params)) {
dispatchLocal({ type: 'doInit', payload: { coordinate, params } });
if (!shallowEqual(coordinate, this.props.coordinate) || !shallowEqual(params, this.props.params || current !== this.props.current)) {
dispatchLocal({ type: 'doInit', payload: { coordinate, params, current, start, end } });
}
}
render() {
const { dispatchLocal } = this.props;
const { props, columns, current, pageSize, filters, list, num } = this.props.model;
const { dispatchLocal, current: outCurrent } = this.props;
const { parsedMeta, props, columns, current: localCurrent, pageSize, filters, list, num } = this.props.model;
const current = outCurrent || localCurrent;
const tableProps = {
dataSource: list,
...props,
parsedMeta,
columns,
filters: filters.map(filter => filter.filter),
loading: this.props.loading.model,
......@@ -36,7 +68,11 @@ class DsTable extends React.Component {
},
onChange: (pagination, theFilters, sorter) => {
if (current !== pagination.current) {
dispatchLocal({ type: 'changeCurrentPage', payload: pagination.current });
if (outCurrent) {
this.props.onPageChange(pagination.current);
} else {
dispatchLocal({ type: 'changeCurrentPage', payload: pagination.current });
}
}
if (pageSize !== pagination.pageSize) {
dispatchLocal({ type: 'changePageSize', payload: pagination.pageSize });
......@@ -50,6 +86,24 @@ class DsTable extends React.Component {
dispatchLocal({ type: 'doFilter', payload: theFilters });
},
};
const { columnData, rowData } = parsedMeta.global;
if (columnData.buttons || rowData.buttons) {
tableProps.columns = [
...columns,
{
title: '操作',
key: createUniqueKey(tableProps.columns, 'operations'),
fixed: 'right',
render: (text, record, rowIndex, meta, globalMeta) => {
return (
<div style={{ whiteSpace: 'nowrap' }}>
{ arrayJoin(globalMeta.buttons.map(renderButton), <Divider type="vertical" />) }
</div>
);
},
},
];
}
return (
<div className={styles.wrapper}>
<div className={styles.container}>
......@@ -71,6 +125,12 @@ DsTable.propTypes = {
}),
],
).isRequired,
onPageChange: PropTypes.func,
current: PropTypes.number,
};
DsTable.defaultProps = {
onPageChange: () => null,
};
export default connect(modelCreator, {
......
import uuid from 'uuid/v4';
import forEach from 'lodash/forEach';
import set from 'lodash/set';
import pickBy from 'lodash/pickBy';
import negate from 'lodash/negate';
import isUndefined from 'lodash/isUndefined';
import toPlainObject from 'lodash/toPlainObject';
import uniqueId from 'lodash/uniqueId';
import flow from 'lodash/fp/flow';
import filter from 'lodash/fp/filter';
import find from 'lodash/fp/find';
import { getKeyName, parseMetas, normMetas, parseQueryResult } from '../../../utils/meta';
import { datasourceApi } from '../../../services/datasource';
const prefix = uuid();
const getKeyName = (meta) => {
const keyProperty = flow(
filter(property => !property.skip),
find(property => property.key),
)(meta.properties || []);
return keyProperty ? keyProperty.name : undefined;
};
const getSource = (property) => {
if (property.source) {
return (property.source.items || []).map((item) => {
......@@ -35,28 +22,12 @@ const getSource = (property) => {
}
};
const parseMetas = (metas) => {
const ret = {};
if (!metas) {
return ret;
}
forEach(metas, (value, key) => {
let finalValue;
try {
finalValue = JSON.parse(value);
} catch (err) {
finalValue = value;
}
set(ret, key, finalValue);
});
return pickBy(ret, negate(isUndefined));
};
const makeProps = (meta) => {
// noinspection JSUnresolvedVariable
if (!meta || !meta.metas) {
return {};
}
const props = parseMetas(meta.metas);
const props = normMetas(meta.metas);
if (!props.rowKey) {
props.rowKey = getKeyName(meta);
}
......@@ -71,7 +42,8 @@ const makeColumns = (meta) => {
return (meta.properties || [])
.filter(property => !property.skip)
.map((property) => {
const props = parseMetas(property.metas);
// noinspection JSUnresolvedVariable
const props = normMetas(property.metas);
if (!props.title && props.label) {
props.title = props.label;
}
......@@ -90,7 +62,14 @@ const makeColumns = (meta) => {
filterEnums: getSource(property),
}, negate(isUndefined));
})
.filter(c => c.visible !== false)
.filter((c) => {
for (const key of Object.keys(c)) {
if (key.startsWith('meta:')) {
return false;
}
}
return c.visible !== false;
})
.sort((c1, c2) => {
const c1Left = c1.fixed === true || c1.fixed === 'left';
const c1Right = c1.fixed === 'right';
......@@ -120,30 +99,6 @@ const getSort = (columns) => {
} : undefined;
};
const getArrayData = ({ dataType, arrayData, singularData }, meta) => {
if (dataType === 'TABLE') {
const data = (arrayData || []).map(() => ({}));
(meta.properties || [])
.filter(property => !property.skip)
.forEach((property, i) => {
data.forEach((row, j) => {
row[property.name] = arrayData[j][i]; // eslint-disable-line no-param-reassign
});
});
return data;
} else if (dataType === 'PROPERTIES') {
const data = [];
(meta.properties || [])
.filter(property => !property.skip)
.forEach((property) => {
data.push((singularData || {})[property.name]);
});
return [toPlainObject(data)];
} else {
throw new Error(`Unsupported data type: ${dataType}`);
}
};
const modelCreator = () => {
const name = 'model';
const namespace = uniqueId(prefix);
......@@ -153,19 +108,28 @@ const modelCreator = () => {
const meta = yield call(api.queryMeta, { params });
yield put({ type: 'applyMeta', payload: meta });
};
const loadData = function* loadData({ select, put, call }) {
const { coordinate, params, meta, columns, filters, current, pageSize } = yield select(state => state[namespace]);
const api = datasourceApi(coordinate);
const psz = pageSize;
const pst = (current - 1) * psz;
const sort = getSort(columns);
const sortBys = sort ? [sort.field] : [];
const sortTypes = sort ? [sort.order] : [];
const options = { pst, psz, params, filters, sortBys, sortTypes };
const num = yield call(api.count, options);
const dsb = yield call(api.query, options);
const list = getArrayData(dsb, meta);
yield put({ type: 'applyData', payload: { num, list } });
const loadData = function* loadData({ select, put, call, start, end }) {
if (start) {
yield put({ type: start });
}
try {
const { coordinate, params, meta, columns, filters, current, pageSize } = yield select(state => state[namespace]);
const api = datasourceApi(coordinate);
const psz = pageSize;
const pst = (current - 1) * psz;
const sort = getSort(columns);
const sortBys = sort ? [sort.field] : [];
const sortTypes = sort ? [sort.order] : [];
const options = { pst, psz, params, filters, sortBys, sortTypes };
const num = yield call(api.count, options);
const dsb = yield call(api.query, options);
const list = parseQueryResult(dsb, meta);
yield put({ type: 'applyData', payload: { num, list } });
} finally {
if (end) {
yield put({ type: end });
}
}
};
return {
name,
......@@ -175,6 +139,13 @@ const modelCreator = () => {
coordinate: null,
params: {},
meta: {},
parsedMeta: {
global: {
columnData: {},
rowData: {},
},
properties: {},
},
columns: [],
props: {},
num: 0,
......@@ -232,6 +203,7 @@ const modelCreator = () => {
return {
...state,
meta,
parsedMeta: parseMetas(meta),
props: makeProps(meta),
columns: makeColumns(meta),
};
......@@ -264,12 +236,12 @@ const modelCreator = () => {
},
},
effects: {
*doInit({ payload: { coordinate, params } }, { put, call, select }) {
*doInit({ payload: { coordinate, params, current = 1, start, end } }, { put, call, select }) {
yield put({ type: 'applyTarget', payload: { coordinate, params } });
yield put({ type: 'applyFilters', payload: [] });
yield put({ type: 'applyCurrentPage', payload: 1 });
yield put({ type: 'applyCurrentPage', payload: current });
yield call(loadMeta, { put, call, select });
yield call(loadData, { put, call, select });
yield call(loadData, { put, call, select, start, end });
},
*doFilter({ payload: filters }, { put, call, select }) {
yield put({ type: 'applyFilters', payload: filters });
......
......@@ -2,6 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Table } from 'antd';
import Search from './search';
import { calcPropertyMeta, calcGlobalMeta } from '../../utils/meta';
class TableEx extends Component {
......@@ -36,38 +37,48 @@ class TableEx extends Component {
makeColumns() {
if (!this.props.columns) {
return [];
}
return this.props.columns.map((column, index) => {
const ret = {
...column,
};
const state = this.state.columns[index];
if (ret.filterType) {
const onSearch = (filter) => {
const newColumns = [...this.state.columns];
newColumns[index].filter = filter;
newColumns[index].filtered = !!filter;
newColumns[index].filterDropdownVisible = false;
this.setState({ columns: newColumns }, () => {
this.props.onFilter(this.state.columns.map(c => ({
key: c.key,
filter: c.filter,
})));
});
} else {
const { parsedMeta, renderCell } = this.props;
return this.props.columns.map((column, index) => {
const ret = {
...column,
};
ret.filterDropdown = (
<Search type={ret.filterType} onSearchSubmit={onSearch} filterEnums={ret.filterEnums} />
);
ret.filterDropdownVisible = state.filterDropdownVisible;
ret.onFilterDropdownVisibleChange = (visible) => {
const newColumns = [...this.state.columns];
newColumns[index].filterDropdownVisible = visible;
this.setState({ columns: newColumns });
const state = this.state.columns[index];
if (ret.filterType) {
const onSearch = (filter) => {
const newColumns = [...this.state.columns];
newColumns[index].filter = filter;
newColumns[index].filtered = !!filter;
newColumns[index].filterDropdownVisible = false;
this.setState({ columns: newColumns }, () => {
this.props.onFilter(this.state.columns.map(c => ({
key: c.key,
filter: c.filter,
})));
});
};
ret.filterDropdown = (
<Search type={ret.filterType} onSearchSubmit={onSearch} filterEnums={ret.filterEnums} />
);
ret.filterDropdownVisible = state.filterDropdownVisible;
ret.onFilterDropdownVisibleChange = (visible) => {
const newColumns = [...this.state.columns];
newColumns[index].filterDropdownVisible = visible;
this.setState({ columns: newColumns });
};
ret.filterIcon = Search.getIcon(ret.filterType, state.filtered);
}
const orgRender = ret.render;
ret.render = (text, record, rowIndex) => {
if (column.dataIndex !== undefined && column.dataIndex !== null) {
return (orgRender || renderCell)(text, record, rowIndex, calcPropertyMeta(column.dataIndex, parsedMeta, record), calcGlobalMeta(parsedMeta, record), column.dataIndex);
} else {
return (orgRender || renderCell)(text, record, rowIndex, null, calcGlobalMeta(parsedMeta, record), column.dataIndex);
}
};
ret.filterIcon = Search.getIcon(ret.filterType, state.filtered);
}
return ret;
}, this);
return ret;
}, this);
}
}
render() {
return (
......@@ -80,9 +91,22 @@ const funcVoid = () => {};
TableEx.propTypes = {
onFilter: PropTypes.func,
parsedMeta: PropTypes.shape({
global: PropTypes.shape({
columnData: PropTypes.object,
rowData: PropTypes.objectOf(PropTypes.string),
}),
properties: PropTypes.objectOf(PropTypes.shape({
columnData: PropTypes.object,
rowData: PropTypes.objectOf(PropTypes.string),
})),
}),
renderCell: PropTypes.func,
};
TableEx.defaultProps = {
onFilter: funcVoid,
// (cellData, rowData, rowIndex, meta, globalMeta, propertyName) => string|React node
renderCell: data => `${data}`,
};
export default TableEx;
......
......@@ -188,3 +188,23 @@ export function shallowEqual(o1, o2, excludes = []) {
}
return true;
}
export const mapObject = (obj, mapper) => {
const newObj = {};
for (const key of Object.keys(obj)) {
newObj[key] = mapper(obj[key], key);
}
return newObj;
};
export const arrayJoin = (arr, joined) => {
const newArr = [];
for (let i = 0; i < arr.length; ++i) {
newArr.push(arr[i]);
newArr.push(joined);
}
if (newArr.length > 0) {
newArr.pop();
}
return newArr;
};
import set from 'lodash/set';
import negate from 'lodash/negate';
import isUndefined from 'lodash/isUndefined';
import pickBy from 'lodash/pickBy';
import forEach from 'lodash/forEach';
import toPlainObject from 'lodash/toPlainObject';
import find from 'lodash/fp/find';
import flow from 'lodash/fp/flow';
import filter from 'lodash/fp/filter';
import { mapObject } from './helper';
export const getKeyName = (meta) => {
const keyProperty = flow(
filter(property => !property.skip),
find(property => property.key),
)(meta.properties || []);
return keyProperty ? keyProperty.name : undefined;
};
export const normMetas = (metas) => {
const ret = {};
if (!metas) {
return ret;
}
forEach(metas, (value, key) => {
let finalValue;
try {
finalValue = JSON.parse(value);
} catch (err) {
finalValue = value;
}
set(ret, key, finalValue);
});
return pickBy(ret, negate(isUndefined));
};
export const parseMetas = (datasource) => {
const res = {
global: {
columnData: {},
rowData: {},
},
properties: {},
};
// noinspection JSUnresolvedVariable
if (datasource.metas) {
res.global.columnData = normMetas(datasource.metas);
}
res.properties = {};
(datasource.properties || [])
.filter(property => !property.skip)
.forEach((property) => {
// noinspection JSUnresolvedVariable
if (property.metas) {
const metas = normMetas(property.metas);
for (const key of Object.keys(metas)) {
const trimedKey = key.trim();
if (trimedKey.startsWith('meta:')) {
const pos = trimedKey.indexOf('.');
if (pos === -1) {
const mName = trimedKey.substring(5).trimLeft();
res.global.rowData[mName] = property.name;
} else {
const pName = trimedKey.substring(5, pos).trimLeft();
const mName = trimedKey.substring(pos + 1).trimLeft();
if (!res.properties[pName]) {
res.properties[pName] = {
columnData: {},
rowData: {},
};
}
res.properties[pName].rowData[mName] = property.name;
}
return;
}
}
if (!res.properties[property.name]) {
res.properties[property.name] = {
columnData: {},
rowData: {},
};
res.properties[property.name].columnData = metas;
}
} else {
res.properties[property.name] = {
columnData: {},
rowData: {},
};
}
});
return res;
};
export const parseQueryResult = ({ dataType, arrayData, singularData }, meta) => {
if (dataType === 'TABLE') {
const data = (arrayData || []).map(() => ({}));
(meta.properties || [])
.filter(property => !property.skip)
.forEach((property, i) => {
data.forEach((row, j) => {
row[property.name] = arrayData[j][i]; // eslint-disable-line no-param-reassign
});
});
return data;
} else if (dataType === 'PROPERTIES') {
const data = [];
(meta.properties || [])
.filter(property => !property.skip)
.forEach((property) => {
data.push((singularData || {})[property.name]);
});
return [toPlainObject(data)];
} else {
throw new Error(`Unsupported data type: ${dataType}`);
}
};
const combineMeta = ({ columnData, rowData }, row) => {
return {
...columnData,
...mapObject(rowData, v => row[v]),
};
};
export const calcPropertyMeta = (property, parsedMeta, row) => {
return combineMeta(parsedMeta.properties[property], row);
};
export const calcGlobalMeta = (parsedMeta, row) => {
return combineMeta(parsedMeta.global, row);
};
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论