Table.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. <script lang="tsx">
  2. import { ElTable, ElTableColumn, ElPagination, ComponentSize, ElTooltipProps } from 'element-plus'
  3. import { defineComponent, PropType, ref, computed, unref, watch, onMounted } from 'vue'
  4. import { propTypes } from '@/utils/propTypes'
  5. import { setIndex } from './helper'
  6. import type { TableProps, TableColumn, Pagination, TableSetProps } from './types'
  7. import { set } from 'lodash-es'
  8. import { CSSProperties } from 'vue'
  9. import { getSlot } from '@/utils/tsxHelper'
  10. export default defineComponent({
  11. name: 'Table',
  12. props: {
  13. pageSize: propTypes.number.def(10),
  14. currentPage: propTypes.number.def(1),
  15. // 是否所有的超出隐藏,优先级低于schema中的showOverflowTooltip,
  16. showOverflowTooltip: propTypes.bool.def(true),
  17. // 表头
  18. columns: {
  19. type: Array as PropType<TableColumn[]>,
  20. default: () => []
  21. },
  22. // 展开行
  23. // expand: propTypes.bool.def(false),
  24. // 是否展示分页
  25. pagination: {
  26. type: Object as PropType<Pagination>,
  27. default: (): Pagination | undefined => undefined
  28. },
  29. // 仅对 type=selection 的列有效,类型为 Boolean,为 true 则会在数据更新之后保留之前选中的数据(需指定 row-key)
  30. reserveSelection: propTypes.bool.def(false),
  31. // 加载状态
  32. loading: propTypes.bool.def(false),
  33. // 是否叠加索引
  34. reserveIndex: propTypes.bool.def(false),
  35. // 对齐方式
  36. align: propTypes.string
  37. .validate((v: string) => ['left', 'center', 'right'].includes(v))
  38. .def('left'),
  39. // 表头对齐方式
  40. headerAlign: propTypes.string
  41. .validate((v: string) => ['left', 'center', 'right'].includes(v))
  42. .def('left'),
  43. data: {
  44. type: Array as PropType<Recordable[]>,
  45. default: () => []
  46. },
  47. height: propTypes.oneOfType([Number, String]),
  48. maxHeight: propTypes.oneOfType([Number, String]),
  49. stripe: propTypes.bool.def(false),
  50. border: propTypes.bool.def(true),
  51. size: {
  52. type: String as PropType<ComponentSize>,
  53. validator: (v: ComponentSize) => ['medium', 'small', 'mini'].includes(v)
  54. },
  55. fit: propTypes.bool.def(true),
  56. showHeader: propTypes.bool.def(true),
  57. highlightCurrentRow: propTypes.bool.def(false),
  58. currentRowKey: propTypes.oneOfType([Number, String]),
  59. // row-class-name, 类型为 (row: Recordable, rowIndex: number) => string | string
  60. rowClassName: {
  61. type: [Function, String] as PropType<(row: Recordable, rowIndex: number) => string | string>,
  62. default: ''
  63. },
  64. rowStyle: {
  65. type: [Function, Object] as PropType<
  66. (row: Recordable, rowIndex: number) => Recordable | CSSProperties
  67. >,
  68. default: () => undefined
  69. },
  70. cellClassName: {
  71. type: [Function, String] as PropType<
  72. (row: Recordable, column: any, rowIndex: number) => string | string
  73. >,
  74. default: ''
  75. },
  76. cellStyle: {
  77. type: [Function, Object] as PropType<
  78. (row: Recordable, column: any, rowIndex: number) => Recordable | CSSProperties
  79. >,
  80. default: () => undefined
  81. },
  82. headerRowClassName: {
  83. type: [Function, String] as PropType<(row: Recordable, rowIndex: number) => string | string>,
  84. default: ''
  85. },
  86. headerRowStyle: {
  87. type: [Function, Object] as PropType<
  88. (row: Recordable, rowIndex: number) => Recordable | CSSProperties
  89. >,
  90. default: () => undefined
  91. },
  92. headerCellClassName: {
  93. type: [Function, String] as PropType<
  94. (row: Recordable, column: any, rowIndex: number) => string | string
  95. >,
  96. default: ''
  97. },
  98. headerCellStyle: {
  99. type: [Function, Object] as PropType<
  100. (row: Recordable, column: any, rowIndex: number) => Recordable | CSSProperties
  101. >,
  102. default: () => undefined
  103. },
  104. rowKey: propTypes.string.def('id'),
  105. emptyText: propTypes.string.def('No Data'),
  106. defaultExpandAll: propTypes.bool.def(false),
  107. expandRowKeys: {
  108. type: Array as PropType<string[]>,
  109. default: () => []
  110. },
  111. defaultSort: {
  112. type: Object as PropType<{ prop: string; order: string }>,
  113. default: () => ({})
  114. },
  115. tooltipEffect: {
  116. type: String as PropType<'dark' | 'light'>,
  117. default: 'dark'
  118. },
  119. tooltipOptions: {
  120. type: Object as PropType<
  121. Pick<
  122. ElTooltipProps,
  123. | 'effect'
  124. | 'enterable'
  125. | 'hideAfter'
  126. | 'offset'
  127. | 'placement'
  128. | 'popperClass'
  129. | 'popperOptions'
  130. | 'showAfter'
  131. | 'showArrow'
  132. >
  133. >,
  134. default: () => ({
  135. enterable: true,
  136. placement: 'top',
  137. showArrow: true,
  138. hideAfter: 200,
  139. popperOptions: { strategy: 'fixed' }
  140. })
  141. },
  142. showSummary: propTypes.bool.def(false),
  143. sumText: propTypes.string.def('Sum'),
  144. summaryMethod: {
  145. type: Function as PropType<(param: { columns: any[]; data: any[] }) => any[]>,
  146. default: () => undefined
  147. },
  148. spanMethod: {
  149. type: Function as PropType<
  150. (param: { row: any; column: any; rowIndex: number; columnIndex: number }) => any[]
  151. >,
  152. default: () => undefined
  153. },
  154. selectOnIndeterminate: propTypes.bool.def(true),
  155. indent: propTypes.number.def(16),
  156. lazy: propTypes.bool.def(false),
  157. load: {
  158. type: Function as PropType<(row: Recordable, treeNode: any, resolve: Function) => void>,
  159. default: () => undefined
  160. },
  161. treeProps: {
  162. type: Object as PropType<{ hasChildren?: string; children?: string; label?: string }>,
  163. default: () => ({ hasChildren: 'hasChildren', children: 'children', label: 'label' })
  164. },
  165. tableLayout: {
  166. type: String as PropType<'auto' | 'fixed'>,
  167. default: 'fixed'
  168. },
  169. scrollbarAlwaysOn: propTypes.bool.def(false),
  170. flexible: propTypes.bool.def(false)
  171. },
  172. emits: ['update:pageSize', 'update:currentPage', 'register'],
  173. setup(props, { attrs, emit, slots, expose }) {
  174. const elTableRef = ref<ComponentRef<typeof ElTable>>()
  175. // 注册
  176. onMounted(() => {
  177. const tableRef = unref(elTableRef)
  178. emit('register', tableRef?.$parent, elTableRef)
  179. })
  180. const pageSizeRef = ref(props.pageSize)
  181. const currentPageRef = ref(props.currentPage)
  182. // useTable传入的props
  183. const outsideProps = ref<TableProps>({})
  184. const mergeProps = ref<TableProps>({})
  185. const getProps = computed(() => {
  186. const propsObj = { ...props }
  187. Object.assign(propsObj, unref(mergeProps))
  188. return propsObj
  189. })
  190. const setProps = (props: TableProps = {}) => {
  191. mergeProps.value = Object.assign(unref(mergeProps), props)
  192. outsideProps.value = { ...props } as any
  193. }
  194. const setColumn = (columnProps: TableSetProps[], columnsChildren?: TableColumn[]) => {
  195. const { columns } = unref(getProps)
  196. for (const v of columnsChildren || columns) {
  197. for (const item of columnProps) {
  198. if (v.field === item.field) {
  199. set(v, item.path, item.value)
  200. } else if (v.children?.length) {
  201. setColumn(columnProps, v.children)
  202. }
  203. }
  204. }
  205. }
  206. const addColumn = (column: TableColumn, index?: number) => {
  207. const { columns } = unref(getProps)
  208. if (index) {
  209. columns.splice(index, 0, column)
  210. } else {
  211. columns.push(column)
  212. }
  213. }
  214. const delColumn = (field: string) => {
  215. const { columns } = unref(getProps)
  216. const index = columns.findIndex((item) => item.field === field)
  217. if (index > -1) {
  218. columns.splice(index, 1)
  219. }
  220. }
  221. const selections = ref<Recordable[]>([])
  222. const selectionChange = (selection: Recordable[]) => {
  223. selections.value = selection
  224. }
  225. expose({
  226. setProps,
  227. setColumn,
  228. delColumn,
  229. addColumn,
  230. selections,
  231. elTableRef
  232. })
  233. const pagination = computed(() => {
  234. return Object.assign(
  235. {
  236. small: false,
  237. background: false,
  238. pagerCount: 7,
  239. layout: 'sizes, prev, pager, next, jumper, ->, total',
  240. pageSizes: [10, 20, 30, 40, 50, 100],
  241. disabled: false,
  242. hideOnSinglePage: false,
  243. total: 10
  244. },
  245. unref(getProps).pagination
  246. )
  247. })
  248. watch(
  249. () => unref(getProps).pageSize,
  250. (val: number) => {
  251. pageSizeRef.value = val
  252. }
  253. )
  254. watch(
  255. () => unref(getProps).currentPage,
  256. (val: number) => {
  257. currentPageRef.value = val
  258. }
  259. )
  260. watch(
  261. () => pageSizeRef.value,
  262. (val: number) => {
  263. emit('update:pageSize', val)
  264. }
  265. )
  266. watch(
  267. () => currentPageRef.value,
  268. (val: number) => {
  269. emit('update:currentPage', val)
  270. }
  271. )
  272. const getBindValue = computed(() => {
  273. const bindValue: Recordable = { ...attrs, ...unref(getProps) }
  274. delete bindValue.columns
  275. delete bindValue.data
  276. return bindValue
  277. })
  278. const renderTreeTableColumn = (columnsChildren: TableColumn[]) => {
  279. const { align, headerAlign, showOverflowTooltip } = unref(getProps)
  280. return columnsChildren.map((v) => {
  281. if (v.hidden) return null
  282. const props = { ...v } as any
  283. if (props.children) delete props.children
  284. const children = v.children
  285. const slots = {
  286. default: (...args: any[]) => {
  287. const data = args[0]
  288. return children && children.length
  289. ? renderTreeTableColumn(children)
  290. : props?.slots?.default
  291. ? props.slots.default(args)
  292. : v?.formatter?.(data.row, data.column, data.row[v.field], data.$index) ||
  293. data.row[v.field]
  294. }
  295. }
  296. if (props?.slots?.header) {
  297. slots['header'] = (...args: any[]) => props.slots.header(args)
  298. }
  299. return (
  300. <ElTableColumn
  301. showOverflowTooltip={showOverflowTooltip}
  302. align={align}
  303. headerAlign={headerAlign}
  304. {...props}
  305. prop={v.field}
  306. >
  307. {slots}
  308. </ElTableColumn>
  309. )
  310. })
  311. }
  312. const renderTableColumn = (columnsChildren?: TableColumn[]) => {
  313. const {
  314. columns,
  315. reserveIndex,
  316. pageSize,
  317. currentPage,
  318. align,
  319. headerAlign,
  320. showOverflowTooltip,
  321. reserveSelection
  322. } = unref(getProps)
  323. return (columnsChildren || columns).map((v) => {
  324. if (v.hidden) return null
  325. if (v.type === 'index') {
  326. return (
  327. <ElTableColumn
  328. type="index"
  329. index={
  330. v.index ? v.index : (index) => setIndex(reserveIndex, index, pageSize, currentPage)
  331. }
  332. align={v.align || align}
  333. headerAlign={v.headerAlign || headerAlign}
  334. label={v.label}
  335. width="65px"
  336. ></ElTableColumn>
  337. )
  338. } else if (v.type === 'selection') {
  339. return (
  340. <ElTableColumn
  341. type="selection"
  342. reserveSelection={reserveSelection}
  343. align={align}
  344. headerAlign={headerAlign}
  345. width="50"
  346. ></ElTableColumn>
  347. )
  348. } else {
  349. const props = { ...v } as any
  350. if (props.children) delete props.children
  351. const children = v.children
  352. const slots = {
  353. default: (...args: any[]) => {
  354. const data = args[0]
  355. return children && children.length
  356. ? renderTreeTableColumn(children)
  357. : props?.slots?.default
  358. ? props.slots.default(args)
  359. : v?.formatter?.(data.row, data.column, data.row[v.field], data.$index) ||
  360. data.row[v.field]
  361. }
  362. }
  363. if (props?.slots?.header) {
  364. slots['header'] = (...args: any[]) => props.slots.header(args)
  365. }
  366. return (
  367. <ElTableColumn
  368. showOverflowTooltip={showOverflowTooltip}
  369. align={align}
  370. headerAlign={headerAlign}
  371. {...props}
  372. prop={v.field}
  373. >
  374. {slots}
  375. </ElTableColumn>
  376. )
  377. }
  378. })
  379. }
  380. return () => (
  381. <div v-loading={unref(getProps).loading}>
  382. <ElTable
  383. ref={elTableRef}
  384. data={unref(getProps).data}
  385. onSelection-change={selectionChange}
  386. {...unref(getBindValue)}
  387. >
  388. {{
  389. default: () => renderTableColumn(),
  390. empty: () => getSlot(slots, 'empty') || unref(getProps).emptyText,
  391. append: () => getSlot(slots, 'append')
  392. }}
  393. </ElTable>
  394. {unref(getProps).pagination ? (
  395. <ElPagination
  396. v-model:pageSize={pageSizeRef.value}
  397. v-model:currentPage={currentPageRef.value}
  398. class="mt-10px"
  399. {...unref(pagination)}
  400. ></ElPagination>
  401. ) : undefined}
  402. </div>
  403. )
  404. }
  405. })
  406. </script>