vue自定義可輸入的選擇框組件
props:
屬性 | 說明 | 類型 | 默認值 |
---|---|---|---|
selectDataList | 下拉框中的內容 | Array | 空數組([]) |
value | 輸入框中的內容 | String | 空字符串("") |
transfer | 是否將下拉框加入到body的直接子節點中,在 Tabs、帶有 fixed 的 Table 列內使用時,建議添加此屬性,它將不受父級樣式影響,從而達到更好的效果 | Boolean | false |
placeholder | 輸入框中的提示信息 | String | 請選擇 |
事件 events:
事件名 | 說明 | 返回值 |
---|---|---|
inputChange | 在輸入框中改變值時才會觸發,js改變輸入框綁定的值不會觸發 | 輸入框中當前值 |
selectChange | 選擇下拉框內容發生改變時觸發 | 當前選擇的值 |
InputAndSelect.vue 文件
<style lang="scss" scoped>
.icon-unfold-transform {
-webkit-transform: rotate(180deg);
transform: rotate(180deg);
}
.select-dropdown {
position: absolute;
z-index: 1060;
will-change: top, left;
visibility: visible;
top: 28px;
left: 0px;
width: inherit;
max-height: 210px;
padding: 5px 0;
margin: 5px 0;
background-color: #fff;
box-sizing: border-box;
border-radius: 2px;
box-shadow: 0 1px 6px rgba(0, 0, 0, .2);
overflow: auto;
.select-not-found {
text-align: center;
color: #999;
}
.select-dropdown-list {
.select-item {
height: 30px;
line-height: 16px;
display: flex;
align-items: baseline;
margin: 0;
padding: 7px 12px;
clear: both;
color: #333;
font-size: 12px;
white-space: nowrap;
list-style: none;
cursor: pointer;
transition: all .2s ease-in-out;
}
}
}
.select-input {
width: inherit;
height: 28px;
position: relative;
text-align: left;
box-sizing: border-box;
outline: 0;
background-color: #fff;
border-radius: 2px;
border: 1px solid #d7dde4;
-webkit-transition: all .2s ease-in-out;
transition: all .2s ease-in-out;
> input {
width: 100%;
vertical-align: top;
display: inline-block;
height: 28px;
line-height: 28px;
padding: 0 24px 0 8px;
font-size: 12px;
outline: 0;
border: none;
-webkit-box-sizing: border-box;
box-sizing: border-box;
color: #333;
background-color: transparent;
position: relative;
}
.select-arrow {
position: absolute;
right: 0;
top: -2px;
height: 28px;
line-height: 28px;
padding: 0 10px;
cursor: pointer;
> i {
font-size: 12px;
color: #9ea7b4;
transition: all .2s ease-in-out;
display: block;
}
}
}
.select-input-color {
border-color: #3597f5;
box-shadow: 0 0 0 2px rgba(41, 141, 255, .2);
}
.select-input:hover {
border-color: #3597f5;
}
</style>
<template>
<div style="width:inherit;">
<div class="parent" style="position: relative;width:inherit;">
<div class="select-input" :class="{'select-input-color':iconUnfoldClass['icon-unfold-transform']}">
<input class="inputDom" :placeholder="placeholder" v-model="value" type="text" autocomplete="off"
@input="inputChange"
>
<div @click="changeIconDirection($event)" class="select-arrow">
<i class="iconfont icon-unfold"
:class="iconUnfoldClass"></i>
</div>
</div>
<div class="select-dropdown" :style="{display: iconUnfoldClass['icon-unfold-transform']?'block':'none'}">
<ul class="select-not-found" :style="{display: !isExistedSelectData?'block':'none'}">
<li>無匹配數據</li>
</ul>
<ul class="select-dropdown-list" :style="{display: isExistedSelectData?'block':'none'}">
<li @click="selectChange(item)" class="select-item" v-for="(item,index) in selectDataList"
:key="index">
{{item.label}}
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
export default {
name: "InputAndSelect",
props: {
selectDataList: {
type: Array,
default: [],
// 驗證必須有label和value
validator: function (value) {
return value.every(item => {
return item.hasOwnProperty("label") && item.hasOwnProperty("value")
})
}
},
// value 與 this.$emit('input', this.value) 配合實現input 輸入框 v-model雙向綁定功能
value: {
type: String,
default: "",
},
// 是否將下拉框加入到body的直接子節點中
// 在 Tabs、帶有 fixed 的 Table 列內使用時,建議添加此屬性,它將不受父級樣式影響,從而達到更好的效果
transfer: {
type: Boolean,
default: false,
},
placeholder: {
type: String,
default: "請選擇",
}
},
data() {
return {
iconUnfoldClass: {
'icon-unfold-transform': false
},
//記錄點擊切換按鈕時對應的事件
ev: null
}
},
methods: {
//在輸入框中改變值時才會觸發,js改變輸入框綁定的值不會觸發
inputChange() {
console.log("inputValue改變了====", this.value)
this.$emit('input', this.value)
this.$emit('inputChange', this.value)
},
//選擇內容發生改變時觸發
selectChange(item) {
this.value = item.value
this.iconUnfoldClass["icon-unfold-transform"] = false
this.$emit('input', this.value)
this.$emit('selectChange', item)
},
changeIconDirection(e) {
//點擊切換圖標按鈕時,先保存當前點擊的事件,在后面監聽的click事件中,判斷如果不是當前點擊的事件,則收起下拉框
this.ev = e
this.iconUnfoldClass["icon-unfold-transform"] = !this.iconUnfoldClass["icon-unfold-transform"]
if (this.iconUnfoldClass["icon-unfold-transform"]) {
this.checkTransfer()
let inputDom = this.$el.querySelector(".inputDom")
inputDom.focus()
}
},
checkTransfer() {
if (this.transfer) {
// 組件監聽頁面resize只能用addEventListener,否則不會生效
window.addEventListener('resize', this.checkTransfer, false)
// 監聽scroll事件的事件傳遞必須使用捕獲階段,讓外部元素事件先觸發
// document.addEventListener('scroll', this.checkTransfer, true)
//window.innerHeight document.documentElement.clientHeight 都是獲取可是去高度
//window.innerHeight獲取的高度包含橫向滾動條,而document.documentElement.clientHeight不包含橫向滾動條
let bodyHeight = document.documentElement.clientHeight // body 可視區域高度
let matchHeight = this.matchDom.clientHeight // 匹配DOM的高度
let rect = this.matchParent.getBoundingClientRect() // 取出匹配父級DOM的矩形對象
// getBoundingClientRect.bottom為元素下邊與頁面上邊的距離,所以元素下邊與頁面下邊距離 = 頁面高度 - getBoundingClientRect.bottom
let bottom = bodyHeight - rect.bottom
this.matchDom.style.left = rect.left + 'px' // 匹配DOM的left與父級一致
let dropdownWidth = rect.width + 'px' //下拉框寬度與父級dom一致
this.matchDom.style.width = dropdownWidth
if (bottom >= matchHeight) { // 父級距離頁面下邊的高度大於等於匹配DOM的高度,則往下展示
this.matchDom.style.bottom = 'auto'
this.matchDom.style.top = (rect.top + rect.height) + 'px' // 匹配DOM的top = 父級矩形對象top + 父級的高度
} else { // 父級距離頁面下邊的高度小於匹配DOM的高度,則往上展示
this.matchDom.style.top = 'auto'
this.matchDom.style.bottom = (bottom + rect.height) + 'px' // 匹配DOM的bottom = 父級矩形對象bottom + 父級的高度
}
}
},
// 點擊切換圖標按鈕以外的其他地方觸發,關閉下拉框
clickOther(e) {
// 判斷如果不是當前點擊的事件,則收起下拉框
if (this.ev == e) return
if (this.iconUnfoldClass["icon-unfold-transform"]) {
this.iconUnfoldClass["icon-unfold-transform"] = false
}
}
},
computed: {
isExistedSelectData() {
return this.selectDataList.length > 0 ? true : false
},
matchDom() { // 匹配框,需要相對於body
return this.$el.getElementsByClassName('select-dropdown')[0]
},
matchParent() { // 匹配框父級
return this.$el.getElementsByClassName('parent')[0]
}
},
created() {
if (this.transfer) {
// 將匹配的下拉框從原有位置移到body節點中
this.$nextTick(() => {
const body = document.querySelector('body')
// 將匹配DOM添加到body中
if (body.append) { // 在IE11中 document.appendChild會報錯: javascript runtime error:HierarchyRequestError
body.append(this.matchDom)
} else {
body.appendChild(this.matchDom)
}
})
}
},
mounted() {
window.addEventListener("click", this.clickOther);
},
}
</script>