native_popup.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. export class NativePopup {
  2. constructor(globalOptions = {}) {
  3. this.sysInfo = uni.getSystemInfoSync()
  4. // 风格配色
  5. const {
  6. bgColor = '#FFFFFF',
  7. titleColor = '#1F1F1F',
  8. contentColor = "#444746",
  9. themeColor = '#0b57d0'
  10. } = globalOptions
  11. this.globalStyle = {
  12. bgColor,
  13. titleColor,
  14. contentColor,
  15. themeColor
  16. }
  17. }
  18. handleClick = (e) => {
  19. const { clientX, clientY } = e
  20. // 获取计算好的布局数据
  21. const { popupTop, popupHeight, popupWidth, btnAreaHeight, padding } = this.layoutData
  22. const { screenWidth } = this.sysInfo
  23. // 弹窗的左边距(因为是居中,计算左侧空白)
  24. const popupLeft = (screenWidth - popupWidth) / 2
  25. // 按钮区域的顶部 Y 坐标
  26. const btnAreaTop = popupTop + popupHeight - btnAreaHeight
  27. // 判断点击是否在按钮行的垂直范围内
  28. if (clientY >= btnAreaTop && clientY <= (popupTop + popupHeight)) {
  29. // 定义按钮大概的宽度(用于触控区域,稍微放宽一点)
  30. const btnWidth = 80
  31. const rightMargin = padding // 右边距
  32. // 确认按钮区域 (最右侧)
  33. // 范围:(弹窗右边界 - 按钮宽 - 边距) ~ (弹窗右边界 - 边距)
  34. const confirmBtnRightX = popupLeft + popupWidth - rightMargin
  35. const confirmBtnLeftX = confirmBtnRightX - btnWidth
  36. if (clientX >= confirmBtnLeftX && clientX <= (confirmBtnRightX + 10)) {
  37. this.currentOptions.onConfirm && this.currentOptions.onConfirm()
  38. this.close()
  39. return
  40. }
  41. // 取消按钮区域 (确认按钮左边)
  42. // 范围:(确认按钮左边 - 按钮宽) ~ (确认按钮左边)
  43. const cancelBtnRightX = confirmBtnLeftX
  44. const cancelBtnLeftX = cancelBtnRightX - btnWidth
  45. if (clientX >= cancelBtnLeftX && clientX <= cancelBtnRightX) {
  46. this.currentOptions.onCancel && this.currentOptions.onCancel()
  47. this.close()
  48. return
  49. }
  50. }
  51. }
  52. createPopup = () => {
  53. const { screenWidth, screenHeight } = this.sysInfo
  54. const { bgColor, titleColor, contentColor, themeColor } = this.globalStyle
  55. const { title, content, confirmText, cancelText } = this.currentOptions
  56. // 布局计算
  57. const popupWidth = Math.min(screenWidth - 48, 320) // 最大宽度320,两侧留空
  58. const popupHeight = 180 // 固定高度 (如需自适应需复杂计算,这里取经验值)
  59. const padding = 24 // Material 3 标准内边距
  60. // 垂直居中计算
  61. const popupTop = (screenHeight - popupHeight) / 2
  62. const popupLeft = (screenWidth - popupWidth) / 2
  63. const btnAreaHeight = 50 // 底部按钮区域高度
  64. // 保存布局数据供 handleClick 使用
  65. this.layoutData = { popupTop, popupHeight, popupWidth, btnAreaHeight, padding }
  66. const popupView = new plus.nativeObj.View('popupView', {
  67. top: 0,
  68. left: 0,
  69. width: '100%',
  70. height: '100%',
  71. })
  72. popupView.addEventListener("click", this.handleClick)
  73. // 1. 绘制全屏半透明蒙层
  74. popupView.drawRect({
  75. color: 'rgba(0, 0, 0, 0.4)',
  76. }, {
  77. top: 0,
  78. left: 0,
  79. width: '100%',
  80. height: '100%',
  81. })
  82. // 2. 绘制卡片背景
  83. popupView.drawRect({
  84. color: bgColor,
  85. radius: '16px'
  86. }, {
  87. top: `${popupTop}px`,
  88. left: `${popupLeft}px`,
  89. width: `${popupWidth}px`,
  90. height: `${popupHeight}px`,
  91. })
  92. // 3. 绘制标题 (更粗,字号 20px)
  93. popupView.drawText(title, {
  94. top: `${popupTop + padding}px`,
  95. left: `${popupLeft + padding}px`,
  96. width: `${popupWidth - padding * 2}px`,
  97. height: "30px",
  98. }, {
  99. size: "20px",
  100. weight: "bold", // 这里的bold在部分原生环境可能不明显,依靠字号区分
  101. align: "left",
  102. color: titleColor,
  103. })
  104. // 4. 绘制内容 (字号 15px,颜色略浅)
  105. popupView.drawText(content, {
  106. top: `${popupTop + padding + 35}px`, // 标题下方
  107. left: `${popupLeft + padding}px`,
  108. width: `${popupWidth - padding * 2}px`,
  109. height: "60px",
  110. }, {
  111. size: "15px",
  112. align: "left",
  113. color: contentColor,
  114. whiteSpace: 'normal',
  115. lineSpacing: '20%', // 增加行间距提升阅读体验
  116. })
  117. // 5. 绘制按钮 (位于右下角)
  118. const btnFontSize = "15px"
  119. const btnWidth = "80px" // 绘制宽度
  120. // 确认按钮
  121. popupView.drawText(confirmText || '确认', {
  122. top: `${popupTop + popupHeight - 40}px`, // 底部留些空隙
  123. left: `${popupLeft + popupWidth - padding - 80}px`, // 右对齐
  124. width: btnWidth,
  125. height: '30px'
  126. }, {
  127. size: btnFontSize,
  128. color: themeColor,
  129. align: 'right', // 文字右对齐
  130. weight: 'bold'
  131. });
  132. // 取消按钮
  133. popupView.drawText(cancelText || '取消', {
  134. top: `${popupTop + popupHeight - 40}px`,
  135. left: `${popupLeft + popupWidth - padding - 80 - 80 - 10}px`, // 确认按钮左侧,间隔10px
  136. width: btnWidth,
  137. height: '30px'
  138. }, {
  139. size: btnFontSize,
  140. color: themeColor, // 取消按钮可以是灰色
  141. align: 'right'
  142. });
  143. this.popupView = popupView
  144. return popupView
  145. }
  146. show = (options = {}) => {
  147. this.close()
  148. this.currentOptions = {
  149. title: '提示',
  150. content: '',
  151. confirmText: '确定',
  152. cancelText: '取消',
  153. ...options
  154. }
  155. this.createPopup()
  156. this.popupView.show()
  157. }
  158. close = () => {
  159. if (this.popupView) {
  160. this.popupView.close()
  161. this.popupView = null
  162. }
  163. }
  164. }
  165. export const popup = new NativePopup()