index.vue 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. <template>
  2. <view class="ss-card" @click="handleCardClick">
  3. <!-- 右上角设置按钮 - 只有buttons且长度>0时显示 -->
  4. <view
  5. v-if="hasButtons"
  6. class="card-setting-header"
  7. @click.stop="handleSettingClick"
  8. >
  9. <view class="setting-icon">
  10. <Icon name="icon-chilun" size="32" color="#999" />
  11. </view>
  12. <!-- 按钮弹窗菜单 - 只有多个按钮时显示 -->
  13. <view
  14. v-if="showButtonMenu && isMultipleButtons"
  15. class="button-menu"
  16. @click.stop
  17. >
  18. <view
  19. v-for="(btn, index) in item.buttons"
  20. :key="index"
  21. class="menu-item"
  22. @click="handleButtonClick(btn, index)"
  23. >
  24. <text v-if="btn.icon" :class="['iconfont', btn.icon]"></text>
  25. <text class="menu-text">{{ btn.title }}</text>
  26. </view>
  27. </view>
  28. </view>
  29. <!-- 卡片内容 -->
  30. <slot></slot>
  31. </view>
  32. </template>
  33. <script setup>
  34. import { ref, computed, onMounted, onUnmounted } from 'vue'
  35. import Icon from '@/components/icon/index.vue';
  36. // Props
  37. const props = defineProps({
  38. item: {
  39. type: Object,
  40. default: () => ({})
  41. }
  42. })
  43. // Emits
  44. const emit = defineEmits(['click', 'buttonClick'])
  45. // 状态
  46. const showButtonMenu = ref(false)
  47. // 计算属性
  48. const hasButtons = computed(() => {
  49. return props.item?.buttons && Array.isArray(props.item.buttons) && props.item.buttons.length > 0
  50. })
  51. const isMultipleButtons = computed(() => {
  52. return props.item?.buttons && props.item.buttons.length > 1
  53. })
  54. const isSingleButton = computed(() => {
  55. return props.item?.buttons && props.item.buttons.length === 1
  56. })
  57. /**
  58. * 处理卡片点击
  59. */
  60. const handleCardClick = () => {
  61. // 如果菜单打开,先关闭菜单
  62. if (showButtonMenu.value) {
  63. showButtonMenu.value = false
  64. return
  65. }
  66. emit('click')
  67. }
  68. /**
  69. * 处理设置按钮点击
  70. */
  71. const handleSettingClick = () => {
  72. if (isSingleButton.value) {
  73. // 只有一个按钮,直接执行
  74. handleButtonClick(props.item.buttons[0], 0)
  75. } else if (isMultipleButtons.value) {
  76. // 先记录当前状态
  77. const wasOpen = showButtonMenu.value
  78. // 关闭其他卡片的菜单
  79. uni.$emit('closeAllCardMenus')
  80. // 切换当前菜单状态(如果之前是关闭的就打开,如果是打开的就关闭)
  81. showButtonMenu.value = !wasOpen
  82. }
  83. }
  84. /**
  85. * 关闭菜单
  86. */
  87. const closeMenu = () => {
  88. showButtonMenu.value = false
  89. }
  90. /**
  91. * 监听全局关闭事件
  92. */
  93. onMounted(() => {
  94. uni.$on('closeAllCardMenus', closeMenu)
  95. })
  96. onUnmounted(() => {
  97. uni.$off('closeAllCardMenus', closeMenu)
  98. })
  99. /**
  100. * 处理按钮点击
  101. */
  102. const handleButtonClick = (btn, index) => {
  103. showButtonMenu.value = false
  104. // 执行按钮的回调
  105. if (btn.onclick && typeof btn.onclick === 'function') {
  106. btn.onclick()
  107. }
  108. // 触发组件事件
  109. emit('buttonClick', { button: btn, index, item: props.item })
  110. }
  111. </script>
  112. <style lang="scss" scoped>
  113. .ss-card {
  114. background: #FFFFFF;
  115. border-radius: 8rpx;
  116. overflow: visible; // 让popup能显示
  117. padding: 25rpx;
  118. border: 1px solid #d9d9d9;
  119. margin-bottom: 30rpx;
  120. box-shadow: 2rpx 6rpx 6rpx rgba(4, 0, 0, 0.15);
  121. box-sizing: border-box;
  122. position: relative; // 为绝对定位的header提供参考
  123. z-index: 1; // 给卡片一个基础层级,但低于popup
  124. }
  125. // 右上角设置按钮区域
  126. .card-setting-header {
  127. position: absolute;
  128. top: 0;
  129. right: 0;
  130. width: 80rpx;
  131. height: 80rpx;
  132. display: flex;
  133. align-items: center;
  134. justify-content: center;
  135. z-index: 1001; // 比ss-select稍高
  136. .setting-icon {
  137. width: 80rpx;
  138. height: 80rpx;
  139. display: flex;
  140. align-items: center;
  141. justify-content: center;
  142. border-radius: 0 8rpx 0 8rpx;
  143. // background: rgba(0, 0, 0, 0.05);
  144. transition: all 0.3s ease;
  145. &:active {
  146. background: rgba(0, 0, 0, 0.1);
  147. }
  148. }
  149. }
  150. // 按钮弹窗菜单 - 参考ss-select样式
  151. .button-menu {
  152. position: absolute;
  153. top: 100%;
  154. right: 0;
  155. background-color: #393D51;
  156. z-index: 10000; // 提高层级,确保不被下一个卡片遮挡
  157. color: #fff;
  158. border: 2rpx solid #393D51;
  159. box-sizing: border-box;
  160. border-radius: 10rpx;
  161. overflow: hidden;
  162. max-height: 600rpx;
  163. overflow: auto;
  164. min-width: 200rpx;
  165. .menu-item {
  166. padding: 20rpx 20rpx 20rpx 32rpx;
  167. cursor: pointer;
  168. position: relative;
  169. display: flex;
  170. align-items: center;
  171. // 分隔线
  172. &::after {
  173. content: "";
  174. position: absolute;
  175. bottom: 0%;
  176. left: 50%;
  177. transform: translateX(-50%);
  178. width: 80%;
  179. height: 2rpx;
  180. background-color: #303445;
  181. }
  182. &:last-child::after {
  183. display: none;
  184. }
  185. &:active {
  186. background-color: #fff;
  187. color: #393D51;
  188. &::after {
  189. display: none;
  190. }
  191. }
  192. .iconfont {
  193. font-size: 28rpx;
  194. color: inherit;
  195. margin-right: 16rpx;
  196. }
  197. .menu-text {
  198. font-size: 28rpx;
  199. color: inherit;
  200. flex: 1;
  201. }
  202. }
  203. }
  204. </style>