uni-app 小程序,常用的弹窗模板。弹窗里边显示时间轴 电脑版发表于:2024/7/16 11:51 tn2> 弹窗的基础使用以及另外的一些模板可以参考:https://www.tnblog.net/aojiancc2/article/details/7064 [TOC] ### 基础模板一:弹窗里边一点方块与文字信息(使用的grid布局) #### 效果图一: <img src="https://img.tnblog.net/arcimg/aojiancc2/4f37130211964bdc8cb89566c863ba67.png" style="width:399px;height:auto;"> #### 弹窗里边的代码与样式一 ``` <template> <view class="popup-container"> <view class="titleWrap"> <view class="title">{{ title }}</view> <view class="closeTag" @tap="handleClose">×</view> </view> <view class="splitline"></view> <view class="pc-content"> <view class="pcc-db-warp"> <view class="pcc-db-item"> <view class="pcc-db-vd-drap"> <view class="pcc-dbi-value">18</view> <view class="pcc-dbi-desc">总积分</view> </view> </view> <view class="pcc-db-item"> <view class="pcc-db-vd-drap"> <view class="pcc-dbi-value">18</view> <view class="pcc-dbi-desc">健康</view> </view> </view> <view class="pcc-db-item"> <view class="pcc-db-vd-drap"> <view class="pcc-dbi-value">21</view> <view class="pcc-dbi-desc">技术</view> </view> </view> <view class="pcc-db-item"> <view class="pcc-db-vd-drap"> <view class="pcc-dbi-value">36</view> <view class="pcc-dbi-desc">思维</view> </view> </view> <view class="pcc-db-item"> <view class="pcc-db-vd-drap"> <view class="pcc-dbi-value">1</view> <view class="pcc-dbi-desc">管理</view> </view> </view> </view> </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', '参数') } </script> <style lang="scss" scoped> .popup-container { height: 520rpx; 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; } .pc-content { padding-left: 20rpx; padding-right: 20rpx; .pcc-db-warp { display: grid; grid-template-columns: repeat(3, 1fr); row-gap: 20rpx; column-gap: 20rpx; margin-top: 26rpx; .pcc-db-item { background: #ECF5FF; border-radius: 6rpx 6rpx 6rpx 6rpx; border: 1rpx solid #B3D8FF; height: 106rpx; display: flex; align-items: center; justify-content: center; // flex-direction: column; .pcc-dbi-value { font-size: 24rpx; color: #4D9DF5; text-align: center; } .pcc-dbi-desc { font-weight: 400; font-size: 24rpx; color: #4D9DF5; text-align: center; margin-top: 2rpx; } } } } } </style> ``` #### 弹窗里边那个圆角是在引用弹窗的时候设置的 ``` <uni-popup ref="cgsDetailsPopup" background-color="#fff" border-radius="60rpx 60rpx 0rpx 0rpx;" type="bottom" @change="methods.change"> <cgsDetails @closePopup="popupMethods.closeCgsDetailsDetails" title="许灵灵"> </cgsDetails> </uni-popup> ``` tn2>以前圆角以及背景色这些是写到弹窗里边的,但是后面发现有些苹果手机拉到最下面背景有点问题,后面就干脆把圆角以及背景色这些写到引用弹窗组件的地方去了。其实要是能解决手机适配问题,这些写到弹窗里边还通用一点,项目时间比较紧就没有慢慢去研究了。 #### 还有引用弹窗组件以及一些方法等代码也贴一个 ``` import cgsDetails from './component/cgs-details.vue' const state = reactive({ popupShow: false, }) const methods = { // 解决滚动穿透 change: function (e: any) { state.popupShow = e.show }, } let cgsDetailsPopup = ref() const popupMethods = { // 打开弹窗 showCgsDetailsDetails: () => { cgsDetailsPopup.value.open() }, // 关闭弹窗 closeCgsDetailsDetails: () => { cgsDetailsPopup.value.close() } } ``` 要解决滚动穿透,需要在页面里边第一个子元素增加一个page-meta ``` <template> <!-- 解决滚动穿透 --> <page-meta :page-style="'overflow:' + (state.popupShow ? 'hidden' : 'visible')"></page-meta> <view></view> </template> ``` ### 基础模板二:弹窗里边使用时间轴,以及每行显示4张图片 #### 效果图二: <img src="https://img.tnblog.net/arcimg/aojiancc2/4ed95c65bc3a49c68879e195f0ba41ae.png" style="width:399px;height:auto;"> #### 弹窗里边的代码与样式二 这里实现了图片超过一行后会自适应布局的,一行显示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> ``` ### 基础模板三:弹窗里边包含tab选择菜单以及最下方包含取消与提交按钮 #### 效果图三: <img src="https://img.tnblog.net/arcimg/aojiancc2/9b0fcf3c3dce4cb5b049e0f8143d6f65.png" style="width:366px;height:auto;"> 最下面的按钮 <img src="https://img.tnblog.net/arcimg/aojiancc2/d831343f3f5c44f681e3dcb7274df468.png" style="width:366px;height:auto;"> 里边也包含一块一块的文字效果,用的flex布局,其实现在使用grid布局来实现更通用一点 #### 弹窗里边的代码与样式三 ``` <template> <view class="container"> <view class="titleWrap"> <view class="tabMenu"> <view :class="state.activeName == 'joinUser' ? 'titlecur' : ''" @click="switchTab('joinUser')" class="title"> 参与人员</view> <view :class="state.activeName == 'noJoin' ? 'titlecur' : ''" @click="switchTab('noJoin')" class="noJoin ">缺勤人员 </view> </view> </view> <view class="splitline"></view> <scroll-view scroll-y="true" style=" height: 799rpx;"> <view class="completeStatusWrap" v-if="state.activeName == 'joinUser'"> <view class="userItemWrap completeInfo"> <view class="userItem" :class="{ choise: index % 4 === 0 }" v-for="(item, index) in 17" :key="index">张三三 </view> </view> </view> <view class="completeStatusWrap" v-if="state.activeName == 'noJoin'"> <view class="userItemWrap noJoinUserWrap"> <view class="userItem " :class="{ choise: index % 5 === 0 }" v-for="(item, index) in 17" :key="index">张三三 </view> </view> </view> </scroll-view> <view class="perateWrap"> <view class="pt-cmbwrap"> <view class="pt-cancelBut" @tap="handleClose">取消</view> <view class="pt-commitBut">提交</view> </view> </view> </view> </template> <script setup lang="ts"> import { ref, reactive } from 'vue' const state = reactive({ activeName: "joinUser", }) const switchTab = (tag: string) => { state.activeName = tag } // 触发closePopup事件 const emit = defineEmits(['closePopup']) const handleClose = () => { emit('closePopup', '参数') } </script> <style lang="scss" scoped> .splitline { // border: 1rpx solid #E6E6E6; margin-left: -20rpx; margin-right: -20rpx; margin-top: 10rpx; height: 1rpx; background: #f3f3f3; } .container { height: 999rpx; background-color: #fff; border-top-left-radius: 20rpx; border-top-right-radius: 20rpx; .titleWrap { display: flex; padding-top: 22rpx; justify-content: space-between; padding-left: 20rpx; padding-right: 20rpx; .tabMenu { display: flex; .title { color: #909399; font-size: 26rpx; } .noJoin { margin-left: 15rpx; font-size: 26rpx; color: #909399; } .titlecur { font-size: 30rpx; color: #4D9DF5 !important; position: relative; } .titlecur:after { content: ""; display: inline-block; position: absolute; bottom: -12rpx; left: 0px; width: 100%; border-bottom: 4rpx solid #4D9DF5; } } } .completeStatusWrap { margin-top: 39rpx; padding-left: 20rpx; padding-right: 20rpx; .userItemWrap { background-color: #fff; display: flex; flex-wrap: wrap; justify-content: space-between; .userItem { flex: 0 0 31%; /* 动态计算间距,除以2是因为3个元素中间只有2个缝隙 */ margin-right: calc(7% / 2); margin-bottom: 15rpx; height: 65rpx; background: #F6F6F6; font-size: 26rpx; border-radius: 6rpx 6rpx 6rpx 6rpx; color: #313960; text-align: center; line-height: 65rpx; } /* 去除每行尾的多余边距 */ .userItem:nth-child(3n) { margin-right: 0; } /* 使最后一个元素的边距填满剩余空间 */ .userItem:last-child { margin-right: auto; } } .completeInfo { .choise { background: #F0F9EB; border: 1rpx solid #C2E7B0; box-sizing: border-box; color: #67C23A; } } .noJoinUserWrap { .choise { background: #FEF0F0; border: 1rpx solid #FBC6C6; box-sizing: border-box; color: #F56C6C; } } } .perateWrap { display: flex; justify-content: center; .pt-cmbwrap { position: absolute; bottom: 20rpx; display: flex; .pt-cancelBut { width: 330rpx; height: 80rpx; border-radius: 10rpx 10rpx 10rpx 10rpx; border: 1rpx solid #4D9DF5; font-size: 36rpx; color: #4D9DF5; text-align: center; line-height: 80rpx; margin-right: 10rpx; } .pt-commitBut { width: 330rpx; height: 80rpx; background: #4D9DF5; border-radius: 10rpx 10rpx 10rpx 10rpx; font-size: 36rpx; color: #FFFFFF; text-align: center; line-height: 80rpx; margin-left: 10rpx; } } } } </style> ``` ### 基础模板四:非常基础的展示一列数据的弹窗 #### 效果图四: <img src="https://img.tnblog.net/arcimg/aojiancc2/92078c7a8ad944668442d12f1a1035c2.png" style="width:399px;height:auto;"> #### 弹窗里边的代码与样式四 ``` <template> <view class="popup-container"> <view class="titleWrap"> <view class="title">{{ title }}</view> <view class="closeTag" @tap="handleClose">×</view> </view> <scroll-view scroll-y="true" style=" height: 666rpx;"> <view class="pc-content"> <view class="pcc-item" v-for="(item, index) in 13" :key="index"> 23级大数据技术1班 </view> </view> </scroll-view> </view> </template> <script setup lang="ts"> import { ref, reactive } from 'vue' // 父组件可以传递参数进来 const props = defineProps({ title: { type: String, default: "" }, dataList: { type: Array, default: [] } }) // 触发closePopup事件(关闭事件) const emit = defineEmits(['closePopup']) const handleClose = () => { emit('closePopup', '参数') } </script> <style lang="scss" scoped> .popup-container { height: 766rpx; 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; height: 50rpx; 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; } } .pc-content { padding-left: 20rpx; padding-right: 20rpx; .pcc-db-warp { display: grid; grid-template-columns: repeat(3, 1fr); row-gap: 20rpx; column-gap: 20rpx; margin-top: 26rpx; .pcc-db-item { background: #ECF5FF; border-radius: 6rpx 6rpx 6rpx 6rpx; border: 1rpx solid #B3D8FF; height: 106rpx; display: flex; align-items: center; justify-content: center; // flex-direction: column; .pcc-dbi-value { font-size: 24rpx; color: #4D9DF5; text-align: center; } .pcc-dbi-desc { font-weight: 400; font-size: 24rpx; color: #4D9DF5; text-align: center; margin-top: 2rpx; } } } } .pc-content { .pcc-item { text-align: center; font-size: 36rpx; color: #313960; margin-top: 20rpx; } } } </style> ``` #### 和上面那个例子一样,就多一个标题和标题下方的分割线 直接上代码 ``` <template> <view class="popup-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: 666rpx;"> <view class="pc-content"> <view class="pcc-item" v-for="(item, index) in 13" :key="index"> 重庆应用技术职业学院 </view> </view> </scroll-view> </view> </template> <script setup lang="ts"> import { ref, reactive } from 'vue' // 父组件可以传递参数进来 const props = defineProps({ title: { type: String, default: "选择院系" }, dataList: { type: Array, default: [] } }) // 触发closePopup事件(关闭事件) const emit = defineEmits(['closePopup']) const handleClose = () => { emit('closePopup', '参数') } </script> <style lang="scss" scoped> .popup-container { height: 766rpx; 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; height: 50rpx; 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; } .pc-content { padding-left: 20rpx; padding-right: 20rpx; margin-top: 5rpx; .pcc-db-warp { display: grid; grid-template-columns: repeat(3, 1fr); row-gap: 20rpx; column-gap: 20rpx; margin-top: 26rpx; .pcc-db-item { background: #ECF5FF; border-radius: 6rpx 6rpx 6rpx 6rpx; border: 1rpx solid #B3D8FF; height: 106rpx; display: flex; align-items: center; justify-content: center; // flex-direction: column; .pcc-dbi-value { font-size: 24rpx; color: #4D9DF5; text-align: center; } .pcc-dbi-desc { font-weight: 400; font-size: 24rpx; color: #4D9DF5; text-align: center; margin-top: 2rpx; } } } } .pc-content { .pcc-item { text-align: center; font-size: 36rpx; color: #313960; margin-top: 20rpx; } } } </style> ```