TableHeader.vue 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. <script setup lang="ts">
  2. import symbolKeys from "@@@/table/symbol"
  3. import VbTr from "@@@/table/partials/header/HeaderTr.vue"
  4. import type { Sort, Header, HeaderEx } from "@@@/table/models"
  5. const propOpts = inject(symbolKeys.header, {
  6. columns: ref([]),
  7. hasCheckbox: true,
  8. isMultipleCheck: true,
  9. sortField: "",
  10. sortOrder: "asc",
  11. headerClass: "fixed",
  12. thTrClass: "text-center text-gray-800 fw-bold fs-7"
  13. })
  14. const { columns } = propOpts
  15. const sort = ref<Sort>({
  16. label: propOpts.sortField,
  17. order: propOpts.sortOrder as "asc" | "desc"
  18. })
  19. const columnList = ref<HeaderEx[][]>([])
  20. const headerColumns = ref<HeaderEx[][]>([])
  21. const rowspan = computed(() => {
  22. return columnList.value.length
  23. })
  24. const convert = (headers: Header[], deep: number): HeaderEx[][] => {
  25. const allLists: HeaderEx[][] = []
  26. const recursion = (h: Header[], d: number): HeaderEx[] => {
  27. if (!allLists[d]) {
  28. allLists[d] = []
  29. }
  30. const currentLevelList = allLists[d]
  31. const currentChildren: HeaderEx[] = []
  32. h.forEach((v) => {
  33. if (
  34. v.visible === false ||
  35. (v.children && v.children.every((child) => child.visible === false))
  36. ) {
  37. return
  38. }
  39. const item: HeaderEx = Object.assign({}, v, {
  40. rowspan: (rs: number) => rs - d,
  41. colspan: v.children?.length ?? 1,
  42. children: [] as HeaderEx[]
  43. })
  44. // 处理有子级的列:行合并为1,递归处理子级
  45. if (v.children && v.children.length > 0) {
  46. delete item.isSort
  47. item.rowspan = 1 // 有子级的列,自身行合并为1
  48. const childItems = recursion(v.children, d + 1)
  49. item.children = childItems
  50. }
  51. currentChildren.push(item)
  52. currentLevelList.push(item)
  53. })
  54. const calcColspan = (children?: HeaderEx[]): number => {
  55. let totalColspan = 0
  56. if (!children || children.length === 0) return totalColspan
  57. children.forEach((child) => {
  58. totalColspan +=
  59. child.children && child.children.length > 0 ? calcColspan(child.children) : child.colspan
  60. })
  61. return totalColspan
  62. }
  63. currentLevelList.forEach((item) => {
  64. if (item.children && item.children.length > 0) {
  65. item.colspan = calcColspan(item.children) // 子级列数求和作为当前列colspan
  66. } else {
  67. item.colspan = 1 // 无子级的列,列合并为1
  68. }
  69. })
  70. return currentChildren
  71. }
  72. recursion(headers, deep)
  73. return allLists
  74. }
  75. watch(
  76. () => columns.value,
  77. async () => {
  78. await nextTick() // 等待DOM更新,确保计算准确
  79. columnList.value = []
  80. headerColumns.value = []
  81. if (columns.value.some((v) => v.children && v.children.length > 0)) {
  82. const convertedLists = convert(columns.value, 0)
  83. columnList.value = convertedLists
  84. headerColumns.value = convertedLists
  85. } else {
  86. const flatColumns = columns.value.filter((v) => v.visible !== false) as HeaderEx[]
  87. flatColumns.forEach((item) => {
  88. item.rowspan = rowspan.value
  89. item.colspan = 1
  90. })
  91. columnList.value = [flatColumns]
  92. headerColumns.value = [flatColumns]
  93. }
  94. },
  95. {
  96. immediate: true, // 初始化执行一次
  97. deep: true // 深度监听columns(包括children变化)
  98. }
  99. )
  100. </script>
  101. <template>
  102. <thead :class="propOpts.headerClass">
  103. <template v-for="(v, i) in headerColumns" :key="i">
  104. <vb-tr
  105. :rowspan="rowspan"
  106. :columns="v"
  107. :sort="sort"
  108. :has-checkbox="i === 0 && propOpts.hasCheckbox"
  109. :is-multiple-check="propOpts.isMultipleCheck"
  110. :th-tr-class="propOpts.thTrClass">
  111. <template v-for="(_, name) in $slots" #[name]="{ row }">
  112. <slot :name="name" :row="row" />
  113. </template>
  114. </vb-tr>
  115. </template>
  116. </thead>
  117. </template>