UniApp 跨平台弹窗组件封装:适配小程序 / 安卓 /iOS/ 平板

内容分享2周前发布
0 0 0

在 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. 基础用法(简单文本弹窗)

UniApp 跨平台弹窗组件封装:适配小程序 / 安卓 /iOS/ 平板



<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. 高级用法(自定义内容弹窗)

适用于需要显示图片、表单、列表等复杂内容的场景:

UniApp 跨平台弹窗组件封装:适配小程序 / 安卓 /iOS/ 平板



<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 的 
-webkit-border-radius
),使用标准 CSS 属性大屏适配:通过 
@media (min-width: 768px)
 专门适配平板,放大字体和内边距,限制最大宽度触摸事件:添加 
@touchmove.stop.prevent
 阻止弹窗滚动穿透,提升体验层级控制:设置 z-index: 9999 确保弹窗在所有组件之上,避免被遮挡

六、扩展建议

添加加载弹窗:通过 slot 传入加载动画,增加 
loading
 props 控制加载状态输入框弹窗:扩展 input 相关 props(如 placeholder、value),支持表单输入滚动内容支持:当内容过长时,给弹窗添加 
max-height
 和 
overflow-y: auto
,实现滚动自定义动画:增加 
animateDuration
(动画时长)、
animateType
(动画类型)等参数全局调用:通过 Vue.prototype 注册全局方法(如 
this.$showPopup(options)
),无需每次导入组件

七、总结

该弹窗组件通过封装统一的逻辑和样式,解决了 UniApp 跨平台开发中弹窗适配的痛点。组件支持灵活配置和自定义扩展,可满足大部分业务场景的需求,同时保证了在小程序、安卓、iOS、平板等设备上的一致性体验。

直接复制组件代码到项目中,即可快速集成使用。如果需要更复杂的功能(如拖拽、手势关闭等),可基于该组件进一步扩展。

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...