首页 > 其他分享 >vue3+ts封装一个uniapp的自动滚动列表,实现看板效果

vue3+ts封装一个uniapp的自动滚动列表,实现看板效果

时间:2024-08-31 09:26:26浏览次数:11  
标签:uniapp const 数据 ts value vue3 滚动 table 无缝

电视机上要以列表展示数据,并且数据会实时更新,电视机不能点击,所以考虑自动播放的一个效果。展示方案有两种:1、列表上下自动滚动实现轮播效果。(此时具体滚动的高度由用户自己决定,每次滚动几条数据)2、列表以“页”的形式做成轮播图的翻页效果。

由于项目的电视机是有任务提示作用的,最后考虑做成第一种方案,用户能更清晰了解任务安排和数据的更新。

搜索之后了解到vue-seamless-scroll支持列表的自动滚动效果,但是一般是vue2使用,所以考虑自己封装一个组件。 一开始是使用uni-app的组件uni-table进行封装,然后发现其实有很多注意事项,可能需要对uni-table进行深度改造,最后决定自己使用原生table来封装。

uni-table的第一次封装如下

<template>
    <view class="fullscreen-table">
      <uni-table ref="table" :data="tableData">
        <uni-tr>
		    <uni-th align="center">title</uni-th>
		    <uni-th align="center">date</uni-th>
	    </uni-tr>
	    <uni-tr v-for="(item,index) in tableData" :key="index">
	        <uni-td>{{ item.title }}</uni-td>
	        <uni-td>{{ item.date }}</uni-td>
	    </uni-tr>
      </uni-table>
    </view>
  </template>
  
  <script setup lang="ts">
  import { defineComponent, onMounted, ref } from 'vue';
    const table = ref<any>(null)
    const tableData = ref<any[]>([
        {
          'title': '无缝滚动第一行无缝滚动第一行',
          'date': '2017-12-16'
        }, {
          'title': '无缝滚动第二行无缝滚动第二行',
          'date': '2017-12-16'
        }, {
          'title': '无缝滚动第三行无缝滚动第三行',
          'date': '2017-12-16'
        }, {
          'title': '无缝滚动第四行无缝滚动第四行',
          'date': '2017-12-16'
        }, {
          'title': '无缝滚动第五行无缝滚动第五行',
          'date': '2017-12-16'
        }, {
          'title': '无缝滚动第六行无缝滚动第六行',
          'date': '2017-12-16'
        }, {
          'title': '无缝滚动第七行无缝滚动第七行',
          'date': '2017-12-16'
        }, {
          'title': '无缝滚动第八行无缝滚动第八行',
          'date': '2017-12-16'
        }, {
          'title': '无缝滚动第九行无缝滚动第九行',
          'date': '2017-12-16'
        }
      ]);
  
      onMounted(() => {
        const tableElement = table.value.$el;
        const scrollHeight = tableElement.scrollHeight;
        const viewportHeight = tableElement.clientHeight;
        let scrollPosition = 0;
        const speed = 1; // 调整滚动速度
  
        function autoScroll() {
          if (scrollPosition + viewportHeight >= scrollHeight) {
            scrollPosition = 0;
          } else {
            scrollPosition += speed;
          }
          tableElement.scrollTop = scrollPosition;
          requestAnimationFrame(autoScroll);
        }
  
        autoScroll();
      })
  </script>
  
  <style scoped>
  .fullscreen-table {
    height: 100vh;
    overflow: hidden; /* 防止表格外部滚动 */
  }
  
  .uni-table {
    overflow-y: auto; /* 允许表格内部滚动 */
  }

  </style>

最后封装代码如下:(第一次自己封装组件,可能有很多没考虑进去并且可以优化的地方,只是分享一个简单的半成品)

