XcTableV2.vue 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. <template>
  2. <div class="xc-table_v2"
  3. ref="tableRef">
  4. <div class="header"
  5. ref="headerRef">
  6. <template v-for="(item,index) in props.columns">
  7. <div class="th"
  8. @click="headerClick(item)"
  9. :style="fixedStyle(item,index)">
  10. <template v-if="item.type">
  11. <component :is="typeEnum[item.type].header()"/>
  12. </template>
  13. {{ item.name }}
  14. </div>
  15. </template>
  16. </div>
  17. <div v-bind="containerProps"
  18. class="xc-body_v2"
  19. :style="{height : props.height + 'px'}">
  20. <div v-bind="wrapperProps"
  21. ref="dataRef">
  22. <div v-for="item in list"
  23. :key="props.rowKey === null ? item.index : props.rowKey"
  24. @contextmenu.prevent.stop="contextmenu(item.data,item.index,$event)"
  25. :style="rowStyle(item.data, item.index)"
  26. @dblclick.stop="dbRowClick(item.data, $event, item.index)"
  27. @click="rowClick(item.data, $event, item.index)"
  28. class="row">
  29. <template v-for="(he,heindex) in props.columns">
  30. <div :title="item.data[he.code]"
  31. class="cell"
  32. :style="fixedStyle(he,heindex)">
  33. <template v-if="he.type">
  34. <component :is="typeEnum[he.type].body(item)"/>
  35. </template>
  36. <template v-else-if="he.cellRenderer">
  37. <component :is="he.cellRenderer({data: item.data,index:item.index,cellData: item.data[he.code]})"/>
  38. </template>
  39. <template v-else>
  40. {{ item.data[he.code] }}
  41. </template>
  42. </div>
  43. </template>
  44. </div>
  45. </div>
  46. </div>
  47. </div>
  48. </template>
  49. <script setup name='XcTableV2' lang="tsx">
  50. import {useVirtualList} from "@vueuse/core";
  51. import {computed, nextTick, onMounted, ref, Ref} from 'vue'
  52. import {ElCheckbox} from "element-plus";
  53. const props = defineProps({
  54. data: Array,
  55. columns: Array,
  56. height: {
  57. type: Number,
  58. default: 300
  59. },
  60. rowStyle: {
  61. type: Function,
  62. default: null
  63. },
  64. rowKey: {
  65. type: String,
  66. default: null
  67. }
  68. })
  69. const tempData = computed(() => {
  70. return props.data
  71. })
  72. const emits = defineEmits(['rowClick', 'dbRowClick', 'contextmenu'])
  73. const {list, containerProps, wrapperProps, scrollTo} = useVirtualList(
  74. tempData,
  75. {
  76. itemHeight: 24,
  77. },
  78. )
  79. const headerClick = (item) => {
  80. if (item.headerFunc) {
  81. item.headerFunc()
  82. }
  83. }
  84. const rowStyle = (data, index) => {
  85. if (props.rowStyle === null) return
  86. return props.rowStyle(data, index)
  87. }
  88. let timer = null
  89. const rowClick = (item, event, index) => {
  90. if (timer) {
  91. clearTimeout(timer)
  92. }
  93. timer = setTimeout(() => {
  94. emits('rowClick', item, index, event)
  95. }, 300)
  96. }
  97. const dbRowClick = (item, event, index) => {
  98. if (timer) {
  99. clearTimeout(timer)
  100. }
  101. emits('dbRowClick', item, index, event)
  102. }
  103. const contextmenu = (data, index, event) => {
  104. emits('contextmenu', data, index, event)
  105. }
  106. const tableRef: Ref<HTMLElement | null> = ref(null)
  107. const dataRef: Ref<HTMLElement | null> = ref(null)
  108. const headerRef: Ref<HTMLElement | null> = ref(null)
  109. const checkAll: Ref<boolean> = ref(false)
  110. const isIndeterminate = computed(() => {
  111. if (selectedData.value.length > 0) {
  112. return true
  113. } else if (selectedData.value.length === selectedData.value.length) {
  114. return false
  115. }
  116. })
  117. const selectedData: Ref<Array<any>> = ref([])
  118. const typeEnum = {
  119. "selected": {
  120. header: () => {
  121. return (<ElCheckbox modelValue={checkAll.value}
  122. onclick={(e) => {
  123. clickSelectedAll(e)
  124. }}
  125. indeterminate={isIndeterminate.value}/>)
  126. },
  127. body: ({data}) => {
  128. return (<ElCheckbox
  129. modelValue={data.tempCheckbox}
  130. onclick={(e) => clickSelected(data, e)}/>)
  131. }
  132. },
  133. "index": {
  134. header: () => (<span>排序</span>),
  135. body: ({index}) => (
  136. <span>{index + 1}</span>
  137. )
  138. }
  139. }
  140. const clickSelected = (data, e: Event) => {
  141. if (e !== null) {
  142. e.preventDefault()
  143. e.stopImmediatePropagation();
  144. }
  145. data.tempCheckbox = !data.tempCheckbox
  146. if (data.tempCheckbox) {
  147. selectedData.value.push(data)
  148. } else {
  149. let index = selectedData.value.findIndex((item) => {
  150. return item === data
  151. })
  152. selectedData.value.splice(index, 1)
  153. }
  154. }
  155. const clickSelectedAll = (e: Event) => {
  156. e.preventDefault()
  157. e.stopPropagation()
  158. checkAll.value = !checkAll.value
  159. if (checkAll.value) {
  160. selectedData.value = []
  161. tempData.value.forEach(item => {
  162. item.tempCheckbox = true
  163. })
  164. } else {
  165. selectedData.value = tempData.value
  166. selectedData.value = []
  167. tempData.value.forEach(item => {
  168. item.tempCheckbox = false
  169. })
  170. }
  171. }
  172. const selectedRow = (data) => {
  173. clickSelected(data, null)
  174. }
  175. const getSelectedData = () => {
  176. return selectedData.value
  177. }
  178. const scrollToByData = (data) => {
  179. let temp = JSON.stringify(data)
  180. let index = -1
  181. for (let i = 0, len = tempData.value.length; i < len; i++) {
  182. let item = JSON.stringify(tempData.value[i])
  183. if (item === temp) {
  184. index = i
  185. break;
  186. }
  187. }
  188. if (index > 0) {
  189. scrollTo(index)
  190. }
  191. }
  192. const scrollToByKey = (key) => {
  193. if (props.rowKey === null) {
  194. throw new Error('请先选择一个 key')
  195. }
  196. let index = -1
  197. for (let i = 0, len = tempData.value.length; i < len; i++) {
  198. if (tempData.value[i][props.rowKey] == key) {
  199. index = i
  200. break;
  201. }
  202. }
  203. if (index > 0) {
  204. scrollTo(index - 2)
  205. }
  206. return index
  207. }
  208. const fixedStyle = (item, index) => {
  209. let str = {width: item.width + 'px'}
  210. let px = 0
  211. if (index > 0) {
  212. for (let i = 0; i < index; i++) {
  213. let he = props.columns[i]
  214. if (he.fixed && he.fixed === item.fixed) {
  215. px += he.width
  216. }
  217. }
  218. }
  219. if (item.fixed) {
  220. str['position'] = 'sticky!important';
  221. str[item.fixed] = px + 'px'
  222. str['z-index'] = 2
  223. str['background'] = '#f5f5f5'
  224. }
  225. return str
  226. }
  227. const clearSelected = () => {
  228. if (selectedData.value.length > 0) {
  229. selectedData.value.forEach(item => {
  230. item.tempCheckbox = false
  231. })
  232. selectedData.value = []
  233. }
  234. }
  235. onMounted(async () => {
  236. await nextTick()
  237. containerProps.ref.value.addEventListener('scroll', () => {
  238. headerRef.value.scrollLeft = containerProps.ref.value.scrollLeft
  239. })
  240. })
  241. defineExpose({
  242. selectedRow,
  243. getSelectedData,
  244. scrollToByData,
  245. scrollToByKey,
  246. scrollTo,
  247. clearSelected
  248. })
  249. </script>
  250. <style lang="scss">
  251. .xc-table_v2 {
  252. overflow-x: hidden;
  253. color: black;
  254. .header {
  255. height: max-content;
  256. display: flex;
  257. align-items: center;
  258. border-bottom: 1px solid;
  259. overflow-x: hidden;
  260. .th {
  261. overflow: hidden;
  262. text-overflow: ellipsis;
  263. white-space: nowrap;
  264. flex-grow: 0;
  265. flex-shrink: 0
  266. }
  267. }
  268. .xc-body_v2 {
  269. &::-webkit-scrollbar {
  270. height: 6px;
  271. }
  272. .row {
  273. height: 22px;
  274. display: flex;
  275. align-items: center;
  276. }
  277. .cell {
  278. overflow: hidden;
  279. border-bottom: 1px solid #fff;
  280. border-top: 1px solid #fff;
  281. display: flex;
  282. align-items: center;
  283. text-overflow: ellipsis;
  284. white-space: nowrap;
  285. height: 100%;
  286. flex-grow: 0;
  287. flex-shrink: 0;
  288. }
  289. }
  290. }
  291. </style>