当前位置: 首页 > news >正文

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

电视机上要以列表展示数据,并且数据会实时更新,电视机不能点击,所以考虑自动播放的一个效果。展示方案有两种: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').offsetHeightinterval.value = setInterval(() => {if (myTable.value) {const oldScrollTop = myTable.value.$el.scrollTopmyTable.value.$el.scrollTop += scrollSpeedif(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.dataawait nextTick()updateHeaderWidth()startScroll()}
)watch(() => props.data, async (newData) => {myTable.value.$el.scrollTop = 0clearInterval(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来模拟数据刷新查看更新数据的效果。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 国内超声波清洗机哪个品牌好?质量好的超声波清洗机推荐
  • 026集——在旧式编码与 Unicode 之间转换(C# 编程指南)——C#学习笔记
  • 【算法】C++贪心算法解题(单调递增数字、坏了的计算器、合并区间)
  • PostgreSQL 中的 `generate_series` 函数使用
  • MAT:一款针对MSSQL服务器的安全检测与审计工具
  • 【C++】C++智能指针详解
  • VUE3 使用 <transition> 实现组件切换的过渡效果
  • 【日常记录-Linux】WebDriver
  • 如何打造抗冲击的超级电容器?用啥材料好?
  • 大数据技术概述
  • U盘常规数据恢复深度解析:原因、方案与预防策略
  • 文件包含PHP伪协议利用方法
  • c++(list)
  • CSS学习4[重点]
  • 原油市场“闪崩”,国际油价单日下跌超4%!
  • Centos6.8 使用rpm安装mysql5.7
  • es6
  • HTML5新特性总结
  • MySQL-事务管理(基础)
  • Perseus-BERT——业内性能极致优化的BERT训练方案
  • Python学习之路13-记分
  • RxJS 实现摩斯密码(Morse) 【内附脑图】
  • Spring Cloud(3) - 服务治理: Spring Cloud Eureka
  • vuex 学习笔记 01
  • 模型微调
  • 排序算法之--选择排序
  • 前端性能优化——回流与重绘
  • 一起来学SpringBoot | 第三篇:SpringBoot日志配置
  • linux 淘宝开源监控工具tsar
  • 数据库巡检项
  • ​马来语翻译中文去哪比较好?
  • #ifdef 的技巧用法
  • #LLM入门|Prompt#1.8_聊天机器人_Chatbot
  • #图像处理
  • $L^p$ 调和函数恒为零
  • (1)常见O(n^2)排序算法解析
  • (13)Latex:基于ΤΕΧ的自动排版系统——写论文必备
  • (2)从源码角度聊聊Jetpack Navigator的工作流程
  • (20)目标检测算法之YOLOv5计算预选框、详解anchor计算
  • (Redis使用系列) Springboot 使用redis的List数据结构实现简单的排队功能场景 九
  • (利用IDEA+Maven)定制属于自己的jar包
  • (十五)使用Nexus创建Maven私服
  • (实战)静默dbca安装创建数据库 --参数说明+举例
  • (贪心) LeetCode 45. 跳跃游戏 II
  • (限时免费)震惊!流落人间的haproxy宝典被找到了!一切玄妙尽在此处!
  • (一)SpringBoot3---尚硅谷总结
  • (转)scrum常见工具列表
  • (转载)深入super,看Python如何解决钻石继承难题
  • .[backups@airmail.cc].faust勒索病毒的最新威胁:如何恢复您的数据?
  • .NET Core MongoDB数据仓储和工作单元模式封装
  • .net core 管理用户机密
  • .net framework 4.8 开发windows系统服务
  • .NET 使用 ILRepack 合并多个程序集(替代 ILMerge),避免引入额外的依赖
  • .NET 事件模型教程(二)
  • [ C++ ] STL_vector -- 迭代器失效问题