You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
264 lines
6.7 KiB
264 lines
6.7 KiB
<template> |
|
<view class="hd-table" :style="rootStyle"> |
|
<scroll-view class="header" :scroll-left="left" @scroll="scroll" enable-flex="true" scroll-x="true"> |
|
<view class="header-container" :style="{ width: scrollWidth }"> |
|
<view |
|
class="header-column" |
|
:style="headerStyle(column.width)" |
|
:class="['header-column', column.fixed ? 'header-column--fixed' : '']" |
|
v-for="(column, index) in columns" |
|
:key="index" |
|
@click="column.sortable && doSort(column, index)" |
|
> |
|
<view class="header-row" :style="headerStyle(column.width)"> |
|
<text>{{ column.label }}</text> |
|
<view class="sort" v-if="column.sortable"> |
|
<view :class="{ 'sort-up': true, 'sort--active': column.sortDirection === 'asc' }"></view> |
|
<view :class="{ 'sort-down': true, 'sort--active': column.sortDirection === 'desc' }"></view> |
|
</view> |
|
</view> |
|
</view> |
|
</view> |
|
</scroll-view> |
|
<scroll-view :class="{ body: true, 'body--stripe': stripe }" scroll-x="true" enable-flex="true" :scroll-left="left" @scroll="scroll"> |
|
<view style="display: flex" :style="{ width: scrollWidth }"> |
|
<slot></slot> |
|
</view> |
|
</scroll-view> |
|
</view> |
|
</template> |
|
|
|
<script lang="ts" setup> |
|
import { computed, getCurrentInstance, nextTick, onMounted, provide, ref, watch } from 'vue' |
|
import { CommonUtil, debounce } from '../..' |
|
|
|
/** |
|
* Table 表格 |
|
*/ |
|
|
|
interface Props { |
|
// table数据源 |
|
dataSource: Array<any> |
|
// table行是否为斑马纹 |
|
stripe?: boolean |
|
// table高度 |
|
height?: string |
|
// 行高 |
|
rowHeight?: number | string |
|
} |
|
|
|
const props = withDefaults(defineProps<Props>(), { |
|
// table数据源 |
|
dataSource: () => [], |
|
// table行是否为斑马纹 |
|
stripe: false, |
|
// table高度 |
|
height: '80vh', |
|
// 行高 |
|
rowHeight: '80rpx' |
|
}) |
|
|
|
// 监听数据源变化改变privide出去的$dataSource |
|
watch( |
|
props, |
|
() => { |
|
$props.value = props |
|
}, |
|
{ deep: true } |
|
) |
|
|
|
const left = ref<number>(0) // scroll-view滚动距离 |
|
const scrollWidth = ref<number | string>('auto') // 动态设置滚动宽度,兼容微信scroll-view中sticky失效的问题 |
|
const columns = ref<Array<Record<string, any>>>([]) // 数据列 |
|
const $props = ref<Props>(props) |
|
provide('$props', $props) // 行高provide |
|
const emit = defineEmits(['click', 'sort-method']) |
|
const scroll = debounce(doScroll, 100, false) // 滚动事件 |
|
|
|
/** |
|
* 根节点样式 |
|
*/ |
|
const rootStyle = computed(() => { |
|
const style: Record<string, string | number> = {} |
|
if (props.height && props.height.indexOf('px') > -1) { |
|
// width存在且包含px则转成px |
|
style['height'] = CommonUtil.addUnit(CommonUtil.getPx(props.height), 'px') |
|
} else if (props.height) { |
|
// width存在则直接使用 |
|
style['height'] = props.height |
|
} |
|
return CommonUtil.style(style) |
|
}) |
|
|
|
/** |
|
* 根节点样式 |
|
*/ |
|
const headerStyle = computed(() => { |
|
return (width: string | number) => { |
|
const style: Record<string, string | number> = {} |
|
if (width && width.toString().indexOf('px') > -1) { |
|
// width存在且包含px则转成px |
|
style['width'] = CommonUtil.addUnit(CommonUtil.getPx(width), 'px') |
|
} else if (width) { |
|
// width存在则直接使用 |
|
style['width'] = width |
|
} |
|
|
|
return CommonUtil.style(style) |
|
} |
|
}) |
|
|
|
const { proxy } = getCurrentInstance() as any |
|
onMounted(() => { |
|
nextTick(() => { |
|
CommonUtil.getRect('.header-container', false, proxy).then((data: any) => { |
|
if (data && data.width) { |
|
scrollWidth.value = CommonUtil.addUnit(data.width, 'px') |
|
} |
|
}) |
|
}) |
|
}) |
|
|
|
/** |
|
* 设置列 |
|
* @param column 列 |
|
*/ |
|
function setColumns(column: Record<string, any>) { |
|
columns.value = CommonUtil.deepClone([...columns.value, column]) |
|
} |
|
|
|
provide('setColumns', setColumns) |
|
|
|
/** |
|
* 排序事件 |
|
*/ |
|
function doSort(column, index) { |
|
if (column.sortDirection === 'asc') { |
|
columns.value[index].sortDirection = 'desc' |
|
} else if (columns.value[index].sortDirection === 'desc') { |
|
columns.value[index].sortDirection = '' |
|
} else { |
|
columns.value[index].sortDirection = 'asc' |
|
} |
|
columns.value.forEach((col, i) => { |
|
if (index != i) { |
|
col.sortDirection = '' |
|
} |
|
}) |
|
emit('sort-method', columns.value[index]) |
|
} |
|
/** |
|
* 滚动事件 |
|
*/ |
|
function doScroll(event) { |
|
left.value = event.detail.scrollLeft |
|
if (scrollWidth.value !== event.detail.scrollWidth) { |
|
scrollWidth.value = CommonUtil.addUnit(event.detail.scrollWidth, 'px') |
|
} |
|
} |
|
</script> |
|
<style lang="scss" scoped> |
|
@import '../../libs/css/index.scss'; |
|
.hd-table { |
|
width: 100%; |
|
height: 80vh; |
|
overflow: auto; |
|
background: #ffffff; |
|
.header { |
|
position: sticky; |
|
z-index: 6; |
|
top: 0; |
|
width: 100%; |
|
height: 80rpx; |
|
display: flex; |
|
overflow-x: auto; |
|
white-space: nowrap; |
|
&-column { |
|
display: inline-block; |
|
position: relative; |
|
z-index: 1; |
|
&--fixed { |
|
position: sticky; |
|
z-index: 3; |
|
left: 0; |
|
&::after { |
|
content: ' '; |
|
position: absolute; |
|
height: 100%; |
|
right: -30rpx; |
|
top: 0; |
|
width: 30rpx; |
|
height: 100%; |
|
background: linear-gradient(270deg, rgba(255, 255, 255, 0) 0%, rgba(0, 0, 0, 0.04) 100%); |
|
} |
|
} |
|
} |
|
&-row { |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
height: 80rpx; |
|
width: 220rpx; |
|
font-size: 24rpx; |
|
line-height: 26rpx; |
|
font-family: PingFangSC-Medium, PingFang SC; |
|
font-weight: 500; |
|
color: $color-text-fourth; |
|
background: #f1f5ff; |
|
text { |
|
padding: 0 12rpx; |
|
} |
|
} |
|
.sort { |
|
position: relative; |
|
margin-left: 4rpx; |
|
height: 100%; |
|
width: 8rpx; |
|
transform: scale(0.7); |
|
.sort-up { |
|
position: absolute; |
|
left: 0; |
|
bottom: 50%; |
|
width: 0px; |
|
height: 0px; |
|
border-width: 10rpx 10rpx 12rpx; |
|
border-style: solid; |
|
border-color: transparent transparent #8c8fa5; |
|
border-image: initial; |
|
transform: translateY(-2rpx); |
|
} |
|
.sort-down { |
|
position: absolute; |
|
top: 50%; |
|
left: 0; |
|
width: 0px; |
|
height: 0px; |
|
border-width: 10rpx 10rpx 12rpx; |
|
border-style: solid; |
|
border-color: transparent transparent #8c8fa5; |
|
border-image: initial; |
|
transform: rotate(180deg) translateY(-2rpx); |
|
} |
|
.sort--active { |
|
border-color: transparent transparent $color-primary; |
|
} |
|
} |
|
} |
|
|
|
.body { |
|
display: flex; |
|
overflow: auto; |
|
white-space: nowrap !important; |
|
width: 100%; |
|
} |
|
} |
|
|
|
scroll-view::-webkit-scrollbar { |
|
display: none; |
|
width: 0; |
|
height: 0; |
|
border-radius: 0; |
|
background-color: transparent; |
|
color: transparent; |
|
} |
|
</style>
|
|
|