Skip to content

uniapp命令式弹窗组件v2版

uniapp的命令式弹窗组件和vue的有点不一样,vue的是挂载到body上的,uniapp跟小程序一样是没有body的

components/MessageBox/MessageBox.vue组件结构文件

vue
<template>
  <view class="message-box" v-if="visible">
    <view class="mask" @click="handleMaskClick"></view>
    <view class="content">
      <view class="header" v-if="title">{{ title }}</view>
      <view class="body">{{ content }}</view>
      <view class="footer">
        <button 
          v-if="showCancel" 
          class="btn cancel" 
          @click.stop="handleCancel"
        >{{ cancelText }}</button>
        <button 
          class="btn confirm" 
          @click.stop="handleConfirm"
        >{{ confirmText }}</button>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  name: 'message-box',
  data() {
    return {
      visible: false,
      title: '',
      content: '',
      showCancel: true,
      cancelText: '取消',
      confirmText: '确定',
      resolve: null,
      reject: null
    }
  },
  methods: {
    show(options) {
      return new Promise((resolve, reject) => {
        this.resolve = resolve
        this.reject = reject
        
        const {
          title = '',
          content = '',
          showCancel = true,
          cancelText = '取消',
          confirmText = '确定'
        } = options

        this.title = title
        this.content = content
        this.showCancel = showCancel
        this.cancelText = cancelText
        this.confirmText = confirmText
        this.visible = true
      })
    },
    handleConfirm() {
      if (this.resolve) {
        this.resolve({ confirm: true, cancel: false })
      }
      this.visible = false
    },
    handleCancel() {
      if (this.resolve) {
        this.resolve({ confirm: false, cancel: true })
      }
      this.visible = false
    },
    handleMaskClick() {
      if (this.showCancel) {
        this.handleCancel()
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.message-box {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 9999;
  
  .mask {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.5);
  }
  
  .content {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 80%;
    max-width: 600rpx;
    background: #fff;
    border-radius: 8rpx;
    overflow: hidden;
  }
  
  .header {
	width: 100%;
    padding: 30rpx;
    text-align: center;
    font-size: 32rpx;
    font-weight: bold;
    border-bottom: 1rpx solid #eee;
  }
  
  .body {
	width: 100%;
    padding: 30rpx;
    text-align: center;
    font-size: 28rpx;
    color: #666;
    min-height: 80rpx;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  
  .footer {
	width: 100%;
    display: flex;
    border-top: 1rpx solid #eee;
    
    .btn {
      flex: 1;
      height: 88rpx;
      line-height: 88rpx;
      text-align: center;
      font-size: 28rpx;
      background: #fff;
      border: none;
      border-radius: 0;
      
      &::after {
        border: none;
      }
      
      &.cancel {
        color: #666;
        border-right: 1rpx solid #eee;
      }
      
      &.confirm {
        color: $uni-color-primary;
      }
    }
  }
}
</style>
<template>
  <view class="message-box" v-if="visible">
    <view class="mask" @click="handleMaskClick"></view>
    <view class="content">
      <view class="header" v-if="title">{{ title }}</view>
      <view class="body">{{ content }}</view>
      <view class="footer">
        <button 
          v-if="showCancel" 
          class="btn cancel" 
          @click.stop="handleCancel"
        >{{ cancelText }}</button>
        <button 
          class="btn confirm" 
          @click.stop="handleConfirm"
        >{{ confirmText }}</button>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  name: 'message-box',
  data() {
    return {
      visible: false,
      title: '',
      content: '',
      showCancel: true,
      cancelText: '取消',
      confirmText: '确定',
      resolve: null,
      reject: null
    }
  },
  methods: {
    show(options) {
      return new Promise((resolve, reject) => {
        this.resolve = resolve
        this.reject = reject
        
        const {
          title = '',
          content = '',
          showCancel = true,
          cancelText = '取消',
          confirmText = '确定'
        } = options

        this.title = title
        this.content = content
        this.showCancel = showCancel
        this.cancelText = cancelText
        this.confirmText = confirmText
        this.visible = true
      })
    },
    handleConfirm() {
      if (this.resolve) {
        this.resolve({ confirm: true, cancel: false })
      }
      this.visible = false
    },
    handleCancel() {
      if (this.resolve) {
        this.resolve({ confirm: false, cancel: true })
      }
      this.visible = false
    },
    handleMaskClick() {
      if (this.showCancel) {
        this.handleCancel()
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.message-box {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 9999;
  
  .mask {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.5);
  }
  
  .content {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 80%;
    max-width: 600rpx;
    background: #fff;
    border-radius: 8rpx;
    overflow: hidden;
  }
  
  .header {
	width: 100%;
    padding: 30rpx;
    text-align: center;
    font-size: 32rpx;
    font-weight: bold;
    border-bottom: 1rpx solid #eee;
  }
  
  .body {
	width: 100%;
    padding: 30rpx;
    text-align: center;
    font-size: 28rpx;
    color: #666;
    min-height: 80rpx;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  
  .footer {
	width: 100%;
    display: flex;
    border-top: 1rpx solid #eee;
    
    .btn {
      flex: 1;
      height: 88rpx;
      line-height: 88rpx;
      text-align: center;
      font-size: 28rpx;
      background: #fff;
      border: none;
      border-radius: 0;
      
      &::after {
        border: none;
      }
      
      &.cancel {
        color: #666;
        border-right: 1rpx solid #eee;
      }
      
      &.confirm {
        color: $uni-color-primary;
      }
    }
  }
}
</style>

utils/MessageBox.js挂载结构

js
import Vue from 'vue'
import MessageBoxComponent from '../components/MessageBox/MessageBox.vue'

let messageBoxInstance = null

const initInstance = () => {
  const MessageBoxConstructor = Vue.extend(MessageBoxComponent)
  messageBoxInstance = new MessageBoxConstructor()
  
  // #ifdef H5
  const parent = document.createElement('div')
  parent.id = 'message-box-wrapper'
  messageBoxInstance.$mount(parent)
  document.body.appendChild(messageBoxInstance.$el)
  // #endif
  
  // #ifndef H5
  messageBoxInstance.$mount() // 小程序中直接挂载
  // #endif
}

const MessageBox = {
  show(options) {
    if (!messageBoxInstance) {
      initInstance()
    }
    return messageBoxInstance.show(options)
  }
}

export default MessageBox
import Vue from 'vue'
import MessageBoxComponent from '../components/MessageBox/MessageBox.vue'

let messageBoxInstance = null

const initInstance = () => {
  const MessageBoxConstructor = Vue.extend(MessageBoxComponent)
  messageBoxInstance = new MessageBoxConstructor()
  
  // #ifdef H5
  const parent = document.createElement('div')
  parent.id = 'message-box-wrapper'
  messageBoxInstance.$mount(parent)
  document.body.appendChild(messageBoxInstance.$el)
  // #endif
  
  // #ifndef H5
  messageBoxInstance.$mount() // 小程序中直接挂载
  // #endif
}

const MessageBox = {
  show(options) {
    if (!messageBoxInstance) {
      initInstance()
    }
    return messageBoxInstance.show(options)
  }
}

export default MessageBox

页面调用

vue
<template>
	<view class="content">
		<button @click="showMessage">显示弹窗</button>
		<message-box ref="messageBox"></message-box>
	</view>
</template>

<script>
import MessageBox from '../../components/MessageBox/MessageBox.vue'

export default {
	components: {
		'message-box': MessageBox
	},
	data() {
		return {
			title: 'Hello'
		}
	},
	onLoad() {

	},
	methods: {
		async showMessage() {
			try {
				setTimeout(async () => {
					if (this.$refs.messageBox) {
						const res = await this.$refs.messageBox.show({
							title: '提示',
							content: '这是一个自定义弹窗',
							confirmText: '确定',
							cancelText: '取消',
							showCancel: true
						})
						
						if (res.confirm) {
							console.log('用户点击确定')
						} else if (res.cancel) {
							console.log('用户点击取消')
						}
					} else {
						console.error('MessageBox ref not found')
					}
				}, 100)
			} catch (error) {
				console.error('MessageBox error:', error)
			}
		}
	}
}
</script>
<template>
	<view class="content">
		<button @click="showMessage">显示弹窗</button>
		<message-box ref="messageBox"></message-box>
	</view>
</template>

<script>
import MessageBox from '../../components/MessageBox/MessageBox.vue'

export default {
	components: {
		'message-box': MessageBox
	},
	data() {
		return {
			title: 'Hello'
		}
	},
	onLoad() {

	},
	methods: {
		async showMessage() {
			try {
				setTimeout(async () => {
					if (this.$refs.messageBox) {
						const res = await this.$refs.messageBox.show({
							title: '提示',
							content: '这是一个自定义弹窗',
							confirmText: '确定',
							cancelText: '取消',
							showCancel: true
						})
						
						if (res.confirm) {
							console.log('用户点击确定')
						} else if (res.cancel) {
							console.log('用户点击取消')
						}
					} else {
						console.error('MessageBox ref not found')
					}
				}, 100)
			} catch (error) {
				console.error('MessageBox error:', error)
			}
		}
	}
}
</script>

程序员小洛文档