低代码可视化-uniapp气泡弹窗组件可视化-代码生成器
气泡弹窗组件(diy-popover )指的是当触发某项操作时,在页面上方或特定位置展示的弹出层容器,容器内可展示文本、按钮、列表、标签、表单项等内容。组件库代码实现如下.-- 移除@tap.stop.prevent -->mask?showDrawer?/*** popover 汽泡组件* @description 汽泡组件,用于汽泡组件、信息提示等内容,支持上、下、左、右和中部弹出。组件只提供
·
气泡弹窗组件是产品设计中常用的控件之一,以下是对uniapp气泡弹窗组件可视化的详细解析:
一、组件定义
气泡弹窗组件(diy-popover )指的是当触发某项操作时,在页面上方或特定位置展示的弹出层容器,容器内可展示文本、按钮、列表、标签、表单项等内容。组件库代码实现如下.
<template>
<view v-if="visibleSync" :style="[customStyle, {
zIndex: uZindex - 1
}]" class="diy-popover" :class="mask?'mask':''" hover-stop-propagation>
<u-mask v-if="mask" :blur="blur" :duration="duration" :custom-style="maskCustomStyle" :maskClickAble="maskCloseAble"
:z-index="uZindex - 2" :show="showDrawer && mask" @click="maskClick"></u-mask>
<!-- 移除 @tap.stop.prevent -->
<view class="popover" :class="[
mask?'':'nomask',
safeAreaInsetBottom ? 'safe-area-inset-bottom' : '',
showDrawer ? 'diy-popover-visible' : ''
]" @touchmove.stop.prevent
:style="getPositionStyle()" @click="closeByPopover">
<text :class="['popover-'+diymode,'popover-'+dynPlace]" :style="{width:'0px',height:'0px'}"></text>
<slot></slot>
<view class="clearfix"></view>
</view>
</view>
</template>
<script>
/**
* popover 汽泡组件
* @description 汽泡组件,用于汽泡组件、信息提示等内容,支持上、下、左、右和中部弹出。组件只提供容器,内部内容由用户自定义
* @property {String} mode 弹出方向(默认left)
* @property {Boolean} mask 是否显示遮罩(默认true)
* @property {Stringr | Number} length mode=top
* @property {Boolean} zoom 是否开启缩放动画,只在mode为center时有效(默认true)
* @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
* @property {Boolean} mask-close-able 点击遮罩是否可以关闭弹出层(默认true)
* @property {Numberr | String} z-index 弹出内容的z-index值(默认1075)
* @event {Function} open 弹出层打开
* @event {Function} close 弹出层收起
*/
export default {
name: 'diy-popover',
emits: ["update:modelValue", "input", "open", "close"],
props: {
value: {
type: Boolean,
default: false
},
modelValue: {
type: Boolean,
default: false
},
/**
* 弹出方向,left|right|top|bottom
*/
mode: {
type: String,
default: 'top-center'
},
initType:{
type: String,
default: ''
},
/**
* 是否显示遮罩
*/
mask: {
type: Boolean,
default: true
},
// 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
safeAreaInsetBottom: {
type: Boolean,
default: false
},
// 是否可以通过点击遮罩进行关闭
maskCloseAble: {
type: Boolean,
default: true
},
// 是否可以通过点击内容区进行关闭
contentCloseAble:{
type: Boolean,
default: false,
},
// 显示显示弹窗的圆角,单位rpx
borderRadius: {
type: [Number, String],
default: 0
},
zIndex: {
type: [Number, String],
default: ''
},
// 背景颜色
bgcolor: {
type: String,
default: '#fff'
},
// 点击元素左边坐标
triggerLeft: {
type: [String, Number],
default: 0
},
// 点击元素上方坐标
triggerTop: {
type: [String, Number],
default: 0
},
// 点击元素高度
triggerHeight: {
type: [String, Number],
default: 12
},
// 点击元素宽度
triggerWidth: {
type: [String, Number],
default: 24
},
width: {
type: String,
default: '200px'
},
// 遮罩的样式,一般用于修改遮罩的透明度
maskCustomStyle: {
type: Object,
default () {
return {
backgroundColor: 'none'
}
}
},
// 遮罩打开或收起的动画过渡时间,单位ms
duration: {
type: [String, Number],
default: 250
},
// 遮罩的模糊度
blur: {
type: [String, Number],
default: 0
},
},
data() {
return {
statusBarHeight:0,
diymode:this.mode,
popoverTransform: 'scale(1)',
popoverTop: '0px',
popoverLeft: '0px',
diybgcolor: this.bgcolor ? this.bgcolor : '#fff',
visibleSync: false,
showDrawer: false,
timer: null,
dynPlace: '',
closeFromInner: false, // value的值改变,是发生在内部还是外部
};
},
computed: {
valueCom() {
// #ifndef VUE3
return this.value;
// #endif
// #ifdef VUE3
return this.modelValue;
// #endif
},
// 计算整理后的z-index值
uZindex() {
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
}
},
watch: {
valueCom(val) {
if (val) {
this.open();
this.popoverPosition()
} else if (!this.closeFromInner) {
this.close();
}
this.closeFromInner = false;
},
},
mounted() {
if(this.mode.indexOf("-")>0){
this.diymode = this.mode.substring(0,this.mode.indexOf("-"))
}else{
this.diymode = this.mode
}
// 组件渲染完成时,检查value是否为true,如果是,弹出popup
if(this.valueCom){
this.open()
this.popoverPosition()
}
},
methods: {
getPositionStyle(){
let style = {width:this.width,background:this.diybgcolor,'--arrow-color':this.diybgcolor};
if(this.initType!=''){
if(this.initType.indexOf("left") >= 0){
style['left'] = this.triggerLeft+'px'
}
if(this.initType.indexOf("right") >= 0){
style['right'] = this.triggerLeft+'px'
}
if(this.initType.indexOf("top") >= 0){
// #ifdef H5
style['top'] = (this.statusBarHeight + this.triggerTop) +'px'
// #endif
// #ifndef H5
style['top'] = this.triggerTop +'px'
// #endif
}
if(this.initType.indexOf("bottom") >= 0){
style['bottom'] = this.triggerTop+'px'
}
}else{
style['top'] = this.popoverTop
style['left'] = this.popoverLeft
}
return style
},
getTopOrBottomPlacement(wrapperwidth) {
let width = uni.getSystemInfoSync().windowWidth
//X坐标大于屏幕一半大小且大于屏幕的大小时
if (this.triggerLeft + this.triggerWidth / 2 + wrapperwidth / 2 - width > 0) {
return 'right'
} else if (this.triggerLeft + this.triggerWidth / 2 - wrapperwidth / 2 > 0) {
return 'center'
} else {
return 'left'
}
},
getLeftOrRightPlacement(wrapperHeight) {
let height = uni.getSystemInfoSync().windowHeight
if (this.triggerTop + this.triggerHeight / 2 + wrapperHeight / 2 - height > 0) {
return 'right'
} else if (this.triggerTop + this.triggerHeight / 2 - wrapperHeight / 2 > 0) {
return 'center'
} else {
return 'left'
}
},
async popoverPosition() {
let statusBar = await this.getStatusBar()
statusBar = statusBar||0
this.statusBarHeight = statusBar||0
if(this.initType!=''){
this.dynPlace = this.mode.indexOf("-")>0?this.mode:(this.mode+"-center")
}else{
let popoverDom = uni.createSelectorQuery().in(this).select(".popover")
popoverDom.fields({
size: true,
}, (data) => {
let width = data.width
let height = data.height
let y = this.triggerTop + statusBar
let x = this.triggerLeft
this.dynPlace = this.mode
if (this.mode == 'top' || this.mode == 'bottom') {
this.dynPlace = this.mode + "-" + this.getTopOrBottomPlacement(width)
} else if (this.mode == 'left' || this.mode == 'right') {
this.dynPlace = this.mode + "-" + this.getLeftOrRightPlacement(height)
}else {
this.dynPlace = this.mode
}
let popoverTop = 0
let popoverLeft = 0
switch (this.dynPlace) {
case 'top-left':
y = y + this.triggerHeight + 9
popoverTop = `${y}px`
x = x - 10
x = x < 0 ? 2 : x
popoverLeft = `${x}px`
this.popoverLeft = popoverLeft
this.popoverTop = popoverTop
break;
case 'top-center':
y = y + this.triggerHeight
popoverTop = `${y+9}px`
x = x + this.triggerWidth / 2 - width / 2
x = x < 0 ? 2 : x
popoverLeft = `${x}px`
this.popoverLeft = popoverLeft
this.popoverTop = popoverTop
break;
case 'top-right':
y = y + this.triggerHeight
this.popoverTop = `${y+9}px`
x = x + this.triggerWidth - width
this.popoverLeft = `${x}px`
break;
case 'top-left':
this.popoverTop = `${y-12-height}px`
x = x - 10
x = x < 0 ? 2 : x
this.popoverLeft = `${x}px`
break;
case 'bottom-center':
this.popoverTop = `${y-12-height}px`
x = x + this.triggerWidth / 2 - width / 2
x = x < 0 ? 2 : x
popoverLeft = `${x}px`
this.popoverLeft = popoverLeft
break;
case 'bottom-right':
this.popoverTop = `${y-12-height}px`
x = x + this.triggerWidth - width
this.popoverLeft = `${x}px`
break;
case 'left-top':
this.popoverTop = `${y}px`
this.popoverLeft = `${x -width -15}px`
break;
case 'left-center':
y = y - height / 2 + this.triggerHeight / 2
this.popoverTop = `${y}px`
this.popoverLeft = `${x -width -15}px`
break;
case 'left-bottom':
y = y - height + this.triggerHeight + 5
this.popoverTop = `${y}px`
this.popoverLeft = `${x - width -15}px`
break;
case 'right-top':
this.popoverTop = `${y}px`
x = x + this.triggerWidth + 15
this.popoverLeft = `${x}px`
break;
case 'right-center':
y = y - height / 2 + this.triggerHeight / 2
this.popoverTop = `${y}px`
x = x + this.triggerWidth + 15
this.popoverLeft = `${x}px`
break;
case 'right-bottom':
y = y - height + this.triggerHeight + 5
this.popoverTop = `${y}px`
x = x + this.triggerWidth + 15
this.popoverLeft = `${x}px`
break;
}
}).exec();
}
},
getStatusBar() {
let promise = new Promise((resolve, reject) => {
uni.getSystemInfo({
success: function(e) {
let customBar
// #ifdef H5
customBar = e.statusBarHeight + e.windowTop;
// #endif
resolve(customBar)
}
})
})
return promise
},
// 判断传入的值,是否带有单位,如果没有,就默认用rpx单位
getUnitValue(val) {
if (/(%|px|rpx|auto)$/.test(val)) return val;
else return val + 'rpx'
},
//是否可以通过点击遮罩进行关闭
closeByPopover(){
if(this.contentCloseAble){
this.close();
}
},
// 遮罩被点击
maskClick() {
this.close();
},
close() {
// 标记关闭是内部发生的,否则修改了value值,导致watch中对value检测,导致再执行一遍close
// 造成@close事件触发两次
this.closeFromInner = true;
this.change('showDrawer', 'visibleSync', false);
},
open() {
this.change('visibleSync', 'showDrawer', true);
},
// 此处的原理是,关闭时先通过动画隐藏弹窗和遮罩,再移除整个组件
// 打开时,先渲染组件,延时一定时间再让遮罩和弹窗的动画起作用
change(param1, param2, status) {
// 如果this.popup为false,意味着为picker,actionsheet等组件调用了popup组件
if (this.popup == true) {
this.$emit('input', status);
}
this.$emit("update:modelValue", status);
this[param1] = status;
if (status) {
// #ifdef H5 || MP
this.timer = setTimeout(() => {
this[param2] = status;
this.$emit(status ? 'open' : 'close');
}, 50);
// #endif
// #ifndef H5 || MP
this.$nextTick(() => {
this[param2] = status;
this.$emit(status ? 'open' : 'close');
})
// #endif
} else {
this.timer = setTimeout(() => {
this[param2] = status;
this.$emit(status ? 'open' : 'close');
}, this.duration);
}
}
}
};
</script>
<style scoped lang="scss">
@import "../../libs/css/style.components.scss";
.diy-popover {
/* #ifndef APP-NVUE */
display: block;
/* #endif */
&.mask{
position: fixed;
top: 0;
left: 0;
right: 0;
overflow: hidden;
bottom: 0;
}
}
.popover {
position: absolute;
&.nomask{
position: fixed;
}
padding: 20rpx;
z-index: 99999999;
border-radius: 10rpx;
display: flex;
transition: opacity .15s, transform .15s;
box-shadow: 0upx 0upx 30upx rgba(0, 0, 0, 0.2);
.popover-top:after {
content: "";
position: absolute;
border-width: 0 20rpx 20rpx;
border-style: solid;
border-color: transparent transparent var(--arrow-color);
}
.popover-top-left:after {
top: -18rpx;
left: 10rpx;
}
.popover-top-center:after {
top: -18rpx;
right: 50%;
transform: translateX(50%);
}
.popover-top-right:after {
top: -18rpx;
right: 10rpx;
}
.popover-bottom:after {
content: "";
position: absolute;
border-width: 20rpx 20rpx 0;
border-style: solid;
border-color: var(--arrow-color) transparent transparent;
}
.popover-bottom-left:after {
bottom: -18rpx;
left: 10rpx;
}
.popover-bottom-center:after {
bottom: -18rpx;
right: 50%;
transform: translateX(50%);
}
.popover-bottom-right:after {
bottom: -18rpx;
right: 10rpx;
}
.popover-left:after {
content: "";
position: absolute;
border-width: 20rpx 0 20rpx 20rpx;
border-style: solid;
border-color: transparent transparent transparent var(--arrow-color);
}
.popover-left-top:after {
top: 10rpx;
right: -18rpx;
}
.popover-left-center:after {
top: 50%;
right: -18rpx;
transform: translateY(-50%);
}
.popover-left-bottom:after {
bottom: 10rpx;
right: -18rpx;
}
.popover-right:after {
content: "";
position: absolute;
border-width: 20rpx 20rpx 20rpx 0;
border-style: solid;
border-color: transparent var(--arrow-color) transparent transparent;
}
.popover-right-top:after {
top: 10rpx;
left: -18rpx;
}
.popover-right-center:after {
top: 50%;
left: -18rpx;
transform: translateY(-50%);
}
.popover-right-bottom:after {
bottom: 10rpx;
left: -18rpx;
}
}
.diy-popover-visible {
transform: translate3D(0px, 0px, 0px) !important;
}
</style>
二、分类
根据弹出位置和设计手法的不同,气泡弹窗组件可以分为多种类型,例如模态弹窗和非模态弹窗。模态弹窗采用模态设计手法,将用户之前看到的内容与当前看到的内容进行区分,并需要用户通过明确的操作才能退出该模式。非模态弹窗则相对自由,不会打断用户的正常操作。
三、功能
气泡弹窗组件的主要功能包括:
- 告知用户信息:通过弹窗展示重要信息,提醒用户注意。
- 提醒用户操作:引导用户进行下一步操作,提高用户参与度。
- 加强用户互动:通过弹窗与用户进行交互,收集用户反馈或意见。
四、设计要点
在设计气泡弹窗组件时,需要注意以下几点:
- 明确弹窗目的:确保弹窗的内容清晰、简洁,能够准确传达信息或引导用户操作。
- 控制弹窗大小:避免弹窗过大,影响用户体验。同时,也要确保弹窗内容足够显眼,能够引起用户注意。
- 合理设置关闭按钮:给予用户关闭弹窗的权利,避免强制用户阅读或操作。关闭按钮的位置应便于用户点击,减少操作成本。
- 优化交互体验:确保弹窗的触发方式、显示方式以及消失方式都符合用户的使用习惯,提高用户体验。
五、在线设计
把弹窗组件拖进设计器。然后支持其他 组件往组件容器里拖进去,比如我们直接拖进宫格组件进去。
设置气泡弹窗组件属性。
由于气泡弹窗组件在默认运行时,不显示,需要点击来显示此组件,拖动任意一个按钮组件进设计器。
设置按钮点击事件
保存源码至本地查看组件效果。
六、生成的源码
<template>
<view class="container container329152">
<button @tap="navigateTo" data-type="openPopover" data-id="btn-popover" id="btn-popover" class="diygw-col-24 btn-clz diygw-btn-default">按钮</button>
<diy-popover v-model="popoverData.show" width="200px" bgcolor="#fff" mode="top" :triggerLeft="popoverData.left" :triggerTop="popoverData.top" :triggerWidth="popoverData.width" :triggerHeight="popoverData.height">
<view class="flex flex-content diygw-col-24">
<view class="flex diygw-col-24">
<view class="diygw-grid col-3">
<view class="diygw-grid-item">
<view class="diygw-grid-inner">
<view class="diygw-grid-icon diygw-avatar">
<image mode="aspectFit" class="diygw-avatar-img" src="/static/global/grid1.png"></image>
</view>
<view class="diygw-grid-title"> 菜单一 </view>
</view>
</view>
<view class="diygw-grid-item">
<view class="diygw-grid-inner">
<view class="diygw-grid-icon diygw-avatar">
<image mode="aspectFit" class="diygw-avatar-img" src="/static/global/grid2.png"></image>
</view>
<view class="diygw-grid-title"> 菜单二 </view>
</view>
</view>
<view class="diygw-grid-item">
<view class="diygw-grid-inner">
<view class="diygw-grid-icon diygw-avatar">
<image mode="aspectFit" class="diygw-avatar-img" src="/static/global/grid3.png"></image>
</view>
<view class="diygw-grid-title"> 菜单三 </view>
</view>
</view>
<view class="diygw-grid-item">
<view class="diygw-grid-inner">
<view class="diygw-grid-icon diygw-avatar">
<image mode="aspectFit" class="diygw-avatar-img" src="/static/global/grid4.png"></image>
</view>
<view class="diygw-grid-title"> 菜单四 </view>
</view>
</view>
<view class="diygw-grid-item">
<view class="diygw-grid-inner">
<view class="diygw-grid-icon diygw-avatar">
<image mode="aspectFit" class="diygw-avatar-img" src="/static/grid5.png"></image>
</view>
<view class="diygw-grid-title"> 菜单五 </view>
</view>
</view>
<view class="diygw-grid-item">
<view class="diygw-grid-inner">
<view class="diygw-grid-icon diygw-avatar">
<image mode="aspectFit" class="diygw-avatar-img" src="/static/grid6.png"></image>
</view>
<view class="diygw-grid-title"> 菜单六 </view>
</view>
</view>
</view>
</view>
</view>
</diy-popover>
<view class="clearfix"></view>
</view>
</template>
<script>
export default {
data() {
return {
//用户全局信息
userInfo: {},
//页面传参
globalOption: {},
//自定义全局变量
globalData: {},
listNum: 1,
list: {
code: 200,
msg: '获取数据成功',
data: [
{
title: '标题1',
remark: '描述1',
id: 1,
attr: {
title: '标题1'
},
img: 'https://php.diygw.com/logo.png'
},
{
title: '标题2',
remark: '描述2',
id: 2,
attr: {
title: '标题2'
},
img: 'https://php.diygw.com/logo.png'
},
{
title: '标题3',
remark: '描述3',
id: 3,
attr: {
title: '标题3'
},
img: 'https://php.diygw.com/logo.png'
},
{
title: '标题4',
remark: '描述4',
id: 4,
attr: {
title: '标题4'
},
img: 'https://php.diygw.com/logo.png'
},
{
title: '标题5',
remark: '描述5',
id: 5,
attr: {
title: '标题5'
},
img: 'https://php.diygw.com/logo.png'
},
{
title: '标题6',
remark: '描述6',
id: 6,
attr: {
title: '标题6'
},
img: 'https://php.diygw.com/logo.png'
},
{
title: '标题7',
remark: '描述7',
id: 7,
attr: {
title: '标题7'
},
img: 'https://php.diygw.com/logo.png'
},
{
title: '标题8',
remark: '描述8',
id: 8,
attr: {
title: '标题8'
},
img: 'https://php.diygw.com/logo.png'
},
{
title: '标题9',
remark: '描述9',
id: 9,
attr: {
title: '标题9'
},
img: 'https://php.diygw.com/logo.png'
},
{
title: '标题10',
remark: '描述10',
id: 10,
attr: {
title: '标题10'
},
img: 'https://php.diygw.com/logo.png'
}
]
},
popoverData: {
left: 0,
top: 10,
height: 0,
width: 0,
show: false
}
};
},
onPageScroll(e) {
const scrollTop = e.scrollTop;
this.headerBackgroundStyle = this.headerBackgroundStyle || { background: 'none' };
if (scrollTop <= 80) {
const opacity = scrollTop / 100;
const color = `rgba(255, 255, 255, ${opacity})`;
this.headerBackgroundStyle.background = color;
} else {
this.headerBackgroundStyle.background = '#ffffff';
}
},
onShow() {
this.setCurrentPage(this);
},
onLoad(option) {
this.setCurrentPage(this);
if (option) {
this.setData({
globalOption: this.getOption(option)
});
}
this.init();
},
methods: {
async init() {
await this.listApi();
},
// 列表数据 API请求方法
async listApi(param) {
let thiz = this;
param = param || {};
//如果请求要重置页面,请配置点击附加参数refresh=1 增加判断如输入框回调param不是对象
if (param.refresh || typeof param != 'object') {
this.listNum = 1;
}
//请求地址及请求数据,可以在加载前执行上面增加自己的代码逻辑
let http_url = 'https://php.diygw.com/article.php';
let http_data = {
pageNum: this.listNum,
pageSize: 10,
sctdown: param.sctdown || this.sctdown
};
let http_header = {};
let list = await this.$http.post(http_url, http_data, http_header, 'json');
let datarows = list.rows;
if (http_data.pageNum == 1) {
this.list = list;
} else if (datarows) {
let rows = this.list.rows.concat(datarows);
list.rows = rows;
this.list = list;
}
if (datarows && datarows.length > 0) {
this.listNum = this.listNum + 1;
}
this.globalData.isshow = true;
console.log(http_data.sctdown);
},
openPopover(evt) {
let view = uni.createSelectorQuery().in(this);
view.select('#' + evt.id).boundingClientRect();
view.exec((data) => {
if (data && data.length > 0 && data[0]) {
this.popoverData.left = data[0].left;
this.popoverData.top = data[0].top;
this.popoverData.height = data[0].height;
this.popoverData.width = data[0].width;
this.popoverData.show = true;
}
});
},
closePopover(evt) {
this.popoverData.show = false;
}
},
onPullDownRefresh() {
// 列表数据 API请求方法
this.listNum = 1;
this.listApi();
uni.stopPullDownRefresh();
},
onReachBottom() {
// 列表数据 API请求方法
this.listApi();
}
};
</script>
<style lang="scss" scoped>
.btn-clz {
padding-top: 20rpx;
border-bottom-left-radius: 12rpx;
color: #fff;
padding-left: 20rpx;
padding-bottom: 20rpx;
border-top-right-radius: 12rpx;
margin-right: 10rpx;
background-color: #07c160;
margin-left: 10rpx;
overflow: hidden;
width: calc(100% - 10rpx - 10rpx) !important;
border-top-left-radius: 12rpx;
margin-top: 10rpx;
border-bottom-right-radius: 12rpx;
margin-bottom: 10rpx;
text-align: center;
padding-right: 20rpx;
}
.diygw-dialog-popover {
}
.container329152 {
}
</style>
更多推荐
已为社区贡献8条内容
所有评论(0)