cwg-popup.vue 7.4 KB

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