cwg-popup.vue 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. <template>
  2. <uni-popup ref="popupRef" type="center" @change="handlePopupChange" class="crm-popup" @maskClick="closeDialog" :isMaskClick="maskClick">
  3. <view class="cwg-dialog" :style="{ width: width }">
  4. <!-- 弹窗头部 -->
  5. <view class="dialog-header" v-if="title">
  6. <text class="dialog-title">{{ title || t('Tips.DeleteAccount') }}</text>
  7. <uni-icons class="dialog-close1" 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. <view class="btn-content">
  22. <slot name="footer" />
  23. </view>
  24. </template>
  25. <!-- 无按钮模式:只显示一条线或不显示任何内容 -->
  26. <template v-else-if="footerType === 'none'">
  27. <!-- 不显示任何按钮,只留一个占位线(可选) -->
  28. <view v-if="showFooterLine" class="footer-line"></view>
  29. </template>
  30. <!-- 单按钮模式 -->
  31. <template v-else-if="footerType === 'single'">
  32. <view class="btn-content">
  33. <button class="single-btn" :class="singleBtnType" @click="handleSingleBtnClick">
  34. {{ singleBtnText || t('common.confirm') }}
  35. </button>
  36. </view>
  37. </template>
  38. <!-- 双按钮模式(默认) -->
  39. <template v-else>
  40. <view class="btn-content">
  41. <button class="cancel-btn" @click="closeDialog">
  42. {{ cancelText || t('common.cancel') }}
  43. </button>
  44. <button class="confirm-btn" :class="confirmBtnType" @click="handleConfirm">
  45. {{ confirmText || t('common.confirm') }}
  46. </button>
  47. </view>
  48. </template>
  49. </view>
  50. </view>
  51. </uni-popup>
  52. </template>
  53. <script setup>
  54. import { ref, watch, computed, useSlots } from 'vue'
  55. import { useI18n } from 'vue-i18n'
  56. const { t } = useI18n()
  57. const props = defineProps({
  58. // 是否显示弹窗
  59. visible: {
  60. type: Boolean,
  61. default: false
  62. },
  63. // 弹窗标题
  64. title: {
  65. type: String,
  66. default: ''
  67. },
  68. // 是否显示底部
  69. showFooters: {
  70. type: Boolean,
  71. default: true
  72. },
  73. // 底部按钮类型:double(双按钮), single(单按钮), none(无按钮)
  74. footerType: {
  75. type: String,
  76. default: 'double',
  77. validator: (value) => ['double', 'single', 'none'].includes(value)
  78. },
  79. // 单按钮文本
  80. singleBtnText: {
  81. type: String,
  82. default: ''
  83. },
  84. // 单按钮类型:primary, danger, default
  85. singleBtnType: {
  86. type: String,
  87. default: 'primary'
  88. },
  89. // 取消按钮文本
  90. cancelText: {
  91. type: String,
  92. default: ''
  93. },
  94. // 确认按钮文本
  95. confirmText: {
  96. type: String,
  97. default: ''
  98. },
  99. // 确认按钮类型:primary, danger
  100. confirmBtnType: {
  101. type: String,
  102. default: 'primary'
  103. },
  104. // 是否显示底部线条(当footerType为none时)
  105. showFooterLine: {
  106. type: Boolean,
  107. default: false
  108. },
  109. // 加载状态
  110. loading: {
  111. type: Boolean,
  112. default: false
  113. },
  114. // 自定义插槽名称
  115. slotName: {
  116. type: String,
  117. default: ''
  118. },
  119. // 内容宽度
  120. width: {
  121. type: String,
  122. default: '600px'
  123. },
  124. // 遮罩层点击
  125. maskClick: {
  126. type: Boolean,
  127. default: true
  128. },
  129. // 分页信息(传递给父组件使用)
  130. pagerInfo: {
  131. type: Object,
  132. default: () => ({
  133. current: 1,
  134. row: 10,
  135. pageTotal: 0,
  136. rowTotal: 0
  137. })
  138. }
  139. })
  140. const emit = defineEmits(['update:visible', 'confirm', 'close', 'single-click'])
  141. // 弹窗引用
  142. const popupRef = ref(null)
  143. const slots = useSlots()
  144. // 监听 visible 变化
  145. watch(() => props.visible, (val) => {
  146. if (val) {
  147. popupRef.value?.open()
  148. } else {
  149. popupRef.value?.close()
  150. }
  151. }, { immediate: true })
  152. // 弹窗状态变化
  153. const handlePopupChange = (e) => {
  154. if (!e.show) {
  155. setTimeout(() => {
  156. emit('close')
  157. }, 300)
  158. }
  159. }
  160. // 关闭弹窗
  161. const closeDialog = () => {
  162. popupRef.value?.close()
  163. emit('update:visible', false)
  164. }
  165. // 确认操作
  166. const handleConfirm = () => {
  167. emit('confirm')
  168. }
  169. // 单按钮点击
  170. const handleSingleBtnClick = () => {
  171. emit('single-click')
  172. }
  173. // 暴露方法给父组件
  174. defineExpose({
  175. close: closeDialog,
  176. open: () => popupRef.value?.open()
  177. })
  178. </script>
  179. <style scoped lang="scss">
  180. @import "@/uni.scss";
  181. .crm-popup {
  182. z-index: 999;
  183. }
  184. .cwg-dialog {
  185. background-color: rgba(var(--bs-body-bg-rgb), 1) !important;
  186. border-radius: px2rpx(8);
  187. overflow: hidden;
  188. width: px2rpx(600);
  189. max-width: 90vw;
  190. color: var(--bs-emphasis-color) !important;
  191. }
  192. .dialog-header {
  193. display: flex;
  194. align-items: center;
  195. justify-content: space-between;
  196. padding: px2rpx(30) px2rpx(30) px2rpx(20);
  197. border-bottom: 1px solid var(--bs-border-color);
  198. .dialog-title {
  199. font-size: px2rpx(20);
  200. font-weight: 600;
  201. color: var(--bs-emphasis-color);
  202. }
  203. .dialog-close1 {
  204. width: px2rpx(36);
  205. height: px2rpx(36);
  206. display: flex;
  207. align-items: center;
  208. justify-content: center;
  209. font-size: px2rpx(32);
  210. color: var(--bs-emphasis-color);
  211. cursor: pointer;
  212. transition: all 0.3s;
  213. border-radius: 50%;
  214. box-sizing: border-box;
  215. padding: 0;
  216. background: rgba(255, 255, 255, 0.1);
  217. &:hover {
  218. background: rgba(255, 255, 255, 0.2);
  219. transform: rotate(90deg);
  220. }
  221. &:active {
  222. transform: rotate(90deg) scale(0.9);
  223. }
  224. }
  225. }
  226. .dialog-content {
  227. padding: px2rpx(20) px2rpx(30);
  228. max-height: 60vh;
  229. overflow-y: auto;
  230. // 自定义滚动条样式
  231. &::-webkit-scrollbar {
  232. width: px2rpx(6);
  233. }
  234. &::-webkit-scrollbar-thumb {
  235. background-color: #ddd;
  236. border-radius: px2rpx(3);
  237. }
  238. }
  239. @media screen and (max-width: 768px) {
  240. :deep(.cwg-dialog) {
  241. width: px2rpx(600) !important;
  242. }
  243. .dialog-content {
  244. padding: px2rpx(20) px2rpx(20);
  245. max-height: 60vh;
  246. overflow-y: auto;
  247. // 自定义滚动条样式
  248. &::-webkit-scrollbar {
  249. width: px2rpx(6);
  250. }
  251. &::-webkit-scrollbar-thumb {
  252. background-color: #ddd;
  253. border-radius: px2rpx(3);
  254. }
  255. }
  256. }
  257. </style>