Table.vue 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. <template>
  2. <table class = "row-hover">
  3. <thead>
  4. <th v-for="(header, index) in visibleHeaders" :key="index" @click="toggleSort(header)" :class="{ sortable: header.sortable }" :style="{ width: header.width || 'auto' }">
  5. {{ header.label || header.key }}
  6. <span v-if="header.sortable" style:= "width: 1.5em; display: inline-block; text-align: center;">
  7. <span v-if="sortBy === header.key">
  8. <span v-if="sortDir === 'asc'">▲</span>
  9. <span v-else-if="sortDir === 'desc'">▼</span>
  10. </span>
  11. </span>
  12. </th>
  13. </thead>
  14. <tbody>
  15. <tr v-if="pagedItems.length === 0">
  16. <td :colspan="visibleHeaders.length">No items found</td>
  17. </tr>
  18. <tr v-else v-for="(row, index) in pagedItems" :key="index">
  19. <td v-for="(header, index) in visibleHeaders" :key="index" :class="{ hidden: header.hidden }">
  20. <component
  21. v-if="slotFor(header.key)"
  22. :is="slotFor(header.key)"
  23. :class="{ hidden: header.hidden }"
  24. :row="row"
  25. :value="row[header.key]"
  26. :key="index"
  27. />
  28. <span v-else>
  29. {{ row[header.key] }}
  30. </span>
  31. </td>
  32. </tr>
  33. </tbody>
  34. </table>
  35. <div class = "padding">
  36. <Pagination :total = "total" v-model:page="page" v-model:page-size="pageSize" />
  37. </div>
  38. </template>
  39. <script setup>
  40. import { ref, computed, onMounted, watch, useSlots } from 'vue';
  41. import Pagination from './Pagination.vue';
  42. const sortBy = ref(null);
  43. const sortDir = ref('asc');
  44. const page = ref(1);
  45. const pageSize = ref(10);
  46. const props = defineProps({
  47. headers: {
  48. type: Array,
  49. default: () => ['id'],
  50. },
  51. data: {
  52. type: Array,
  53. default: () => [],
  54. },
  55. });
  56. const slots = useSlots()
  57. function slotFor(key) {
  58. let s = slots[`cell-${key}`];
  59. return s || slots.cell || null;
  60. }
  61. const items = computed(() => {
  62. const arr = [...props.data];
  63. return arr;
  64. })
  65. const sortedItems = computed(() => {
  66. if (!sortBy.value) return [...items.value];
  67. const col = sortBy.value;
  68. return [...items.value].sort((a, b) => {
  69. const av = a[col];
  70. const bv = b[col];
  71. console.log("sort values", col, av, bv)
  72. if (av === bv) return 0;
  73. if (av === null || av === undefined) return 1;
  74. if (bv === null || bv === undefined) return -1;
  75. if (typeof av === 'string' && typeof bv === 'string') {
  76. return sortDir.value === 'asc' ? av.localeCompare(bv) : bv.localeCompare(av);
  77. }
  78. if (typeof av === 'number' && typeof bv === 'number') {
  79. return sortDir.value === 'asc' ? av - bv : bv - av;
  80. }
  81. if (typeof av === 'boolean' && typeof bv === 'boolean') {
  82. return sortDir.value === 'asc' ? (av ? 1 : 0) - (bv ? 1 : 0) : (bv ? 1 : 0) - (av ? 1 : 0);
  83. }
  84. if (av < bv) return sortDir.value === 'asc' ? -1 : 1;
  85. });
  86. });
  87. watch([sortedItems, pageSize], () => { page.value = 1 });
  88. const pagedItems = computed(() => {
  89. const start = (page.value - 1) * pageSize.value;
  90. const ret = sortedItems.value.slice(start, start + pageSize.value);
  91. return ret
  92. });
  93. const total = computed(() => sortedItems.value.length);
  94. const totalPages = computed(() => Math.ceil(total.value / pageSize.value));
  95. const visibleHeaders = computed(() => {
  96. return props.headers.filter(h => !h.hidden);
  97. });
  98. function toggleSort(header) {
  99. if (!header.sortable) return;
  100. if (sortBy.value === header.key) {
  101. sortDir.value = sortDir.value === 'asc' ? 'desc' : 'asc';
  102. } else {
  103. sortBy.value = header.key;
  104. sortDir.value = 'asc';
  105. }
  106. console.log(`Sorting by ${sortBy.value} in ${sortDir.value} order`);
  107. }
  108. </script>
  109. <style scoped>
  110. table thead th.sortable:hover {
  111. cursor: pointer;
  112. color: #0366d6;
  113. }
  114. td:first-child, th:first-child {
  115. padding-left: 1rem;
  116. }
  117. </style>