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

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

上级 aacf7a82
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Divider } from 'antd';
import modelCreator from './model'; import modelCreator from './model';
import connect from '../../hoc/stateful'; import connect from '../../hoc/stateful';
import TableEx from '../../../components/table/index'; 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'; 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 { class DsTable extends React.Component {
componentDidMount() { componentDidMount() {
const { coordinate, params, dispatchLocal } = this.props; const { coordinate, params, dispatchLocal, current = 1, start, end } = this.props;
dispatchLocal({ type: 'doInit', payload: { coordinate, params } }); dispatchLocal({ type: 'doInit', payload: { coordinate, params, current, start, end } });
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
const { coordinate, params } = nextProps; const { coordinate, params, current, start, end } = nextProps;
const { dispatchLocal } = this.props; const { dispatchLocal } = this.props;
if (!shallowEqual(coordinate, this.props.coordinate) || !shallowEqual(params, this.props.params)) { if (!shallowEqual(coordinate, this.props.coordinate) || !shallowEqual(params, this.props.params || current !== this.props.current)) {
dispatchLocal({ type: 'doInit', payload: { coordinate, params } }); dispatchLocal({ type: 'doInit', payload: { coordinate, params, current, start, end } });
} }
} }
render() { render() {
const { dispatchLocal } = this.props; const { dispatchLocal, current: outCurrent } = this.props;
const { props, columns, current, pageSize, filters, list, num } = this.props.model; const { parsedMeta, props, columns, current: localCurrent, pageSize, filters, list, num } = this.props.model;
const current = outCurrent || localCurrent;
const tableProps = { const tableProps = {
dataSource: list, dataSource: list,
...props, ...props,
parsedMeta,
columns, columns,
filters: filters.map(filter => filter.filter), filters: filters.map(filter => filter.filter),
loading: this.props.loading.model, loading: this.props.loading.model,
...@@ -36,7 +68,11 @@ class DsTable extends React.Component { ...@@ -36,7 +68,11 @@ class DsTable extends React.Component {
}, },
onChange: (pagination, theFilters, sorter) => { onChange: (pagination, theFilters, sorter) => {
if (current !== pagination.current) { 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) { if (pageSize !== pagination.pageSize) {
dispatchLocal({ type: 'changePageSize', payload: pagination.pageSize }); dispatchLocal({ type: 'changePageSize', payload: pagination.pageSize });
...@@ -50,6 +86,24 @@ class DsTable extends React.Component { ...@@ -50,6 +86,24 @@ class DsTable extends React.Component {
dispatchLocal({ type: 'doFilter', payload: theFilters }); 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 ( return (
<div className={styles.wrapper}> <div className={styles.wrapper}>
<div className={styles.container}> <div className={styles.container}>
...@@ -71,6 +125,12 @@ DsTable.propTypes = { ...@@ -71,6 +125,12 @@ DsTable.propTypes = {
}), }),
], ],
).isRequired, ).isRequired,
onPageChange: PropTypes.func,
current: PropTypes.number,
};
DsTable.defaultProps = {
onPageChange: () => null,
}; };
export default connect(modelCreator, { export default connect(modelCreator, {
......
import uuid from 'uuid/v4'; import uuid from 'uuid/v4';
import forEach from 'lodash/forEach';
import set from 'lodash/set';
import pickBy from 'lodash/pickBy'; import pickBy from 'lodash/pickBy';
import negate from 'lodash/negate'; import negate from 'lodash/negate';
import isUndefined from 'lodash/isUndefined'; import isUndefined from 'lodash/isUndefined';
import toPlainObject from 'lodash/toPlainObject';
import uniqueId from 'lodash/uniqueId'; import uniqueId from 'lodash/uniqueId';
import flow from 'lodash/fp/flow'; import { getKeyName, parseMetas, normMetas, parseQueryResult } from '../../../utils/meta';
import filter from 'lodash/fp/filter';
import find from 'lodash/fp/find';
import { datasourceApi } from '../../../services/datasource'; import { datasourceApi } from '../../../services/datasource';
const prefix = uuid(); 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) => { const getSource = (property) => {
if (property.source) { if (property.source) {
return (property.source.items || []).map((item) => { return (property.source.items || []).map((item) => {
...@@ -35,28 +22,12 @@ const getSource = (property) => { ...@@ -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) => { const makeProps = (meta) => {
// noinspection JSUnresolvedVariable
if (!meta || !meta.metas) { if (!meta || !meta.metas) {
return {}; return {};
} }
const props = parseMetas(meta.metas); const props = normMetas(meta.metas);
if (!props.rowKey) { if (!props.rowKey) {
props.rowKey = getKeyName(meta); props.rowKey = getKeyName(meta);
} }
...@@ -71,7 +42,8 @@ const makeColumns = (meta) => { ...@@ -71,7 +42,8 @@ const makeColumns = (meta) => {
return (meta.properties || []) return (meta.properties || [])
.filter(property => !property.skip) .filter(property => !property.skip)
.map((property) => { .map((property) => {
const props = parseMetas(property.metas); // noinspection JSUnresolvedVariable
const props = normMetas(property.metas);
if (!props.title && props.label) { if (!props.title && props.label) {
props.title = props.label; props.title = props.label;
} }
...@@ -90,7 +62,14 @@ const makeColumns = (meta) => { ...@@ -90,7 +62,14 @@ const makeColumns = (meta) => {
filterEnums: getSource(property), filterEnums: getSource(property),
}, negate(isUndefined)); }, 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) => { .sort((c1, c2) => {
const c1Left = c1.fixed === true || c1.fixed === 'left'; const c1Left = c1.fixed === true || c1.fixed === 'left';
const c1Right = c1.fixed === 'right'; const c1Right = c1.fixed === 'right';
...@@ -120,30 +99,6 @@ const getSort = (columns) => { ...@@ -120,30 +99,6 @@ const getSort = (columns) => {
} : undefined; } : 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 modelCreator = () => {
const name = 'model'; const name = 'model';
const namespace = uniqueId(prefix); const namespace = uniqueId(prefix);
...@@ -153,19 +108,28 @@ const modelCreator = () => { ...@@ -153,19 +108,28 @@ const modelCreator = () => {
const meta = yield call(api.queryMeta, { params }); const meta = yield call(api.queryMeta, { params });
yield put({ type: 'applyMeta', payload: meta }); yield put({ type: 'applyMeta', payload: meta });
}; };
const loadData = function* loadData({ select, put, call }) { const loadData = function* loadData({ select, put, call, start, end }) {
const { coordinate, params, meta, columns, filters, current, pageSize } = yield select(state => state[namespace]); if (start) {
const api = datasourceApi(coordinate); yield put({ type: start });
const psz = pageSize; }
const pst = (current - 1) * psz; try {
const sort = getSort(columns); const { coordinate, params, meta, columns, filters, current, pageSize } = yield select(state => state[namespace]);
const sortBys = sort ? [sort.field] : []; const api = datasourceApi(coordinate);
const sortTypes = sort ? [sort.order] : []; const psz = pageSize;
const options = { pst, psz, params, filters, sortBys, sortTypes }; const pst = (current - 1) * psz;
const num = yield call(api.count, options); const sort = getSort(columns);
const dsb = yield call(api.query, options); const sortBys = sort ? [sort.field] : [];
const list = getArrayData(dsb, meta); const sortTypes = sort ? [sort.order] : [];
yield put({ type: 'applyData', payload: { num, list } }); 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 { return {
name, name,
...@@ -175,6 +139,13 @@ const modelCreator = () => { ...@@ -175,6 +139,13 @@ const modelCreator = () => {
coordinate: null, coordinate: null,
params: {}, params: {},
meta: {}, meta: {},
parsedMeta: {
global: {
columnData: {},
rowData: {},
},
properties: {},
},
columns: [], columns: [],
props: {}, props: {},
num: 0, num: 0,
...@@ -232,6 +203,7 @@ const modelCreator = () => { ...@@ -232,6 +203,7 @@ const modelCreator = () => {
return { return {
...state, ...state,
meta, meta,
parsedMeta: parseMetas(meta),
props: makeProps(meta), props: makeProps(meta),
columns: makeColumns(meta), columns: makeColumns(meta),
}; };
...@@ -264,12 +236,12 @@ const modelCreator = () => { ...@@ -264,12 +236,12 @@ const modelCreator = () => {
}, },
}, },
effects: { 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: 'applyTarget', payload: { coordinate, params } });
yield put({ type: 'applyFilters', payload: [] }); 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(loadMeta, { put, call, select });
yield call(loadData, { put, call, select }); yield call(loadData, { put, call, select, start, end });
}, },
*doFilter({ payload: filters }, { put, call, select }) { *doFilter({ payload: filters }, { put, call, select }) {
yield put({ type: 'applyFilters', payload: filters }); yield put({ type: 'applyFilters', payload: filters });
......
...@@ -2,6 +2,7 @@ import React, { Component } from 'react'; ...@@ -2,6 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Table } from 'antd'; import { Table } from 'antd';
import Search from './search'; import Search from './search';
import { calcPropertyMeta, calcGlobalMeta } from '../../utils/meta';
class TableEx extends Component { class TableEx extends Component {
...@@ -36,38 +37,48 @@ class TableEx extends Component { ...@@ -36,38 +37,48 @@ class TableEx extends Component {
makeColumns() { makeColumns() {
if (!this.props.columns) { if (!this.props.columns) {
return []; return [];
} } else {
return this.props.columns.map((column, index) => { const { parsedMeta, renderCell } = this.props;
const ret = { return this.props.columns.map((column, index) => {
...column, 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,
})));
});
}; };
ret.filterDropdown = ( const state = this.state.columns[index];
<Search type={ret.filterType} onSearchSubmit={onSearch} filterEnums={ret.filterEnums} /> if (ret.filterType) {
); const onSearch = (filter) => {
ret.filterDropdownVisible = state.filterDropdownVisible; const newColumns = [...this.state.columns];
ret.onFilterDropdownVisibleChange = (visible) => { newColumns[index].filter = filter;
const newColumns = [...this.state.columns]; newColumns[index].filtered = !!filter;
newColumns[index].filterDropdownVisible = visible; newColumns[index].filterDropdownVisible = false;
this.setState({ columns: newColumns }); 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() { render() {
return ( return (
...@@ -80,9 +91,22 @@ const funcVoid = () => {}; ...@@ -80,9 +91,22 @@ const funcVoid = () => {};
TableEx.propTypes = { TableEx.propTypes = {
onFilter: PropTypes.func, 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 = { TableEx.defaultProps = {
onFilter: funcVoid, onFilter: funcVoid,
// (cellData, rowData, rowIndex, meta, globalMeta, propertyName) => string|React node
renderCell: data => `${data}`,
}; };
export default TableEx; export default TableEx;
......
...@@ -188,3 +188,23 @@ export function shallowEqual(o1, o2, excludes = []) { ...@@ -188,3 +188,23 @@ export function shallowEqual(o1, o2, excludes = []) {
} }
return true; 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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论