index.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. <template>
  2. <el-table
  3. ref="table"
  4. v-bind="tableProps"
  5. v-el-table-infinite-scroll="scrollOver"
  6. :data="dealedTableData"
  7. >
  8. <el-table-column
  9. v-if="sequence || customSequence"
  10. :prop="customSequence ? 'index' : undefined"
  11. :type="customSequence ? undefined : 'index'"
  12. :fixed="hasFixedColumn"
  13. :width="50"
  14. align="center"
  15. >
  16. <template #header>
  17. <TableHeaderCell label="序号" />
  18. </template>
  19. </el-table-column>
  20. <el-table-column
  21. v-for="column in tableColumns"
  22. :key="column.columnName"
  23. v-bind="computedColumnProps(column)"
  24. >
  25. <template #header>
  26. <TableHeaderCell
  27. v-model:filter-values="filterValueMap[column.columnName]"
  28. v-model:sort-rule="sortRuleMap[column.columnName]"
  29. :label="labelFormatter(column.columnLabel)"
  30. :desc="column.columnDescribe"
  31. :show-desc="column.showDesc"
  32. :filter-options="filterOptionMap[column.columnName]"
  33. :sortable="!!column.needSort"
  34. filter-style="arrow"
  35. @update:sort-rule="
  36. sortRule => {
  37. sortRuleChangeHandler(column.columnName, sortRule)
  38. }
  39. "
  40. />
  41. </template>
  42. <template v-if="column.customRender" #default="scope">
  43. <component :is="column.customRender(scope)" />
  44. </template>
  45. </el-table-column>
  46. </el-table>
  47. </template>
  48. <script setup lang="ts">
  49. import TableHeaderCell from '@/components/TableHeaderCell/index.vue'
  50. import type { CSSProperties, VNode } from 'vue'
  51. import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
  52. import { CommonData, CommonTableColumn } from '~/common'
  53. import { Options, useTableFilterAndSort } from '@/hooks/useTableFilterAndSort'
  54. import { ElTable } from 'element-plus'
  55. import { useTableSettingsStore } from '@/store/tableSettings'
  56. type SummaryMethod<T> = (data: {
  57. columns: TableColumnCtx<T>[]
  58. data: T[]
  59. }) => string[]
  60. type ColumnCls<T> = string | ((data: { row: T; rowIndex: number }) => string)
  61. type ColumnStyle<T> =
  62. | CSSProperties
  63. | ((data: { row: T; rowIndex: number }) => CSSProperties)
  64. type CellCls<T> =
  65. | string
  66. | ((data: {
  67. row: T
  68. rowIndex: number
  69. column: TableColumnCtx<T>
  70. columnIndex: number
  71. }) => string)
  72. type CellStyle<T> =
  73. | CSSProperties
  74. | ((data: {
  75. row: T
  76. rowIndex: number
  77. column: TableColumnCtx<T>
  78. columnIndex: number
  79. }) => CSSProperties)
  80. type Sort = {
  81. prop: string
  82. order: 'ascending' | 'descending'
  83. init?: any
  84. silent?: any
  85. }
  86. type TreeNode = {
  87. expanded?: boolean
  88. loading?: boolean
  89. noLazyChildren?: boolean
  90. indent?: number
  91. level?: number
  92. display?: boolean
  93. }
  94. type Layout = 'fixed' | 'auto'
  95. type TableColumnProps<T> = {
  96. type?: string
  97. index?: number | ((index: number) => number)
  98. columnKey?: string
  99. width?: string | number
  100. minWidth?: string | number
  101. fixed?: boolean | string
  102. renderHeader?: (data: { column: TableColumnCtx<T>; $index: number }) => VNode
  103. resizable?: boolean
  104. formatter?: (
  105. row: T,
  106. column: TableColumnCtx<T>,
  107. cellValue: any,
  108. index: number
  109. ) => VNode | string
  110. showOverflowTooltip?: boolean
  111. align?: string
  112. headerAlign?: string
  113. className?: string
  114. labelClassName?: string
  115. selectable?: (row: T, index: number) => boolean
  116. reserveSelection?: boolean
  117. }
  118. const props = withDefaults(
  119. defineProps<{
  120. data: CommonData[]
  121. size?: string
  122. width?: string | number
  123. height?: string | number
  124. maxHeight?: string | number
  125. fit?: boolean
  126. stripe?: boolean
  127. border?: boolean
  128. rowKey?: string | ((row: CommonData) => string)
  129. showHeader?: boolean
  130. showSummary?: boolean
  131. sumText?: string
  132. summaryMethod?: SummaryMethod<CommonData>
  133. rowClassName?: ColumnCls<CommonData>
  134. rowStyle?: ColumnStyle<CommonData>
  135. cellClassName?: CellCls<CommonData>
  136. cellStyle?: CellStyle<CommonData>
  137. headerRowClassName?: ColumnCls<CommonData>
  138. headerRowStyle?: ColumnStyle<CommonData>
  139. headerCellClassName?: CellCls<CommonData>
  140. headerCellStyle?: CellStyle<CommonData>
  141. highlightCurrentRow?: boolean
  142. currentRowKey?: string | number
  143. emptyText?: string
  144. expandRowKeys?: any[]
  145. defaultExpandAll?: boolean
  146. defaultSort?: Sort
  147. tooltipEffect?: string
  148. spanMethod?: (data: {
  149. row: CommonData
  150. rowIndex: number
  151. column: TableColumnCtx<CommonData>
  152. columnIndex: number
  153. }) =>
  154. | number[]
  155. | {
  156. rowspan: number
  157. colspan: number
  158. }
  159. | undefined
  160. selectOnIndeterminate?: boolean
  161. indent?: number
  162. treeProps?: {
  163. hasChildren?: string
  164. children?: string
  165. }
  166. lazy?: boolean
  167. load?: (
  168. row: CommonData,
  169. treeNode: TreeNode,
  170. resolve: (data: CommonData[]) => void
  171. ) => void
  172. className?: string
  173. style?: CSSProperties
  174. tableLayout?: Layout
  175. flexible?: boolean
  176. scrollbarAlwaysOn?: boolean
  177. // 上面是el-table原生属性,下面是自定义属性
  178. columnProps?: TableColumnProps<CommonData>
  179. columns: (CommonTableColumn & TableColumnProps<CommonData>)[]
  180. sequence?: boolean
  181. customSequence?: boolean
  182. filterSortOptions?: Options
  183. cacheKeys?: string[]
  184. labelFormatter?: (label: string) => string
  185. }>(),
  186. {
  187. size: 'default',
  188. height: '100%',
  189. maxHeight: '100%',
  190. stripe: true,
  191. border: true,
  192. fit: true,
  193. showHeader: true,
  194. labelFormatter: (label: string) => label,
  195. }
  196. )
  197. const defaultSummaryMethod: SummaryMethod<CommonData> = ({ columns, data }) => {
  198. const sums: string[] = []
  199. columns.forEach((column, index) => {
  200. const countColumn = tableColumns.value.find(
  201. col => column.property === col.columnName && col.needCount
  202. )
  203. if (countColumn) {
  204. const sumNumber = data.reduce((prev: number, curr: CommonData) => {
  205. const cellData = curr[column.property]
  206. if (countColumn.countMode === 'all') {
  207. return prev + 1
  208. }
  209. if (countColumn.countMode === 'notNull') {
  210. return cellData ? prev + 1 : prev
  211. }
  212. const value = Number(cellData)
  213. if (!Number.isNaN(value)) {
  214. prev += value
  215. }
  216. return prev
  217. }, 0)
  218. sums[index] = sumNumber.toString()
  219. }
  220. })
  221. sums[0] = '合计:' + (sums[0] ?? '')
  222. return sums
  223. }
  224. const tableProps = computed(() => {
  225. const rawProps = toRaw(props)
  226. const result: { [x: string]: any } = {}
  227. Object.entries(rawProps).forEach(([key, value]) => {
  228. if (
  229. ![
  230. 'columnProps',
  231. 'columns',
  232. 'sequence',
  233. 'customSequence',
  234. 'filterSortOptions',
  235. 'cacheKeys',
  236. 'labelFormatter',
  237. ].includes(key) &&
  238. (value ?? '') !== ''
  239. ) {
  240. result[key] = value
  241. }
  242. if (props.columns.some(column => column.needCount)) {
  243. result.showSummary = true
  244. }
  245. if (!result.summaryMethod) {
  246. result.summaryMethod = defaultSummaryMethod
  247. }
  248. })
  249. return result
  250. })
  251. const computedColumnProps = computed(() => {
  252. const defaultColumnProps: TableColumnProps<CommonData> = {
  253. align: 'center',
  254. }
  255. return (column: CommonTableColumn & TableColumnProps<CommonData>) => ({
  256. ...defaultColumnProps,
  257. ...props.columnProps,
  258. ...column,
  259. })
  260. })
  261. const tableColumns = ref<CommonTableColumn[]>([])
  262. const tableData = ref<CommonData[]>([])
  263. watchEffect(() => {
  264. tableColumns.value = props.columns.reduce(
  265. (prevColumns: CommonTableColumn[], column) => {
  266. if (!column.hidden) {
  267. prevColumns.push({
  268. label: column.columnLabel,
  269. prop: column.columnName,
  270. ...column,
  271. })
  272. }
  273. return prevColumns
  274. },
  275. []
  276. )
  277. tableData.value = props.data
  278. })
  279. const hasFixedColumn = computed(() =>
  280. tableColumns.value.some(column => column.fixed)
  281. )
  282. const {
  283. filterOptionMap,
  284. filterValueMap,
  285. sortRuleMap,
  286. dealedTableData,
  287. sortChangeHandler,
  288. } = useTableFilterAndSort(tableColumns, tableData, props.filterSortOptions)
  289. const { saveTableFilterValues } = useTableSettingsStore()
  290. watch(
  291. sortRuleMap,
  292. map => {
  293. emit('sortRuleChange', map)
  294. },
  295. { deep: true }
  296. )
  297. const sortRuleChangeHandler = (columnName: string, sortRule: string) => {
  298. sortRuleMap[columnName] = sortRule
  299. sortChangeHandler(columnName, sortRule)
  300. }
  301. if (props.cacheKeys?.length) {
  302. watch(filterValueMap, map => {
  303. const values: { [x: string]: string[] } = {}
  304. props.cacheKeys!.forEach(columnName => {
  305. values[columnName] = map[columnName]
  306. })
  307. saveTableFilterValues(values)
  308. })
  309. }
  310. // 组件的inheritAttrs属性默认为true,此时组件上的属性(包括v-on)会被添加到根元素上
  311. const emit = defineEmits([
  312. 'sortRuleChange',
  313. 'scrollOver',
  314. ])
  315. const scrollOver = () => {
  316. emit('scrollOver')
  317. }
  318. const table = ref<InstanceType<typeof ElTable> | null>(null)
  319. defineExpose({
  320. table,
  321. })
  322. </script>
  323. <style scoped lang="scss">
  324. .el-table :deep {
  325. .el-table__cell {
  326. padding: 0;
  327. height: 40px;
  328. &.cell-filter {
  329. position: relative;
  330. &::before {
  331. content: '';
  332. position: absolute;
  333. width: 100%;
  334. height: 100%;
  335. top: 0;
  336. left: 0;
  337. z-index: 1;
  338. }
  339. &.cell-filter-yellow::before {
  340. opacity: 0.47;
  341. background-color: #eef3d6;
  342. }
  343. &.cell-filter-green::before {
  344. opacity: 0.73;
  345. background-color: #eef3d6;
  346. }
  347. &.cell-filter-cyan::before {
  348. opacity: 0.73;
  349. background-color: #d6e6f3;
  350. }
  351. .cell {
  352. position: relative;
  353. z-index: 2;
  354. }
  355. }
  356. .cell {
  357. padding: 0;
  358. font-size: 14px;
  359. color: #101116;
  360. font-family: DIN, Microsoft YaHei;
  361. &:not(.el-tooltip) {
  362. white-space: pre-line;
  363. }
  364. }
  365. }
  366. .el-table__header .el-table__cell {
  367. background: #ffffff;
  368. font-weight: bold;
  369. .cell {
  370. height: 100%;
  371. }
  372. }
  373. .el-table__body {
  374. .el-table__row--striped .el-table__cell {
  375. background-color: #f0f3f7;
  376. }
  377. .el-table__cell.cell-click .cell {
  378. color: #2d67e3;
  379. cursor: pointer;
  380. }
  381. }
  382. .el-scrollbar__bar {
  383. &.is-horizontal {
  384. height: 15px;
  385. }
  386. &.is-vertical {
  387. width: 15px;
  388. }
  389. }
  390. }
  391. </style>