cwg-popup.vue 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. <template>
  2. <uni-popup ref="popupRef" type="center" @change="handlePopupChange" class="crm-popup">
  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-close" 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. pagerInfo: {
  126. type: Object,
  127. default: () => ({
  128. current: 1,
  129. row: 10,
  130. pageTotal: 0,
  131. rowTotal: 0
  132. })
  133. }
  134. })
  135. const emit = defineEmits(['update:visible', 'confirm', 'close', 'single-click'])
  136. // 弹窗引用
  137. const popupRef = ref(null)
  138. const slots = useSlots()
  139. // 监听 visible 变化
  140. watch(() => props.visible, (val) => {
  141. if (val) {
  142. popupRef.value?.open()
  143. } else {
  144. popupRef.value?.close()
  145. }
  146. }, { immediate: true })
  147. // 弹窗状态变化
  148. const handlePopupChange = (e) => {
  149. if (!e.show) {
  150. emit('close')
  151. }
  152. }
  153. // 关闭弹窗
  154. const closeDialog = () => {
  155. popupRef.value?.close()
  156. emit('update:visible', false)
  157. }
  158. // 确认操作
  159. const handleConfirm = () => {
  160. emit('confirm')
  161. }
  162. // 单按钮点击
  163. const handleSingleBtnClick = () => {
  164. emit('single-click')
  165. }
  166. // 暴露方法给父组件
  167. defineExpose({
  168. close: closeDialog,
  169. open: () => popupRef.value?.open()
  170. })
  171. </script>
  172. <style scoped lang="scss">
  173. @import "@/uni.scss";
  174. .crm-popup {
  175. z-index: 9999;
  176. }
  177. .cwg-dialog {
  178. background-color: rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important;
  179. border-radius: px2rpx(8);
  180. overflow: hidden;
  181. width: px2rpx(600);
  182. max-width: 90vw;
  183. }
  184. .dialog-header {
  185. display: flex;
  186. align-items: center;
  187. justify-content: space-between;
  188. padding: px2rpx(30) px2rpx(30) px2rpx(20);
  189. border-bottom: 1px solid var(--bs-border-color);
  190. .dialog-title {
  191. font-size: px2rpx(20);
  192. font-weight: 600;
  193. color: var(--bs-heading-color);
  194. }
  195. .dialog-close {
  196. width: px2rpx(36);
  197. height: px2rpx(36);
  198. display: flex;
  199. align-items: center;
  200. justify-content: center;
  201. font-size: px2rpx(32);
  202. color: rgba(255, 255, 255, 0.9);
  203. cursor: pointer;
  204. transition: all 0.3s;
  205. border-radius: 50%;
  206. background: rgba(255, 255, 255, 0.1);
  207. &:hover {
  208. background: rgba(255, 255, 255, 0.2);
  209. transform: rotate(90deg);
  210. }
  211. &:active {
  212. transform: rotate(90deg) scale(0.9);
  213. }
  214. }
  215. }
  216. .dialog-content {
  217. padding: px2rpx(20) px2rpx(30);
  218. max-height: 60vh;
  219. overflow-y: auto;
  220. // 自定义滚动条样式
  221. &::-webkit-scrollbar {
  222. width: px2rpx(6);
  223. }
  224. &::-webkit-scrollbar-thumb {
  225. background-color: #ddd;
  226. border-radius: px2rpx(3);
  227. }
  228. }
  229. @media screen and (max-width: 768px) {
  230. :deep(.cwg-dialog) {
  231. width: px2rpx(600) !important;
  232. }
  233. .dialog-content {
  234. padding: px2rpx(20) px2rpx(10);
  235. max-height: 60vh;
  236. overflow-y: auto;
  237. // 自定义滚动条样式
  238. &::-webkit-scrollbar {
  239. width: px2rpx(6);
  240. }
  241. &::-webkit-scrollbar-thumb {
  242. background-color: #ddd;
  243. border-radius: px2rpx(3);
  244. }
  245. }
  246. }
  247. </style>