<template>
    <view class="table-container">
      <view class="table-header">
        <table>
          <thead>
            <tr class="header" align="left" :style="{height: headerHeight+'px', fontSize: headerFontSize+'px', lineHeight: headerHeight+'px'}">
              <th :class="'headTh'+index" v-for="(header, index) in headers" :key="index">{{ Object.values(header)[0] }}</th>
            </tr>
          </thead>
        </table>
      </view>
      <view ref="myTable" class="table-body">
        <view v-if="!(data.length>0)" style="width: 100%; height: 100%; font-size: 50px;  display: flex; justify-content: center; align-items: center;">暂无数据</view>
        <view v-else>
          <table>
          <tbody>
            <tr v-for="(row, rowIndex) in Data" class="cell" :key="rowIndex" :style="{height: bodyHeight+'px', fontSize: bodyFontSize+'px'}">
                <td v-for="(header, headerIndex) in headers" :key="headerIndex" :style="computedStyle(header)" :class="'cellTd'+headerIndex">
                  {{ row[Object.keys(header)[0]] }}
                </td>
            </tr>
          </tbody>
        </table>
        </view>
      </view>
    </view>
  </template>
  
  <script lang="ts" setup>
  import { defineProps, nextTick, onMounted, ref, watch, computed, onUpdated } from 'vue'
  export interface TableProps {
    headers: any[]
    data: any[]
    headerFontSize?: Number,
    bodyFontSize?: Number,
    headerHeight?: Number,
    bodyHeight?: Number,
    intervalTime?: Number
  }
  
  const props = withDefaults(defineProps<TableProps>(), {
    headers: () => [],
    data: () => [],
    headerFontSize:  () => 20,
    bodyFontSize:  () => 20,
    headerHeight:  () => 80,
    bodyHeight:  () => 80,
    intervalTime: () => 3000
})
const Data: Ref<any[]> = ref([])
const myTable = ref(null)
let scrollSpeed = 0
const interval = ref<NodeJS.Timeout | null>(null)
const oldData = ref(props.data)
// 根据header传的style值,动态设置表格的style
const computedStyle = function(header: Object) {
      const keys = Object.keys(header)
      const values = Object.values(header)
      let styles = ''
      if(keys.length>1){
        for(let i=1; i<keys.length; i++){
          styles += (keys[i]+':'+values[i])
          if(i!=keys.length-1) styles+=','
        }
      }
      const styleObject: { [key: string]: string } = {};
      styles.split(',').forEach(style => {
      const [key, value] = style.split(':')
        styleObject[key] = value
      })
      return styleObject
    }
// 开始滚动
const startScroll = () => {
  // 设置滚动速度为每行的高度
  scrollSpeed = props.bodyHeight ? props.bodyHeight : document.querySelector('.cell').offsetHeight
  interval.value = setInterval(() => {
    if (myTable.value) {
      const oldScrollTop = myTable.value.$el.scrollTop
      myTable.value.$el.scrollTop += scrollSpeed
      if(myTable.value.$el.scrollTop===oldScrollTop) {
        myTable.value.$el.scrollTop=0
      }
      if (myTable.value.$el.scrollTop >= myTable.value.$el.scrollHeight / 2) {
        myTable.value.$el.scrollTop = 0
      }
    }
  }, props.intervalTime)
}
// 设置表头和表格共同列宽
function updateHeaderWidth() {
  if (props.data.length>0) {
      for(let index=0; index<props.headers.length; index++)
      {
        document.querySelector(`.headTh${index}`).style.width = document.querySelector(`.cellTd${index}`)?.offsetWidth + 'px'
      }
    }
      
}

onMounted( async () => {
    Data.value = props.data
    await nextTick()
    updateHeaderWidth()
    startScroll()
  }
)

watch(() => props.data, async (newData) => {
  myTable.value.$el.scrollTop = 0
  clearInterval(interval.value)
  updateData(newData)
})

onUpdated(() => {
  updateHeaderWidth()
  startScroll()
})

const updateData = (newData: any[]) => {
      const oldDataArray = oldData.value.slice()
      oldData.value = oldDataArray.filter(item => {
        return newData.some(newItem => {
          return JSON.stringify(newItem) === JSON.stringify(item)
        })
      })
      newData.forEach(item => {
        if (!oldDataArray.some(oldItem => JSON.stringify(oldItem) === JSON.stringify(item))) {
          oldData.value.unshift(item)
        }
      })
      Data.value = oldData.value
    }
  </script>
  
  <style scoped>
  .table-container {
    display: flex;
    flex-direction: column;
    height: 100vh; 
    padding: 20px;
  }
  
  .table-header {
    position: sticky;
    top: 0;
    z-index: 10; 
    margin-bottom: 20px;
  }
  
  .table-header table {
    width: 100%;
    border-collapse: collapse;
  }
  
  .table-body {
    overflow-y: auto;
    flex: 1;    
    padding: 20px;
  }
  
  .table-body table {
    width: 100%;
    border-collapse: collapse;
  }

  .header th{
    font-weight: bolder;
    white-space: nowrap;
    padding: 20px;
    box-sizing: border-box;
  }

  .cell td{
    padding: 20px;
    box-sizing: border-box;
  }

.table-body::-webkit-scrollbar {
  width: 0;
  height: 0;
}

.table-body {
  -ms-overflow-style: none; 
  scrollbar-width: none;
}
  </style>
  

