uni-app弹窗,小程序,弹出层。自定义组件弹窗,解滚动穿透 电脑版发表于:2021/6/25 10:14 官方文档:https://uniapp.dcloud.net.cn/component/uniui/uni-popup.html [TOC] ### 最简单的弹出一句话: 需要什么东西在内容里边加就行了 ``` <template> <view> <uni-popup ref="popup" background-color="#fff" > 士大夫大师傅 </uni-popup> <button type="default" @click="openView()">点击</button> </view> </template> <script> export default { data() { return { } }, methods: { openView() { this.$refs.popup.open(); } } } </script> <style> </style> ``` ### 弹出中加两个按钮: ``` <template> <view> <uni-popup ref="popup" background-color="#fff" > <view class="popup-content" :class="{ 'popup-height': type === 'left' || type === 'right' }"> <button class="button popup-success" @click="toAddNoteHtml()"><text class="button-text success-text">html编辑器(app上推荐使用)</text></button> <button style="margin-top: 10px;" class="button popup-error" @click="toAddNoteMarkdown()"><text class="button-text error-text">markdown编辑器(临时简版)</text></button> </view> </uni-popup> <button type="default" @click="openView()">点击</button> </view> </template> <script> export default { data() { return { } }, methods: { openView() { this.$refs.popup.open(); } } } </script> <style> </style> ``` ### 弹出中添加表单元素: ``` <template> <view style="width: 100%;"> <uni-popup ref="popup" style="width: 100%;" background-color="#fff"> <view class="popupView"> <!-- 基础表单校验 --> <uni-forms ref="valiForm" :rules="rules" :modelValue="valiFormData" label-position="top"> <uni-forms-item label="标题" style="margin-top: -8px;" required name="name"> <uni-easyinput v-model="valiFormData.name" placeholder="请输入标题" /> </uni-forms-item> <uni-forms-item label="类型" style="margin-top: -8px;" required name="type"> <uni-data-picker :localdata="items" :clearable="false"></uni-data-picker> </uni-forms-item> </uni-forms> <button type="primary" @click="submit('valiForm')">提交</button> </view> </uni-popup> <button type="default" @click="openView()">点击</button> </view> </template> <script> export default { data() { return { items: [{ text: "一年级", value: "1-0", children: [{ text: "1.1班", value: "1-1" }, { text: "1.2班", value: "1-2" } ] }, { text: "二年级", value: "2-0" }, { text: "三年级", value: "3-0" } ], // 校验表单数据 valiFormData: { name: '', type: '', }, // 校验规则 rules: { name: { rules: [{ required: true, errorMessage: '笔记标题不能为空' }] }, type: { rules: [{ required: true, errorMessage: '类型不能为空' }] } } } }, methods: { openView() { this.$refs.popup.open(); }, submit(ref) { this.$refs[ref].validate().then(res => { uni.showToast({ title: "功能开发中...", icon: "none" }) console.log(res); }).catch(err => { console.log('err', err); }) } } } </script> <style scoped> /deep/.uni-popup .uni-popup__wrapper { width: 80% !important; padding: 40rpx; } /* .popupView { width: 600rpx; padding: 40rpx; } */ </style> ``` ### 在vue3中的使用 #### 基本使用 页面上也是一样 ``` <uni-popup ref="classRankingPopup" type="bottom">班级排名</uni-popup> ``` 打开弹窗的地方这么写: ``` // 这里的名称保持和uni-popup上面的ref名称一致即可 let classRankingPopup =ref() const openClassRankingPopup = ()=>{ classRankingPopup.value.open() } ``` #### 弹窗里边加入一个组件 ##### 实现的效果: <img src="https://img.tnblog.net/arcimg/aojiancc2/1ba0d9d627a340af9cf854eb3258b99b.png" style="width:399px;height:auto;"> 组件的代码,圆角这些也是写到这个组件里边,相当于就是弹窗里边的内容,取名叫classSort ``` <template> <view class="container"> <scroll-view scroll-y="true" style=" height: 1099rpx;"> <view class="titleWrap"> <view class="title">班级排名</view> <view>×</view> </view> <view class="splitline"></view> <view class="sortWrap"> <view class="sortItem" :class="index>2?'simpleIndex':''" v-for="(item, index) in 9" v-bind:key="index"> <!-- 前三名使用图标展示 --> <view class="sortUserWrap" v-if="index<=2"> <image class="sort_img" :src="'../../static/sort_'+(index+1)+'.png'"> </image> <view class="userName">李美丽</view> </view> <view class="sortUserWrap" v-else> <view class="sortIndex">{{(index+1)}}</view> <view class="userName">李美丽{{(index+3)}}</view> </view> <view class="sortCompleteWrap"> <view class="scoreItem">健康:6</view> <view class="scoreItem">思维:6</view> <view class="scoreItem">技术:6</view> <view class="scoreItem">管理:6</view> <view class="scoreItem">总分:6</view> </view> </view> </view> </scroll-view> </view> </template> <script setup lang="ts"> import { ref, reactive } from 'vue' </script> <style lang="scss" scoped> .container { height: 1099rpx; background-color: #fff; // border-radius: 10px; border-top-left-radius: 20rpx; border-top-right-radius: 20rpx; padding-left: 20rpx; padding-right: 20rpx; .sortWrap { margin-top: 39rpx; } .sortItem { display: flex; align-items: center; margin-top: 30rpx; .sortUserWrap { display: flex; align-items: center; width: 200rpx; // background-color: #ffabcd; //宽高比 0.765957 .sort_img{ width: 50rpx; height: 65.27rpx; } .sortIndex { background: #CFE4FF; color: #4E84FD; width: 50rpx; height: 50rpx; border-radius: 50%; text-align: center; line-height: 50rpx; } .userName { font-size: 31rpx; margin-left: 10rpx; color: #174A90; } } } .simpleIndex{ margin-top: 39rpx; } .sortCompleteWrap { display: flex; margin-left: 15rpx; color: #565A67; font-size: 30rpx; // background: #abcdff; justify-content: space-between; flex: 1; // .scoreItem { // margin-left: 7rpx; // } } } .titleWrap { display: flex; padding-top: 22rpx; justify-content: space-between; padding-left: 20rpx; padding-right: 20rpx; .title { color: #3A3B42; } } .splitline { // border: 1rpx solid #E6E6E6; margin-left: -20rpx; margin-right: -20rpx; margin-top: 30rpx; height: 1rpx; background: #f3f3f3; } </style> ``` **页面上的地方** ``` <uni-popup ref="classRankingPopup" type="bottom"> <classSort></classSort> </uni-popup> ``` js: ``` <script lang="ts" setup> import classSort from '@/pages/home/classSort.vue' let classRankingPopup = ref() const openClassRankingPopup = () => { classRankingPopup.value.open() } </script> ``` tn4>在微信小程序中自定义组件时,编译可能会报Component is not found in path '...'。可以尝试关闭后重新编译一下,或者修改一下组件的目录级别试试,比如加一个component文件夹。 #### 组件里边实现弹窗的关闭 由于弹窗的关闭按钮我们写到了组件里边,所以需要在组件里边来触发父组件的方法,定义一个事件在子组件里边调用一下方法即可。 ##### 子组件 给叉叉加一个点击事件: ``` <view class="titleWrap"> <view class="title">班级排名</view> <view @click="handleClose">×</view> </view> ``` 点击事件里边去调用父组件传递过来的方法: ``` <script setup lang="ts" name="classSort"> import { ref, reactive } from 'vue' // 触发closePopup事件 const emit = defineEmits(['closePopup']) const handleClose = () => { emit('closePopup', '参数') } </script> ``` ##### 父组件 绑定一下closePopup事件: ``` <uni-popup ref="classStarMorePopup" type="bottom" @change="change"> <classStarMore @closePopup="closeClassStarMorePopup" /> </uni-popup> ``` 在事件里边去关闭弹窗即可: ``` const closeClassStarMorePopup = () => { classStarMorePopup.value.close() } ``` #### 解决滚动穿透 tn2>防止在弹窗里边滚动的时候主页面也跟着滚动了。 **在引入弹窗的页面加入如下的代码:** 加到最上面的位置 ``` <template> <!-- 解决滚动穿透 --> <page-meta :page-style="'overflow:' + (state.popupShow ? 'hidden' : 'visible')"></page-meta> ``` **然后需要有一个state.popupShow属性** ``` const state = reactive({ popupShow: false, title: '更新', }) ``` **然后在弹窗里边加一个change事件** 用于监听弹窗是否被打开,打开的时候就要禁用当前页面的滚动了撒。如果有多个弹窗可以监听一个事件即可。 ``` <uni-popup ref="classStarMorePopup" type="bottom" @change="change"> <classStarMore @closePopup="closeclassStarMorePopup" /> </uni-popup> <uni-popup ref="scoreRecordPopup" type="bottom" @change="change"> <scoreRecord @closePopup="closescoreRecordPopup" /> </uni-popup> ``` change事件就是修改一下state.popupShow属性的状态 ``` const change = (e: any) => { state.popupShow = e.show } ``` #### 弹窗样式效果2,带一点tag菜单的效果 ##### 效果图: <img src="https://img.tnblog.net/arcimg/aojiancc2/0c54ba658d2f44a2bfe9a166d7e115be.png" style="width:399px;height:auto;"> ##### 代码: ``` <template> <view class="classStarMore_container"> <scroll-view scroll-y="true" style=" height: 999rpx;"> <view class="titleWrap"> <view class="title">班级之星</view> <view @click="handleClose">×</view> </view> <view class="classStar"> <view class="splitline"></view> <view class="tagWrap"> <view class="tagContent"> <view class="tagItem" :class="dealClassStarChoise(1)" @tap="getStuScoreSortByClassAndType(1)"> 健康 </view> <view class="tagItem" :class="dealClassStarChoise(2)" @tap="getStuScoreSortByClassAndType(2)"> 思维 </view> <view class="tagItem" :class="dealClassStarChoise(3)" @tap="getStuScoreSortByClassAndType(3)"> 技术 </view> <view class="tagItem" :class="dealClassStarChoise(4)" @tap="getStuScoreSortByClassAndType(4)"> 管理 </view> </view> <view> <!-- <view class="moreTag">更多 ></view> --> </view> </view> <view class="sortWrap"> <view class="sortItem" :class="index > 2 ? 'simpleIndex' : ''" v-for="(item, index) in 10" v-bind:key="index"> <view class="sortUserWrap" v-if="index <= 3"> <image class="sort_img" :src="'../../static/sort_' + index + '.png'"> </image> <view class="userName">缪喵{{ index }}</view> </view> <view class="sortUserWrap" v-else> <view class="sortIndex">{{ index }}</view> <view class="userName">缪喵{{ index }}</view> </view> <view class="sortCompleteWrap"> <view class="completeScoreValue">积分:{{ index * 20 }}</view> </view> </view> </view> </view> </scroll-view> </view> </template> <script setup lang="ts"> import { ref, reactive, defineProps, inject } from 'vue' import { onShow } from '@dcloudio/uni-app' const state = reactive({ studentScoreSortType: [] as any, // 这里配置默认选中的 taskTypeEnum: 2 }) const emit = defineEmits(['closePopup']) const handleClose = () => { emit('closePopup', '参数') } onShow(() => { }) const getStuScoreSortByClassAndType = async (taskTypeEnum: number) => { console.log("处理点击",taskTypeEnum) // 数据请求换成自己的逻辑 state.taskTypeEnum = taskTypeEnum } // 处理样式选中 const dealClassStarChoise = (taskTypeEnum: number) => { if (taskTypeEnum === state.taskTypeEnum) { return "cur" } } </script> <style lang="scss" scoped> .classStarMore_container { height: 999rpx; background-color: #fff; // border-radius: 10px; 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; font-size: 30rpx; .title { color: #3A3B42; } } .splitline { margin-top: 20rpx; height: 1rpx; background: #E6E6E6; } .classStar { background-color: #fff; margin-top: 23rpx; // padding-top: 23rpx; padding-bottom: 23rpx; .classStarTitle { font-size: 30rpx; color: #3A3B42; margin-left: 20rpx; // margin-top: 23rpx; } .tagWrap { display: flex; justify-content: space-between; margin-top: 23rpx; .tagContent { display: flex; margin-left: 20rpx; .tagItem { width: 110rpx; height: 45rpx; line-height: 45rpx; text-align: center; margin-right: 30rpx; background: #F5F5F5; border-radius: 25px 25px 25px 25px; font-size: 28rpx; color: #909399; } .cur { background-color: #4D9DF5; color: #FFFFFF; } } .moreTag { color: #4D9DF5; font-size: 24rpx; height: 45rpx; line-height: 45rpx; margin-right: 30rpx; } } .sortItem { display: flex; align-items: center; margin-top: 30rpx; margin-left: 36rpx; margin-right: 36rpx; justify-content: space-between; .sortUserWrap { display: flex; align-items: center; //宽高比 0.765957 .sort_img { // width: 43rpx; // height: 56.13rpx; width: 36rpx; height: 47.14rpx; } .sortIndex { background: #CFE4FF; color: #4E84FD; width: 36rpx; height: 36rpx; border-radius: 50%; text-align: center; line-height: 36rpx; font-size: 24rpx; } .userName { font-size: 26rpx; margin-left: 10rpx; color: #174A90; } } .completeScoreValue { color: #565A67; font-size: 24rpx; } } } } </style> ``` #### 弹窗样式效果3 ##### 效果图如下: <img src="https://img.tnblog.net/arcimg/aojiancc2/48db549a2583436f9086a4126d397ea2.jpg" style="width:399px;height:auto;"> ##### 代码如下: ``` <template> <view class="completeUsers-container"> <scroll-view scroll-y="true" style=" height: 799rpx;"> <view class="titleWrap"> <view class="title">完成人次</view> <view class="closeTag" @click="handleClose">×</view> </view> <view class="splitline"></view> <div class="cc-users-content" style=""> <div class="item" v-for="(item,index) in 18" :key="index">缪喵喵</div> </div> </scroll-view> </view> </template> <script setup lang="ts" name="completeUsers"> import { ref, reactive } from 'vue' // 触发closePopup事件 const emit = defineEmits(['closePopup']) const handleClose = () => { emit('closePopup', '参数') } </script> <style lang="scss" scoped> .completeUsers-container { height: 799rpx; background-color: #fff; border-top-left-radius: 60rpx; border-top-right-radius: 60rpx; padding-left: 20rpx; padding-right: 20rpx; .cc-users-content { display: flex; justify-content: space-between; flex-wrap: wrap; margin-top: 20rpx; .item { font-family: PingFang SC, PingFang SC; font-weight: 400; font-size: 28rpx; color: #313960; background-color: #F7F8FB; border-radius: 10rpx 10rpx 10rpx 10rpx; flex: 0 0 24%; height: 60rpx; text-align: center; line-height: 60rpx; /* 边距懒得算,css函数代替 */ margin-right: calc(4% / 3); margin-bottom: calc(4% / 3); } /* 去除每行尾的多余边距 */ .item:nth-child(4n) { margin-right: 0; } /* 使最后一个元素的边距填满剩余空间 */ .item:last-child { margin-right: auto; } } } .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: 10rpx; } } .splitline { // border: 1rpx solid #E6E6E6; margin-left: -20rpx; margin-right: -20rpx; margin-top: 30rpx; height: 1rpx; background: #f3f3f3; } </style> ```