wd-table.vue 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. <template>
  2. <view :class="`wd-table ${border ? 'is-border' : ''} ${customClass}`" :style="tableStyle">
  3. <template v-if="fixedHeader">
  4. <scroll-view
  5. :enable-flex="true"
  6. :throttle="false"
  7. :scrollLeft="state.scrollLeft"
  8. :scroll-x="true"
  9. class="wd-table__header"
  10. @scroll="scroll"
  11. v-if="showHeader"
  12. >
  13. <view id="table-header" class="wd-table__content wd-table__content--header" :style="realWidthStyle">
  14. <view
  15. :class="`wd-table__cell ${border ? 'is-border' : ''} ${column.fixed ? 'is-fixed' : ''} ${stripe ? 'is-stripe' : ''} is-${column.align} ${
  16. getIsLastFixed(column) && state.scrollLeft ? 'is-shadow' : ''
  17. }`"
  18. :style="getCellStyle(index)"
  19. v-for="(column, index) in children"
  20. :key="index"
  21. >
  22. <wd-sort-button
  23. v-model="column.$.exposed!.sortDirection.value"
  24. allow-reset
  25. :line="false"
  26. :title="column.label"
  27. @change="({ value }) => handleSortChange(value, index)"
  28. v-if="column.sortable"
  29. />
  30. <text v-else :class="`wd-table__value ${ellipsis ? 'is-ellipsis' : ''}`">{{ column.label }}</text>
  31. </view>
  32. </view>
  33. </scroll-view>
  34. <scroll-view
  35. class="wd-table__body"
  36. :style="bodyStyle"
  37. :enable-flex="true"
  38. :throttle="false"
  39. :scroll-x="true"
  40. @scroll="scroll"
  41. :scrollLeft="state.scrollLeft"
  42. >
  43. <view id="table-body" class="wd-table__content" :style="realWidthStyle">
  44. <wd-table-col
  45. v-if="index !== false"
  46. :prop="indexColumn.prop"
  47. :label="indexColumn.label"
  48. :width="indexColumn.width"
  49. :sortable="indexColumn.sortable"
  50. :fixed="indexColumn.fixed"
  51. :align="indexColumn.align"
  52. >
  53. <template #value="{ index }">
  54. <text>{{ index + 1 }}</text>
  55. </template>
  56. </wd-table-col>
  57. <slot></slot>
  58. </view>
  59. </scroll-view>
  60. </template>
  61. <!-- 非固定表头时使用单个scroll-view -->
  62. <template v-else>
  63. <scroll-view class="wd-table__wrapper" :enable-flex="true" :throttle="false" :scroll-x="true" @scroll="scroll" :scrollLeft="state.scrollLeft">
  64. <view class="wd-table__inner" :style="realWidthStyle">
  65. <!-- 表头部分 -->
  66. <view v-if="showHeader" class="wd-table__header-row">
  67. <view
  68. v-for="(column, index) in children"
  69. :key="index"
  70. :class="`wd-table__cell ${border ? 'is-border' : ''} ${column.fixed ? 'is-fixed' : ''} ${stripe ? 'is-stripe' : ''} is-${
  71. column.align
  72. } ${getIsLastFixed(column) && state.scrollLeft ? 'is-shadow' : ''}`"
  73. :style="getCellStyle(index)"
  74. >
  75. <wd-sort-button
  76. v-if="column.sortable"
  77. v-model="column.$.exposed!.sortDirection.value"
  78. allow-reset
  79. :line="false"
  80. :title="column.label"
  81. @change="({ value }) => handleSortChange(value, index)"
  82. />
  83. <text v-else :class="`wd-table__value ${ellipsis ? 'is-ellipsis' : ''}`">{{ column.label }}</text>
  84. </view>
  85. </view>
  86. <!-- 表格内容部分 -->
  87. <view class="wd-table__content" :style="bodyStyle">
  88. <wd-table-col
  89. v-if="index !== false"
  90. :prop="indexColumn.prop"
  91. :label="indexColumn.label"
  92. :width="indexColumn.width"
  93. :sortable="indexColumn.sortable"
  94. :fixed="indexColumn.fixed"
  95. :align="indexColumn.align"
  96. >
  97. <template #value="{ index }">
  98. <text>{{ index + 1 }}</text>
  99. </template>
  100. </wd-table-col>
  101. <slot></slot>
  102. </view>
  103. </view>
  104. </scroll-view>
  105. </template>
  106. </view>
  107. </template>
  108. <script lang="ts">
  109. export default {
  110. name: 'wd-table',
  111. options: {
  112. addGlobalClass: true,
  113. virtualHost: true,
  114. styleIsolation: 'shared'
  115. }
  116. }
  117. </script>
  118. <script lang="ts" setup>
  119. import wdTableCol from '../wd-table-col/wd-table-col.vue'
  120. import wdSortButton from '../wd-sort-button/wd-sort-button.vue'
  121. import { type CSSProperties, computed, reactive, ref } from 'vue'
  122. import { addUnit, debounce, isDef, isObj, objToStyle, uuid } from '../common/util'
  123. import type { SortDirection, TableColumn, TableColumnInstance, TableColumnProps } from '../wd-table-col/types'
  124. import { TABLE_KEY, tableProps, type TableProvide } from './types'
  125. import WdTableCol from '../wd-table-col/wd-table-col.vue'
  126. import { useTranslate } from '../composables/useTranslate'
  127. import { useChildren } from '../composables/useChildren'
  128. const { translate } = useTranslate('tableCol')
  129. const props = defineProps(tableProps)
  130. const emit = defineEmits(['sort-method', 'row-click'])
  131. const state = reactive({
  132. scrollLeft: 0
  133. })
  134. const { linkChildren, children } = useChildren<TableColumnInstance, TableProvide>(TABLE_KEY)
  135. linkChildren({ props, state, rowClick, getIsLastFixed, getFixedStyle })
  136. const indexUUID = uuid()
  137. const indexColumn = ref<TableColumnProps>({
  138. prop: indexUUID,
  139. label: translate('indexLabel'),
  140. width: '100rpx',
  141. sortable: false,
  142. fixed: false,
  143. align: 'left',
  144. ...(isObj(props.index) ? props.index : {})
  145. })
  146. const scroll = debounce(handleScroll, 100, { leading: false }) // 滚动事件
  147. /**
  148. * 容器样式
  149. */
  150. const tableStyle = computed(() => {
  151. const style: CSSProperties = {}
  152. if (isDef(props.height)) {
  153. style['max-height'] = addUnit(props.height)
  154. }
  155. return `${objToStyle(style)}${props.customStyle}`
  156. })
  157. const realWidthStyle = computed(() => {
  158. const style: CSSProperties = {
  159. display: 'flex'
  160. }
  161. let width: string | number = ''
  162. children.forEach((child) => {
  163. width = width ? `${width} + ${addUnit(child.width)}` : addUnit(child.width)
  164. })
  165. style['width'] = `calc(${width})`
  166. return objToStyle(style)
  167. })
  168. const bodyStyle = computed(() => {
  169. const style: CSSProperties = {}
  170. if (isDef(props.height)) {
  171. style['height'] = isDef(props.rowHeight) ? `calc(${props.data.length} * ${addUnit(props.rowHeight)})` : `calc(${props.data.length} * 50px)`
  172. }
  173. return `${objToStyle(style)}`
  174. })
  175. /**
  176. * 是否最后一个固定元素
  177. * @param column 列数据
  178. */
  179. function getIsLastFixed(column: { fixed: boolean; prop: string }) {
  180. let isLastFixed: boolean = false
  181. if (column.fixed && isDef(children)) {
  182. const columns = children.filter((child) => {
  183. return child.fixed
  184. })
  185. if (columns.length && columns[columns.length - 1].prop === column.prop) {
  186. isLastFixed = true
  187. }
  188. }
  189. return isLastFixed
  190. }
  191. /**
  192. * 表头单元格样式
  193. */
  194. function getCellStyle(columnIndex: number) {
  195. let style: CSSProperties = {}
  196. if (isDef(children[columnIndex].width)) {
  197. style['width'] = addUnit(children[columnIndex].width)
  198. }
  199. if (children[columnIndex].fixed) {
  200. style = getFixedStyle(columnIndex, style)
  201. }
  202. return objToStyle(style)
  203. }
  204. /**
  205. * 获取固定列样式
  206. * @param columnIndex
  207. */
  208. function getFixedStyle(columnIndex: number, style: CSSProperties) {
  209. if (columnIndex > 0) {
  210. let left: string | number = ''
  211. children.forEach((column, index) => {
  212. if (index < columnIndex) {
  213. left = left ? `${left} + ${addUnit(column.width)}` : addUnit(column.width)
  214. }
  215. })
  216. style['left'] = `calc(${left})`
  217. } else {
  218. style['left'] = 0
  219. }
  220. return style
  221. }
  222. /**
  223. * 排序
  224. * @param value
  225. * @param index
  226. */
  227. function handleSortChange(value: SortDirection, index: number) {
  228. children[index].$.exposed!.sortDirection.value = value
  229. children.forEach((col, i) => {
  230. if (index != i) {
  231. col.$.exposed!.sortDirection.value = 0
  232. }
  233. })
  234. const column: TableColumn = {
  235. // 列对应字段
  236. prop: children[index].prop,
  237. // 列对应字段标题
  238. label: children[index].label,
  239. // 列宽度
  240. width: children[index].width,
  241. // 是否开启列排序
  242. sortable: children[index].sortable,
  243. // 列的对齐方式,可选值left,center,right
  244. align: children[index].align,
  245. // 列的排序方向
  246. sortDirection: value,
  247. // 是否i固定列
  248. fixed: children[index].fixed
  249. }
  250. emit('sort-method', column)
  251. }
  252. /**
  253. * 滚动事件
  254. */
  255. function handleScroll(event: any) {
  256. state.scrollLeft = event.detail.scrollLeft
  257. }
  258. function rowClick(index: number) {
  259. emit('row-click', { rowIndex: index })
  260. }
  261. </script>
  262. <style lang="scss" scoped>
  263. @import './index.scss';
  264. </style>