cwg-popup.vue 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. <template>
  2. <uni-popup ref="popupRef" type="center" @change="handlePopupChange">
  3. <view class="cwg-dialog">
  4. <!-- 弹窗头部 -->
  5. <view class="dialog-header" v-if="title">
  6. <text class="dialog-title">{{ title || t('Tips.DeleteAccount') }}</text>
  7. <uni-icons type="closeempty" size="20" color="#999" @click="closeDialog" />
  8. </view>
  9. <!-- 弹窗内容 -->
  10. <view class="dialog-content">
  11. <slot />
  12. </view>
  13. <!-- 底部按钮区域 - 支持多种模式 -->
  14. <view class="dialog-footer" v-if="props.showFooters">
  15. <!-- 底部-额外自定义插槽 -->
  16. <template v-if="slotName && slots[slotName]">
  17. <slot :name="slotName" />
  18. </template>
  19. <!-- 自定义底部插槽 -->
  20. <template v-if="slots.footer">
  21. <slot name="footer" />
  22. </template>
  23. <!-- 无按钮模式:只显示一条线或不显示任何内容 -->
  24. <template v-else-if="footerType === 'none'">
  25. <!-- 不显示任何按钮,只留一个占位线(可选) -->
  26. <view v-if="showFooterLine" class="footer-line"></view>
  27. </template>
  28. <!-- 单按钮模式 -->
  29. <template v-else-if="footerType === 'single'">
  30. <view class="btn-content">
  31. <button class="single-btn" :class="singleBtnType" @click="handleSingleBtnClick">
  32. {{ singleBtnText || t('Common.Confirm') }}
  33. </button>
  34. </view>
  35. </template>
  36. <!-- 双按钮模式(默认) -->
  37. <template v-else>
  38. <view class="btn-content">
  39. <button class="cancel-btn" @click="closeDialog">
  40. {{ cancelText || t('Common.Cancel') }}
  41. </button>
  42. <button class="confirm-btn" :class="confirmBtnType" @click="handleConfirm">
  43. {{ confirmText || t('Common.Confirm') }}
  44. </button>
  45. </view>
  46. </template>
  47. </view>
  48. </view>
  49. </uni-popup>
  50. </template>
  51. <script setup>
  52. import { ref, watch, computed,useSlots } from 'vue'
  53. import { useI18n } from 'vue-i18n'
  54. const { t } = useI18n()
  55. const props = defineProps({
  56. // 是否显示弹窗
  57. visible: {
  58. type: Boolean,
  59. default: false
  60. },
  61. // 弹窗标题
  62. title: {
  63. type: String,
  64. default: ''
  65. },
  66. // 是否显示底部
  67. showFooters: {
  68. type: Boolean,
  69. default: true
  70. },
  71. // 底部按钮类型:double(双按钮), single(单按钮), none(无按钮)
  72. footerType: {
  73. type: String,
  74. default: 'double',
  75. validator: (value) => ['double', 'single', 'none'].includes(value)
  76. },
  77. // 单按钮文本
  78. singleBtnText: {
  79. type: String,
  80. default: ''
  81. },
  82. // 单按钮类型:primary, danger, default
  83. singleBtnType: {
  84. type: String,
  85. default: 'primary'
  86. },
  87. // 取消按钮文本
  88. cancelText: {
  89. type: String,
  90. default: ''
  91. },
  92. // 确认按钮文本
  93. confirmText: {
  94. type: String,
  95. default: ''
  96. },
  97. // 确认按钮类型:primary, danger
  98. confirmBtnType: {
  99. type: String,
  100. default: 'primary'
  101. },
  102. // 是否显示底部线条(当footerType为none时)
  103. showFooterLine: {
  104. type: Boolean,
  105. default: false
  106. },
  107. // 加载状态
  108. loading: {
  109. type: Boolean,
  110. default: false
  111. },
  112. // 自定义插槽名称
  113. slotName: {
  114. type: String,
  115. default: ''
  116. },
  117. // 分页信息(传递给父组件使用)
  118. pagerInfo: {
  119. type: Object,
  120. default: () => ({
  121. current: 1,
  122. row: 10,
  123. pageTotal: 0,
  124. rowTotal: 0
  125. })
  126. }
  127. })
  128. console.log('props.showFooters:', props.showFooters)
  129. const emit = defineEmits(['update:visible', 'confirm', 'close', 'single-click'])
  130. // 弹窗引用
  131. const popupRef = ref(null)
  132. const slots = useSlots()
  133. // 监听 visible 变化
  134. watch(() => props.visible, (val) => {
  135. console.log(slots)
  136. if (val) {
  137. popupRef.value?.open()
  138. } else {
  139. popupRef.value?.close()
  140. }
  141. }, { immediate: true })
  142. // 弹窗状态变化
  143. const handlePopupChange = (e) => {
  144. if (!e.show) {
  145. emit('update:visible', false)
  146. emit('close')
  147. }
  148. }
  149. // 关闭弹窗
  150. const closeDialog = () => {
  151. popupRef.value?.close()
  152. }
  153. // 确认操作
  154. const handleConfirm = () => {
  155. emit('confirm')
  156. }
  157. // 单按钮点击
  158. const handleSingleBtnClick = () => {
  159. emit('single-click')
  160. }
  161. // 暴露方法给父组件
  162. defineExpose({
  163. close: closeDialog,
  164. open: () => popupRef.value?.open()
  165. })
  166. </script>
  167. <style scoped lang="scss">
  168. @import "@/uni.scss";
  169. .cwg-dialog {
  170. background-color: var(--color-white);
  171. border-radius: px2rpx(8);
  172. overflow: hidden;
  173. width: px2rpx(600);
  174. max-width: 90vw;
  175. }
  176. @media (min-width: 768px) {
  177. :deep(.cwg-dialog) {
  178. width: px2rpx(600) !important;
  179. }
  180. }
  181. .dialog-header {
  182. display: flex;
  183. align-items: center;
  184. justify-content: space-between;
  185. padding: px2rpx(30) px2rpx(30) px2rpx(20);
  186. border-bottom: 1px solid #f0f0f0;
  187. .dialog-title {
  188. font-size: px2rpx(20);
  189. font-weight: 600;
  190. color: #333;
  191. }
  192. }
  193. .dialog-content {
  194. padding: px2rpx(20) px2rpx(30);
  195. max-height: 60vh;
  196. overflow-y: auto;
  197. // 自定义滚动条样式
  198. &::-webkit-scrollbar {
  199. width: px2rpx(6);
  200. }
  201. &::-webkit-scrollbar-thumb {
  202. background-color: #ddd;
  203. border-radius: px2rpx(3);
  204. }
  205. }
  206. </style>