NormalTable.vue 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. <template>
  2. <n-data-table class="normal-table"
  3. :columns="columns as DataTableColumns"
  4. :data="tableData"
  5. :single-line="false"
  6. />
  7. </template>
  8. <script setup lang="ts">
  9. import { h } from 'vue'
  10. import { NDataTable, DataTableColumn, DataTableColumns, DataTableColumnGroup, DataTableBaseColumn, DataTableSelectionColumn, DataTableExpandColumn } from 'naive-ui'
  11. interface ChildColumnConfig {
  12. title: string;
  13. key?: string;
  14. children?: ChildTableConfig;
  15. align?: 'left' | 'right' | 'center'; // 列文本对齐方式,默认left
  16. titleAlign?: 'left' | 'right' | 'center'; // 列标题对齐方式,默认同文本
  17. minWidth?: number | string;
  18. maxWidth?: number | string;
  19. width?: number | string;
  20. vertical?: boolean; // 控制标题文件的显示方向为纵向
  21. }
  22. interface ColumnConfig extends ChildColumnConfig {
  23. // 类型选择,不填写默认为普通列
  24. // selection: 选项列,用于勾选数据行
  25. // expand: 可展开列,用于展开行
  26. type?: 'selection' | 'expand';
  27. // selection参数
  28. selectionMultiple?: boolean; // 默认true,false-单选,true-多选
  29. }
  30. type ChildTableConfig = (string | ChildColumnConfig)[]
  31. type TableConfig = (string | ColumnConfig)[]
  32. // type TableRowData = Record<string, unknown>;
  33. type TableRowData = CellDataType;
  34. // type TableColumn = DataTableColumn<TableRowData>;
  35. // type TableColumns = DataTableColumns<TableRowData>;
  36. type TableBaseColumn = DataTableBaseColumn<TableRowData>;
  37. // type TableColumnGroup = DataTableColumnGroup<TableRowData>;
  38. type TableSelectionColumn = DataTableSelectionColumn<TableRowData>;
  39. type TableExpandColumn = DataTableExpandColumn<TableRowData>;
  40. // 规避naive-ui库bug,children类型定义错误
  41. interface DataTableColumnGroupEx<T = TableRowData> extends Omit<DataTableColumnGroup<T>, 'children'> {
  42. children: ChildTableColumns
  43. }
  44. type DataTableColumnEx<T = TableRowData> = Exclude<DataTableColumn<T>, DataTableColumnGroup<T>> | DataTableColumnGroupEx<T>;
  45. type TableColumnGroup = DataTableColumnGroupEx<TableRowData>;
  46. type TableColumn = DataTableColumnEx<TableRowData>;
  47. type TableColumns = Array<DataTableColumnEx<TableRowData>>;
  48. type ChildTableColumn = DataTableBaseColumn<TableRowData> | DataTableColumnGroupEx<TableRowData>;
  49. type ChildTableColumns = Array<(DataTableBaseColumn<TableRowData> | DataTableColumnGroupEx<TableRowData>)>;
  50. interface CellDataExt {
  51. text: string;
  52. colspan?: number | undefined; // 向右合并 N 个单元格
  53. rowspan?: number | undefined; // 向下合并 N 个单元格
  54. }
  55. type CellDataType = {
  56. [title: string]: string | CellDataExt,
  57. } & {
  58. // expand参数
  59. _expandable?: boolean;
  60. _expandString?: string;
  61. }
  62. interface Props {
  63. config?: TableConfig;
  64. data?: CellDataType[];
  65. }
  66. const props = withDefaults(defineProps<Props>(), {
  67. config: () => [],
  68. data: () => []
  69. })
  70. const handleRowSpan = (rowData: TableRowData, rowIndex: number, key: string): number => {
  71. const data = props.data[rowIndex]
  72. if (!data) return 1
  73. const keyValue = data[key]
  74. if (!keyValue) return 1
  75. if (typeof keyValue === "string") return 1
  76. return keyValue.rowspan ?? 1
  77. }
  78. const handleColSpan = (rowData: TableRowData, rowIndex: number, key: string): number => {
  79. const data = props.data[rowIndex]
  80. if (!data) return 1
  81. const keyValue = data[key]
  82. if (!keyValue) return 1
  83. if (typeof keyValue === "string") return 1
  84. return keyValue.colspan ?? 1
  85. }
  86. function createStringColumn(column: string, parentKey?: string): TableBaseColumn {
  87. const key = parentKey ? parentKey + `.${column}` : column
  88. const columnObj: TableBaseColumn = {
  89. title: column,
  90. key,
  91. rowSpan: (rowData, rowIndex) => {
  92. return handleRowSpan(rowData, rowIndex, key)
  93. },
  94. colSpan: (rowData, rowIndex) => {
  95. return handleColSpan(rowData, rowIndex, key)
  96. }
  97. }
  98. return columnObj
  99. }
  100. // 标题纵向渲染
  101. const verticalTitle = (text: string) => () =>
  102. h('div', {
  103. style: {
  104. writingMode: 'vertical-rl',
  105. transform: 'rotate(180deg)',
  106. whiteSpace: 'nowrap',
  107. textAlign: 'center'
  108. }
  109. }, text)
  110. function createBaseColumn(column: ChildColumnConfig, parentKey?: string): TableBaseColumn | TableColumnGroup {
  111. const columnKey = column.key ? column.key : column.title
  112. const key = parentKey ? parentKey + `.${columnKey}` : columnKey
  113. const title = column.vertical ? verticalTitle(column.title) : column.title
  114. const columnObj: TableBaseColumn | TableColumnGroup = {
  115. title,
  116. key,
  117. align: column.align,
  118. titleAlign: column.titleAlign,
  119. minWidth: column.minWidth,
  120. maxWidth: column.maxWidth,
  121. width: column.width,
  122. ...(() => {
  123. if (column.children === undefined) {
  124. return {
  125. rowSpan: (rowData, rowIndex) => {
  126. return handleRowSpan(rowData, rowIndex, key)
  127. },
  128. colSpan: (rowData, rowIndex) => {
  129. return handleColSpan(rowData, rowIndex, key)
  130. }
  131. }
  132. } else {
  133. const children = traversalChildren(column.children, key)
  134. return children ? { children } : {}
  135. }
  136. })()
  137. }
  138. return columnObj
  139. }
  140. function createSelectionColumn(column: ColumnConfig): TableSelectionColumn {
  141. const columnObj: TableSelectionColumn = {
  142. type: 'selection',
  143. multiple: column.selectionMultiple
  144. }
  145. return columnObj
  146. }
  147. function createExpandColumn(column: ColumnConfig): TableExpandColumn {
  148. const columnObj: TableExpandColumn = {
  149. type: 'expand',
  150. expandable: (rowData: TableRowData) => {
  151. return rowData._expandable ? rowData._expandable : false
  152. },
  153. renderExpand: (rowData: TableRowData, rowIndex: number) => {
  154. if (rowData._expandable === undefined || rowData._expandable === false) return undefined
  155. return rowData._expandString
  156. }
  157. }
  158. return columnObj
  159. }
  160. function traversalChildren(child: ChildTableConfig | undefined, parentKey?: string): ChildTableColumns | undefined {
  161. if (child === undefined) return undefined
  162. return child.map(item => {
  163. let obj: TableColumn | undefined = undefined
  164. if (typeof item === 'string') {
  165. obj = { ...createStringColumn(item, parentKey) }
  166. } else {
  167. obj = { ...createBaseColumn(item, parentKey) }
  168. }
  169. return obj
  170. }).filter(item => item !== undefined)
  171. }
  172. function createColumns(config: TableConfig): TableColumns {
  173. return config.map(column => {
  174. let obj: TableColumn | undefined = undefined
  175. if (typeof column === 'string') {
  176. obj = { ...createStringColumn(column) }
  177. } else if (column.type === 'selection') {
  178. obj = { ...createSelectionColumn(column) }
  179. } else if (column.type === 'expand') {
  180. obj = { ...createExpandColumn(column) }
  181. } else {
  182. obj = { ...createBaseColumn(column) }
  183. }
  184. return obj
  185. }).filter(item => item !== undefined)
  186. }
  187. function createData(data: CellDataType[]) {
  188. return data.map((row, index) => {
  189. let obj = {
  190. key: index
  191. }
  192. for (const key in row) {
  193. const value = row[key as keyof typeof row];
  194. if (typeof value === "string") {
  195. obj[key] = value
  196. } else {
  197. obj[key] = value.text
  198. }
  199. }
  200. return obj
  201. })
  202. }
  203. const columns = createColumns(props.config)
  204. const tableData = createData(props.data)
  205. </script>
  206. <style scoped>
  207. /* 覆盖.vp-doc table */
  208. .normal-table :deep(table) {
  209. margin: 0 0 !important;
  210. }
  211. .normal-table :deep(td) {
  212. white-space: pre-wrap; /* 保留换行和空格 */
  213. }
  214. </style>