| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 |
- <template>
- <view class="ss-card" @click="handleCardClick">
- <!-- 右上角设置按钮 - 只有buttons且长度>0时显示 -->
- <view
- v-if="hasButtons"
- class="card-setting-header"
- @click.stop="handleSettingClick"
- >
- <view class="setting-icon">
- <Icon name="icon-chilun" size="32" color="#999" />
- </view>
- <!-- 按钮弹窗菜单 - 只有多个按钮时显示 -->
- <view
- v-if="showButtonMenu && isMultipleButtons"
- class="button-menu"
- @click.stop
- >
- <view
- v-for="(btn, index) in item.buttons"
- :key="index"
- class="menu-item"
- @click="handleButtonClick(btn, index)"
- >
- <text v-if="btn.icon" :class="['iconfont', btn.icon]"></text>
- <text class="menu-text">{{ btn.title }}</text>
- </view>
- </view>
- </view>
- <!-- 卡片内容 -->
- <slot></slot>
- </view>
- </template>
- <script setup>
- import { ref, computed, onMounted, onUnmounted } from 'vue'
- import Icon from '@/components/icon/index.vue';
- // Props
- const props = defineProps({
- item: {
- type: Object,
- default: () => ({})
- }
- })
- // Emits
- const emit = defineEmits(['click', 'buttonClick'])
- // 状态
- const showButtonMenu = ref(false)
- // 计算属性
- const hasButtons = computed(() => {
- return props.item?.buttons && Array.isArray(props.item.buttons) && props.item.buttons.length > 0
- })
- const isMultipleButtons = computed(() => {
- return props.item?.buttons && props.item.buttons.length > 1
- })
- const isSingleButton = computed(() => {
- return props.item?.buttons && props.item.buttons.length === 1
- })
- /**
- * 处理卡片点击
- */
- const handleCardClick = () => {
- // 如果菜单打开,先关闭菜单
- if (showButtonMenu.value) {
- showButtonMenu.value = false
- return
- }
- emit('click')
- }
- /**
- * 处理设置按钮点击
- */
- const handleSettingClick = () => {
- if (isSingleButton.value) {
- // 只有一个按钮,直接执行
- handleButtonClick(props.item.buttons[0], 0)
- } else if (isMultipleButtons.value) {
- // 先记录当前状态
- const wasOpen = showButtonMenu.value
- // 关闭其他卡片的菜单
- uni.$emit('closeAllCardMenus')
- // 切换当前菜单状态(如果之前是关闭的就打开,如果是打开的就关闭)
- showButtonMenu.value = !wasOpen
- }
- }
- /**
- * 关闭菜单
- */
- const closeMenu = () => {
- showButtonMenu.value = false
- }
- /**
- * 监听全局关闭事件
- */
- onMounted(() => {
- uni.$on('closeAllCardMenus', closeMenu)
- })
- onUnmounted(() => {
- uni.$off('closeAllCardMenus', closeMenu)
- })
- /**
- * 处理按钮点击
- */
- const handleButtonClick = (btn, index) => {
- showButtonMenu.value = false
- // 执行按钮的回调
- if (btn.onclick && typeof btn.onclick === 'function') {
- btn.onclick()
- }
- // 触发组件事件
- emit('buttonClick', { button: btn, index, item: props.item })
- }
- </script>
- <style lang="scss" scoped>
- .ss-card {
- background: #FFFFFF;
- border-radius: 8rpx;
- overflow: visible; // 让popup能显示
- padding: 25rpx;
- border: 1px solid #d9d9d9;
- margin-bottom: 30rpx;
- box-shadow: 2rpx 6rpx 6rpx rgba(4, 0, 0, 0.15);
- box-sizing: border-box;
- position: relative; // 为绝对定位的header提供参考
- z-index: 1; // 给卡片一个基础层级,但低于popup
- }
- // 右上角设置按钮区域
- .card-setting-header {
- position: absolute;
- top: 0;
- right: 0;
- width: 80rpx;
- height: 80rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 1001; // 比ss-select稍高
- .setting-icon {
- width: 80rpx;
- height: 80rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 0 8rpx 0 8rpx;
- // background: rgba(0, 0, 0, 0.05);
- transition: all 0.3s ease;
- &:active {
- background: rgba(0, 0, 0, 0.1);
- }
- }
- }
- // 按钮弹窗菜单 - 参考ss-select样式
- .button-menu {
- position: absolute;
- top: 100%;
- right: 0;
- background-color: #393D51;
- z-index: 10000; // 提高层级,确保不被下一个卡片遮挡
- color: #fff;
- border: 2rpx solid #393D51;
- box-sizing: border-box;
- border-radius: 10rpx;
- overflow: hidden;
- max-height: 600rpx;
- overflow: auto;
- min-width: 200rpx;
- .menu-item {
- padding: 20rpx 20rpx 20rpx 32rpx;
- cursor: pointer;
- position: relative;
- display: flex;
- align-items: center;
- // 分隔线
- &::after {
- content: "";
- position: absolute;
- bottom: 0%;
- left: 50%;
- transform: translateX(-50%);
- width: 80%;
- height: 2rpx;
- background-color: #303445;
- }
- &:last-child::after {
- display: none;
- }
- &:active {
- background-color: #fff;
- color: #393D51;
- &::after {
- display: none;
- }
- }
- .iconfont {
- font-size: 28rpx;
- color: inherit;
- margin-right: 16rpx;
- }
- .menu-text {
- font-size: 28rpx;
- color: inherit;
- flex: 1;
- }
- }
- }
- </style>
|