wap端设置父元素根据内容宽度变化而变化

博客涉及Python和前端JavaScript相关内容,Python是后端开发常用语言,JavaScript则是前端开发的关键技术,二者在信息技术领域都有重要作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

<template>
  <div class="container">
    <div class="select-wrapper" ref="selectRef">
      <div class="select-input-box" @click="showSelectList" :class="{
        focus: visible, 'show-close':
          !props.hideCloseIcon, multiple: props.multiple, disabled: props.disabled
      }">
        <div class="select-value"> {{ selectValue }}</div>
        <!-- <common-input v-if="!props.multiple" :disabled="props.disabled" :type="'text'" v-model:input-value="selectValue"
          :readonly="true" class="select-input" :placeholder="placeholder"></common-input> -->
        <!-- <common-input v-if="!props.multiple" :disabled="props.disabled" :type="'text'" v-model:input-value="selectValue"
        :readonly="true" class="select-input" :placeholder="placeholder"></common-input> -->
        <!-- <div v-else class="multiple-select-box">
        <div class="multiple-select-item" v-for="(item, index) in multipleSelectValue" :key="item">
          <div>{{ labelValueMap[item] }}</div>
          <div class="close-icon-box">
            <common-svg class="multiple-close-icon" @click="multipleClear(index, $event)" name="close">
            </common-svg>
          </div>
        </div>
      </div> -->
        <common-svg v-if="!props.disabled" :class="{ 'list-show': visible, 'has-value': selectValue }" class="arrow-icon"
          name="arrow-icon">
        </common-svg>
        <common-svg v-if="!props.hideCloseIcon" class="close-icon" @click="clear($event)"
          :class="{ 'has-value': selectValue }" name="close">
        </common-svg>
      </div>
      <common-error-tip :error-msg="props.errorTip"></common-error-tip>
      <Transition v-if="!props.disabled" :name="placement === 'top' ? 'select-top-transition' : 'select-transition'">
        <div class="select-list" :class="{ 'placement-top': placement === 'top' }" v-show="visible">
          <div class="select-option" v-for="item in props.selectList" :key="item[props.valueAttr]"
            @click="selectChange(item)" @mouseenter="selectMouseenter(item)"
            :class="{ disabled: isDisabled(item), 'height-light': heightLightItem === item[props.valueAttr], 'multiple-selected': props.multiple && multipleSelectValue.indexOf(item[props.valueAttr]) > -1 }">
            <span>{{ item[props.labelAttr] }}</span>
            <common-svg class="checkbox-icon" name="checkbox"></common-svg>
          </div>
        </div>
      </Transition>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { PropType } from 'vue'
import { onClickOutside } from '@vueuse/core'

type selectOption = Record<string, any>

const props = defineProps({
  placeholder: {
    type: String,
    default: 'Please select',
  },
  errorTip: {
    type: String,
    default: '',
  },
  modelValue: {
    type: [String, Number, Boolean, Array],
  },
  selectList: {
    type: Array as PropType<selectOption[]>,
    default: () => []
  },
  inputStyle: {
    type: Object,
    default: () => { }
  },
  // 列表中可选的集合
  optionalList: {
    type: Array as PropType<(string | number)[]>,
    default: () => []
  },
  labelAttr: {
    type: String,
    default: 'label',
  },
  valueAttr: {
    type: String,
    default: 'value',
  },
  hideCloseIcon: {
    type: Boolean,
    default: false,
  },
  // 是否多选
  multiple: {
    type: Boolean,
    default: false,
  },
  disabled: {
    type: Boolean,
    default: false,
  }
})
const emits = defineEmits(['update:modelValue', 'change'])
// 下拉列表是否显示
const visible = ref(false)
// 下拉选择的元素
const selectRef = ref()
// 点击下拉选择的元素外的区域
onClickOutside(selectRef, () => {
  visible.value = false
})
// 高亮选项
const heightLightItem = ref<string | number | boolean | undefined>('')
// 位置
const placement = ref('')

/**
 * @description: 点击选择框
 * @author: depp.chen
 */
function showSelectList() {
  visible.value = !visible.value
  if (visible.value) {
    let wHeight = document.documentElement.clientHeight || document.body.clientHeight
    if (wHeight - selectRef.value.getBoundingClientRect().bottom < 308) {
      placement.value = "top"
    } else {
      placement.value = ""
    }
    if (!props.multiple) {
      heightLightItem.value = props.modelValue as string | number | boolean
    }
  }
}
// 选择的选项
const selectValue = ref('')
// 多选选择的选项
const multipleSelectValue = ref<string[]>([])


watch(() => props.modelValue, (val: any) => {
  if (props.multiple) {
    if (val && val.length) {
      multipleSelectValue.value = val.map((e: any) => e + '')
    } else {
      multipleSelectValue.value = []
    }
  } else {
    if (val !== undefined) {
      let target = props.selectList.find(e => e[props.valueAttr] === val)
      if (target) {
        selectValue.value = target[props.labelAttr]
      }
    } else {
      selectValue.value = ''
    }
  }
}, { immediate: true })

const labelValueMap = computed(() => {
  let result: Record<string, any> = {}
  if (props.selectList && props.selectList.length) {
    props.selectList.forEach(e => {
      result[e[props.valueAttr]] = e[props.labelAttr]
    })
  }
  return result
})

/**
 * @description: 清空数值
 * @author: depp.chen
 */
function clear(event: Event) {
  event.stopPropagation()
  selectValue.value = ''
  heightLightItem.value = ''
  emits('update:modelValue', undefined)
  emits('change', undefined)
}