封装过程中发现因为表头和内容是两个分开的table,所以会存在表头和表格不能上下对齐的情况,这时候考虑代码中的updateHeaderWidth函数,在组件挂载完的时候将头部表格和内容表格的宽度一一对应。

通过 headers: any[]接收父组件传的表头展示数据。
data: any[]接收父组件传的表格内容数据
都为必传内容,但是也有默认赋值。
通过
headerFontSize?: Number,表头的文字大小(一般表头会更醒目一些)
bodyFontSize?: Number,表格的文字大小
headerHeight?: Number,表头高度(默认根据传值让表头内容在单元格中居中)
bodyHeight?: Number,表格高度
intervalTime?: Number滚动间隙(多少毫秒滚动一次)

组件挂载完的时候,通过startScroll来开始滚动,组件默认将bodyHeight表格高度设置为滚动速度scrollSpeed,实现每次滚动底部刷新出最新一条数据的效果。

对于数据的刷新,本来计划新数据和旧数据进行一个简单的diff比较差异,然后将没有的数据加入到旧数据的最后面,保存更新数据前的滚动高度scrollTop,更新数据之后继续从该高度开始滚动。但是后面又意识到不仅仅有增加,还会有删除,这个时候滚动高度scrollTop不适配了,数据也不一定会接着更新数据前的内容展示。

这个时候考虑先将旧数据中在新数据中仍存在的值过滤出来,然后将没有的数据加入到旧数据的最前面(数据data要求是一个数组,因此加数据的时候采用unshift),每次更新数据都将滚动高度scrollTop置0开始重新滚动,用户每次都会看到最新的数据。

后面发现更新数据后的总的滚动高度scrollHeight 获取不正常,滚动会停止,以为是数据更新之后还没重新渲染完就获取了导致的,但是在onUpdated中获取的也是同样的值,后面排查也没发现具体原因,因此直接写死代码,让出现异常的时候判断出来重新将滚动高度scrollTop置0。

监听数据变化完之后,表格会重新渲染,此时表头由于数据没有更新会保持原宽度不变,所以在onUpdated中再次调用updateHeaderWidth函数并且重新启动自动滚动startScroll

考虑到对于表格的展示,用户可能有不同的要求,比如时间的字段实际太长了,需要将字体调小来实现一行展示。因此提供computedStyle方法来实现动态style。此时的传值会在headers中,因为data实际都是接口获取值,不会说每个数据字段都告诉你要什么样式。

调用代码如下:

<template>
    <view class="body">
      <ScrollTable :headers="headers" :data="tableData" :body-font-size="30" :body-height="110" :header-font-size="50" :header-height="120" :interval-time="2000" />
    </view>
</template>
  
<script setup lang="ts">
import { ref } from 'vue';
import ScrollTable from '@/components/ScrollTable/ScrollTable.vue';
const headers = ref<any[]>([{'type': '异常类型', 'whiteSpace': 'nowrap'}, {'reason': '异常原因'}, {'dateTime': '异常时间', 'fontSize': '25px', 'whiteSpace': 'nowrap'}])
cosnt const tableData = ref<any[]>([
        {
          'type': 'wcs扫监管码异常',
          'reason': '订单XXXXXXXXXXXXXXX出现WCS扫监管码异常,我试试原因很长的时候会不会自动换行',
          'dateTime': '2024-08-30 09:17:10'
        }, {
          'type': '视觉扫描异常',
          'reason': '订单XXXXXXXXXXXXXXX出现视觉扫描异常',
          'dateTime': '2024-08-30 10:17:10'
        }, {
          'type': '未识别托盘码',
          'reason': '订单XXXXXXXXXXXXXXX未能识别托盘码',
          'dateTime': '2024-08-30 11:17:10'
        }, {
          'type': '遗漏未入库物料',
          'reason': '订单XXXXXXXXXXXXXXX存在有物料未入库',
          'dateTime': '2024-08-30 12:17:10'
        }, {
          'type': '外包装异常',
          'reason': '订单XXXXXXXXXXXXXXX存在有物料未入库',
          'dateTime': '2024-08-30 13:17:10'
        },
        {
          'type': 'wcs扫监管码异常',
          'reason': '订单XXXXXXXXXXXXXXX出现WCS扫监管码异常1',
          'dateTime': '2024-08-30 13:27:10'
        }, {
          'type': '视觉扫描异常',
          'reason': '订单XXXXXXXXXXXXXXX出现视觉扫描异常2',
          'dateTime': '2024-08-30 13:37:10'
        }, {
          'type': '未识别托盘码',
          'reason': '订单XXXXXXXXXXXXXXX未能识别托盘码3',
          'dateTime': '2024-08-30 13:47:10'
        }, {
          'type': '遗漏未入库物料',
          'reason': '订单XXXXXXXXXXXXXXX存在有物料未入库',
          'dateTime': '2024-08-30 13:57:10'
        }, {
          'type': '外包装异常',
          'reason': '订单XXXXXXXXXXXXXXX存在有物料未入库',
          'dateTime': '2024-08-30 14:17:10'
        }
    ])

