cwg-popup.vue 7.0 KB

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