/**
 * @description: 多选单个删除
 * @author: depp.chen
 */
function multipleClear(index: number, event: Event) {
  event.stopPropagation()
  multipleSelectValue.value.splice(index, 1)
  emits('update:modelValue', multipleSelectValue.value)
  emits('change', multipleSelectValue.value)
}

/**
 * @description: 是否禁用
 * @author: depp.chen
 */
function isDisabled(item: selectOption) {
  return props.optionalList && props.optionalList.length && props.optionalList.indexOf(item[props.valueAttr]) < 0
}

/**
 * @description: 下拉列表选择事件
 * @author: depp.chen
 */
function selectChange(item: selectOption) {
  if (isDisabled(item)) {
    return
  }
  if (props.multiple) {
    let targetIndex = multipleSelectValue.value.indexOf(item[props.valueAttr])
    if (targetIndex > -1) {
      multipleSelectValue.value.splice(targetIndex, 1)
    } else {
      multipleSelectValue.value.push(item[props.valueAttr])
    }
    emits('update:modelValue', multipleSelectValue.value)
    emits('change', multipleSelectValue.value)
  } else {
    visible.value = false
    heightLightItem.value = item[props.valueAttr]
    emits('update:modelValue', item[props.valueAttr])
    emits('change', item[props.valueAttr], item)
  }
}

/**
 * @description: 鼠标进入选项
 * @author: depp.chen
 */
function selectMouseenter(item: selectOption) {
  heightLightItem.value = item[props.valueAttr]
}
</script>

<style scoped lang="scss">
.container {
  width: max-content;

  .select-wrapper {
    min-width: 120px;
    max-width: 600px;
    display: flex;
    font-size: 14px;
    height: 32px;
    border-radius: 20px;
    border: 1px solid var(--main-theme-color) !important;


    .select-input-box {
      border: none !important;
    }

    .select-input-box {
      display: flex;
      align-items: center;
      width: 100%;
      height: 100%;
      padding: 8px;
      border-radius: 4px;
      border: 1px solid #cccccc;
      box-sizing: border-box;
      cursor: pointer;

      &.focus {
        border-color: #333333;
      }

      &.multiple {
        height: auto;
        padding: 6px 12px;
      }

      &.disabled {
        background: #E6E6E6 !important;
      }

      .select-input {
        outline: none;
        background: none;
        border: none;
        /* flex: 1; */

        min-width: 0;
        color: inherit;
      }

      .multiple-select-box {
        display: flex;
        flex-wrap: wrap;
        gap: 4px;
        flex: 1;
        min-width: 0;

        .multiple-select-item {
          display: flex;
          align-items: center;
          padding: 4px 7px;
          background: #f0f2f5;
          border-radius: 4px;

          .close-icon-box {
            display: flex;
            align-items: center;
            justify-content: center;
            width: 18px;
            height: 18px;
            margin-left: 8px;
            border-radius: 50%;
            cursor: pointer;

            &:hover {
              background: #cccccc;
            }

            .close-icon {
              width: 14px;
              height: 14px;
              color: #333333;
            }
          }
        }
      }

      .arrow-icon {
        flex-shrink: 0;
        width: 20px;
        height: 20px;
        color: #999;
        transform: rotateZ(0deg);
        transition: transform 0.3s;

        &.list-show {
          transform: rotateZ(-180deg);
        }
      }

      .close-icon {
        display: none;
        cursor: pointer;
        flex-shrink: 0;
        width: 20px;
        height: 20px;
        color: #999;
      }

      &.show-close:hover {
        .arrow-icon.has-value {
          display: none;
        }

        .close-icon.has-value {
          display: block;
        }
      }
    }

    .select-list {
      position: absolute;
      left: 0;
      top: calc(100%);
      width: 100%;
      background: #fff;
      box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.16);
      border-radius: 4px;
      z-index: $z-index-height-fifth;
      color: #333333;
      max-height: 300px;
      overflow-y: auto;
      @include scrollBar;

      &.placement-top {
        top: auto;
        bottom: calc(100% + 8px);
      }

      .select-option {
        width: 100%;
        height: 42px;
        padding: 12px;
        cursor: pointer;

        &:nth-child(1) {
          border-radius: 4px 4px 0 0;
        }

        &:nth-last-child(1) {
          border-radius: 0 0 4px 4px;
        }

        &.height-light {
          background: #f6f6f6;
        }

        &.disabled {
          color: #c0c4cc;
          cursor: not-allowed;

          &:hover {
            background: #fff;
          }
        }

        .checkbox-icon {
          display: none;
        }

        &.multiple-selected {
          display: flex;
          justify-content: space-between;
          color: var(--main-theme-color);

          .checkbox-icon {
            display: block;
            width: 18px;
            height: 18px;
            color: var(--main-theme-color);
          }
        }
      }
    }
  }
}

.select-transition-enter-active,
.select-transition-leave-active {
  opacity: 1;
  transform: scaleY(1);
  transition: transform 0.3s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.3s cubic-bezier(0.23, 1, 0.32, 1);
  transform-origin: center top;
}

.select-transition-enter-from,
.select-transition-leave-active {
  opacity: 0;
  transform: scaleY(0);
}

.select-top-transition-enter-active,
.select-top-transition-leave-active {
  opacity: 1;
  transform: scaleY(1);
  transition: transform 0.3s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.3s cubic-bezier(0.23, 1, 0.32, 1);
  transform-origin: center bottom;
}

.select-top-transition-enter-from,
.select-top-transition-leave-active {
  opacity: 0;
  transform: scaleY(0);
}
</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值