最后可以使用setTimeOut来模拟数据刷新查看更新数据的效果。

标签:uniapp,const,数据,ts,value,vue3,滚动,table,无缝
From: https://blog.csdn.net/qq_51384850/article/details/141738352

相关文章

  • 关于requests的使用方法
    我们从四个模块来讲解:GET请求POST请求响应高级用法(cookie,session等)GET请求:最基本的使用:导入requests库importrequestsr=requests.get('https://www.httpbin.org/get')将返回的内容以文本形式打印出来print(r.text)------------------输出结果--------------------......
  • CF1741F-Multi-ColoredSegments
    https://www.luogu.com.cn/problem/CF1741Fhttps://codeforces.com/contest/1741/problem/F参考:https://www.luogu.com.cn/article/bb54tb8m考虑用线段树维护每个点被几条线段覆盖,然后按照颜色分类,每次做其中一类,把同类颜色从线段树中去掉,然后先区间求和看有没有重叠,再左端点往......
  • Mysql中用exists代替in
    exists对外表用loop逐条查询,每次查询都会查看exists的条件语句,当exists里的条件语句能够返回记录行时(无论记录行是的多少,只要能返回),条件就为真,返回当前loop到的这条记录,反之如果exists里的条件语句不能返回记录行,则当前loop到的这条记录被丢弃,exists的条件就像一个bool条件,当......
  • [Redis]Intset
    intset小整数集合set集合容纳的元素都是整数并且元素个数较少时,Redis会使用intset来存储集合元素。intset是紧凑的数组结构,同时支持16位、32位和64位整数structintset<T>{ int32encoding;//决定整数位宽是16位、32位还是64 int32length;//元素个数 i......
  • vue3下拉菜单点击之后缓慢展开与缓慢关闭
    利用 max-height 来实现下拉菜单的缓慢展开和关闭效果。通过设置一个固定的 max-height 值以及过渡效果,可以让菜单在展开和关闭时产生动画效果。<template><divclass="dropdown"><divclass="selected"@click="toggleDropdown">......
  • uniapp自定义头部导航栏布局(优化版)
    H5与微信小程序效果图普通版//utils/system.js //获取系统信息 constsystemInfo=uni.getSystemInfoSync(); //获取状态栏的高度,H5状态栏的高度默认是0 exportconstgetStatusBarHeight=()=>systemInfo.statusBarHeight||0; //获取标题栏高度 exportconstgetT......
  • LTspice使用教程,LTspice仿真教程资源大全
      LTspice简介 LTspice是英文SimulationProgramwithIntegratedCircuitEmphasis的缩写,意思是集成电路通用模拟程序。是ADI旗下的一款免费软件,很多国外的工程师、教授、学生基本用的都是LTspice,感觉应该是最好用的一款,也是教程在国内普及的比较好的一款仿真软件......
  • Vue3+.NET7最新框架实战,手写电商管理后台|2023全新录制,前后分离架构(C#/.NET6/.NET Co
    Vue3+.NET7最新框架实战,手写电商管理后台|2023全新录制,前后分离架构(C#/.NET6/.NETCore)https://pan.baidu.com/s/1SBt4RTT_m6uA9pk857KlcQ?pwd=6666https://www.bilibili.com/video/BV16s4y1m7bd/?spm_id_from=333.337.search-card.all.click&vd_source=e6b56a12a1d9ef11f6c13......
  • LTSPICE仿真教程,LTSPICE使用教程汇总
    LTspice介绍LTspice由亚德诺半导体(ADI)推出。LTspice是一款功能强大的SPICE仿真器软件,具备原理图捕获图形界面,能够探测原理图并生成仿真结果,用户可通过其内置的波形查看器轻松探索仿真结果。与其他SPICE解决方案相比,LTspice的增强功能和模型极大地提升了模拟电路仿真......
  • 使用ClassLoader.getSystemResource更新上线后空指针异常
     目录 问题描述:原问题代码:问题原因以及解决思路:解决方法:问题描述:项目中使用到一个功能,于是在资源路径下加了点依赖包:更新上线后,发现使用ClassLoader.getSystemResource("dependencies")找不到依赖包原问题代码:URLresourceURL=ClassLoader.getSystemResource(......