@arcblock/ux 2.1.6 → 2.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/Datatable/CustomToolbar.js +350 -0
- package/lib/Datatable/DatatableContext.js +47 -0
- package/lib/Datatable/TableSearch.js +98 -0
- package/lib/Datatable/index.js +338 -0
- package/lib/Datatable/utils.js +154 -0
- package/lib/Layout/dashboard/header.js +1 -1
- package/lib/Layout/dashboard/index.js +1 -1
- package/lib/index.js +8 -0
- package/package.json +5 -4
- package/src/Datatable/CustomToolbar.js +406 -0
- package/src/Datatable/DatatableContext.js +32 -0
- package/src/Datatable/TableSearch.js +130 -0
- package/src/Datatable/index.js +314 -0
- package/src/Datatable/utils.js +165 -0
- package/src/Layout/dashboard/header.js +1 -1
- package/src/Layout/dashboard/index.js +1 -1
- package/src/index.js +2 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import MUIDataTable, { TableFilterList, TableFooter } from 'mui-datatables';
|
|
4
|
+
import styled from 'styled-components';
|
|
5
|
+
import isObject from 'lodash/isObject';
|
|
6
|
+
import cloneDeep from 'lodash/cloneDeep';
|
|
7
|
+
import CustomToolbar from './CustomToolbar';
|
|
8
|
+
import { DatatableProvide, useDatatableContext } from './DatatableContext';
|
|
9
|
+
|
|
10
|
+
export default function Datatable({ ...props }) {
|
|
11
|
+
return (
|
|
12
|
+
<DatatableProvide>
|
|
13
|
+
<ReDatatable {...props} />
|
|
14
|
+
</DatatableProvide>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {Object} props.options The options of mui-datatable,detail see https://github.com/gregnb/mui-datatables/tree/b8d2eee6af4589d254b40918e5d7e70b1ee4baca
|
|
20
|
+
* @param {Array} props.customButtons Custom buttons for toolbar
|
|
21
|
+
* @param {Function} props.onChange When onChange is present, serverSide mode is activated by default https://github.com/gregnb/mui-datatables/tree/b8d2eee6af4589d254b40918e5d7e70b1ee4baca#remote-data
|
|
22
|
+
* @param {Boolean} props.loading For dynamic data, usually used with onChange
|
|
23
|
+
* @returns
|
|
24
|
+
*/
|
|
25
|
+
function ReDatatable({
|
|
26
|
+
data: originData,
|
|
27
|
+
columns: originColumns,
|
|
28
|
+
locale,
|
|
29
|
+
options,
|
|
30
|
+
style,
|
|
31
|
+
customButtons,
|
|
32
|
+
onChange,
|
|
33
|
+
loading,
|
|
34
|
+
disabled,
|
|
35
|
+
...rest
|
|
36
|
+
}) {
|
|
37
|
+
const container = useRef(null);
|
|
38
|
+
const oldState = useRef(null);
|
|
39
|
+
const { setCustomButtons, setFilterLabel, setLoading, setDisabled } = useDatatableContext();
|
|
40
|
+
|
|
41
|
+
const disabledCellStyle = {
|
|
42
|
+
cursor: 'not-allowed',
|
|
43
|
+
pointerEvents: 'none',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const keys = [];
|
|
47
|
+
|
|
48
|
+
// Convert Columns fields to object sets to support the width function
|
|
49
|
+
const columns = originColumns.map(e => {
|
|
50
|
+
let tempObj;
|
|
51
|
+
|
|
52
|
+
if (!isObject(e)) {
|
|
53
|
+
tempObj = {
|
|
54
|
+
label: e,
|
|
55
|
+
name: e,
|
|
56
|
+
};
|
|
57
|
+
} else {
|
|
58
|
+
tempObj = cloneDeep(e);
|
|
59
|
+
}
|
|
60
|
+
keys.push(tempObj.name);
|
|
61
|
+
|
|
62
|
+
if (!tempObj.options) {
|
|
63
|
+
tempObj.options = {};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const { setCellHeaderProps } = tempObj.options;
|
|
67
|
+
tempObj.options.setCellHeaderProps = columnMeta => {
|
|
68
|
+
let cellProps = {};
|
|
69
|
+
|
|
70
|
+
// Complementing width while inheriting old setCellHeaderProps
|
|
71
|
+
if (setCellHeaderProps && !setCellHeaderProps.__innerFunc) {
|
|
72
|
+
cellProps = setCellHeaderProps(columnMeta) || {};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (loading || disabled) {
|
|
76
|
+
cellProps = { ...cellProps, style: disabledCellStyle };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (tempObj.width) {
|
|
80
|
+
cellProps.width = tempObj.width;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return cellProps;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Prevent memory xie caused by recursive forwarding of setCellHeaderProps functions
|
|
87
|
+
tempObj.options.setCellHeaderProps.__innerFunc = 1;
|
|
88
|
+
|
|
89
|
+
return tempObj;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Fixing object-type structures
|
|
93
|
+
const data = originData.map(e => {
|
|
94
|
+
if (!Array.isArray(e) && isObject(e)) {
|
|
95
|
+
return keys.map(key => e[key]);
|
|
96
|
+
}
|
|
97
|
+
return e;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
useEffect(() => setCustomButtons(customButtons || []), [customButtons]);
|
|
101
|
+
useEffect(() => setLoading(loading), [loading]);
|
|
102
|
+
useEffect(() => setDisabled(disabled), [disabled]);
|
|
103
|
+
|
|
104
|
+
let textLabels = {
|
|
105
|
+
body: { noMatch: 'Sorry, no matching records found', toolTip: 'Sort' },
|
|
106
|
+
pagination: {
|
|
107
|
+
next: 'Next Page',
|
|
108
|
+
previous: 'Previous Page',
|
|
109
|
+
rowsPerPage: 'Rows per page:',
|
|
110
|
+
displayRows: 'of',
|
|
111
|
+
jumpToPage: 'Jump to Page:',
|
|
112
|
+
},
|
|
113
|
+
toolbar: {
|
|
114
|
+
search: 'Search',
|
|
115
|
+
downloadCsv: 'Download CSV',
|
|
116
|
+
print: 'Print',
|
|
117
|
+
viewColumns: 'View Columns',
|
|
118
|
+
filterTable: 'Filter Table',
|
|
119
|
+
},
|
|
120
|
+
filter: { all: 'All', title: 'FILTERS', reset: 'RESET' },
|
|
121
|
+
viewColumns: { title: 'Show Columns', titleAria: 'Show/Hide Table Columns' },
|
|
122
|
+
selectedRows: { text: 'row(s) selected', delete: 'Delete', deleteAria: 'Delete Selected Rows' },
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
if (locale === 'zh') {
|
|
126
|
+
textLabels = {
|
|
127
|
+
body: { noMatch: '对不起,没有找到匹配的记录', toolTip: '排序' },
|
|
128
|
+
pagination: {
|
|
129
|
+
next: '下一页',
|
|
130
|
+
previous: '上一页',
|
|
131
|
+
rowsPerPage: '每页行数',
|
|
132
|
+
displayRows: '/',
|
|
133
|
+
jumpToPage: '跳转到页面:',
|
|
134
|
+
},
|
|
135
|
+
toolbar: {
|
|
136
|
+
search: '搜索',
|
|
137
|
+
downloadCsv: '下载CSV',
|
|
138
|
+
print: '打印',
|
|
139
|
+
viewColumns: '查看列',
|
|
140
|
+
filterTable: '筛选表格',
|
|
141
|
+
},
|
|
142
|
+
filter: { all: '全部', title: '筛选器', reset: '重置' },
|
|
143
|
+
viewColumns: { title: '显示的列', titleAria: '显示/隐藏 表格的列' },
|
|
144
|
+
selectedRows: { text: '个已选项目', delete: '删除', deleteAria: '删除所选项目' },
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
useEffect(() => setFilterLabel(textLabels.filter.title), [textLabels.filter.title]);
|
|
149
|
+
|
|
150
|
+
const opts = {
|
|
151
|
+
selectableRows: 'none',
|
|
152
|
+
textLabels,
|
|
153
|
+
rowsPerPage: 10,
|
|
154
|
+
rowsPerPageOptions: [10, 20, 50],
|
|
155
|
+
...options,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
if (onChange) {
|
|
159
|
+
Object.assign(opts, {
|
|
160
|
+
serverSide: true,
|
|
161
|
+
// Wrap the more friendly onChange callback by listening to onTableChange,
|
|
162
|
+
// which will only be triggered when the table key state changes
|
|
163
|
+
onTableChange: (action, tableState) => {
|
|
164
|
+
if (action === 'propsUpdate') {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const state = {
|
|
168
|
+
count: tableState.count,
|
|
169
|
+
page: tableState.page,
|
|
170
|
+
rowsPerPage: tableState.rowsPerPage,
|
|
171
|
+
searchText: tableState.searchText,
|
|
172
|
+
sortOrder: tableState.sortOrder, //
|
|
173
|
+
filterList: tableState.filterList,
|
|
174
|
+
};
|
|
175
|
+
const stateStr = JSON.stringify(state);
|
|
176
|
+
if (stateStr === oldState.current) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
oldState.current = stateStr;
|
|
180
|
+
onChange(state, action);
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const props = {
|
|
186
|
+
options: opts,
|
|
187
|
+
...rest,
|
|
188
|
+
components: {
|
|
189
|
+
TableToolbar: CustomToolbar,
|
|
190
|
+
TableFooter: WrapTableFooter,
|
|
191
|
+
TableFilterList: WrapFilterList,
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
ReDatatable.propTypes = {
|
|
196
|
+
data: PropTypes.array.isRequired,
|
|
197
|
+
columns: PropTypes.array.isRequired,
|
|
198
|
+
options: PropTypes.object,
|
|
199
|
+
style: PropTypes.object,
|
|
200
|
+
locale: PropTypes.string,
|
|
201
|
+
loading: PropTypes.bool,
|
|
202
|
+
disabled: PropTypes.bool,
|
|
203
|
+
customButtons: PropTypes.array,
|
|
204
|
+
onChange: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
ReDatatable.defaultProps = {
|
|
208
|
+
options: {},
|
|
209
|
+
style: {},
|
|
210
|
+
locale: 'en',
|
|
211
|
+
loading: false,
|
|
212
|
+
disabled: false,
|
|
213
|
+
customButtons: [],
|
|
214
|
+
onChange: '',
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
<TableContainer ref={container} style={style}>
|
|
219
|
+
<MUIDataTable data={data} columns={columns} {...props} />
|
|
220
|
+
</TableContainer>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const TableContainer = styled.div`
|
|
225
|
+
height: 100%;
|
|
226
|
+
> .MuiPaper-root {
|
|
227
|
+
display: flex;
|
|
228
|
+
flex-direction: column;
|
|
229
|
+
height: 100%;
|
|
230
|
+
box-shadow: none;
|
|
231
|
+
}
|
|
232
|
+
${props => props.theme.breakpoints.down('md')} {
|
|
233
|
+
[class*='MUIDataTableBody-emptyTitle'] {
|
|
234
|
+
padding-left: 16px;
|
|
235
|
+
width: 200%;
|
|
236
|
+
margin-left: -100%;
|
|
237
|
+
text-align: center;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
`;
|
|
241
|
+
|
|
242
|
+
const FilterLine = styled.div`
|
|
243
|
+
display: flex;
|
|
244
|
+
align-items: center;
|
|
245
|
+
.toolbar-filter-content {
|
|
246
|
+
margin-bottom: 8px;
|
|
247
|
+
}
|
|
248
|
+
.toolbar-filter-title {
|
|
249
|
+
font-weight: 700;
|
|
250
|
+
font-size: 14px;
|
|
251
|
+
}
|
|
252
|
+
`;
|
|
253
|
+
|
|
254
|
+
const WrapFilterList = props => {
|
|
255
|
+
const { filterLabel } = useDatatableContext();
|
|
256
|
+
const hasFilter = !!props.filterList.filter(e => e.length).length;
|
|
257
|
+
if (hasFilter) {
|
|
258
|
+
return (
|
|
259
|
+
<FilterLine>
|
|
260
|
+
{hasFilter && <div className="toolbar-filter-title">{filterLabel}</div>}
|
|
261
|
+
<div className="toolbar-filter-content">
|
|
262
|
+
<TableFilterList {...props} />
|
|
263
|
+
</div>
|
|
264
|
+
</FilterLine>
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
return '';
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
WrapFilterList.propTypes = {
|
|
271
|
+
filterList: PropTypes.array,
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
WrapFilterList.defaultProps = {
|
|
275
|
+
filterList: [],
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const WrapTableFooter = props => {
|
|
279
|
+
const { loading, disabled } = useDatatableContext();
|
|
280
|
+
|
|
281
|
+
return (
|
|
282
|
+
<FooterContainer>
|
|
283
|
+
<div className={`datatable-footer ${loading || disabled ? 'datatable-footer-disabled' : ''}`}>
|
|
284
|
+
<TableFooter {...props} />
|
|
285
|
+
</div>
|
|
286
|
+
</FooterContainer>
|
|
287
|
+
);
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const FooterContainer = styled.div`
|
|
291
|
+
display: flex;
|
|
292
|
+
align-items: center;
|
|
293
|
+
.datatable-footer {
|
|
294
|
+
position: relative;
|
|
295
|
+
margin-left: auto;
|
|
296
|
+
&.datatable-footer-disabled {
|
|
297
|
+
position: relative;
|
|
298
|
+
.MuiTablePagination-root {
|
|
299
|
+
opacity: 0.6;
|
|
300
|
+
}
|
|
301
|
+
&:after {
|
|
302
|
+
position: absolute;
|
|
303
|
+
display: block;
|
|
304
|
+
z-index: 2;
|
|
305
|
+
width: 100%;
|
|
306
|
+
height: 100%;
|
|
307
|
+
left: 0;
|
|
308
|
+
top: 0;
|
|
309
|
+
content: '';
|
|
310
|
+
cursor: not-allowed;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
`;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import find from 'lodash/find';
|
|
2
|
+
|
|
3
|
+
function escapeDangerousCSVCharacters(data) {
|
|
4
|
+
if (typeof data === 'string') {
|
|
5
|
+
// Places single quote before the appearance of dangerous characters if they
|
|
6
|
+
// are the first in the data string.
|
|
7
|
+
// eslint-disable-next-line no-useless-escape
|
|
8
|
+
return data.replace(/^\+|^\-|^\=|^\@/g, "'$&");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return data;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function buildCSV(columns, data, options) {
|
|
15
|
+
const replaceDoubleQuoteInString = columnData =>
|
|
16
|
+
typeof columnData === 'string' ? columnData.replace(/"/g, '""') : columnData;
|
|
17
|
+
|
|
18
|
+
// eslint-disable-next-line no-shadow
|
|
19
|
+
const buildHead = columns => {
|
|
20
|
+
return `${columns
|
|
21
|
+
.reduce(
|
|
22
|
+
(soFar, column) =>
|
|
23
|
+
column.download
|
|
24
|
+
? `${soFar}"${escapeDangerousCSVCharacters(
|
|
25
|
+
replaceDoubleQuoteInString(column.label || column.name)
|
|
26
|
+
)}"${options.downloadOptions.separator}`
|
|
27
|
+
: soFar,
|
|
28
|
+
''
|
|
29
|
+
)
|
|
30
|
+
.slice(0, -1)}\r\n`;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const CSVHead = buildHead(columns);
|
|
34
|
+
|
|
35
|
+
// eslint-disable-next-line no-shadow
|
|
36
|
+
const buildBody = data => {
|
|
37
|
+
if (!data.length) return '';
|
|
38
|
+
return data
|
|
39
|
+
.reduce(
|
|
40
|
+
(soFar, row) =>
|
|
41
|
+
`${soFar}"${row.data
|
|
42
|
+
.filter((_, index) => columns[index].download)
|
|
43
|
+
.map(columnData => escapeDangerousCSVCharacters(replaceDoubleQuoteInString(columnData)))
|
|
44
|
+
.join(`"${options.downloadOptions.separator}"`)}"\r\n`,
|
|
45
|
+
''
|
|
46
|
+
)
|
|
47
|
+
.trim();
|
|
48
|
+
};
|
|
49
|
+
const CSVBody = buildBody(data);
|
|
50
|
+
|
|
51
|
+
const csv = options.onDownload
|
|
52
|
+
? options.onDownload(buildHead, buildBody, columns, data)
|
|
53
|
+
: `${CSVHead}${CSVBody}`.trim();
|
|
54
|
+
|
|
55
|
+
return csv;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function downloadCSV(csv, filename) {
|
|
59
|
+
const blob = new Blob([csv], { type: 'text/csv' });
|
|
60
|
+
|
|
61
|
+
/* taken from react-csv */
|
|
62
|
+
if (navigator && navigator.msSaveOrOpenBlob) {
|
|
63
|
+
navigator.msSaveOrOpenBlob(blob, filename);
|
|
64
|
+
} else {
|
|
65
|
+
const dataURI = `data:text/csv;charset=utf-8,${csv}`;
|
|
66
|
+
|
|
67
|
+
const URL = window.URL || window.webkitURL;
|
|
68
|
+
const downloadURI =
|
|
69
|
+
typeof URL.createObjectURL === 'undefined' ? dataURI : URL.createObjectURL(blob);
|
|
70
|
+
|
|
71
|
+
const link = document.createElement('a');
|
|
72
|
+
link.setAttribute('href', downloadURI);
|
|
73
|
+
link.setAttribute('download', filename);
|
|
74
|
+
document.body.appendChild(link);
|
|
75
|
+
link.click();
|
|
76
|
+
document.body.removeChild(link);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// eslint-disable-next-line no-shadow
|
|
81
|
+
function createCSVDownload(columns, data, options, downloadCSV) {
|
|
82
|
+
const csv = buildCSV(columns, data, options);
|
|
83
|
+
|
|
84
|
+
if (options.onDownload && csv === false) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
downloadCSV(csv, options.downloadOptions.filename);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function handleCSVDownload(props) {
|
|
92
|
+
const { data, displayData, columns, options, columnOrder } = props;
|
|
93
|
+
let dataToDownload = [];
|
|
94
|
+
let columnsToDownload = [];
|
|
95
|
+
let columnOrderCopy = Array.isArray(columnOrder) ? columnOrder.slice(0) : [];
|
|
96
|
+
|
|
97
|
+
if (columnOrderCopy.length === 0) {
|
|
98
|
+
columnOrderCopy = columns.map((item, idx) => idx);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
data.forEach(row => {
|
|
102
|
+
const newRow = { index: row.index, data: [] };
|
|
103
|
+
columnOrderCopy.forEach(idx => {
|
|
104
|
+
newRow.data.push(row.data[idx]);
|
|
105
|
+
});
|
|
106
|
+
dataToDownload.push(newRow);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
columnOrderCopy.forEach(idx => {
|
|
110
|
+
columnsToDownload.push(columns[idx]);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (options.downloadOptions && options.downloadOptions.filterOptions) {
|
|
114
|
+
// check rows first:
|
|
115
|
+
if (options.downloadOptions.filterOptions.useDisplayedRowsOnly) {
|
|
116
|
+
const filteredDataToDownload = displayData.map((row, index) => {
|
|
117
|
+
let i = -1;
|
|
118
|
+
|
|
119
|
+
// Help to preserve sort order in custom render columns
|
|
120
|
+
row.index = index;
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
data: row.data.map(column => {
|
|
124
|
+
i += 1;
|
|
125
|
+
|
|
126
|
+
// if we have a custom render, which will appear as a react element, we must grab the actual value from data
|
|
127
|
+
// that matches the dataIndex and column
|
|
128
|
+
// TODO: Create a utility function for checking whether or not something is a react object
|
|
129
|
+
let val =
|
|
130
|
+
typeof column === 'object' && column !== null && !Array.isArray(column)
|
|
131
|
+
? find(data, d => d.index === row.dataIndex).data[i]
|
|
132
|
+
: column;
|
|
133
|
+
val =
|
|
134
|
+
typeof val === 'function' ? find(data, d => d.index === row.dataIndex).data[i] : val;
|
|
135
|
+
return val;
|
|
136
|
+
}),
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
dataToDownload = [];
|
|
141
|
+
filteredDataToDownload.forEach(row => {
|
|
142
|
+
const newRow = { index: row.index, data: [] };
|
|
143
|
+
columnOrderCopy.forEach(idx => {
|
|
144
|
+
newRow.data.push(row.data[idx]);
|
|
145
|
+
});
|
|
146
|
+
dataToDownload.push(newRow);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// now, check columns:
|
|
151
|
+
if (options.downloadOptions.filterOptions.useDisplayedColumnsOnly) {
|
|
152
|
+
columnsToDownload = columnsToDownload.filter(_ => _.display === 'true');
|
|
153
|
+
|
|
154
|
+
dataToDownload = dataToDownload.map(row => {
|
|
155
|
+
row.data = row.data.filter(
|
|
156
|
+
(_, index) => columns[columnOrderCopy[index]].display === 'true'
|
|
157
|
+
);
|
|
158
|
+
return row;
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
createCSVDownload(columnsToDownload, dataToDownload, options, downloadCSV);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export default { handleCSVDownload };
|
|
@@ -141,7 +141,7 @@ Header.propTypes = {
|
|
|
141
141
|
onToggleMenu: PropTypes.func.isRequired,
|
|
142
142
|
brand: PropTypes.string.isRequired,
|
|
143
143
|
brandAddon: PropTypes.object,
|
|
144
|
-
description: PropTypes.
|
|
144
|
+
description: PropTypes.any.isRequired,
|
|
145
145
|
children: PropTypes.any,
|
|
146
146
|
addons: PropTypes.any,
|
|
147
147
|
homeUrl: PropTypes.string,
|
|
@@ -126,7 +126,7 @@ Dashboard.propTypes = {
|
|
|
126
126
|
links: PropTypes.array.isRequired,
|
|
127
127
|
images: PropTypes.object.isRequired,
|
|
128
128
|
brandAddon: PropTypes.object,
|
|
129
|
-
description: PropTypes.
|
|
129
|
+
description: PropTypes.any.isRequired,
|
|
130
130
|
headerAddon: PropTypes.any,
|
|
131
131
|
prefix: PropTypes.string,
|
|
132
132
|
// 兼容旧版的设置,新版使用 fullWidth 进行设置
|
package/src/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import Icon from './Icon';
|
|
|
15
15
|
import LocaleSelector from './Locale/selector';
|
|
16
16
|
import Logo from './Logo';
|
|
17
17
|
import RelativeTime from './RelativeTime';
|
|
18
|
+
import Datatable from './Datatable';
|
|
18
19
|
import Tabs from './Tabs';
|
|
19
20
|
import Tag from './Tag';
|
|
20
21
|
import Terminal from './Terminal';
|
|
@@ -50,6 +51,7 @@ export {
|
|
|
50
51
|
Logo,
|
|
51
52
|
Tabs,
|
|
52
53
|
RelativeTime,
|
|
54
|
+
Datatable,
|
|
53
55
|
Tag,
|
|
54
56
|
Terminal,
|
|
55
57
|
TerminalPlayer,
|