data-table.component.html 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. <div class="fiori-card overflow-hidden flex flex-col" [style.max-height]="maxHeight">
  2. @if (showSearch) {
  3. <div class="px-4 py-3 border-b border-surface-100 dark:border-surface-300 flex-shrink-0">
  4. <div class="relative max-w-sm">
  5. <fa-icon [icon]="['fas', 'search']" class="absolute left-3 top-1/2 -translate-y-1/2 text-surface-400 dark:text-surface-500 text-sm" />
  6. <input
  7. type="text"
  8. [(ngModel)]="searchTerm"
  9. (input)="currentPage = 1"
  10. placeholder="搜索..."
  11. class="w-full h-9 pl-9 pr-8 rounded-lg border border-surface-200 dark:border-surface-300 bg-white dark:bg-surface-100 text-sm text-surface-800 dark:text-surface-800 placeholder-surface-400 dark:placeholder-surface-500 outline-none focus:border-primary-400 focus:ring-1 focus:ring-primary-100 dark:focus:ring-primary-100/20"
  12. />
  13. @if (searchTerm) {
  14. <button (click)="clearSearch()" class="absolute right-2 top-1/2 -translate-y-1/2 text-surface-400 dark:text-surface-500 hover:text-surface-600 dark:hover:text-surface-700">
  15. <fa-icon [icon]="['fas', 'xmark']" size="sm" />
  16. </button>
  17. }
  18. </div>
  19. </div>
  20. }
  21. @if (loading) {
  22. <div class="p-6 flex-1">
  23. @for (i of [1,2,3,4,5]; track i) {
  24. <div class="animate-pulse flex gap-4 py-3 border-b border-surface-50 dark:border-surface-300">
  25. @for (col of columns; track col.key) {
  26. <div class="h-4 bg-surface-200 dark:bg-surface-300 rounded flex-1"></div>
  27. }
  28. </div>
  29. }
  30. </div>
  31. } @else if (data.length === 0) {
  32. <div class="py-12 text-center text-surface-400 dark:text-surface-500 flex-1 flex flex-col items-center justify-center">
  33. <fa-icon [icon]="['fas', 'inbox']" size="2x" class="mb-2" />
  34. <p class="text-sm">暂无数据</p>
  35. </div>
  36. } @else {
  37. <!-- Scrollable table area -->
  38. <div class="flex-1 overflow-x-auto">
  39. <table class="w-full text-sm">
  40. <thead class="sticky top-0 z-10">
  41. <tr class="border-b border-surface-200 dark:border-surface-300 bg-surface-50 dark:bg-surface-200">
  42. @for (col of columns; track col.key) {
  43. <th
  44. class="px-4 py-3 text-xs font-semibold text-surface-500 dark:text-surface-500 uppercase tracking-wider whitespace-nowrap"
  45. [style.width]="col.width || 'auto'"
  46. [style.text-align]="cellAlign(col)"
  47. [class.cursor-pointer]="col.sortable"
  48. (click)="col.sortable && onSort(col.key)"
  49. >
  50. <div class="flex items-center gap-1" [class.justify-end]="cellAlign(col) === 'right'" [class.justify-center]="cellAlign(col) === 'center'">
  51. {{ col.label }}
  52. @if (col.sortable && sortKey === col.key) {
  53. <fa-icon [icon]="['fas', sortDirection === 'asc' ? 'sort-up' : 'sort-down']" class="text-surface-400 text-xs" />
  54. }
  55. </div>
  56. </th>
  57. }
  58. </tr>
  59. </thead>
  60. <tbody>
  61. @for (row of displayData; track row['id'] ?? row['msgUniqueIdentifier'] ?? $index) {
  62. <tr
  63. class="border-b border-surface-50 dark:border-surface-200 hover:bg-surface-50 dark:hover:bg-surface-200 transition-colors group"
  64. [class.cursor-pointer]="rowClickable"
  65. (click)="onRowClick(row)"
  66. >
  67. @for (col of columns; track col.key) {
  68. <td
  69. class="px-4 py-3 text-surface-900 dark:text-surface-900 font-medium whitespace-nowrap overflow-hidden text-ellipsis"
  70. [style.width]="col.width || 'auto'"
  71. [style.max-width]="col.width || '200px'"
  72. [style.text-align]="cellAlign(col)"
  73. [title]="formatCellValue(row, col)"
  74. >
  75. @if (col.template === 'status') {
  76. <app-status-badge [status]="row[col.key]" />
  77. } @else if (col.template === 'action') {
  78. <div class="flex items-center gap-3 justify-end opacity-0 group-hover:opacity-100 transition-opacity">
  79. <button (click)="onActionClick($event, 'view', row)" class="text-primary-600 hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300 font-medium transition-colors cursor-pointer" title="">
  80. 查看
  81. </button>
  82. <button (click)="onActionClick($event, 'edit', row)" class="text-primary-600 hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300 font-medium transition-colors cursor-pointer" title="">
  83. 编辑
  84. </button>
  85. </div>
  86. } @else {
  87. {{ formatCellValue(row, col) }}
  88. }
  89. </td>
  90. }
  91. </tr>
  92. }
  93. </tbody>
  94. </table>
  95. </div>
  96. @if (showPagination && totalPages > 1) {
  97. <div class="flex items-center justify-between px-4 py-3 border-t border-surface-100 dark:border-surface-300 flex-shrink-0 bg-white dark:bg-surface-100">
  98. <span class="text-xs text-surface-500 dark:text-surface-500">
  99. 共 {{ filteredData.length }} 条,第 {{ currentPage }}/{{ totalPages }} 页
  100. </span>
  101. <div class="flex items-center gap-1">
  102. <button
  103. (click)="goToPage(currentPage - 1)"
  104. [disabled]="currentPage === 1"
  105. class="w-8 h-8 flex items-center justify-center rounded text-sm text-surface-500 dark:text-surface-500 hover:bg-surface-100 dark:hover:bg-surface-200 disabled:opacity-30 disabled:cursor-not-allowed"
  106. >
  107. <fa-icon [icon]="['fas', 'chevron-left']" size="xs" />
  108. </button>
  109. @for (p of pages; track p) {
  110. <button
  111. (click)="goToPage(p)"
  112. class="w-8 h-8 flex items-center justify-center rounded text-sm"
  113. [class.bg-primary-600]="p === currentPage"
  114. [class.text-white]="p === currentPage"
  115. [class.text-surface-700]="p !== currentPage"
  116. [class.dark:text-surface-700]="p !== currentPage"
  117. [class.hover:bg-surface-100]="p !== currentPage"
  118. [class.dark:hover:bg-surface-200]="p !== currentPage"
  119. >
  120. {{ p }}
  121. </button>
  122. }
  123. <button
  124. (click)="goToPage(currentPage + 1)"
  125. [disabled]="currentPage === totalPages"
  126. class="w-8 h-8 flex items-center justify-center rounded text-sm text-surface-500 dark:text-surface-500 hover:bg-surface-100 dark:hover:bg-surface-200 disabled:opacity-30 disabled:cursor-not-allowed"
  127. >
  128. <fa-icon [icon]="['fas', 'chevron-right']" size="xs" />
  129. </button>
  130. </div>
  131. </div>
  132. }
  133. }
  134. </div>