index.vue 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. <template>
  2. <view v-if="visible" class="ss-confirm" catchtouchmove="true" @touchmove.stop.prevent="noop">
  3. <!-- 遮罩层 - 模糊背景 -->
  4. <view class="confirm-mask" catchtouchmove="true" @touchmove.stop.prevent="noop" @click="handleMaskClick"></view>
  5. <!-- 弹窗内容 -->
  6. <view class="confirm-content" :style="contentStyle" catchtouchmove="true" @touchmove.stop.prevent="noop">
  7. <!-- 头部信息 - 可选 -->
  8. <view v-if="headerVisible" class="confirm-header">
  9. <slot name="header">
  10. <view class="header-title">{{ title }}</view>
  11. </slot>
  12. </view>
  13. <view class="header-line" v-if="headerVisible"></view>
  14. <!-- 主要内容区域 - 通过slot传入 -->
  15. <view class="confirm-main">
  16. <view class="confirm-body">
  17. <slot></slot>
  18. </view>
  19. </view>
  20. <!-- 底部按钮 -->
  21. <view class="confirm-bottom">
  22. <SsBottom :buttons="bottomButtons" @button-click="handleBottomClick" />
  23. </view>
  24. </view>
  25. </view>
  26. </template>
  27. <script setup>
  28. import { computed } from 'vue'
  29. import SsBottom from '@/components/SsBottom/index.vue'
  30. // Props定义
  31. const props = defineProps({
  32. // 弹窗显示状态
  33. visible: {
  34. type: Boolean,
  35. default: false
  36. },
  37. // 弹窗标题
  38. title: {
  39. type: String,
  40. default: ''
  41. },
  42. // 是否显示头部
  43. showHeader: {
  44. type: Boolean,
  45. default: true
  46. },
  47. // 弹窗宽度
  48. width: {
  49. type: [String, Number],
  50. default: '600rpx'
  51. },
  52. // 弹窗高度
  53. height: {
  54. type: [String, Number],
  55. default: '500rpx'
  56. },
  57. // 弹窗最大高度
  58. // maxHeight: {
  59. // type: [String, Number],
  60. // default: '500rpx'
  61. // },
  62. // 底部按钮配置
  63. bottomButtons: {
  64. type: Array,
  65. default: () => [
  66. { text: '取消', type: 'default' },
  67. { text: '确认', type: 'primary' }
  68. ]
  69. },
  70. // 点击遮罩是否关闭
  71. maskClosable: {
  72. type: Boolean,
  73. default: true
  74. }
  75. })
  76. // 事件定义
  77. const emit = defineEmits(['update:visible', 'close', 'button-click', 'mask-click'])
  78. const noop = () => {}
  79. const headerVisible = computed(() => {
  80. return props.showHeader && !!String(props.title || '').trim()
  81. })
  82. // 计算弹窗样式
  83. const contentStyle = computed(() => {
  84. const style = {}
  85. // 处理宽度
  86. if (typeof props.width === 'number') {
  87. style.width = props.width + 'rpx'
  88. } else {
  89. style.width = props.width
  90. }
  91. // 处理高度
  92. if (props.height !== 'auto') {
  93. if (typeof props.height === 'number') {
  94. style.height = props.height + 'rpx'
  95. } else {
  96. style.height = props.height
  97. }
  98. }
  99. return style
  100. })
  101. /**
  102. * 处理遮罩点击
  103. */
  104. const handleMaskClick = () => {
  105. emit('mask-click')
  106. if (props.maskClosable) {
  107. emit('update:visible', false)
  108. emit('close')
  109. }
  110. }
  111. /**
  112. * 处理底部按钮点击
  113. */
  114. const handleBottomClick = (button, index) => {
  115. emit('button-click', button, index)
  116. }
  117. </script>
  118. <style lang="scss" scoped>
  119. .ss-confirm {
  120. position: fixed;
  121. top: 0;
  122. left: 0;
  123. right: 0;
  124. bottom: 0;
  125. z-index: 9999;
  126. display: flex;
  127. flex-direction: column;
  128. }
  129. // 遮罩层
  130. .confirm-mask {
  131. position: absolute;
  132. top: 0;
  133. left: 0;
  134. right: 0;
  135. bottom: 0;
  136. backdrop-filter: blur(4rpx); // 只要模糊效果,不要颜色
  137. -webkit-backdrop-filter: blur(41rpxrpx);
  138. }
  139. // 弹窗内容
  140. .confirm-content {
  141. --confirm-bottom-height: 100rpx;
  142. position: absolute;
  143. top: 50%;
  144. left: 50%;
  145. transform: translate(-50%, -50%);
  146. background: #f2f3f4;
  147. border-radius: 10rpx;
  148. overflow: hidden;
  149. display: flex;
  150. flex-direction: column;
  151. box-shadow: 12rpx 10rpx 5rpx rgba(0, 0, 0, 0.3);
  152. // 宽高通过 :style 动态设置
  153. }
  154. // 头部信息
  155. .confirm-header {
  156. padding-top: 41rpx;
  157. padding-bottom: 20rpx;
  158. border-bottom: 1rpx solid #f2f3f4;
  159. .header-title {
  160. font-size: 42rpx;
  161. color: #333;
  162. text-align: center;
  163. }
  164. }
  165. .header-line {
  166. width: calc(100% - 104rpx);
  167. height: 1rpx;
  168. background: #e6e6e6;
  169. margin: 0 auto;
  170. }
  171. // 主要内容区域
  172. .confirm-main {
  173. flex: 1;
  174. min-height: 0;
  175. box-sizing: border-box;
  176. padding-bottom: var(--confirm-bottom-height);
  177. }
  178. .confirm-body {
  179. width: 100%;
  180. height: 100%;
  181. overflow: hidden;
  182. display: flex;
  183. flex-direction: column;
  184. }
  185. // 底部按钮
  186. .confirm-bottom {
  187. position: absolute;
  188. left: 0;
  189. right: 0;
  190. bottom: 0;
  191. height: var(--confirm-bottom-height);
  192. border-top: 1rpx solid #eee;
  193. :deep(.bottom) {
  194. position: static;
  195. left: auto;
  196. bottom: auto;
  197. z-index: auto;
  198. }
  199. }
  200. </style>