| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561 | <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">		<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"		>		<!-- #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: '200px'	  },	  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: '请选择'	  },	  // 显示的内容	  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: 200px;	--select-wrap-height: 30px;	--select-border-radius: 4px;	--select-border: 1px solid #dcdfe6;	--select-active-border: 1px solid #409eff;	--select-options-max-height: 150px;	--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 2px;		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: 999;		overflow: scroll;		padding: 10px;		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: 5px;		  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>  
 |