123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562 |
- <template>
- <view
- class="w-select"
- id="wSelect"
- :style="{
- '--select-wrap-width': width,
- '--select-wrap-height': height,
- '--select-bg-color': bgColor
- }"
- >
- <view :class="isShow ? 'select-wrap-active' : ''" class="select-wrap" @click="changeShow" style="height: 35px;">
- <view v-if="multiple" class="select-content">
- <view class="select-content-item-default" v-if="multiSelectList.length === 0 && !filterable">
- {{ defaultValue }}
- </view>
- <view class="select-content-item" v-if="multiSelectList.length > 0">
- {{ multiSelectList[0][valueName] }}
- </view>
- <view class="select-content-item" v-if="multiSelectList.length > 1">
- {{ multiLength }}
- </view>
- </view>
- <input
- v-if="!multiple || filterable"
- type="text"
- @input="inputChange"
- @blur="blurChange"
- :placeholder="multiple ? multiSelectList.length === 0 ? defaultValue : '' : defaultValue"
- :disabled="!filterable"
- :style="!filterable ? 'pointer-events: none' : ''"
- :value="inputData"
- style="font-size: 13px;"
- >
- <!-- #ifdef VUE2 -->
- <view
- @click.stop="refreshValue"
- class="close-icon"
- v-if="showClose && (multiple ? value.length > 0 : value)"
- >
- <image :src="refreshUrl" mode="" />
- </view>
- <view
- v-if="value.length <= 0 || !showClose"
- :class="isShow ? 'w-select-arrow-up' : ''"
- class="w-select-arrow "
- />
- <!-- #endif -->
- <!-- #ifdef VUE3 -->
- <view
- @click.stop="refreshValue"
- class="close-icon"
- v-if="showClose && (multiple ? modelValue.length > 0 : modelValue)"
- >
- <image :src="refreshUrl" mode="" />
- </view>
- <view
- v-if="modelValue.length <= 0 || !showClose"
- :class="isShow ? 'w-select-arrow-up' : ''"
- class="w-select-arrow "
- />
- <!-- #endif -->
-
- <scroll-view
- scroll-y
- v-show="optionsShow"
- :class="[
- isShow
- ? showPosition === 'bottom'
- ? 'animation-bottom-in'
- : 'animation-top-in'
- : showPosition === 'bottom'
- ? 'animation-bottom-out'
- : 'animation-top-out',
- showPosition === 'bottom'
- ? 'position-bottom'
- : 'position-top'
- ]"
- class="select-options"
- >
- <!-- #ifdef VUE2 -->
- <view
- @click.stop="handleClickItem(item)"
- :class="
- multiple &&
- multiSelectList.find(
- res => res[keyName] === item[keyName]
- )
- ? 'item-active'
- : value === item[keyName]
- ? 'item-active'
- : ''
- "
- v-for="item in filterList"
- :key="item[keyName]"
- class="select-option-item"
- >
- {{ item[valueName] }}
- </view>
- <!-- #endif -->
- <!-- #ifdef VUE3 -->
- <view
- @click.stop="handleClickItem(item)"
- :class="
- multiple &&
- multiSelectList.find(
- res => res[keyName] === item[keyName]
- )
- ? 'item-active'
- : modelValue === item[keyName]
- ? 'item-active'
- : ''
- "
- v-for="item in filterList"
- :key="item[keyName]"
- class="select-option-item"
- >
- {{ item[valueName] }}
- </view>
- <!-- #endif -->
-
- <view class="options-no-data" v-if="filterList.length < 1">
- 无匹配数据~
- </view>
- </scroll-view>
- </view>
- <view v-if="isShow" @click="closeContentSelect" class="contentMask" />
- </view>
- </template>
-
- <script>
- export default {
- props: {
- width: {
- type: String,
- default: '100%'
- },
- height: {
- type: String,
- default: '30px'
- },
- bgColor: {
- type: String,
- default: '#fff'
- },
- // 是否多选
- multiple: {
- type: Boolean,
- default: false
- },
- // 是否可搜索
- filterable: {
- type: Boolean,
- default: false
- },
- // 是否显示关闭按钮
- showClose: {
- type: Boolean,
- default: false
- },
- // 渲染列表
- list: {
- type: Array,
- default: () => []
- },
- // #ifdef VUE3
- // 双向绑定的值
- modelValue: {
- type: [Array, String, Number],
- default: ''
- },
- // #endif
- // #ifdef VUE2
- // 双向绑定的值
- value: {
- type: [Array, String, Number],
- default: ''
- },
- // #endif
- // 默认显示的内容
- defaultValue: {
- type: String,
- default: '请输入所在公司名称,4个字及以上'
- },
- // 显示的内容
- valueName: {
- type: String,
- default: 'label'
- },
- // 绑定的内容
- keyName: {
- type: String,
- default: 'value'
- }
- },
- // #ifdef VUE3
- emits: ['update:modelValue', 'change'],
- // #endif
- watch: {
- list: {
- immediate: true,
- deep: true,
- handler (news) {
- this.filterList = news
- const findItem = news.find(item => {
- let isItem = ''
- // #ifdef VUE3
- if (item[this.keyName] === this.modelValue) {
- isItem = true
- } else {
- isItem = false
- }
- // #endif
-
- // #ifdef VUE2
- if (item[this.keyName] === this.value) {
- isItem = true
- } else {
- isItem = false
- }
- // #endif
- return isItem
- })
- if (findItem) {
- this.inputData = findItem[this.valueName]
- }
- }
- }
- },
- computed: {
- multiLength () {
- const length = this.multiSelectList.length - 1
- return '+' + length
- },
- bottomDistance () {
- return (
- this.windowHeight - this.distanceTop - this.curHeight
- ) // 当前元素距离可视屏幕底部的距离
- }
- },
- data () {
- return {
- inputData: '',
- // #ifdef VUE3
- multiSelectList: this.multiple ? this.modelValue : [],
- // #endif
- // #ifdef VUE2
- multiSelectList: this.multiple ? this.value : [],
- // #endif
- isShow: false,
- optionsShow: false,
- windowHeight: null,
- curHeight: null,
- distanceTop: null,
- showPosition: 'bottom',
- filterList: [],
- refreshUrl: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDQ4IDQ4IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxyZWN0IHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgZmlsbD0id2hpdGUiIGZpbGwtb3BhY2l0eT0iMC4wMSIvPjxwYXRoIGQ9Ik0yNCA0NEMzNS4wNDU3IDQ0IDQ0IDM1LjA0NTcgNDQgMjRDNDQgMTIuOTU0MyAzNS4wNDU3IDQgMjQgNEMxMi45NTQzIDQgNCAxMi45NTQzIDQgMjRDNCAzNS4wNDU3IDEyLjk1NDMgNDQgMjQgNDRaIiBmaWxsPSJub25lIiBzdHJva2U9IiM3YzZlNmUiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjxwYXRoIGQ9Ik0yOS42NTY5IDE4LjM0MzFMMTguMzQzMiAyOS42NTY4IiBzdHJva2U9IiM3YzZlNmUiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PHBhdGggZD0iTTE4LjM0MzIgMTguMzQzMUwyOS42NTY5IDI5LjY1NjgiIHN0cm9rZT0iIzdjNmU2ZSIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48L3N2Zz4='
- }
- },
- mounted () {
- this.$nextTick(() => {
- const res = uni.getSystemInfoSync()
- this.windowHeight = res.windowHeight // 当前设备屏幕高度
- uni
- .createSelectorQuery()
- .in(this)
- .select('#wSelect')
- .boundingClientRect(data => {
- this.distanceTop = data.top // 当前元素距离顶部的距离
- this.curHeight = data.height
- })
- .exec()
- })
- },
- methods: {
- showPositon () {
- this.showPosition = 'bottom'
- if (this.bottomDistance < this.windowHeight / 3) {
- this.showPosition = 'top'
- }
- },
- changeShow () {
- this.isShow = !this.isShow
- if (this.isShow === false) {
- this.filterList = this.list
- setTimeout(() => {
- this.optionsShow = false
- }, 200)
- } else {
- this.showPositon()
- this.optionsShow = this.isShow
- }
- },
- closeContentSelect () {
- this.isShow = false
- setTimeout(() => {
- this.optionsShow = false
- }, 200)
- },
- setValue (value = '') {
- // #ifdef VUE3
- this.$emit('update:modelValue', value)
- // #endif
-
- // #ifdef VUE2
- this.$emit('input', value)
- // #endif
- },
- inputChange (e) {
- const value = e.detail.value
- if(this.multiple && this.filterable) {
- this.inputData = value
- }else {
- this.setValue(value)
- this.inputData = value
- }
-
- this.filterList = this.list.filter(item =>
- item[this.valueName].includes(value)
- )
- },
- blurChange(e) {
- const value = e.detail.value
- if(this.multiple && this.filterable && value) {
- let curValue ={
- [this.keyName]:value,
- [this.valueName]:value
- }
- this.multiSelect(curValue)
- }
- },
- refreshValue () {
- this.setValue('')
- this.inputData = ''
- this.$emit('change', '')
- this.filterList = this.list
- if (this.multiple) {
- this.multiSelectList = []
- }
- },
- handleClickItem (e) {
- if (this.multiple) {
- this.multiSelect(e)
- } else {
- this.setValue(e[this.keyName])
- this.inputData = e[this.valueName]
- this.$emit('change', e)
- this.changeShow()
- }
- },
- multiSelect (item) {
- const index = this.multiSelectList.findIndex(
- res => res[this.valueName] === item[this.valueName]
- )
- if (index > -1) {
- this.multiSelectList.splice(index, 1)
- } else {
- this.multiSelectList.push(item)
- }
- this.inputData = ''
- this.filterList = this.list
- this.setValue(this.multiSelectList)
- this.$emit('change', item)
- }
- }
- }
- </script>
- <style lang="scss" scoped>
- .w-select {
- --select-wrap-width: 100%;
- --select-wrap-height: 30px;
- --select-border-radius: 4px;
- --select-border: 1px solid #dcdfe6;
- --select-active-border: 1px solid #409eff;
- --select-options-max-height: 70vh;
- --select-option-item-font-size: 14px;
- --select-input-font-size: 14px;
- --no-data-default-color: #999999;
- --select-options-box-shadow: 0px 0px 12px rgb(0 0 0 / 12%);
- --select-bg-color: #ffffff;
- .select-wrap {
- position: relative;
- display: flex;
- justify-content: space-between;
- align-items: center;
- width: var(--select-wrap-width);
- height: var(--select-wrap-height);
- border: var(--select-border);
- border-radius: var(--select-border-radius);
- background-color: var(--select-bg-color);
- transition: all 0.2s;
- input {
- padding: 0 10px;
- width: 100%;
- min-width: 0;
- height: 100%;
- font-size: var(--select-input-font-size);
- flex: 1;
- }
- .select-content {
- display: flex;
- align-items: center;
- font-size: var(--select-option-item-font-size);
- .select-content-item {
- margin-left: 5px;
- padding: 2px 6px;
- border-radius: var(--select-border-radius);
- color: #aa93b1;
- background-color: #f4f4f5;
- }
- .select-content-item-default {
- margin-left: 5px;
- color: var(--no-data-default-color);
- }
- }
- .close-icon {
- position: absolute;
- top: 50%;
- right: 7px;
- z-index: 1000;
- width: 15px;
- height: 15px;
- transform: translateY(-50%);
- image {
- width: 100%;
- height: 100%;
- }
- }
- .position-bottom {
- top: calc(var(--select-wrap-height) + 10px);
- }
- .position-top {
- bottom: calc(var(--select-wrap-height) + 10px);
- }
- .select-options {
- position: absolute;
- right: 0;
- left: 0;
- z-index: 9999;
- overflow: scroll;
- padding: 10px 0 10px 0px;
- max-height: var(--select-options-max-height);
- border-radius: var(--select-border-radius);
- background-color: var(--select-bg-color);
- box-shadow: var(--select-options-box-shadow);
- .select-option-item {
- margin-bottom: 5px;
- padding: 10px;
- font-size: var(--select-option-item-font-size);
- transition: background-color 0.2s;
- }
- .item-active {
- font-weight: 700;
- color: #409eff;
- background-color: #f5f7fa;
- }
- .options-no-data {
- font-size: var(--select-option-item-font-size);
- text-align: center;
- color: var(--no-data-default-color);
- }
- }
- .w-select-arrow {
- display: inline-block;
- margin: 3px 10px 0;
- width: 8px;
- height: 8px;
- border-top: 1px solid transparent;
- border-right: 1px solid transparent;
- border-bottom: 1px solid #999999;
- border-left: 1px solid #999999;
- transition: all 0.3s;
- transform: translateY(-50%) rotate(-45deg);
- }
- .w-select-arrow-up {
- transform: rotate(-225deg);
- }
- }
- .select-wrap-active {
- border: var(--select-active-border);
- }
- .animation-bottom-in {
- animation-name: bottom-in;
- animation-duration: 0.4s;
- animation-timing-function: ease-out;
- animation-fill-mode: both;
- }
- .animation-bottom-out {
- animation-name: bottom-out;
- animation-duration: 0.2s;
- animation-timing-function: ease-out;
- animation-fill-mode: both;
- }
- .animation-top-in {
- animation-name: top-in;
- animation-duration: 0.4s;
- animation-timing-function: ease-out;
- animation-fill-mode: both;
- }
- .animation-top-out {
- animation-name: top-out;
- animation-duration: 0.2s;
- animation-timing-function: ease-out;
- animation-fill-mode: both;
- }
-
- @keyframes bottom-in {
- 0% {
- opacity: 0;
- transform: translateY(-15%);
- }
- 100% {
- opacity: 1;
- transform: translateY(0);
- }
- }
-
- @keyframes bottom-out {
- 0% {
- opacity: 1;
- transform: translateY(0);
- }
- 100% {
- opacity: 0;
- transform: translateY(-20%);
- }
- }
-
- @keyframes top-in {
- 0% {
- opacity: 0;
- transform: translateY(15%);
- }
- 100% {
- opacity: 1;
- transform: translateY(0);
- }
- }
-
- @keyframes top-out {
- 0% {
- opacity: 1;
- transform: translateY(0);
- }
- 100% {
- opacity: 0;
- transform: translateY(20%);
- }
- }
- .contentMask {
- position: fixed;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- z-index: 998;
- width: 100%;
- height: 100%;
- }
- }
- </style>
-
|