组件:Ant Design Vue(3.2.13)的a-table
框架:vue3
问题描述:在移动端开发中需要给页面增加一个table组件展示数据,并且组件带有全屏功能(点击全屏按钮后,弹出对话框窗口,横屏展示该table)。但在旋转页面后,发现table的X轴滚动失效。
组件样式:
点击后展示页面:
问题出现原因:由于横屏展示是通过transform将页面旋转90度实现的,旋转后原X轴变成了Y轴,但组件仍需要按照盒子的原方向去计算滚动(滚动、宽高等需要按盒子未旋转前方向;触发事件按旋转后方向)。所以造成了计算数据的混乱,导致触碰拖动滚动表格失效。
解决办法:重写touchstart(手指开始点击屏幕)、touchmove(接触移动)、touchend(手指离开屏幕)三个事件监听。
const container = tableRef.value.querySelector('.ant-table-content');
let start;
container.addEventListener('touchstart', (e) => {
start = e.touches[0].clientY;
isDragging.value = true;
});
container.addEventListener('touchmove', (e) => {
if (isDragging.value) {
const scrollWidth = container.scrollWidth; //元素内容区域的宽度,包括被隐藏的部分。
const scrollLeft = container.scrollLeft; //左边元素内容超过容器的宽度
const clientWidth = container.clientWidth; //容器的可见宽度,包括内边距,但不包括元素的边框和滚动条的宽度
// 向左滑为负数,向右滑为正数
const difY = e.touches[0].clientY - start;
if ((scrollLeft === 0 && difY > 0) ||
(scrollLeft + clientWidth >= scrollWidth && difY < 0)) {
// 已经滚动到最左侧或最右侧,不触发滚动操作
return;
}
container.scrollBy(-difY, 0);
start = e.touches[0].clientY;
}
});
container.addEventListener('touchend', () => {
isDragging.value = false;
});
完整代码:
index.vue(引用的页面)
<template>
<div class="capacity-table" v-show="capacityShow">
<div class="table-box">
<SvgIcon class="table-btn" name="quanping" @click="openFullTable"></SvgIcon>
</div>
<RatioTable v-if="updateTable" v-bind="capacityTable" :xWidth="xWidth" />
</div>
<FullTable :dataSource="capacityTable.dataSource" :columns="capacityTable.columns" :xWidth="xWidth" :openMark="openMark" @closeFullTable="onCloseFullTable"></FullTable>
</template>
<script setup>
import SvgIcon from "@/components/svgIcon.vue";
import RatioTable from "../../components/home/RatioTable.vue";
import FullTable from "../../components/health/fullTable.vue";
import { ref, reactive, onMounted, nextTick } from "vue";
// 表格宽度
const xWidth = ref(1300);
let openMark = ref<boolean>(false);
const capacityTable = reactive<any>({
dataSource: [],
columns: [],
loading: false,
})
// 打开全屏表格组件
const openFullTable = () =>{
openMark.value = true;
}
const onCloseFullTable = (val) =>{
openMark.value = false;
}
</script>
fullTable.vue
<template>
<van-popup teleport="body" :show="openMark" position="top" style="width: 100vw; height: 100vh;overflow: hidden;">
<div class="popup-box" :style="{
transform: originSet === 0 ? 'rotate(90deg)' : '',
transformOrigin: originSet === 0 ? '50vw 50vw' : '',
height: originSet === 0 ? '100vw' : '100vh',
width: originSet === 0 ? '100vh' : '100vw',
}">
<div class="close-box">
<SvgIcon class="close-btn" name="close" @click="onCloseFullTable"></SvgIcon>
</div>
<RatioTable v-bind="props" />
</div>
</van-popup>
</template>
<script setup>
import {
ref,
onMounted,
onBeforeMount,
defineProps,
onBeforeUnmount
} from "vue";
import RatioTable from "../../components/home/RatioTable.vue";
import SvgIcon from "@/components/svgIcon.vue";
//获取 dom 和 父组件数据 并定义"myChart"用于初始化图表
const chartDom = ref();
let myChart = null;
const props = defineProps({
columns: {
type: Array,
default: () =>[]
},
dataSource: {
type: Array,
default: () =>[{}]
},
xWidth: {
type: [Number, Boolean],
default: false
},
openMark: {
type: Boolean,
default: false
},
loading: {
type: Boolean,
default: false,
}
});
let originSet = ref(0);
const emit = defineEmits(["closeFullTable"]);
/** 关闭全屏组件 */
const onCloseFullTable = () =>{
emit('closeFullTable');
}
onBeforeMount(() => {
if (window.innerWidth > 550) {
originSet.value = 1;
}
})
//设置防抖,保证无论拖动窗口大小,只执行一次获取浏览器宽高的方法
const debounce = (fun, delay) => {
let timer;
return function () {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fun();
}, delay);
};
};
const cancalDebounce = debounce(() =>{
if (window.innerWidth > 550) {
originSet.value = 1;
}else {
originSet.value = 0;
}
}, 500);
onMounted(() => {
window.addEventListener("resize", cancalDebounce);
});
//页面销毁前,销毁事件和实例
onBeforeUnmount(() => {
window.removeEventListener("resize", cancalDebounce);
});
</script>
<style lang="scss" scoped>
.popup-box {
padding: 5px 10px 0;
}
.close-box {
display: flex;
justify-content: flex-end;
padding-right: 10px;
padding-bottom: 5px;
.close-btn {
font-size: 52px;
stroke: #989898;
stroke-width: 20px;
fill: #989898;
cursor: pointer;
}
}
@media (min-width: 451px) {
.popup-box {
padding: 10px 10px 0;
}
.close-box {
display: flex;
justify-content: flex-end;
padding-right: 40px;
padding-bottom: 20px;
.close-btn {
font-size: 52px;
stroke: #989898;
stroke-width: 20px;
}
}
}
</style>
RatioTable.vue
<template>
<div class="table" ref="tableRef">
<a-table :dataSource="props.dataSource" style="width: 100%; height: 100%" :loading="loading" :columns="props.columns" bordered :pagination="false" :scroll="{ x: props.xWidth }">
<template #headerCell="{ column }">
<span class="title1">{{ column.title }}</span>
</template>
<template #bodyCell="{ column, record, text }">
<template v-if="column.key === 'title'">
<template v-if="typeof(record[column.dataIndex]) === 'string' && record[column.dataIndex].indexOf('<br/>') > -1">
<span v-html="record[column.dataIndex]" class="title"></span>
</template>
<template v-else >
<span class="title">{{ record[column.dataIndex] }}</span>
</template>
</template>
<template v-else>
<span class="num">{{ pointHandle(text) }}</span>
</template>
</template>
</a-table>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, watch, nextTick, onMounted } from "vue";
import { ElTable, ElTableColumn } from "element-plus";
const props = defineProps({
dataSource: {
type: Object,
default: () =>[{}],
},
columns: Object,
xWidth: {
type: [Boolean, Number, String],
default: false,
},
loading: {
type: Boolean,
default: false,
}
});
const tableRef = ref();
const isDragging = ref(false);
onMounted(() => {
const container = tableRef.value.querySelector('.ant-table-content');
let start;
container.addEventListener('touchstart', (e) => {
start = e.touches[0].clientY;
isDragging.value = true;
});
container.addEventListener('touchmove', (e) => {
if (isDragging.value) {
const scrollWidth = container.scrollWidth; //元素内容区域的宽度,包括被隐藏的部分。
const scrollLeft = container.scrollLeft; //左边元素内容超过容器的宽度
const clientWidth = container.clientWidth; //容器的可见宽度,包括内边距,但不包括元素的边框和滚动条的宽度
// 向左滑为负数,向右滑为正数
const difY = e.touches[0].clientY - start;
if ((scrollLeft === 0 && difY > 0) ||
(scrollLeft + clientWidth >= scrollWidth && difY < 0)) {
// 已经滚动到最左侧或最右侧,不触发滚动操作
return;
}
container.scrollBy(-difY, 0);
start = e.touches[0].clientY;
}
});
container.addEventListener('touchend', () => {
isDragging.value = false;
});
});
const pointHandle = (num: any) => {
if (!num && num != 0 || num == '') {
return "--";
}else if(typeof(num) === 'string' && num.indexOf('%') > -1) {
return num;
}else {
num = Number(num).toFixed(2);
return num.toLocaleString();
}
};
</script>
<style lang="scss" scoped>
.table {
margin: 0 auto;
width: 95%;
display: flex;
justify-content: center;
border-radius: 5px;
}
:deep() .ant-table-cell {
padding: 5px !important;
margin: 0 auto;
text-align: center;
}
.num {
font-size: 3.5vw;
font-weight: bold;
padding: 0px 5px;
}
.title {
font-size: 3.5vw;
}
.title1 {
font-size: 3.5vw;
text-align: center;
}
:deep() .ant-table-wrapper {
width: 100%;
}
@media (min-width: 451px) {
.num {
font-size: 12px;
font-weight: bold;
display: block;
line-height: 30px;
height: 30px;
padding: 0px;
}
.title {
font-size: 12px;
padding: 0px 5px;
}
.title1 {
font-size: 12px;
text-align: center;
display: block;
line-height: 30px;
height: 30px;
}
:deep() .ant-table-cell {
padding: 5px !important;
}
}
</style>