uni-app app,小程序 时间轴样式,时间轴效果。弹窗里边使用时间轴。悬浮菜单 电脑版发表于:2024/5/31 17:21 tn2>虽然可以用一些时间轴组件比如,timeline,但是有时候还是自己写点样式定制性好一点,不需要像组件那么通用。 [TOC] ### 时间轴效果一 #### 效果图 ![](https://img.tnblog.net/arcimg/aojiancc2/79d1cf2a8d824ac59a88db5245e2d842.jpg) #### 具体的代码和样式 ``` <template> <view class="teaching-dynamics-container"> <view class="tdc-course-info"> <view class="tdc-grade">优</view> <view class="tdc-coursename">《MySQL数据库设计与实现(第二版)》</view> </view> <view class="tdc-class-schedule"> <view class="tdc-classinfo"> <view class="tcs-classtag">班级:</view> <view class="tsc-classname">22级移动互联应用技术1班</view> </view> <view class="tdc-cs-schedule"> <view class="tcs-cs-scheduletag">进度:</view> <view class="tsc-cs-schedulename">第3章 MySQL存储引擎和数据类型</view> </view> <view class="tdc-line"></view> <view class="tdc-cs-completeinfo"> <view class="tdc-csc-warp"> <view>应完成:</view> <view>60%</view> </view> <view class="tdc-csc-warp tdc-csc-warp-right"> <view>完成:</view> <view>59%</view> </view> </view> </view> <view class="system-study-situation" style="margin-top: 35rpx"> <view class="timeline-list"> <block v-for="(item, index) in 3" :key="index"> <view class="timeline-item"> <view class="timeline-item_tail"></view> <view class="timeline-item_node"> <view class="timeline-item-innode"></view> </view> <view class="timeline-item_wrapper"> <view class="sss-title-complete"> <view class="sss-title"> <view>评估系统</view> <!-- <view class="sss-grade grade-excellent">优</view> --> <view class="sss-grade grade-excellent" :class="state.gradeStyle[index]">{{ state.gradeDesc[index] }}</view> </view> <view class="sss-complete-info"> <view>完成80%</view> <view>应完成70%</view> </view> </view> <view class="sss-chapter-study-situation" v-for="(initem, inindex) in 2" :key="inindex"> <view class="sss-css-item"> <view class="sss-ci-title">第1章MySQL数据库基础</view> <view class="sss-ci-situation">未开始</view> </view> </view> </view> </view> </block> </view> </view> </view> </template> <script setup lang="ts" name="tasks"> import { defineAsyncComponent, reactive, onMounted, toRefs, ref } from 'vue'; const state = reactive({ title: '更新', schoolID: null, // 优,良,差样式 gradeStyle: { 0: "grade-excellent", 1: "grade-good", 2: "grade-bad", }, // 优,良,差描述 gradeDesc: { 0: "优", 1: "良", 2: "差", } }) onMounted(() => { }) </script> <!-- 时间轴的样式 --> <style lang="scss" scoped> .timeline-list { margin: 10rpx 0px 10px 5px; margin-top: 62rpx; font-size: 28rpx; list-style: none; } .timeline-item:last-child .timeline-item_tail { display: none; } .timeline-item { position: relative; padding-bottom: 20rpx; } .timeline-item_tail { position: absolute; left: 4rpx; height: 100%; border-left: 2rpx solid rgba(109, 209, 201, 0.3); // 时间轴左边的线修改为虚线 border-left-style: dashed; } .timeline-item_node { position: absolute; background-color: #FFFFFF; border-radius: 50%; display: flex; justify-content: center; align-items: center; left: -8rpx; width: 22rpx; height: 22rpx; background: #fff; border: 2rpx solid #6DD1C9; .timeline-item-innode { width: 14rpx; height: 14rpx; background: #6DD1C9; border-radius: 50%; } } .timeline-item_wrapper { position: relative; padding: 26rpx 0rpx 0rpx 0rpx; margin-left: 39rpx; top: -34rpx; background-color: #ffffff; border-radius: 20rpx; // width: 600rpx; } .timeline-item_timestamp { color: #38254D; font-weight: bold; font-size: 30rpx; line-height: 32rpx; } .timeline-item_content { display: flex; flex-direction: column; margin-top: 20rpx; margin-bottom: 20rpx; text { font-size: 26rpx; color: #574966; line-height: 40rpx; } .btn { align-self: flex-end; font-size: 26rpx; color: #F06245; line-height: 60rpx; text-align: center; margin-top: -40rpx; width: 140rpx; height: 60rpx; border: 1rpx solid #F06245; border-radius: 30rpx; } } </style> <!-- 其他的内容样式 --> <style scoped="scoped" lang="scss"> .teaching-dynamics-container { padding: 39rpx 30rpx 30rpx 30rpx; // 优秀 .grade-excellent { background: #5ECDB6; } // 良好 .grade-good { background: #FF8B64; } // 差 .grade-bad { background: #FF6363; } .tdc-line { height: 1rpx; background: #ECEDEF; // background: #ff6666; margin-top: 25rpx; margin-bottom: 25rpx; } .tdc-course-info { display: flex; .tdc-grade { width: 54rpx; height: 36rpx; background: #5ECDB6; border-radius: 24rpx 24rpx 0rpx 24rpx; text-align: center; font-family: PingFang SC, PingFang SC; font-weight: 400; font-size: 24rpx; color: #FFFFFF; } .tdc-coursename { font-family: PingFang SC, PingFang SC; font-weight: 400; font-size: 28rpx; color: #313960; padding-left: 13rpx; } } .tdc-class-schedule { margin-top: 30rpx; // height: 196rpx; background: #F7F8FB; border-radius: 8rpx 8rpx 8rpx 8rpx; padding: 30rpx 30rpx; .tdc-classinfo { display: flex; padding-left: 46rpx; .tcs-classtag { color: #B3B3B3; font-size: 24rpx; font-family: PingFang SC, PingFang SC; } .tsc-classname { color: #313960; font-size: 24rpx; font-family: PingFang SC, PingFang SC; } } .tdc-cs-schedule { display: flex; margin-top: 20rpx; padding-left: 46rpx; .tcs-cs-scheduletag { color: #B3B3B3; font-size: 24rpx; font-family: PingFang SC, PingFang SC; } .tsc-cs-schedulename { color: #313960; font-size: 24rpx; font-family: PingFang SC, PingFang SC; } } .tdc-cs-completeinfo { display: flex; font-family: PingFang SC, PingFang SC; font-weight: 400; font-size: 24rpx; color: #55B6FF; padding-left: 46rpx; .tdc-csc-warp { display: flex; } } .tdc-csc-warp-right { margin-left: 100rpx; } } .system-study-situation { .sss-title-complete { display: flex; justify-content: space-between; align-items: center; .sss-title { font-size: 30rpx; color: #313960; display: flex; align-items: center; .sss-grade { width: 39rpx; height: 32rpx; // background: #5ECDB6; border-radius: 10rpx 8rpx 10rpx 8rpx; margin-left: 10rpx; // 微调一下间距 margin-top: -4rpx; text-align: center; font-family: PingFang SC, PingFang SC; font-weight: 400; font-size: 24rpx; color: #FFFFFF; } } .sss-complete-info { display: flex; font-family: PingFang SC, PingFang SC; font-weight: 400; font-size: 24rpx; color: #313960; :last-child { margin-left: 20rpx; } } } .sss-chapter-study-situation { .sss-css-item { display: flex; justify-content: space-between; align-items: center; height: 73rpx; background: #F9F9F9; border-radius: 8rpx 8rpx 8rpx 8rpx; font-family: PingFang SC, PingFang SC; font-weight: 400; font-size: 24rpx; color: rgba(0, 0, 0, 0.6); margin-top: 20rpx; padding: 0px 15rpx; // .sss-ci-title { // } // .sss-ci-situation{ // } } } } } </style> ``` ### 时间轴效果2 #### 注意横向滚动条的问题 tn6>有可能会因为宽度问题出现横向滚动条,注意根据实际情况微调一下样式即可。这里的样式是不会出现横向滚动条的,要改造样式的话注意一下因为宽度问题造成的横向滚动条即可。 一般因为宽度问题出现横向滚动条是因为这里的样式: ``` .timeline-item_wrapper { position: relative; padding: 32rpx 24rpx; // ****就是这一句,宽度窄的时候不会出现横向滚动条,但是宽度撑满的时候就会出现了**** left: 42rpx; top: -32rpx; background-color: #ffffff; border-radius: 20rpx; width: 600rpx; } ``` 所以其实建议这样修改更科学一点(通过padding与margin-left来实现不使用left): ``` .timeline-item_wrapper { position: relative; padding: 0rpx 0rpx 20rpx 25rpx; // 加一个left可能会出现横向滚动条,体验不太好,可以通过margin-left来设置就不会有问题了 // left: 32rpx; margin-left: 32rpx; top: -6rpx; background-color: #ffffff; } ``` 贴的其他例子中,如果timeline-item_wrapper里边使用left来实现位置调整的,都可能有这个问题,就不在累述了,根据上面的规则调整即可 #### 效果图2 ![](https://img.tnblog.net/arcimg/aojiancc2/aa3cb59bf858420282707bc0984c3ed4.png) #### 具体的代码和样式2 ``` <template> <view class="content"> <view class="describle"> 参加班级、寝室第1次扫除道并上传打扫完之后的图片<span style="color:#4D9DF5">(已完成3次)</span> </view> <view class="writeContent"> <textarea style="background-color: #fff;width: 100%;"></textarea> </view> <view class="timeline-list"> <block v-for="(item, index) in 3" :key="index"> <view class="timeline-item"> <view class="timeline-item_tail"></view> <view class="timeline-item_node"></view> <view class="timeline-item_wrapper"> <div>已完成《参加班级扫除道》并上传照片</div> <div class="image-content"> <image class="imgitem" @click="previewImg(0)" mode="scaleToFill" src="https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg"></image> <image class="imgitem" @click="previewImg(1)" src="https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg"></image> </div> <div class="task_time"> 2023-08-30 16:53:08 </div> </view> </view> </block> </view> </view> </template> <script setup lang="ts"> import { ref } from 'vue' const title = ref('Hello') // import UaTimeline from '@/component/ua-timeline/ua-timeline.vue' // import UaTimelineItem from '@/component/ua-timeline-item/ua-timeline-item.vue' const previewImg = (index = 0) => { console.log("预览!") uni.previewImage({ urls: ['https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg', 'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg', 'https://fuss10.elemecdn.com/9/bb/e27858e973f5d7d3904835f46abbdjpeg.jpeg'], current: index }); } </script> <style lang="scss" scoped> .content { background: #F4F6F9; .describle { // font-size: 32rpx; margin-top: 25rpx; font-family: PingFang SC-Regular, PingFang SC; font-weight: 400; } .writeContent { margin-top: 30rpx; } .taskContent { background-color: #fff; margin-top: 30rpx; padding-top: 10px; } .image-content { margin-top: 10px; display: flex; flex-wrap: wrap; justify-content: space-between; .imgitem { flex: 0 0 32%; /* 动态计算间距 */ margin-right: calc(4% / 3); // margin-bottom: calc(5% / 3); margin-bottom: 6px; // width: 124px; height: 139px; background: #F6F6F6; border-radius: 0px 0px 0px 0px; opacity: 1; } /* 去除每行尾的多余边距 */ .imgitem:nth-child(3n) { margin-right: 0; } /* 使最后一个元素的边距填满剩余空间 */ .imgitem:last-child { margin-right: auto; } } } </style> <!-- 时间轴样式 --> <style lang="scss" scoped> .timeline-list { margin: 32rpx; margin-top: 62rpx; font-size: 28rpx; list-style: none; } .timeline-item:last-child .timeline-item_tail { display: none; } .timeline-item { position: relative; padding-bottom: 20rpx; } .timeline-item_tail { position: absolute; left: 4rpx; height: 100%; border-left: 2rpx solid rgba(109, 209, 201, 0.3); } .timeline-item_node { position: absolute; background-color: #FFFFFF; border-radius: 50%; display: flex; justify-content: center; align-items: center; left: -12rpx; width: 22rpx; height: 22rpx; background: #fff; border: 6rpx solid #6DD1C9; } .timeline-item_wrapper { position: relative; padding: 32rpx 24rpx; left: 42rpx; top: -32rpx; background-color: #ffffff; border-radius: 20rpx; width: 600rpx; } .timeline-item_timestamp { color: #38254D; font-weight: bold; font-size: 30rpx; line-height: 32rpx; } .timeline-item_content { display: flex; flex-direction: column; margin-top: 20rpx; margin-bottom: 20rpx; text { font-size: 26rpx; color: #574966; line-height: 40rpx; } .btn { align-self: flex-end; font-size: 26rpx; color: #F06245; line-height: 60rpx; text-align: center; margin-top: -40rpx; width: 140rpx; height: 60rpx; border: 1rpx solid #F06245; border-radius: 30rpx; } } </style> ``` ### 时间轴效果3(在uni-app小程序弹窗里边使用的) #### 效果图3 <img src="https://img.tnblog.net/arcimg/aojiancc2/4ed95c65bc3a49c68879e195f0ba41ae.png" style="width:399px;height:auto;"> #### 具体的代码和样式3 这里实现了图片超过一行后会自适应布局的,一行显示4张图 **弹窗里边的内容** ``` <template> <view class="monthTaskDetails-container"> <view class="titleWrap"> <view class="title">{{ title }}</view> <view class="closeTag" @tap="handleClose">×</view> </view> <view class="splitline"></view> <scroll-view scroll-y="true" style=" height: 700rpx;"> <view class="timeline-list"> <block v-for="(item, index) in 3" :key="index"> <view class="timeline-item"> <view class="timeline-item_tail"></view> <view class="timeline-item_node"></view> <view class="timeline-item_wrapper"> <div>已完成《参加班级扫除道》并上传照片</div> <view class="image-content"> <image class="imgitem" @click="previewImg(0)" mode="scaleToFill" src="https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg"> </image> <image class="imgitem" @click="previewImg(1)" src="https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg"> </image> <image class="imgitem" @click="previewImg(2)" src="https://fuss10.elemecdn.com/d/e6/c4d93a3805b3ce3f323f7974e6f78jpeg.jpeg"> </image> <image class="imgitem" @click="previewImg(3)" src="https://fuss10.elemecdn.com/3/28/bbf893f792f03a54408b3b7a7ebf0jpeg.jpeg"> </image> <image class="imgitem" @click="previewImg(4)" src="https://fuss10.elemecdn.com/2/11/6535bcfb26e4c79b48ddde44f4b6fjpeg.jpeg"> </image> <image class="imgitem" @click="previewImg(5)" src="https://fuss10.elemecdn.com/9/bb/e27858e973f5d7d3904835f46abbdjpeg.jpeg"> </image> </view> <view class="extraData"> <view class="task_time"> 2023-08-30 16:53:08 </view> <image class="deleteimg" src="../../static/delete.png"> </image> </view> </view> </view> </block> </view> </scroll-view> </view> </template> <script setup lang="ts"> import { ref, reactive } from 'vue' const props = defineProps({ title: { type: String, default: "完成人次" }, userList: { type: Array, default: [] } }) // 触发closePopup事件 const emit = defineEmits(['closePopup']) const handleClose = () => { emit('closePopup', '参数') } // 图片预览 const previewImg = (index = 0) => { uni.previewImage({ urls: ['https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg', 'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg', 'https://fuss10.elemecdn.com/d/e6/c4d93a3805b3ce3f323f7974e6f78jpeg.jpeg', 'https://fuss10.elemecdn.com/3/28/bbf893f792f03a54408b3b7a7ebf0jpeg.jpeg', 'https://fuss10.elemecdn.com/2/11/6535bcfb26e4c79b48ddde44f4b6fjpeg.jpeg', 'https://fuss10.elemecdn.com/9/bb/e27858e973f5d7d3904835f46abbdjpeg.jpeg'], current: index }); } </script> <style lang="scss" scoped> .monthTaskDetails-container { height: 799rpx; background-color: #fff; border-top-left-radius: 60rpx; border-top-right-radius: 60rpx; .titleWrap { display: flex; padding-top: 22rpx; padding-left: 20rpx; padding-right: 20rpx; justify-content: center; align-items: center; /* 垂直居中 */ position: relative; .title { font-family: PingFang SC, PingFang SC; font-weight: bold; font-size: 36rpx; color: #313960; } .closeTag { position: absolute; right: 16rpx; padding: 10rpx 20rpx; font-size: 50rpx; } } .splitline { margin-left: -20rpx; margin-right: -20rpx; margin-top: 30rpx; height: 1rpx; background: #f3f3f3; } .image-content { margin-top: 10px; display: flex; flex-wrap: wrap; justify-content: space-between; .imgitem { flex: 0 0 23%; /* 动态计算间距 */ margin-right: calc(8% / 3); margin-bottom: 15rpx; height: 200rpx; background: #F6F6F6; border-radius: 0px 0px 0px 0px; opacity: 1; } /* 去除每行尾的多余边距 */ .imgitem:nth-child(4n) { margin-right: 0; } /* 使最后一个元素的边距填满剩余空间 */ .imgitem:last-child { margin-right: auto; } } .extraData { display: flex; margin-top: 10rpx; align-items: center; .task_time { font-size: 24rpx; color: #909399; } .deleteimg { width: 22rpx; height: 22rpx; margin-left: 20rpx; margin-top: -2rpx; } } } </style> <!-- 时间轴样式 --> <style lang="scss" scoped> .timeline-list { margin: 32rpx; margin-top: 62rpx; font-size: 28rpx; list-style: none; } .timeline-item:last-child .timeline-item_tail { display: none; } .timeline-item { position: relative; padding-bottom: 20rpx; } // 时间轴的竖线样式 .timeline-item_tail { position: absolute; left: 2rpx; height: 100%; border-left: 2rpx solid rgba(109, 209, 201, 0.3); } .timeline-item_node { position: absolute; background-color: #FFFFFF; border-radius: 50%; display: flex; justify-content: center; align-items: center; left: -12rpx; width: 17rpx; height: 17rpx; background: #fff; border: 6rpx solid #E5E5E5; } // 时间轴的第一个圈的颜色 .timeline-item:first-child { .timeline-item_node { border: 6rpx solid #4D9DF5; } } .timeline-item_wrapper { position: relative; // padding: 32rpx 24rpx; padding: 32rpx calc(8% / 3) 32rpx 24rpx; left: 26rpx; top: -39rpx; background-color: #ffffff; border-radius: 20rpx; } .timeline-item_timestamp { color: #38254D; font-weight: bold; font-size: 30rpx; line-height: 32rpx; } .timeline-item_content { display: flex; flex-direction: column; margin-top: 20rpx; margin-bottom: 20rpx; text { font-size: 26rpx; color: #574966; line-height: 40rpx; } .btn { align-self: flex-end; font-size: 26rpx; color: #F06245; line-height: 60rpx; text-align: center; margin-top: -40rpx; width: 140rpx; height: 60rpx; border: 1rpx solid #F06245; border-radius: 30rpx; } } </style> ``` **父组件,使用弹窗的地方** ``` <template> <view class="body-view"> <uni-popup ref="monthTaskDetailsPopup" background-color="#fff" border-radius="60rpx 60rpx 0rpx 0rpx;" type="bottom"> <monthTaskDetails @closePopup="closeMonthTaskDetails" title="参加班级扫除道"> </monthTaskDetails> </uni-popup> </view> </template> <script setup lang="ts"> import monthTaskDetails from './component/monthTaskDetails.vue' let monthTaskDetailsPopup = ref() // 打开弹窗 const showMonthTaskDetails = () => { monthTaskDetailsPopup.value.open() } // 关闭弹窗 const closeMonthTaskDetails = () => { monthTaskDetailsPopup.value.close() } </script> ``` ### 时间轴效果4(包含grid布局实现一行显示3个文字,图文消息,参与人员与缺勤人员,悬浮菜单等) #### 效果图4 <img src="https://img.tnblog.net/arcimg/aojiancc2/af23221ebd464820aed6b5049251ee98.jpg" style="width:399px;height:auto;"> #### 具体的代码和样式4 ``` <template> <view class="content"> <view class="describle"> {{ state.taskName }}<text>(已完成3次)</text> </view> <!-- <view v-for="(stu, index) in 50" :key="index"> {{index}} </view> --> <view class="timeline-list"> <template v-for="item in 3" :key="item.id"> <view class="timeline-item"> <view class="timeline-item_tail" /> <view class="timeline-item_node" /> <view class="timeline-item_wrapper"> <view class="stu-name"> 王佳佳 </view> <view class="task-content">已完成《参加班级扫除道》并上传照片</view> <view class="image-content"> <image class="imgitem" v-for="(img, index) in state.imgLists" :key="index" mode="aspectFill" :src="img" /> <view class="" style="flex: 1" /> </view> <view class="task-person"> <view class="participant-list"> <!-- 参与人员 --> <view class="p-item" v-for="(stu, index) in 15" :key="index"> <view class="tp-stu">小明{{ index }}:</view> <view class="tp-status">参与</view> </view> <!-- 缺勤人员 --> <view class="p-item" v-for="(stu, index) in 5" :key="index"> <view class="tp-stu">小红{{ index }}:</view> <view class="tp-status-absence">缺勤</view> </view> </view> </view> <view class="task_time"> 2024-7-24 12:30 <!-- <text class="task-reject">删除</text> --> </view> </view> </view> </template> </view> <view class="task-complate-to"></view> </view> </template> <script setup lang="ts"> import { ref, reactive } from 'vue' import { onLoad, onShow } from '@dcloudio/uni-app' import http from '@/common/request.ts' import config from '@/common/config.ts' import dayjs from 'dayjs' const state = reactive({ taskName: '参加早迎接活动', imgLists: ['https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg', 'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg', 'https://fuss10.elemecdn.com/d/e6/c4d93a3805b3ce3f323f7974e6f78jpeg.jpeg', 'https://fuss10.elemecdn.com/3/28/bbf893f792f03a54408b3b7a7ebf0jpeg.jpeg', 'https://fuss10.elemecdn.com/2/11/6535bcfb26e4c79b48ddde44f4b6fjpeg.jpeg', 'https://fuss10.elemecdn.com/9/bb/e27858e973f5d7d3904835f46abbdjpeg.jpeg'] }) </script> <style lang="scss" scoped> .content { background: #f4f6f9; .describle { padding: 20rpx; font-weight: 400; font-size: 30rpx; color: #313960; border-top: 4rpx solid #ededed; text { margin-left: 20rpx; color: #4d9df5; font-size: 24rpx; } } .image-content { margin-top: 20rpx; display: flex; flex-wrap: wrap; // justify-content: space-between; .imgitem { width: 31%; /* 动态计算间距 */ margin-right: calc(7% / 2); // margin-bottom: calc(5% / 3); margin-bottom: 20rpx; // height: width; height: 199rpx; background: #f6f6f6; border-radius: 0px 0px 0px 0px; opacity: 1; } /* 去除每行尾的多余边距 */ .imgitem:nth-child(3n) { margin-right: 0px; } /* 使最后一个元素的边距填满剩余空间 */ .imgitem:last-child { margin-right: auto; } } .task-complate-to { width: 108rpx; height: 108rpx; background: linear-gradient(180deg, #4d9df5 0%, #63cfff 100%); box-shadow: 0px 4rpx 10rpx 1rpx rgba(77, 157, 245, 0.32); border-radius: 50%; position: fixed; bottom: 102rpx; right: 30rpx; } .task-complate-to::before { content: ' '; width: 56rpx; border-bottom: 4rpx solid #ffffff; position: absolute; left: calc(50% - 28rpx); top: calc(50% - 2rpx); } .task-complate-to::after { content: ' '; height: 56rpx; border-right: 4rpx solid #ffffff; position: absolute; left: calc(50% - 2rpx); top: calc(50% - 28rpx); } } </style> <style lang="scss" scoped> .timeline-list { font-size: 28rpx; list-style: none; padding: 30rpx 30rpx 20rpx 30rpx; padding-bottom: 0; background-color: #ffffff; } .timeline-item:last-child .timeline-item_tail { display: none; } .timeline-item { position: relative; // padding-bottom: 20rpx; .timeline-item_tail { position: absolute; left: 6rpx; top: 0; bottom: 0; border-left: 2rpx solid rgba(109, 209, 201, 0.3); } .timeline-item_node { position: absolute; background-color: #ffffff; border-radius: 50%; display: flex; justify-content: center; align-items: center; left: -9rpx; width: 18rpx; height: 18rpx; background: #fff; border: 6rpx solid #6dd1c9; } .timeline-item_wrapper { position: relative; padding: 0rpx 0rpx 20rpx 25rpx; // 加一个left可能会出现横向滚动条,体验不太好,可以通过margin-left来设置就不会有问题了 // left: 32rpx; margin-left: 32rpx; top: -6rpx; background-color: #ffffff; // margin-left: 42rpx; // border-radius: 20rpx; // width: 600rpx; .stu-name { font-size: 30rpx; font-weight: bold; color: #174a90; margin-bottom: 10rpx; } .task-content { color: #313960; font-size: 30rpx; font-weight: 400; } .task-person { margin-top: 20rpx; font-size: 24rpx; .p-item { display: flex; margin-bottom: 22rpx; .tp-stu { color: #313960; } } .participant-list { display: grid; /*每行显示四个,宽度由内容自适应*/ grid-template-columns: repeat(3, auto); /*显示方式为左右无间距,中间均分*/ justify-content: space-between; grid-column-gap: 19rpx; } .participant-list { .tp-status { color: #67c23a; background: #f0f9eb; border-radius: 4rpx; padding: 0 10rpx; } .tp-status-absence { color: #f56c6c; background: #fef0f0; border-radius: 4rpx; padding: 0 10rpx; } } } .task_time { margin-top: 4rpx; margin-bottom: 40rpx; color: #909399; font-size: 24rpx; .task-reject { color: #f56c6c; font-size: 24rpx; margin-left: 20rpx; } } } } </style> ``` #### 包含vue绑定数据的也贴一下吧 上面那个是静态的可以直接复制进去看效果的,这个加了一点数据绑定解析的 ``` <template> <view class="content"> <view class="describle"> {{ state.taskName }}<text>(已完成{{ state.dataList.length }}次)</text> </view> <view class="timeline-list"> <template v-for="(item, findex) in state.dataList" :key="findex"> <view class="timeline-item"> <view class="timeline-item_tail" /> <view class="timeline-item_node" /> <view class="timeline-item_wrapper"> <view class="stu-name"> 王佳佳 </view> <view class="task-content">{{ item.comment }}</view> <view class="image-content"> <image class="imgitem" v-for="(img, index) in JSON.parse(item.imgJson)" :key="index" mode="aspectFill" :src="GetImgUrl(img)" @tap="previewImg(item.imgJson, index)" /> <view class="" style="flex: 1" /> </view> <view class="task-person"> <view class="participant-list"> <!-- 参与人员 --> <view class="p-item" v-for="(stu, index) in item.groupUsers" :key="index"> <view class="tp-stu">{{ stu }}:</view> <view class="tp-status">参与</view> </view> <!-- 缺勤人员 --> <view class="p-item" v-for="(stu, index) in item.noGroupUsers" :key="index"> <view class="tp-stu">{{ stu }}:</view> <view class="tp-status-absence">缺勤</view> </view> </view> </view> <view class="task_time"> {{dayjs(item.completeDateTime).format('YYYY-MM-DD HH:mm:ss')}} <!-- <text class="task-reject">删除</text> --> </view> </view> </view> </template> </view> <view class="task-complate-to" v-if="false"></view> </view> </template> <script setup lang="ts"> import { ref, reactive } from 'vue' import { onLoad, onShow } from '@dcloudio/uni-app' import http from '@/common/request.ts' import config from '@/common/config.ts' import dayjs from 'dayjs' import { GetImgUrl } from '@/common/utils.ts' const state = reactive({ taskName: '参加早迎接活动', dataList: [] as any }) onLoad((e) => { state.taskName = e?.taskName // 设置页面标题 if (e?.taskName && e?.taskName.length < 18) { uni.setNavigationBarTitle({ title: e?.taskName }); } uni.getStorage({ key: 'studentCompleteDetails', success: function (res) { state.dataList = res.data console.log("跳转过来获得的数据", res.data); } }); }) // 图片预览 const previewImg = (imgListJson: any, index = 0) => { let imgIdList = JSON.parse(imgListJson) let imgList = [] as any // 根据图片id获取图片的浏览地址 for (let index = 0; index < imgIdList.length; index++) { const element = imgIdList[index]; imgList.push(GetImgUrl(element)) } uni.previewImage({ urls: imgList, current: index }); } </script> <style lang="scss" scoped> .content { background: #f4f6f9; .describle { padding: 20rpx; font-weight: 400; font-size: 30rpx; color: #313960; border-top: 4rpx solid #ededed; text { margin-left: 20rpx; color: #4d9df5; // font-size: 24rpx; } } .image-content { margin-top: 20rpx; display: flex; flex-wrap: wrap; // justify-content: space-between; .imgitem { width: 31%; /* 动态计算间距 */ margin-right: calc(7% / 2); // margin-bottom: calc(5% / 3); margin-bottom: 20rpx; // height: width; height: 199rpx; background: #f6f6f6; border-radius: 0px 0px 0px 0px; opacity: 1; } /* 去除每行尾的多余边距 */ .imgitem:nth-child(3n) { margin-right: 0px; } /* 使最后一个元素的边距填满剩余空间 */ .imgitem:last-child { margin-right: auto; } } .task-complate-to { width: 108rpx; height: 108rpx; background: linear-gradient(180deg, #4d9df5 0%, #63cfff 100%); box-shadow: 0px 4rpx 10rpx 1rpx rgba(77, 157, 245, 0.32); border-radius: 50%; position: fixed; bottom: 102rpx; right: 30rpx; } .task-complate-to::before { content: ' '; width: 56rpx; border-bottom: 4rpx solid #ffffff; position: absolute; left: calc(50% - 28rpx); top: calc(50% - 2rpx); } .task-complate-to::after { content: ' '; height: 56rpx; border-right: 4rpx solid #ffffff; position: absolute; left: calc(50% - 2rpx); top: calc(50% - 28rpx); } } </style> <style lang="scss" scoped> .timeline-list { font-size: 28rpx; list-style: none; padding: 30rpx 30rpx 20rpx 30rpx; padding-bottom: 0; background-color: #ffffff; } .timeline-item:last-child .timeline-item_tail { display: none; } .timeline-item { position: relative; // padding-bottom: 20rpx; .timeline-item_tail { position: absolute; left: 6rpx; top: 0; bottom: 0; border-left: 2rpx solid rgba(109, 209, 201, 0.3); } .timeline-item_node { position: absolute; background-color: #ffffff; border-radius: 50%; display: flex; justify-content: center; align-items: center; left: -9rpx; width: 18rpx; height: 18rpx; background: #fff; border: 6rpx solid #6dd1c9; } .timeline-item_wrapper { position: relative; padding: 0rpx 0rpx 20rpx 25rpx; // 加一个left可能会出现横向滚动条,体验不太好,可以通过margin-left来设置就不会有问题了 // left: 32rpx; margin-left: 32rpx; top: -6rpx; background-color: #ffffff; // margin-left: 42rpx; // border-radius: 20rpx; // width: 600rpx; .stu-name { font-size: 30rpx; font-weight: bold; color: #174a90; margin-bottom: 10rpx; } .task-content { color: #313960; font-size: 30rpx; font-weight: 400; } .task-person { margin-top: 20rpx; font-size: 24rpx; .p-item { display: flex; margin-bottom: 22rpx; .tp-stu { color: #313960; } } .participant-list { display: grid; /*每行显示四个,宽度由内容自适应*/ grid-template-columns: repeat(3, auto); /*显示方式为左右无间距,中间均分*/ justify-content: space-between; grid-column-gap: 19rpx; } .participant-list { .tp-status { color: #67c23a; background: #f0f9eb; border-radius: 4rpx; padding: 0 10rpx; } .tp-status-absence { color: #f56c6c; background: #fef0f0; border-radius: 4rpx; padding: 0 10rpx; } } } .task_time { margin-top: 4rpx; margin-bottom: 40rpx; color: #909399; font-size: 24rpx; .task-reject { color: #f56c6c; font-size: 24rpx; margin-left: 20rpx; } } } } </style> ``` ### 备注 tn2> 具体的页面模板以及更多模板存储在云盘里边