移动端table组件(带全屏旋转功能)——解决Ant Design Vue的a-table组件旋转页面后在移动端中滚动轴失效的问题

组件: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>

  • 11
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值