magiccube-vue3
Version:
vue3-js版组件库
348 lines (310 loc) • 12 kB
JavaScript
import { ref, computed } from 'vue'
import * as utils from '../../utils/common'
import CITY_DATA from '../../data/city'
const hotCityCode = ['11', '12', '31', '50', '3301', '4021', '4401', '4403', '5101']
const specialCity = ['11', '12', '31', '50']
function limitLength(string, max = 0) {
const _max = parseInt(max)
return string.length > _max ? string.substring(0, _max) + '...' : string
}
function getChildrenList(a) {
return b => b.value !== a.value && b.value.indexOf(a.value) === 0 && b.level !== a.level && ((a.level === 'province' && b.level === 'city') || (a.level === 'city' && b.level === 'district'))
}
function equal(item, value) {
if (typeof item === 'object') {
return item.value.indexOf(value) === 0 && item.value.length > value.length
} else {
return item === value
}
}
function isSpecialCity(code) {
return specialCity.includes(code.substring(0, 2))
}
function isHotCity(code) {
return hotCityCode.includes(code.substring(0, 4))
}
const Tag = {
props: {
data: Object,
type: String,
idx: Number,
max: {
type: [Number, String],
default: 99
},
active: Boolean,
include: Boolean,
},
emits: ['change', 'click'],
setup(props, { emit }) {
const handleClick = () => {
if (props.type === 'review') return
emit('click', props.data)
}
const handleRemove = (e) => {
e.preventDefault()
e.stopPropagation()
emit('change', props.data)
}
return () => (
<div class={{
'mc-city-tag': true,
review: props.type === 'review',
active: props.active,
include: props.include
}}>
<div class="mc-city-tag__wrap"
onClick={handleClick}>
<span class="mc-city-tag__text">
{
props.data.name && props.data.name.length > parseInt(props.max) ? (
// <McTip position="top"
// content={props.data.name}>{limitLength(props.data.name, props.max)}</McTip>
<>{limitLength(props.data.name, props.max)}</>
) : (
<>{props.data.name}</>
)
}
</span>
{
props.type === 'review' ? (
<span class="mc-city-tag__close"
onClick={handleRemove}></span>
) : ''
}
</div>
</div>
)
}
}
const City = {
name: 'McCity',
props: {
modelValue: Array,
show: Boolean,
title: {
type: String,
default: '选择城市信息'
},
tips: {
type: String,
default: '可选择多项'
},
offsetLeft: {
type: [Number, String],
default: 0
},
offsetTop: {
type: [Number, String],
default: 0
},
max: {
type: [String, Number],
default: 1
},
enabledDistrict: {
type: Boolean,
default: true
},
},
emits: ['update:modelValue', 'change', 'close'],
setup(props, { emit, expose }) {
const selected = ref([])
const level = ref([{ name: '全部', value: '' }])
const keyword = ref('')
const model = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
const handleConfirm = () => {
model.value = utils.deepCopy(selected.value)
emit('change', selected.value)
}
const reset = () => {
selected.value = []
level.value = level.value.slice(0, 1)
}
const set = () => {
selected.value = utils.deepCopy(model.value)
}
const setInvisible = () => {
emit('close')
reset()
}
const handleRemove = (tag) => {
selected.value.splice(selected.value.findIndex(item => item.value === tag.value), 1)
}
const isSelected = (value) => {
return selected.value.some(item => item.value === value)
}
const isInclude = (value) => {
return selected.value.some(item => equal(item, value))
}
const handleSelect = (city) => {
const lastLevel = level.value[level.value.length - 1]
if (isSelected(city.value)) {
if ((city.level === 'province' || city.level === 'city') && lastLevel.value === city.value) return
if (isSpecialCity(city.value) && level.value.length === 2) return
if (city.level === 'district') return
}
if (city.level === 'province' && city.value !== lastLevel.value) {
level.value.push(city)
} else if (city.level === 'city' && isSpecialCity(city.value)) {
// 直辖市 区级别选择
setResult(city)
} else if (city.level === 'city' && city.value !== lastLevel.value && props.enabledDistrict) {
level.value.push(city)
} else {
setResult(city)
}
}
const handleClickLevel = (item) => {
const idx = level.value.findIndex(n => n.value === item.value)
level.value = level.value.slice(0, idx + 1)
}
const setResult = (item) => {
if (parseInt(props.max) === 1) {
selected.value = [item]
handleConfirm()
} else if (selected.value.length >= props.max) {
console.error(`最多可选择${props.max}个`)
} else {
selected.value.push(item)
}
}
const unlimitedItem = () => {
return level.value[level.value.length - 1]
}
const handleChange = (item) => {
const cityItem = item?.data?.value ? CITY_DATA.find(c => c.value === item.data.value) : null
if (cityItem) setResult(cityItem)
}
const handleSelectSearchCity = (city) => {
handleSelect(city)
keyword.value = ''
}
const hotCityList = computed(() => {
return CITY_DATA.filter(c => c.level === 'province' && hotCityCode.includes(c.value))
})
const otherCityList = computed(() => {
return CITY_DATA.filter(c => c.level === 'province' && !hotCityCode.includes(c.value))
})
const childrenCityList = computed(() => {
const item = level.value[level.value.length - 1]
return CITY_DATA.filter(getChildrenList(item))
})
const cityOptions = computed(() => {
return keyword.value? CITY_DATA.filter(n => n.name.indexOf(keyword.value) > -1) : []
})
const headerNode = () => (
<>
<div class="mc-city__header--left">
<span class="mc-city__header--title">{props.title}</span>
<span class="mc-city__header--tips">{props.tips}</span>
</div>
<div class="mc-city__header--right">
<McButton type="cancel" width="88" onClick={setInvisible}>取消</McButton>
<McButton width="88" onClick={handleConfirm}>确定</McButton>
</div>
</>
)
const reviewNode = () => (
<>
{
selected.value.map((tag, i) => (
<Tag key={i}
idx={i}
data={tag}
type="review"
onChange={() => handleRemove(tag)} />
))
}
</>
)
const toolbarNode = () => (
<>
{
level.value.map((item, idx) => (
<div class={{
'mc-city__body--toolbar__tag': true,
active: idx === level.value.length - 1
}} key={idx} onClick={() => handleClickLevel(item)}>
<span>{item.name}</span>
<span class="mc-city__arrow"></span>
</div>
))
}
<div class="mc-city__body--toolbar__search">
<McInput v-model={keyword.value} clear />
<ul class="mc-city__body--toolbar__dropdown">
{cityOptions.value?.length? cityOptions.value.map(n => (
<li class={{
selected: isSelected(n.value)
}} onClick={() => handleSelectSearchCity(n)}>
{n.name} - {n.fullName}
</li>
)) : ''}
</ul>
</div>
</>
)
const tagListNode = (data, idx) => (
<div class="mc-city__body--list__wrap" key={idx}>
<Tag
data={data}
max={3}
active={isSelected(data.value)}
include={isInclude(data.value)}
onClick={() => handleSelect(data)} />
</div>
)
expose({
set,
reset
})
return () => (
<div class="mc-city" style={{
'margin-left': `-${340 - Number(props.offsetLeft)}px`,
'margin-top': `${props.offsetTop}px`
}}>
<div class="mc-city__header">{headerNode()}</div>
<div class="mc-city__review">{reviewNode()}</div>
<div class="mc-city__body">
<div class="mc-city__body--toolbar">{toolbarNode()}</div>
<div class="mc-city__body--content">
{
level.value.length === 1 ? (
<>
<div class="mc-city__body--panel">
<div class="mc-city__body--title">热门城市</div>
<div class="mc-city__body--list">{hotCityList.value.map((city, idx) => tagListNode(city, idx))}</div>
</div>
<div class="mc-city__body--panel">
<div class="mc-city__body--title">其他省市</div>
<div class="mc-city__body--list">{otherCityList.value.map((city, idx) => tagListNode(city, idx))}</div>
</div>
</>
) : (
<div class="mc-city__body--panel">
<div class="mc-city__body--list">
<div>{tagListNode(unlimitedItem(), 0)}</div>
{childrenCityList.value.map((city, idx) => tagListNode(city, idx))}
</div>
</div>
)
}
</div>
</div>
</div>
)
}
}
City.install = (app) => {
app.component(City.name, City)
}
const McCity = City
export { McCity, McCity as default }