| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 |
- <template>
- <view>
- <view class="cwg-dropdown" :style="customStyle" @click="click">
- <slot></slot>
- </view>
- <view class="cwg-dropdown-menu">
- <view class="cwg-dropdown-menu-container" :style="[layout]" @click.stop>
- <slot name="menu">
- <view class="menu">
- <slot name="btn"></slot>
- <view class="menu-item" :class="{ active: props.showActive && isActive(item), disabled: item.disabled || false }"
- v-for="(item, idx) in menuList" :key="idx"
- @click="menuClick(item, idx)">
- <view>{{ item.label || item }}</view>
- <!-- <view v-if="props.showActive && isActive(item)" class="active-icon">✓</view>-->
- </view>
- </view>
- </slot>
- </view>
- </view>
- <view class="cwg-dropdown-mask" :class="{ 'cwg-dropdown-mask-show': maskShow }"
- @click.stop="close" />
- </view>
- </template>
- <script setup>
- import { ref, reactive, nextTick, onMounted, onUnmounted } from 'vue'
- import { queryElementRect } from '@/uni_modules/x-tools/tools/sugar.js'
- import { str2px, commonProps } from '@/uni_modules/x-tools/tools/com.js'
- // 合并 props
- const props = defineProps({
- ...commonProps,
- menuList: {
- type: Object,
- default: () => ['菜单1', '菜单2', '菜单3']
- },
- menuStyle: {
- type: Object,
- default: () => ({})
- },
- interspace: {
- type: [String, Number],
- default: '10rpx'
- },
- // 是否显示选中样式
- showActive: {
- type: Boolean,
- default: false
- },
- // 当前选中项的 key(用于匹配 menuList 中的项)
- activeKey: {
- type: [String, Number],
- default: ''
- }
- })
- const emit = defineEmits(['open', 'close', 'change', 'menuClick'])
- // 判断菜单项是否为选中状态
- const isActive = (item) => {
- if (!props.showActive || !props.activeKey) return false
- // 支持多种 key 字段匹配
- const itemKey = item.key !== undefined ? item.key :
- item.value !== undefined ? item.value :
- item.sysCode !== undefined ? item.sysCode :
- item.type !== undefined ? item.type : ''
- return itemKey === props.activeKey
- }
- // 响应式数据
- const maskShow = ref(false)
- const windowInfo = reactive({
- width: 0,
- height: 0
- })
- const layout = reactive({})
- const innerInterspace = ref(0)
- // 获取系统信息
- const getSystemInfo = () => {
- return new Promise((resolve) => {
- if (windowInfo.width > 0) {
- resolve(windowInfo)
- } else {
- uni.getSystemInfo({
- success: (res) => {
- windowInfo.width = res.windowWidth
- windowInfo.height = res.windowHeight
- resolve(windowInfo)
- },
- fail: () => {
- setTimeout(() => getSystemInfo().then(resolve), 100)
- }
- })
- }
- })
- }
- // 点击触发器
- const click = async (e) => {
- e.stopPropagation()
- await getSystemInfo()
- const triggerRect = await queryElementRect('.cwg-dropdown')
- if (!triggerRect) return
- const tempStyle = {
- transform: 'scaleY(1)',
- visibility: 'hidden',
- top: '-9999px',
- left: '-9999px',
- transition: 'none'
- }
- Object.assign(layout, tempStyle)
- await nextTick()
- const menuRect = await queryElementRect('.cwg-dropdown-menu-container')
- if (!menuRect) {
- Object.keys(layout).forEach(key => delete layout[key])
- layout.transform = 'scaleY(0)'
- return
- }
- const { width: winWidth } = windowInfo
- const { left, right, bottom } = triggerRect
- const interspaceVal = innerInterspace.value
- const finalLayout = {
- transition: 'transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275)',
- transform: 'scaleY(1)',
- top: `10px`
- }
- if (left + menuRect.width < winWidth) {
- finalLayout.left = `-110px`
- } else {
- const val = winWidth - right
- finalLayout.right = `${val > 0 ? val : 0}px`
- }
- Object.keys(layout).forEach(key => delete layout[key])
- Object.assign(layout, finalLayout)
- maskShow.value = true
- emit('open')
- emit('change', true)
- }
- const menuClick = (value, index) => {
- if (value.disabled) return
- emit('menuClick', { value, index })
- close()
- }
- const close = () => {
- maskShow.value = false
- Object.keys(layout).forEach(key => delete layout[key])
- layout.transform = 'scaleY(0)'
- emit('close')
- emit('change', false)
- }
- onMounted(() => {
- getSystemInfo()
- innerInterspace.value = str2px(props.interspace)
-
- uni.$on('logout', () => {
- close()
- })
- })
- onUnmounted(() => {
- uni.$off('logout')
- })
- // 暴露方法
- defineExpose({
- close
- })
- </script>
- <style lang="scss" scoped>
- @import '@/uni.scss';
- .cwg-dropdown {
- width: fit-content;
- position: relative;
- overflow: hidden;
- }
- .cwg-dropdown-menu {
- position: relative;
- z-index: 1000;
- }
- .cwg-dropdown-mask {
- position: fixed;
- top: 0;
- left: 0;
- width: 100vw;
- height: 100vh;
- z-index: 999;
- transition: all 0.3s;
- opacity: 0;
- pointer-events: none;
- background-color: rgba(0, 0, 0, 0);
- }
- .cwg-dropdown-mask-show {
- opacity: 1;
- pointer-events: auto;
- }
- .cwg-dropdown-menu-container {
- position: absolute;
- transform-origin: top;
- transform: scaleY(0);
- .menu {
- position: relative;
- --bs-bg-opacity: 1;
- background-color: rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important;
- box-shadow: 0px 5px 10px 0px rgba(0, 0, 0, 0.05);
- border: 1px solid var(--bs-border-color);
- border-radius: px2rpx(4);
- overflow: hidden;
- .menu-item {
- min-width: px2rpx(150);
- display: flex;
- align-items: center;
- padding: px2rpx(10) px2rpx(16);
- font-size: px2rpx(14);
- line-height: px2rpx(30);
- color: var(--bs-emphasis-color);
- transition: background 0.2s;
- box-sizing: border-box;
- cursor: pointer;
- &:last-child {
- border-bottom: none;
- }
- text {
- flex: 1;
- line-height: 1.4;
- }
- &:hover {
- background-color: rgba(0, 0, 0, 0.05);
- }
- &:active {
- background-color: rgba(0, 0, 0, 0.05);
- }
- &.active {
- background-color: rgba(234, 0, 42, 0.1);
- color: #ea002a;
- }
- &.disabled {
- cursor: not-allowed;
- opacity: 0.5;
- }
- }
- }
- }
- </style>
|