| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136 |
- <div class="fiori-card overflow-hidden flex flex-col" [style.max-height]="maxHeight">
- @if (showSearch) {
- <div class="px-4 py-3 border-b border-surface-100 dark:border-surface-300 flex-shrink-0">
- <div class="relative max-w-sm">
- <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" />
- <input
- type="text"
- [(ngModel)]="searchTerm"
- (input)="currentPage = 1"
- placeholder="搜索..."
- 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"
- />
- @if (searchTerm) {
- <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">
- <fa-icon [icon]="['fas', 'xmark']" size="sm" />
- </button>
- }
- </div>
- </div>
- }
- @if (loading) {
- <div class="p-6 flex-1">
- @for (i of [1,2,3,4,5]; track i) {
- <div class="animate-pulse flex gap-4 py-3 border-b border-surface-50 dark:border-surface-300">
- @for (col of columns; track col.key) {
- <div class="h-4 bg-surface-200 dark:bg-surface-300 rounded flex-1"></div>
- }
- </div>
- }
- </div>
- } @else if (data.length === 0) {
- <div class="py-12 text-center text-surface-400 dark:text-surface-500 flex-1 flex flex-col items-center justify-center">
- <fa-icon [icon]="['fas', 'inbox']" size="2x" class="mb-2" />
- <p class="text-sm">暂无数据</p>
- </div>
- } @else {
- <!-- Scrollable table area -->
- <div class="flex-1 overflow-x-auto">
- <table class="w-full text-sm">
- <thead class="sticky top-0 z-10">
- <tr class="border-b border-surface-200 dark:border-surface-300 bg-surface-50 dark:bg-surface-200">
- @for (col of columns; track col.key) {
- <th
- class="px-4 py-3 text-xs font-semibold text-surface-500 dark:text-surface-500 uppercase tracking-wider whitespace-nowrap"
- [style.width]="col.width || 'auto'"
- [style.text-align]="cellAlign(col)"
- [class.cursor-pointer]="col.sortable"
- (click)="col.sortable && onSort(col.key)"
- >
- <div class="flex items-center gap-1" [class.justify-end]="cellAlign(col) === 'right'" [class.justify-center]="cellAlign(col) === 'center'">
- {{ col.label }}
- @if (col.sortable && sortKey === col.key) {
- <fa-icon [icon]="['fas', sortDirection === 'asc' ? 'sort-up' : 'sort-down']" class="text-surface-400 text-xs" />
- }
- </div>
- </th>
- }
- </tr>
- </thead>
- <tbody>
- @for (row of displayData; track row['id'] ?? row['msgUniqueIdentifier'] ?? $index) {
- <tr
- class="border-b border-surface-50 dark:border-surface-200 hover:bg-surface-50 dark:hover:bg-surface-200 transition-colors group"
- [class.cursor-pointer]="rowClickable"
- (click)="onRowClick(row)"
- >
- @for (col of columns; track col.key) {
- <td
- class="px-4 py-3 text-surface-900 dark:text-surface-900 font-medium whitespace-nowrap overflow-hidden text-ellipsis"
- [style.width]="col.width || 'auto'"
- [style.max-width]="col.width || '200px'"
- [style.text-align]="cellAlign(col)"
- [title]="formatCellValue(row, col)"
- >
- @if (col.template === 'status') {
- <app-status-badge [status]="row[col.key]" />
- } @else if (col.template === 'action') {
- <div class="flex items-center gap-3 justify-end opacity-0 group-hover:opacity-100 transition-opacity">
- <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="">
- 查看
- </button>
- <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="">
- 编辑
- </button>
- </div>
- } @else {
- {{ formatCellValue(row, col) }}
- }
- </td>
- }
- </tr>
- }
- </tbody>
- </table>
- </div>
- @if (showPagination && totalPages > 1) {
- <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">
- <span class="text-xs text-surface-500 dark:text-surface-500">
- 共 {{ filteredData.length }} 条,第 {{ currentPage }}/{{ totalPages }} 页
- </span>
- <div class="flex items-center gap-1">
- <button
- (click)="goToPage(currentPage - 1)"
- [disabled]="currentPage === 1"
- 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"
- >
- <fa-icon [icon]="['fas', 'chevron-left']" size="xs" />
- </button>
- @for (p of pages; track p) {
- <button
- (click)="goToPage(p)"
- class="w-8 h-8 flex items-center justify-center rounded text-sm"
- [class.bg-primary-600]="p === currentPage"
- [class.text-white]="p === currentPage"
- [class.text-surface-700]="p !== currentPage"
- [class.dark:text-surface-700]="p !== currentPage"
- [class.hover:bg-surface-100]="p !== currentPage"
- [class.dark:hover:bg-surface-200]="p !== currentPage"
- >
- {{ p }}
- </button>
- }
- <button
- (click)="goToPage(currentPage + 1)"
- [disabled]="currentPage === totalPages"
- 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"
- >
- <fa-icon [icon]="['fas', 'chevron-right']" size="xs" />
- </button>
- </div>
- </div>
- }
- }
- </div>
|