166 lines
4.2 KiB
Vue
166 lines
4.2 KiB
Vue
<script setup>
|
|
import { ref, onMounted } from 'vue'
|
|
import axios from 'axios'
|
|
|
|
const props = defineProps({
|
|
modelValue: {
|
|
type: [String, Number, Array, Object],
|
|
default: null
|
|
},
|
|
options: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
optionValue: {
|
|
type: String,
|
|
default: 'value'
|
|
},
|
|
optionLabel: {
|
|
type: String,
|
|
default: 'label'
|
|
},
|
|
placeholder: {
|
|
type: String,
|
|
default: 'انتخاب کنید'
|
|
},
|
|
multiple: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
filter: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
loading: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
searchUrl: {
|
|
type: String,
|
|
default: null
|
|
},
|
|
searchParam: {
|
|
type: String,
|
|
default: 'search'
|
|
},
|
|
class: {
|
|
type: String,
|
|
default: '!text-xs !h-full'
|
|
},
|
|
pt: {
|
|
type: Object,
|
|
default: () => ({
|
|
option: '!text-sm',
|
|
placeholder: '!text-sm',
|
|
label: '!pt-3'
|
|
})
|
|
}
|
|
})
|
|
|
|
const emit = defineEmits(['update:modelValue', 'filter', 'change', 'update:options'])
|
|
|
|
const isLoading = ref(false)
|
|
const searchTimeout = ref(null)
|
|
|
|
const handleFilter = (event) => {
|
|
if (searchTimeout.value) {
|
|
clearTimeout(searchTimeout.value)
|
|
}
|
|
|
|
const searchValue = event.value ? event.value.trim() : ''
|
|
|
|
// If search is empty, make request immediately (no debounce)
|
|
if (!searchValue) {
|
|
isLoading.value = true
|
|
|
|
if (props.searchUrl) {
|
|
axios.get(props.searchUrl, {
|
|
params: {}
|
|
}).then(response => {
|
|
if (response.data.success) {
|
|
emit('update:options', response.data.data)
|
|
}
|
|
isLoading.value = false
|
|
}).catch(error => {
|
|
console.error('Search error:', error)
|
|
isLoading.value = false
|
|
})
|
|
} else {
|
|
emit('filter', event)
|
|
isLoading.value = false
|
|
}
|
|
return
|
|
}
|
|
|
|
// If search has value, use debounce
|
|
isLoading.value = true
|
|
|
|
searchTimeout.value = setTimeout(() => {
|
|
if (props.searchUrl) {
|
|
const params = {}
|
|
params[props.searchParam] = searchValue
|
|
|
|
axios.get(props.searchUrl, {
|
|
params: params
|
|
}).then(response => {
|
|
if (response.data.success) {
|
|
const newOptions = response.data.data
|
|
const currentValue = props.modelValue
|
|
const currentOption = props.options.find(opt => {
|
|
const value = typeof opt === 'object' ? opt[props.optionValue] : opt
|
|
return value == currentValue
|
|
})
|
|
|
|
let finalOptions = newOptions
|
|
if (currentOption && !newOptions.find(opt => {
|
|
const value = typeof opt === 'object' ? opt[props.optionValue] : opt
|
|
return value == currentValue
|
|
})) {
|
|
finalOptions = [currentOption, ...newOptions]
|
|
}
|
|
|
|
emit('update:options', finalOptions)
|
|
}
|
|
isLoading.value = false
|
|
}).catch(error => {
|
|
console.error('Search error:', error)
|
|
isLoading.value = false
|
|
})
|
|
} else {
|
|
emit('filter', event)
|
|
isLoading.value = false
|
|
}
|
|
}, 1000)
|
|
}
|
|
|
|
const handleChange = (value) => {
|
|
emit('update:modelValue', value)
|
|
emit('change', value)
|
|
}
|
|
|
|
onMounted(() => {
|
|
return () => {
|
|
if (searchTimeout.value) {
|
|
clearTimeout(searchTimeout.value)
|
|
}
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<Select
|
|
:class="class"
|
|
:placeholder="placeholder"
|
|
:modelValue="modelValue"
|
|
:options="options"
|
|
:optionValue="optionValue"
|
|
:optionLabel="optionLabel"
|
|
:multiple="multiple"
|
|
:filter="filter"
|
|
:loading="loading || isLoading"
|
|
:pt="pt"
|
|
@update:modelValue="handleChange"
|
|
@filter="handleFilter"
|
|
/>
|
|
</template>
|