magiccube-vue3
Version:
vue3-js版组件库
267 lines (230 loc) • 8.87 kB
JavaScript
import Dropdown from '../../utils/dropdown'
import { cityCodeToData } from '../../utils/common'
import { ref, reactive, computed, getCurrentInstance, nextTick } from 'vue'
import getFormValidMethod from '../../utils/form-valid'
import Cascader from './cascader'
import CITY_DATA from '../../data/city.json'
import CLOSE_ICON from '../../img/icon_remove.svg'
import CLEAR_ICON from '../../img/icon_clear.svg'
const CityPicker = {
name: 'McCityPicker',
props: {
modelValue: {
type: [Array, String, Number],
default: () => []
},
cityData: {
type: [Array, Object],
default: () => CITY_DATA
},
// 选择器类型:cascade、singleStage
type: {
type: String,
default: 'cascader'
},
placeholder: {
type: String,
default: '请选择城市'
},
// 选择数量
max: {
type: [String, Number],
default: 1
},
disabled: Boolean,
downMenuHeight: {
type: Number,
default: 430
},
enableSelectProvince: {
type: Boolean,
default: false
},
enableSelectCity: {
type: Boolean,
default: true
},
unchecked: Boolean,
},
emits: ['change', 'update:modelValue'],
setup(props, { emit }) {
const dropdownEl = ref(null)
const innerEl = ref(null)
const pickerEl = ref(null)
const displayInputSearch = ref(false)
const keywordEl = ref(false)
const state = reactive({
keyword: '',
list: [],
slide: false,
})
const instance = getCurrentInstance()
const { fieldName, validator, errorMessage } = getFormValidMethod(instance)
const fieldError = computed(() => fieldName && errorMessage?.value && !props.unchecked ? errorMessage.value[fieldName] : '')
const model = computed({
get() {
if (Array.isArray(props.modelValue)) {
return props.modelValue.every(n => typeof n === 'string') ? cityCodeToData(props.modelValue, props.cityData, 'propertyCode') : props.modelValue
} else if (props.modelValue) {
return cityCodeToData(props.modelValue, props.cityData, 'propertyCode')
} else {
return []
}
},
set(value) {
emit('update:modelValue', value)
validator && validator('change', value)
}
})
const handleVisible = () => {
if (props.disabled) return false
dropdownEl.value.visible()
}
const handleChange = (array) => {
model.value = array
emit('change', array)
if(props.max === array.length) setSlideUp()
}
const handleRemove = (e) => {
e && e.stopPropagation()
const list = model.value
model.value = list.slice(1, list.length)
emit('change', { key: props.keyName, data: model.value })
}
const setSlideDown = (event) => {
const options = {
event,
pickerHeight: pickerEl.value.offsetHeight,
pickerWidth: pickerEl.value.offsetWidth,
dropdownWidth: 668,
dropdownHeight: innerEl.value.offsetHeight || props.downMenuHeight
}
innerEl.value.init()
dropdownEl.value.visible(options)
displayInputSearch.value = true
state.slide = true
// 让模糊搜索框自动聚焦
nextTick(() => {
keywordEl.value && keywordEl.value.focus()
})
}
const setSlideUp = () => {
state.keyword = ''
dropdownEl.value.invisible()
displayInputSearch.value = false
state.slide = false
}
const handleShowDropdown = (e) => {
if (props.disabled || e.target.nodeName.toLowerCase() === 'input') return false
if (state.slide) {
/**
* 隐藏下拉
*/
setSlideUp(e)
return false
}
/**
* 打开下拉
*/
setSlideDown(e)
}
const handleKeywordChange = () => {
// innerEl.value.init()
}
const handleClearKeyword = (e) => {
e && e.stopPropagation()
state.keyword = ''
}
const emptyNode = () => (
<span class="mc-city-picker__result--placeholder">{props.placeholder}</span>
)
const displayName = computed(() => model.value?.length ? model.value[0].defaultName : '')
const multiReviewNode = () => {
const len = model.value.length
return (
<>
<div class="mc-city-picker__result--wrap">
<span class="mc-city-picker__result--name">{displayName.value}</span>
<span class="mc-city-picker__result--close">
<img onClick={handleRemove} src={CLOSE_ICON} />
</span>
</div>
{
len > 1 ? (
<div class="mc-city-picker__result--amount">+{len}</div>
) : ''
}
</>
)
}
const keywordPlaceholder = computed(() => {
if(props.max > 1){
return '请输入关键词'
} else {
return displayName.value || '请输入关键词'
}
})
const singleReviewNode = () => <span class="mc-city-picker__result--single" v-ellipsis={displayName.value}>{displayName.value}</span>
const getReview = () => {
if(props.max === 1){
if(Array.isArray(model.value)){
return displayInputSearch.value ? inputKeywordNode() : (model.value.length === 1? singleReviewNode() : emptyNode())
} else {
return displayInputSearch.value ? inputKeywordNode() : (model.value !== ''? singleReviewNode(): emptyNode())
}
} else {
return displayInputSearch.value ? inputKeywordNode() : (model.value?.length? multiReviewNode(): emptyNode())
}
}
// 关键词搜索输入框dom
const inputKeywordNode = () => (
<div class="mc-city-picker__result--keyword">
<input ref={keywordEl} class="mc-city-picker__result--keyword__input" autocomplete="off" placeholder={keywordPlaceholder.value} v-model={state.keyword} onInput={handleKeywordChange} />
{
state.keyword? (
<i class="mc-city-picker__result--keyword__clear"
onClick={handleClearKeyword}>
<img src={CLEAR_ICON} />
</i>) : ''
}
</div>
)
return () => (
<div ref={pickerEl}
class={{
'mc-city-picker': true,
disabled: props.disabled
}}>
<div class={{
'mc-city-picker__result': true,
error: Boolean(fieldError.value)
}}
onClick={handleShowDropdown}>
{
getReview()
}
<span class={[
'mc-city-picker__result--arrow',
{ 'mc-rotate': displayInputSearch.value }
]}>
<img src={require('../../img/icon_picker_arrow.svg')} />
</span>
</div>
<Dropdown
ref={dropdownEl}
picker={pickerEl.value}
autoClose={false}
onClose={setSlideUp}>
{
props.type === 'cascader' ? <Cascader ref={innerEl} selected={model.value} options={props.cityData} max={props.max} dropdownHeight={props.downMenuHeight} keyword={state.keyword} enable-select-province={props.enableSelectProvince} enable-select-city={props.enableSelectCity} onChange={handleChange} /> : ''
}
</Dropdown>
</div>
)
}
}
CityPicker.install = Vue => {
Vue.component(CityPicker.name, CityPicker)
}
const McCityPicker = CityPicker
export { McCityPicker, McCityPicker as default }