在 UniApp 跨平台开发中,弹窗是高频使用的交互组件。不同平台(微信小程序、支付宝小程序、安卓 App、iOS App、平板)的原生弹窗样式差异较大,直接使用原生组件往往需要大量适配代码。本文将分享一个通用型跨平台弹窗组件,支持自定义样式、布局、动画,完美适配各类设备,可直接集成到项目中使用。
一、组件核心特性
全平台兼容:无缝支持微信 / 支付宝 / 百度等小程序、安卓 /iOS App、平板设备,样式统一协调高度灵活配置:
支持居中 / 顶部 / 底部三种弹窗位置支持横向 / 纵向两种按钮布局自定义标题、内容(简单文本 / 复杂插槽)可配置遮罩透明度、点击遮罩是否关闭支持自定义颜色、圆角、宽度等样式属性
响应式设计:自动适配手机(375px~414px)、平板(768px+)等不同尺寸设备流畅动画:遮罩淡入淡出 + 弹窗缩放动画,提升用户体验完善事件:提供关闭事件、按钮点击事件,支持按钮自动关闭配置
二、完整组件代码
组件目录结构
components/
└── common-popup/
└── common-popup.vue // 弹窗组件
组件代码(common-popup.vue)
<template>
<view class="popup-container" v-if="show" @touchmove.stop.prevent>
<!-- 遮罩层 -->
<view
class="popup-mask"
:class="{ 'mask-animate': animate }"
@tap="handleMaskClick"
></view>
<!-- 弹窗主体 -->
<view
class="popup-content"
:style="{
width: popupWidth,
borderRadius: borderRadius + 'rpx',
backgroundColor: bgColor
}"
:class="{
'content-animate': animate,
'content-center': position === 'center',
'content-bottom': position === 'bottom',
'content-top': position === 'top'
}"
>
<!-- 弹窗标题 -->
<view class="popup-title" v-if="title" :style="{ color: titleColor }">
{{ title }}
</view>
<!-- 自定义内容插槽(复杂内容用这个) -->
<slot name="content"></slot>
<!-- 简单文本内容(无需复杂布局时使用) -->
<view class="popup-message" v-if="message" :style="{ color: messageColor }">
{{ message }}
</view>
<!-- 按钮区域 -->
<view class="popup-buttons" :class="{ 'buttons-horizontal': buttonLayout === 'horizontal' }">
<view
v-for="(btn, index) in buttons"
:key="index"
class="popup-button"
:style="{
color: btn.color || buttonColor,
backgroundColor: btn.bgColor || 'transparent',
borderColor: btn.borderColor || buttonBorderColor
}"
@tap="handleButtonClick(index, btn)"
>
{{ btn.text }}
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'CommonPopup',
props: {
// 是否显示弹窗
show: {
type: Boolean,
default: false
},
// 弹窗标题(可选)
title: {
type: String,
default: ''
},
// 简单文本内容(复杂内容用slot="content")
message: {
type: String,
default: ''
},
// 按钮配置:[{ text: '文本', color: '文字色', bgColor: '背景色', autoClose: 是否点击关闭 }]
buttons: {
type: Array,
default: () => [
{ text: '取消', color: '#999' },
{ text: '确定', color: '#007AFF' }
]
},
// 按钮布局:horizontal(横向) / vertical(纵向)
buttonLayout: {
type: String,
default: 'horizontal'
},
// 弹窗位置:center(居中) / bottom(底部) / top(顶部)
position: {
type: String,
default: 'center'
},
// 是否显示动画
animate: {
type: Boolean,
default: true
},
// 点击遮罩是否关闭弹窗
maskClosable: {
type: Boolean,
default: true
},
// 弹窗宽度(支持百分比、rpx、px,如"80%"、"600rpx")
popupWidth: {
type: String,
default: '80%'
},
// 弹窗背景色
bgColor: {
type: String,
default: '#FFFFFF'
},
// 标题颜色
titleColor: {
type: String,
default: '#1A1A1A'
},
// 文本内容颜色
messageColor: {
type: String,
default: '#666666'
},
// 按钮默认文字颜色
buttonColor: {
type: String,
default: '#007AFF'
},
// 按钮边框颜色
buttonBorderColor: {
type: String,
default: '#EEEEEE'
},
// 弹窗圆角(rpx)
borderRadius: {
type: Number,
default: 16
}
},
methods: {
// 点击遮罩关闭弹窗
handleMaskClick() {
if (this.maskClosable) {
this.$emit('close');
}
},
// 点击按钮触发事件
handleButtonClick(index, btn) {
this.$emit('buttonClick', index, btn);
// 配置autoClose: false时,点击按钮不关闭弹窗
if (btn.autoClose !== false) {
this.$emit('close');
}
}
}
};
</script>
<style scoped>
/* 弹窗容器:固定定位全屏覆盖 */
.popup-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999; /* 确保弹窗在最上层 */
display: flex;
justify-content: center;
align-items: center;
padding: 20rpx;
box-sizing: border-box;
}
/* 遮罩层:半透明黑色 */
.popup-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
opacity: 0;
transition: opacity 0.3s ease; /* 遮罩淡入淡出动画 */
}
/* 遮罩显示动画 */
.mask-animate {
opacity: 1;
}
/* 弹窗主体:默认居中 */
.popup-content {
position: relative;
z-index: 1;
padding: 32rpx;
box-sizing: border-box;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15); /* 阴影提升层次感 */
transform: scale(0.9);
opacity: 0;
transition: all 0.3s ease; /* 弹窗缩放动画 */
}
/* 弹窗显示动画 */
.content-animate {
transform: scale(1);
opacity: 1;
}
/* 居中位置 */
.content-center {
align-self: center;
}
/* 底部位置:取消底部圆角,贴近屏幕底部 */
.content-bottom {
align-self: flex-end;
margin-bottom: 20rpx;
border-bottom-left-radius: 0 !important;
border-bottom-right-radius: 0 !important;
}
/* 顶部位置:取消顶部圆角,贴近屏幕顶部 */
.content-top {
align-self: flex-start;
margin-top: 20rpx;
border-top-left-radius: 0 !important;
border-top-right-radius: 0 !important;
}
/* 标题样式 */
.popup-title {
font-size: 32rpx;
font-weight: 600;
text-align: center;
margin-bottom: 24rpx;
}
/* 文本内容样式 */
.popup-message {
font-size: 28rpx;
line-height: 1.6;
text-align: center;
margin-bottom: 32rpx;
}
/* 按钮区域:默认纵向布局 */
.popup-buttons {
display: flex;
flex-direction: column;
gap: 12rpx;
}
/* 横向按钮布局 */
.buttons-horizontal {
flex-direction: row;
}
/* 按钮样式:自适应宽度 */
.popup-button {
font-size: 32rpx;
font-weight: 500;
text-align: center;
padding: 16rpx 0;
border-radius: 8rpx;
border: 1px solid;
box-sizing: border-box;
flex: 1; /* 按钮平均分配宽度 */
}
/* 平板等大屏设备适配:放大字体和内边距 */
@media (min-width: 768px) {
.popup-content {
max-width: 600rpx; /* 大屏限制最大宽度,避免过宽 */
}
.popup-title {
font-size: 36rpx;
}
.popup-message {
font-size: 32rpx;
}
.popup-button {
font-size: 34rpx;
padding: 20rpx 0;
}
}
</style>
三、组件使用示例
1. 基础用法(简单文本弹窗)

