Table.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
  2. import _extends from "@babel/runtime/helpers/esm/extends";
  3. import { resolveDirective as _resolveDirective, createVNode as _createVNode } from "vue";
  4. import RcTable from '../vc-table';
  5. import { INTERNAL_HOOKS } from '../vc-table/Table';
  6. import Spin from '../spin';
  7. import Pagination from '../pagination';
  8. import usePagination, { DEFAULT_PAGE_SIZE, getPaginationParam } from './hooks/usePagination';
  9. import useLazyKVMap from './hooks/useLazyKVMap';
  10. import useSelection from './hooks/useSelection';
  11. import useSorter, { getSortData } from './hooks/useSorter';
  12. import useFilter, { getFilterData } from './hooks/useFilter';
  13. import useTitleColumns from './hooks/useTitleColumns';
  14. import renderExpandIcon from './ExpandIcon';
  15. import scrollTo from '../_util/scrollTo';
  16. import defaultLocale from '../locale/en_US';
  17. import devWarning from '../vc-util/devWarning';
  18. import { nextTick, reactive, ref, computed, defineComponent, toRef, watchEffect, watch } from 'vue';
  19. import useBreakpoint from '../_util/hooks/useBreakpoint';
  20. import useConfigInject from '../config-provider/hooks/useConfigInject';
  21. import { useLocaleReceiver } from '../locale-provider/LocaleReceiver';
  22. import classNames from '../_util/classNames';
  23. import omit from '../_util/omit';
  24. import { initDefaultProps } from '../_util/props-util';
  25. import { useProvideSlots, useProvideTableContext } from './context';
  26. import useColumns from './hooks/useColumns';
  27. import { convertChildrenToColumns } from './util';
  28. import { stringType, booleanType, arrayType, someType, functionType, objectType } from '../_util/type';
  29. // CSSINJS
  30. import useStyle from './style';
  31. const EMPTY_LIST = [];
  32. export const tableProps = () => {
  33. return {
  34. prefixCls: stringType(),
  35. columns: arrayType(),
  36. rowKey: someType([String, Function]),
  37. tableLayout: stringType(),
  38. rowClassName: someType([String, Function]),
  39. title: functionType(),
  40. footer: functionType(),
  41. id: stringType(),
  42. showHeader: booleanType(),
  43. components: objectType(),
  44. customRow: functionType(),
  45. customHeaderRow: functionType(),
  46. direction: stringType(),
  47. expandFixed: someType([Boolean, String]),
  48. expandColumnWidth: Number,
  49. expandedRowKeys: arrayType(),
  50. defaultExpandedRowKeys: arrayType(),
  51. expandedRowRender: functionType(),
  52. expandRowByClick: booleanType(),
  53. expandIcon: functionType(),
  54. onExpand: functionType(),
  55. onExpandedRowsChange: functionType(),
  56. 'onUpdate:expandedRowKeys': functionType(),
  57. defaultExpandAllRows: booleanType(),
  58. indentSize: Number,
  59. /** @deprecated Please use `EXPAND_COLUMN` in `columns` directly */
  60. expandIconColumnIndex: Number,
  61. showExpandColumn: booleanType(),
  62. expandedRowClassName: functionType(),
  63. childrenColumnName: stringType(),
  64. rowExpandable: functionType(),
  65. sticky: someType([Boolean, Object]),
  66. dropdownPrefixCls: String,
  67. dataSource: arrayType(),
  68. pagination: someType([Boolean, Object]),
  69. loading: someType([Boolean, Object]),
  70. size: stringType(),
  71. bordered: booleanType(),
  72. locale: objectType(),
  73. onChange: functionType(),
  74. onResizeColumn: functionType(),
  75. rowSelection: objectType(),
  76. getPopupContainer: functionType(),
  77. scroll: objectType(),
  78. sortDirections: arrayType(),
  79. showSorterTooltip: someType([Boolean, Object], true),
  80. transformCellText: functionType()
  81. };
  82. };
  83. const InternalTable = defineComponent({
  84. name: 'InternalTable',
  85. inheritAttrs: false,
  86. props: initDefaultProps(_extends(_extends({}, tableProps()), {
  87. contextSlots: objectType()
  88. }), {
  89. rowKey: 'key'
  90. }),
  91. setup(props, _ref) {
  92. let {
  93. attrs,
  94. slots,
  95. expose,
  96. emit
  97. } = _ref;
  98. devWarning(!(typeof props.rowKey === 'function' && props.rowKey.length > 1), 'Table', '`index` parameter of `rowKey` function is deprecated. There is no guarantee that it will work as expected.');
  99. useProvideSlots(computed(() => props.contextSlots));
  100. useProvideTableContext({
  101. onResizeColumn: (w, col) => {
  102. emit('resizeColumn', w, col);
  103. }
  104. });
  105. const screens = useBreakpoint();
  106. const mergedColumns = computed(() => {
  107. const matched = new Set(Object.keys(screens.value).filter(m => screens.value[m]));
  108. return props.columns.filter(c => !c.responsive || c.responsive.some(r => matched.has(r)));
  109. });
  110. const {
  111. size: mergedSize,
  112. renderEmpty,
  113. direction,
  114. prefixCls,
  115. configProvider
  116. } = useConfigInject('table', props);
  117. // Style
  118. const [wrapSSR, hashId] = useStyle(prefixCls);
  119. const transformCellText = computed(() => {
  120. var _a;
  121. return props.transformCellText || ((_a = configProvider.transformCellText) === null || _a === void 0 ? void 0 : _a.value);
  122. });
  123. const [tableLocale] = useLocaleReceiver('Table', defaultLocale.Table, toRef(props, 'locale'));
  124. const rawData = computed(() => props.dataSource || EMPTY_LIST);
  125. const dropdownPrefixCls = computed(() => configProvider.getPrefixCls('dropdown', props.dropdownPrefixCls));
  126. const childrenColumnName = computed(() => props.childrenColumnName || 'children');
  127. const expandType = computed(() => {
  128. if (rawData.value.some(item => item === null || item === void 0 ? void 0 : item[childrenColumnName.value])) {
  129. return 'nest';
  130. }
  131. if (props.expandedRowRender) {
  132. return 'row';
  133. }
  134. return null;
  135. });
  136. const internalRefs = reactive({
  137. body: null
  138. });
  139. const updateInternalRefs = refs => {
  140. _extends(internalRefs, refs);
  141. };
  142. // ============================ RowKey ============================
  143. const getRowKey = computed(() => {
  144. if (typeof props.rowKey === 'function') {
  145. return props.rowKey;
  146. }
  147. return record => record === null || record === void 0 ? void 0 : record[props.rowKey];
  148. });
  149. const [getRecordByKey] = useLazyKVMap(rawData, childrenColumnName, getRowKey);
  150. // ============================ Events =============================
  151. const changeEventInfo = {};
  152. const triggerOnChange = function (info, action) {
  153. let reset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
  154. const {
  155. pagination,
  156. scroll,
  157. onChange
  158. } = props;
  159. const changeInfo = _extends(_extends({}, changeEventInfo), info);
  160. if (reset) {
  161. changeEventInfo.resetPagination();
  162. // Reset event param
  163. if (changeInfo.pagination.current) {
  164. changeInfo.pagination.current = 1;
  165. }
  166. // Trigger pagination events
  167. if (pagination && pagination.onChange) {
  168. pagination.onChange(1, changeInfo.pagination.pageSize);
  169. }
  170. }
  171. if (scroll && scroll.scrollToFirstRowOnChange !== false && internalRefs.body) {
  172. scrollTo(0, {
  173. getContainer: () => internalRefs.body
  174. });
  175. }
  176. onChange === null || onChange === void 0 ? void 0 : onChange(changeInfo.pagination, changeInfo.filters, changeInfo.sorter, {
  177. currentDataSource: getFilterData(getSortData(rawData.value, changeInfo.sorterStates, childrenColumnName.value), changeInfo.filterStates),
  178. action
  179. });
  180. };
  181. /**
  182. * Controlled state in `columns` is not a good idea that makes too many code (1000+ line?) to read
  183. * state out and then put it back to title render. Move these code into `hooks` but still too
  184. * complex. We should provides Table props like `sorter` & `filter` to handle control in next big version.
  185. */
  186. // ============================ Sorter =============================
  187. const onSorterChange = (sorter, sorterStates) => {
  188. triggerOnChange({
  189. sorter,
  190. sorterStates
  191. }, 'sort', false);
  192. };
  193. const [transformSorterColumns, sortStates, sorterTitleProps, sorters] = useSorter({
  194. prefixCls,
  195. mergedColumns,
  196. onSorterChange,
  197. sortDirections: computed(() => props.sortDirections || ['ascend', 'descend']),
  198. tableLocale,
  199. showSorterTooltip: toRef(props, 'showSorterTooltip')
  200. });
  201. const sortedData = computed(() => getSortData(rawData.value, sortStates.value, childrenColumnName.value));
  202. // ============================ Filter ============================
  203. const onFilterChange = (filters, filterStates) => {
  204. triggerOnChange({
  205. filters,
  206. filterStates
  207. }, 'filter', true);
  208. };
  209. const [transformFilterColumns, filterStates, filters] = useFilter({
  210. prefixCls,
  211. locale: tableLocale,
  212. dropdownPrefixCls,
  213. mergedColumns,
  214. onFilterChange,
  215. getPopupContainer: toRef(props, 'getPopupContainer')
  216. });
  217. const mergedData = computed(() => getFilterData(sortedData.value, filterStates.value));
  218. // ============================ Column ============================
  219. const [transformBasicColumns] = useColumns(toRef(props, 'contextSlots'));
  220. const columnTitleProps = computed(() => {
  221. const mergedFilters = {};
  222. const filtersValue = filters.value;
  223. Object.keys(filtersValue).forEach(filterKey => {
  224. if (filtersValue[filterKey] !== null) {
  225. mergedFilters[filterKey] = filtersValue[filterKey];
  226. }
  227. });
  228. return _extends(_extends({}, sorterTitleProps.value), {
  229. filters: mergedFilters
  230. });
  231. });
  232. const [transformTitleColumns] = useTitleColumns(columnTitleProps);
  233. // ========================== Pagination ==========================
  234. const onPaginationChange = (current, pageSize) => {
  235. triggerOnChange({
  236. pagination: _extends(_extends({}, changeEventInfo.pagination), {
  237. current,
  238. pageSize
  239. })
  240. }, 'paginate');
  241. };
  242. const [mergedPagination, resetPagination] = usePagination(computed(() => mergedData.value.length), toRef(props, 'pagination'), onPaginationChange);
  243. watchEffect(() => {
  244. changeEventInfo.sorter = sorters.value;
  245. changeEventInfo.sorterStates = sortStates.value;
  246. changeEventInfo.filters = filters.value;
  247. changeEventInfo.filterStates = filterStates.value;
  248. changeEventInfo.pagination = props.pagination === false ? {} : getPaginationParam(mergedPagination.value, props.pagination);
  249. changeEventInfo.resetPagination = resetPagination;
  250. });
  251. // ============================= Data =============================
  252. const pageData = computed(() => {
  253. if (props.pagination === false || !mergedPagination.value.pageSize) {
  254. return mergedData.value;
  255. }
  256. const {
  257. current = 1,
  258. total,
  259. pageSize = DEFAULT_PAGE_SIZE
  260. } = mergedPagination.value;
  261. devWarning(current > 0, 'Table', '`current` should be positive number.');
  262. // Dynamic table data
  263. if (mergedData.value.length < total) {
  264. if (mergedData.value.length > pageSize) {
  265. return mergedData.value.slice((current - 1) * pageSize, current * pageSize);
  266. }
  267. return mergedData.value;
  268. }
  269. return mergedData.value.slice((current - 1) * pageSize, current * pageSize);
  270. });
  271. watchEffect(() => {
  272. nextTick(() => {
  273. const {
  274. total,
  275. pageSize = DEFAULT_PAGE_SIZE
  276. } = mergedPagination.value;
  277. // Dynamic table data
  278. if (mergedData.value.length < total) {
  279. if (mergedData.value.length > pageSize) {
  280. devWarning(false, 'Table', '`dataSource` length is less than `pagination.total` but large than `pagination.pageSize`. Please make sure your config correct data with async mode.');
  281. }
  282. }
  283. });
  284. }, {
  285. flush: 'post'
  286. });
  287. const expandIconColumnIndex = computed(() => {
  288. if (props.showExpandColumn === false) return -1;
  289. // Adjust expand icon index, no overwrite expandIconColumnIndex if set.
  290. if (expandType.value === 'nest' && props.expandIconColumnIndex === undefined) {
  291. return props.rowSelection ? 1 : 0;
  292. } else if (props.expandIconColumnIndex > 0 && props.rowSelection) {
  293. return props.expandIconColumnIndex - 1;
  294. }
  295. return props.expandIconColumnIndex;
  296. });
  297. const rowSelection = ref();
  298. watch(() => props.rowSelection, () => {
  299. rowSelection.value = props.rowSelection ? _extends({}, props.rowSelection) : props.rowSelection;
  300. }, {
  301. deep: true,
  302. immediate: true
  303. });
  304. // ========================== Selections ==========================
  305. const [transformSelectionColumns, selectedKeySet] = useSelection(rowSelection, {
  306. prefixCls,
  307. data: mergedData,
  308. pageData,
  309. getRowKey,
  310. getRecordByKey,
  311. expandType,
  312. childrenColumnName,
  313. locale: tableLocale,
  314. getPopupContainer: computed(() => props.getPopupContainer)
  315. });
  316. const internalRowClassName = (record, index, indent) => {
  317. let mergedRowClassName;
  318. const {
  319. rowClassName
  320. } = props;
  321. if (typeof rowClassName === 'function') {
  322. mergedRowClassName = classNames(rowClassName(record, index, indent));
  323. } else {
  324. mergedRowClassName = classNames(rowClassName);
  325. }
  326. return classNames({
  327. [`${prefixCls.value}-row-selected`]: selectedKeySet.value.has(getRowKey.value(record, index))
  328. }, mergedRowClassName);
  329. };
  330. expose({
  331. selectedKeySet
  332. });
  333. const indentSize = computed(() => {
  334. // Indent size
  335. return typeof props.indentSize === 'number' ? props.indentSize : 15;
  336. });
  337. const transformColumns = innerColumns => {
  338. const res = transformTitleColumns(transformSelectionColumns(transformFilterColumns(transformSorterColumns(transformBasicColumns(innerColumns)))));
  339. return res;
  340. };
  341. return () => {
  342. var _a;
  343. const {
  344. expandIcon = slots.expandIcon || renderExpandIcon(tableLocale.value),
  345. pagination,
  346. loading,
  347. bordered
  348. } = props;
  349. let topPaginationNode;
  350. let bottomPaginationNode;
  351. if (pagination !== false && ((_a = mergedPagination.value) === null || _a === void 0 ? void 0 : _a.total)) {
  352. let paginationSize;
  353. if (mergedPagination.value.size) {
  354. paginationSize = mergedPagination.value.size;
  355. } else {
  356. paginationSize = mergedSize.value === 'small' || mergedSize.value === 'middle' ? 'small' : undefined;
  357. }
  358. const renderPagination = position => _createVNode(Pagination, _objectSpread(_objectSpread({}, mergedPagination.value), {}, {
  359. "class": [`${prefixCls.value}-pagination ${prefixCls.value}-pagination-${position}`, mergedPagination.value.class],
  360. "size": paginationSize
  361. }), null);
  362. const defaultPosition = direction.value === 'rtl' ? 'left' : 'right';
  363. const {
  364. position
  365. } = mergedPagination.value;
  366. if (position !== null && Array.isArray(position)) {
  367. const topPos = position.find(p => p.includes('top'));
  368. const bottomPos = position.find(p => p.includes('bottom'));
  369. const isDisable = position.every(p => `${p}` === 'none');
  370. if (!topPos && !bottomPos && !isDisable) {
  371. bottomPaginationNode = renderPagination(defaultPosition);
  372. }
  373. if (topPos) {
  374. topPaginationNode = renderPagination(topPos.toLowerCase().replace('top', ''));
  375. }
  376. if (bottomPos) {
  377. bottomPaginationNode = renderPagination(bottomPos.toLowerCase().replace('bottom', ''));
  378. }
  379. } else {
  380. bottomPaginationNode = renderPagination(defaultPosition);
  381. }
  382. }
  383. // >>>>>>>>> Spinning
  384. let spinProps;
  385. if (typeof loading === 'boolean') {
  386. spinProps = {
  387. spinning: loading
  388. };
  389. } else if (typeof loading === 'object') {
  390. spinProps = _extends({
  391. spinning: true
  392. }, loading);
  393. }
  394. const wrapperClassNames = classNames(`${prefixCls.value}-wrapper`, {
  395. [`${prefixCls.value}-wrapper-rtl`]: direction.value === 'rtl'
  396. }, attrs.class, hashId.value);
  397. const tableProps = omit(props, ['columns']);
  398. return wrapSSR(_createVNode("div", {
  399. "class": wrapperClassNames,
  400. "style": attrs.style
  401. }, [_createVNode(Spin, _objectSpread({
  402. "spinning": false
  403. }, spinProps), {
  404. default: () => [topPaginationNode, _createVNode(RcTable, _objectSpread(_objectSpread(_objectSpread({}, attrs), tableProps), {}, {
  405. "expandedRowKeys": props.expandedRowKeys,
  406. "defaultExpandedRowKeys": props.defaultExpandedRowKeys,
  407. "expandIconColumnIndex": expandIconColumnIndex.value,
  408. "indentSize": indentSize.value,
  409. "expandIcon": expandIcon,
  410. "columns": mergedColumns.value,
  411. "direction": direction.value,
  412. "prefixCls": prefixCls.value,
  413. "class": classNames({
  414. [`${prefixCls.value}-middle`]: mergedSize.value === 'middle',
  415. [`${prefixCls.value}-small`]: mergedSize.value === 'small',
  416. [`${prefixCls.value}-bordered`]: bordered,
  417. [`${prefixCls.value}-empty`]: rawData.value.length === 0
  418. }),
  419. "data": pageData.value,
  420. "rowKey": getRowKey.value,
  421. "rowClassName": internalRowClassName,
  422. "internalHooks": INTERNAL_HOOKS,
  423. "internalRefs": internalRefs,
  424. "onUpdateInternalRefs": updateInternalRefs,
  425. "transformColumns": transformColumns,
  426. "transformCellText": transformCellText.value
  427. }), _extends(_extends({}, slots), {
  428. emptyText: () => {
  429. var _a, _b;
  430. return ((_a = slots.emptyText) === null || _a === void 0 ? void 0 : _a.call(slots)) || ((_b = props.locale) === null || _b === void 0 ? void 0 : _b.emptyText) || renderEmpty('Table');
  431. }
  432. })), bottomPaginationNode]
  433. })]));
  434. };
  435. }
  436. });
  437. const Table = defineComponent({
  438. name: 'ATable',
  439. inheritAttrs: false,
  440. props: initDefaultProps(tableProps(), {
  441. rowKey: 'key'
  442. }),
  443. slots: Object,
  444. setup(props, _ref2) {
  445. let {
  446. attrs,
  447. slots,
  448. expose
  449. } = _ref2;
  450. const table = ref();
  451. expose({
  452. table
  453. });
  454. return () => {
  455. var _a;
  456. const columns = props.columns || convertChildrenToColumns((_a = slots.default) === null || _a === void 0 ? void 0 : _a.call(slots));
  457. return _createVNode(InternalTable, _objectSpread(_objectSpread(_objectSpread({
  458. "ref": table
  459. }, attrs), props), {}, {
  460. "columns": columns || [],
  461. "expandedRowRender": slots.expandedRowRender || props.expandedRowRender,
  462. "contextSlots": _extends({}, slots)
  463. }), slots);
  464. };
  465. }
  466. });
  467. export default Table;