index.vue 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. <template>
  2. <div>
  3. <div v-if="isTools" class="text-end mb-3">
  4. <v-btn class="ml-2" color="primary" @click="emit('add')">
  5. <v-icon left>mdi-plus</v-icon>
  6. {{ t('common.add') }}
  7. <!-- 新增 -->
  8. </v-btn>
  9. <slot name="addToTools"></slot>
  10. </div>
  11. <v-data-table
  12. ref="table"
  13. v-model="selected"
  14. :class="`elevation-${elevation} ${showFixedLastItem ? 'fixed-last-item' : ''}`"
  15. :headers="headers"
  16. :items="items"
  17. :item-key="itemKey"
  18. :show-select="showSelect"
  19. :loading="loading"
  20. :select-strategy="selectStrategy"
  21. :return-object="returnObject"
  22. color="primary"
  23. hover
  24. :height="height"
  25. hide-default-footer
  26. loading-text="Loading... Please wait"
  27. fixed-header
  28. :disable-sort="disableSort"
  29. :items-per-page="itemsPerPage"
  30. :no-data-text="noDataText || t('common.noData')"
  31. :hide-default-header="hideDefaultHeader"
  32. @update:modelValue="handleSelect"
  33. >
  34. <template v-for="name in itemSlot" v-slot:[`item.${name}`]="slotProps">
  35. <slot :name="name" v-bind="slotProps"></slot>
  36. </template>
  37. <template v-for="name in headerSlot" v-slot:[`${name}`]="slotProps">
  38. <slot :name="name" v-bind="slotProps"></slot>
  39. </template>
  40. <template v-if="!Object.keys(slot).includes('actions')" v-slot:[`item.actions`]="{ item }">
  41. <td>
  42. <v-btn variant="text" color="primary" @click="edit(item)">{{ t('common.edit') }}</v-btn>
  43. <v-btn variant="text" color="error" @click="del(item)">{{ t('common.delete') }}</v-btn>
  44. </td>
  45. </template>
  46. <template #bottom>
  47. <div v-if="showPage && total > 0">
  48. <v-divider></v-divider>
  49. <CtPagination :total="total" :page="pageInfo.pageNo" :limit="pageInfo.pageSize" @handleChange="handleChangePage"></CtPagination>
  50. </div>
  51. </template>
  52. </v-data-table>
  53. </div>
  54. </template>
  55. <script setup>
  56. defineOptions({ name: 'CtTable'})
  57. import { ref, computed, useSlots, watch, onMounted } from 'vue'
  58. import { useI18n } from '@/hooks/web/useI18n'
  59. const { t } = useI18n()
  60. const selected = ref([])
  61. const emit = defineEmits(['pageHandleChange', 'del', 'edit', 'add', 'selected'])
  62. const props = defineProps({
  63. modelValue: Array,
  64. elevation: {
  65. type: [Number, String],
  66. default: 0
  67. },
  68. itemKey: {
  69. type: String,
  70. default: 'id'
  71. },
  72. disableSort: {
  73. type: Boolean,
  74. default: true
  75. },
  76. // 展示列表所有数据
  77. itemsPerPage: {
  78. type: Number,
  79. default: -1
  80. },
  81. hideDefaultHeader: {
  82. type: Boolean,
  83. default: false
  84. },
  85. showPage: {
  86. type: Boolean,
  87. default: true
  88. },
  89. loading: {
  90. type: Boolean,
  91. default: true
  92. },
  93. headers: {
  94. type: Array,
  95. default: () => []
  96. },
  97. items: {
  98. type: Array,
  99. default: () => []
  100. },
  101. total: {
  102. type: [String, Number],
  103. default: 0
  104. },
  105. pageInfo: {
  106. type: Object,
  107. default: () => ({
  108. size: 10
  109. })
  110. },
  111. isTools: {
  112. type: Boolean,
  113. default: true
  114. },
  115. height: {
  116. type: [String, Number],
  117. default: ''
  118. },
  119. noDataText: {
  120. type: String,
  121. default: ''
  122. },
  123. showSelect: {
  124. type: Boolean,
  125. default: false
  126. },
  127. returnObject: {
  128. type: Boolean,
  129. default: false
  130. },
  131. selectStrategy: {
  132. type: String,
  133. default: 'single'
  134. },
  135. // 是否将最后一项固定在表格右侧
  136. showFixedLastItem: {
  137. type: Boolean,
  138. default: true
  139. }
  140. })
  141. watch(() => props.modelValue, (val) => {
  142. selected.value = val
  143. }, { deep: true, immediate: true })
  144. const table = ref()
  145. const slot = useSlots()
  146. const itemSlot = computed(() => {
  147. return Object.keys(slot).filter(key => {
  148. const data = key.split('.')
  149. return data.length === 1
  150. })
  151. })
  152. const headerSlot = computed(() => {
  153. return Object.keys(slot).filter(key => {
  154. const data = key.split('.')
  155. return data.length === 2 && data[0] === 'header'
  156. })
  157. })
  158. onMounted(() => {
  159. const wrapper = table.value.$el.querySelector('.v-table__wrapper');
  160. const observer = new ResizeObserver(() => {
  161. if (wrapper.scrollWidth > wrapper.clientWidth) {
  162. wrapper.classList.add('hasScroll');
  163. } else {
  164. wrapper.classList.remove('hasScroll');
  165. }
  166. });
  167. observer.observe(wrapper);
  168. });
  169. const edit = (item) => {
  170. emit('edit', item)
  171. }
  172. const del = (item) => {
  173. emit('del', item)
  174. }
  175. const handleChangePage = (e) => {
  176. emit('pageHandleChange', e)
  177. }
  178. const handleSelect = (e) => {
  179. emit('selected', e)
  180. }
  181. </script>
  182. <style scoped lang="scss">
  183. :deep(.v-table.v-table--fixed-header > .v-table__wrapper > table > thead > tr > th) {
  184. text-wrap: nowrap !important;
  185. background-color: #f7f8fa !important;
  186. }
  187. :deep(.v-selection-control__input) {
  188. color: var(--v-primary-base) !important;
  189. }
  190. :deep(.v-table.v-table--hover > .v-table__wrapper > table > tbody > tr > td) {
  191. white-space: nowrap !important;
  192. }
  193. :deep(table > thead > tr > th:last-child) {
  194. border-bottom: 1px solid #e0e0e0 !important;
  195. }
  196. .fixed-last-item {
  197. :deep(.v-table__wrapper) { position: relative; }
  198. :deep(.v-table__wrapper::-webkit-scrollbar:horizontal) { height: 8px; }
  199. :deep(.v-table__wrapper:not(:hover)::-webkit-scrollbar:horizontal) { display: none; }
  200. :deep(.v-table__wrapper:not(:hover), .v-table__wrapper::-webkit-scrollbar-thumb:horizontal) { background: transparent; }
  201. :deep(table > tbody > tr > td:last-child) {
  202. position: sticky !important;
  203. position: -webkit-sticky !important;
  204. right: 0;
  205. z-index: 1;
  206. background: white !important;
  207. box-shadow: none;
  208. }
  209. :deep(table > thead > tr > th:last-child) {
  210. position: sticky !important;
  211. position: -webkit-sticky !important;
  212. right: 0;
  213. z-index: 1;
  214. background: white !important;
  215. box-shadow: none;
  216. }
  217. :deep(.v-table__wrapper.hasScroll table > tbody > tr > td:last-child) {
  218. border-left: 1px solid #e0e0e0 !important;
  219. }
  220. :deep(.v-table__wrapper.hasScroll table > thead > tr > th:last-child) {
  221. border-left: 1px solid #e0e0e0 !important;
  222. }
  223. // :deep {
  224. // .v-table__wrapper {
  225. // position: relative;
  226. // &::-webkit-scrollbar:horizontal {
  227. // height: 8px;
  228. // }
  229. // &:not(:hover)::-webkit-scrollbar:horizontal {
  230. // display: none;
  231. // }
  232. // }
  233. // .v-table__wrapper:not(:hover),
  234. // .v-table__wrapper::-webkit-scrollbar-thumb:horizontal {
  235. // background: transparent;
  236. // }
  237. // table > tbody > tr > td:last-child,
  238. // table > thead > tr > th:last-child {
  239. // position: sticky !important;
  240. // position: -webkit-sticky !important;
  241. // right: 0;
  242. // z-index: 1;
  243. // background: white !important;
  244. // box-shadow: none;
  245. // }
  246. // .v-table__wrapper.hasScroll {
  247. // table > tbody > tr > td:last-child,
  248. // table > thead > tr > th:last-child {
  249. // border-left: 1px solid #e0e0e0 !important;
  250. // // box-shadow: inset 10px 0 10px -10px rgba(0, 0, 0, .35) !important;
  251. // }
  252. // // table > thead > tr > th:last-child {
  253. // // z-index: 10 !important;
  254. // // // box-shadow: inset 10px 0 10px -10px rgba(0, 0, 0, .35) !important;
  255. // // border-bottom: 1px solid #e0e0e0 !important;
  256. // // }
  257. // }
  258. // }
  259. }
  260. </style>