<template>
<view class="container">
<button @tap="showBasePopup = true" class="demo-btn">打开基础弹窗</button>
<!-- 基础弹窗 -->
<common-popup
:show="showBasePopup"
title="操作提示"
message="确定要删除该数据吗?删除后不可恢复"
:buttons="[
{ text: '取消', color: '#999' },
{ text: '确定', color: '#FF3B30', bgColor: '#FEE' }
]"
@close="showBasePopup = false"
@buttonClick="handleBaseBtnClick"
></common-popup>
</view>
</template>
<script>
import CommonPopup from '@/components/common-popup/common-popup.vue';
export default {
components: { CommonPopup },
data() {
return {
showBasePopup: false
};
},
methods: {
handleBaseBtnClick(index, btn) {
console.log(`点击了${btn.text}按钮,索引:${index}`);
// 执行删除逻辑...
}
}
};
</script>
<style scoped>
.container {
padding: 40rpx;
}
.demo-btn {
width: 100%;
height: 80rpx;
line-height: 80rpx;
background-color: #007AFF;
color: #fff;
border-radius: 40rpx;
}
</style>
2. 高级用法(自定义内容弹窗)
适用于需要显示图片、表单、列表等复杂内容的场景:

<template>
<view class="container">
<button @tap="showCustomPopup = true" class="demo-btn">打开自定义弹窗</button>
<!-- 自定义内容弹窗(底部弹出) -->
<common-popup
:show="showCustomPopup"
title="选择支付方式"
position="bottom"
buttonLayout="vertical"
:maskClosable="true"
@close="showCustomPopup = false"
>
<!-- 自定义内容插槽 -->
<template #content>
<view class="custom-content">
<view class="pay-item" @tap="selectPayType('wechat')">
<image src="/static/wechat.png" class="pay-icon"></image>
<view class="pay-name">微信支付</view>
<image src="/static/select.png" class="select-icon" v-if="selectedPayType === 'wechat'"></image>
</view>
<view class="pay-item" @tap="selectPayType('alipay')">
<image src="/static/alipay.png" class="pay-icon"></image>
<view class="pay-name">支付宝支付</view>
<image src="/static/select.png" class="select-icon" v-if="selectedPayType === 'alipay'"></image>
</view>
</view>
</template>
<!-- 自定义按钮区域(可选) -->
<template #buttons>
<view class="custom-buttons">
<view class="popup-button" @tap="showCustomPopup = false">
取消
</view>
<view class="popup-button" @tap="handlePay">
立即支付 ¥99.00
</view>
</view>
</template>
</common-popup>
</view>
</template>
<script>
import CommonPopup from '@/components/common-popup/common-popup.vue';
export default {
components: { CommonPopup },
data() {
return {
showCustomPopup: false,
selectedPayType: 'wechat' // 默认选中微信支付
};
},
methods: {
selectPayType(type) {
this.selectedPayType = type;
},
handlePay() {
console.log(`选择${this.selectedPayType}支付`);
// 执行支付逻辑...
this.showCustomPopup = false;
}
}
};
</script>
<style scoped>
/* 自定义内容样式 */
.custom-content {
padding: 16rpx 0;
}
.pay-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 0;
border-bottom: 1px solid #f5f5f5;
}
.pay-icon {
width: 60rpx;
height: 60rpx;
margin-right: 20rpx;
}
.pay-name {
font-size: 28rpx;
flex: 1;
}
.select-icon {
width: 36rpx;
height: 36rpx;
}
.custom-buttons {
gap: 16rpx;
}
</style>
四、参数与事件说明
1. 核心参数
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| show | Boolean | false | 控制弹窗显示 / 隐藏 |
| title | String | '' | 弹窗标题(可选) |
| message | String | '' | 简单文本内容(复杂内容用 slot) |
| buttons | Array | 取消 / 确定按钮 | 按钮配置,每项支持:text (文字)、color (文字色)、bgColor (背景色)、borderColor (边框色)、autoClose (是否点击关闭) |
| buttonLayout | String | 'horizontal' | 按钮布局:horizontal (横向)/vertical (纵向) |
| position | String | 'center' | 弹窗位置:center (居中)/bottom (底部)/top (顶部) |
| animate | Boolean | true | 是否启用动画 |
| maskClosable | Boolean | true | 点击遮罩是否关闭弹窗 |
| popupWidth | String | '80%' | 弹窗宽度(支持百分比、rpx、px) |
| bgColor | String | '#FFFFFF' | 弹窗背景色 |
| borderRadius | Number | 16 | 弹窗圆角(rpx) |
2. 事件说明
| 事件名 | 触发时机 | 回调参数 |
|---|---|---|
| close | 弹窗关闭时(遮罩点击 / 按钮点击) | – |
| buttonClick | 按钮被点击时 | index (按钮索引)、btn (按钮配置对象) |
五、跨平台适配要点
单位选择:使用 rpx 作为默认单位,UniApp 会自动转换为对应平台的像素单位(小程序 / APP 自适应)样式兼容性:避免使用平台特有样式(如 iOS 的 ),使用标准 CSS 属性大屏适配:通过
-webkit-border-radius 专门适配平板,放大字体和内边距,限制最大宽度触摸事件:添加
@media (min-width: 768px) 阻止弹窗滚动穿透,提升体验层级控制:设置 z-index: 9999 确保弹窗在所有组件之上,避免被遮挡
@touchmove.stop.prevent
六、扩展建议
添加加载弹窗:通过 slot 传入加载动画,增加 props 控制加载状态输入框弹窗:扩展 input 相关 props(如 placeholder、value),支持表单输入滚动内容支持:当内容过长时,给弹窗添加
loading 和
max-height,实现滚动自定义动画:增加
overflow-y: auto(动画时长)、
animateDuration(动画类型)等参数全局调用:通过 Vue.prototype 注册全局方法(如
animateType),无需每次导入组件
this.$showPopup(options)
七、总结
该弹窗组件通过封装统一的逻辑和样式,解决了 UniApp 跨平台开发中弹窗适配的痛点。组件支持灵活配置和自定义扩展,可满足大部分业务场景的需求,同时保证了在小程序、安卓、iOS、平板等设备上的一致性体验。
直接复制组件代码到项目中,即可快速集成使用。如果需要更复杂的功能(如拖拽、手势关闭等),可基于该组件进一步扩展。


