| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284 |
- <template>
- <view class="fuzzy-select-container">
- <!-- 搜索输入框和删除按钮 -->
- <view class="input-container">
- <uni-easyinput ref="inputRef" v-model="searchKeyword" :placeholder="placeholder" :clearable="true"
- :disabled="disabled" @input="handleInput" @focus="showDropdown = true" @blur="handleBlur" />
-
- <!-- 删除按钮 -->
- <view v-if="value && !disabled" class="delete-btn" @click="handleDelete">
- <text class="delete-icon">×</text>
- </view>
- </view>
- <!-- 下拉选项列表 -->
- <view v-if="showDropdown && filteredOptions.length > 0" class="dropdown-list">
- <scroll-view scroll-y="true" class="dropdown-scroll">
- <view v-for="(option, index) in filteredOptions" :key="index" class="dropdown-item"
- @click="selectOption(option)">
- {{ option[dataText] }}
- </view>
- </scroll-view>
- </view>
- <!-- 无匹配结果提示 -->
- <view v-if="showDropdown && filteredOptions.length === 0 && searchKeyword" class="no-result">
- 无匹配结果
- </view>
- </view>
- </template>
- <script>
- export default {
- name: "FuzzySelect",
- props: {
- // 绑定值
- value: {
- type: [String, Number],
- default: ''
- },
- // 选项列表
- options: {
- type: Array,
- default: () => []
- },
- // 占位符
- placeholder: {
- type: String,
- default: '请输入搜索'
- },
- // 值字段名
- dataValue: {
- type: String,
- default: 'value'
- },
- // 显示文本字段名
- dataText: {
- type: String,
- default: 'text'
- },
- // 是否开启搜索
- filterable: {
- type: Boolean,
- default: true
- },
- // 是否禁用
- disabled: {
- type: Boolean,
- default: false
- }
- },
- data() {
- return {
- searchKeyword: '',
- showDropdown: false,
- filteredOptions: this.options,
- selectedOption: null
- }
- },
- watch: {
- options(newOptions) {
- this.filterOptions(this.searchKeyword, newOptions)
- // 当options更新后,需要重新根据当前value设置选中项
- this.updateSelectedOption(this.value)
- },
- value(newValue) {
- // 只有当value确实变化时才更新选中项
- if (newValue !== this.selectedOption?.[this.dataValue]) {
- this.updateSelectedOption(newValue)
- }
- }
- },
- mounted() {
- this.updateSelectedOption(this.value)
- },
- methods: {
- // 根据value值更新选中的选项
- updateSelectedOption(value) {
-
- if (!value) {
- this.searchKeyword = ''
- this.selectedOption = null
- return
- }
- const foundOption = this.options.find(option =>
- String(option[this.dataValue]) === String(value)
- )
- if (foundOption) {
-
- this.selectedOption = foundOption
- this.searchKeyword = foundOption[this.dataText]
- // 确保filteredOptions包含当前选中的选项
- if (!this.filteredOptions.some(opt => String(opt[this.dataValue]) === String(value))) {
- this.filteredOptions = this.options
- }
- } else {
- this.searchKeyword = ''
- this.selectedOption = null
- }
- },
- // 处理输入
- handleInput(value) {
- if (this.disabled) return;
- this.searchKeyword = value
- this.filterOptions(value)
- this.showDropdown = true
- },
- // 过滤选项
- filterOptions(keyword = '', options = this.options) {
- if (!this.filterable || !keyword.trim()) {
- this.filteredOptions = options
- return
- }
- const lowerKeyword = keyword.toLowerCase()
- this.filteredOptions = options.filter(option => {
- const text = String(option[this.dataText] || '').toLowerCase()
- const value = String(option[this.dataValue] || '').toLowerCase()
- return text.includes(lowerKeyword) || value.includes(lowerKeyword)
- })
- },
- // 选择选项
- selectOption(option) {
- if (this.disabled) return;
- this.selectedOption = option
- this.searchKeyword = option[this.dataText]
- this.$emit('input', option[this.dataValue])
- this.$emit('change', option[this.dataValue])
- this.showDropdown = false
- },
- // 处理失焦
- handleBlur() {
- // 延迟隐藏下拉框,确保点击选项能触发
- setTimeout(() => {
- this.showDropdown = false
- }, 200)
- },
- // 清空搜索
- clearSearch() {
- this.searchKeyword = ''
- this.filteredOptions = this.options
- this.selectedOption = null
- this.$emit('input', '')
- this.$emit('change', '')
- },
- // 聚焦输入框
- focus() {
- if (this.$refs.inputRef) {
- this.$refs.inputRef.focus()
- }
- },
- // 失焦输入框
- blur() {
- if (this.$refs.inputRef) {
- this.$refs.inputRef.blur()
- }
- },
- // 处理删除
- handleDelete() {
- if (this.disabled) return;
- this.clearSearch()
- this.$emit('delete')
- },
- }
- }
- </script>
- <style scoped>
- .fuzzy-select-container {
- position: relative;
- width: 100%;
- }
- .input-container {
- position: relative;
- display: flex;
- align-items: center;
- }
- .delete-btn {
- position: absolute;
- right: 8px;
- top: 50%;
- transform: translateY(-50%);
- width: 20px;
- height: 20px;
- border-radius: 50%;
- background-color: #f0f0f0;
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- z-index: 10;
- }
- .delete-btn:hover {
- background-color: #e0e0e0;
- }
- .delete-icon {
- font-size: 16px;
- color: #999;
- font-weight: bold;
- line-height: 1;
- }
- .dropdown-list {
- position: absolute;
- top: 100%;
- left: 0;
- right: 0;
- max-height: 200px;
- background-color: #fff;
- border: 1px solid #e5e5e5;
- border-radius: 4px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- z-index: 999;
- margin-top: 4px;
- }
- .dropdown-scroll {
- max-height: 200px;
- }
- .dropdown-item {
- padding: 12px 16px;
- font-size: 14px;
- color: #333;
- border-bottom: 1px solid #f0f0f0;
- cursor: pointer;
- }
- .dropdown-item:hover {
- background-color: #f5f5f5;
- }
- .dropdown-item:last-child {
- border-bottom: none;
- }
- .no-result {
- position: absolute;
- top: 100%;
- left: 0;
- right: 0;
- padding: 12px 16px;
- background-color: #fff;
- border: 1px solid #e5e5e5;
- border-radius: 4px;
- font-size: 14px;
- color: #999;
- text-align: center;
- z-index: 999;
- margin-top: 4px;
- }
- </style>
|