最近在写一个vue2中后台项目需要用到 省市区 区域选择组件,组件需支持如下功能:
- 可支持多选
- 省、市、区/县 均可支持选择
- 当前省选中时默认下面的市、县/区默认选中,当前市选中时同理
- 当选中省时只需返回当前省的那一项数据,下面市县/区不需要返回,选中市时同理
但是找了下没有很符合这些需求的vue2组件,于是自己封装了这个组件
除上述功能之外该组件还支持双向绑定和两种数据格式
效果图
禁用时效果图
完整代码如下
<template>
<div v-clickoutside="closeTree">
<div @click="inputFocus">
<div
:style="'width:' + width + 'px'"
:class="'box ' + (tabData.length ? 'arrow':'') + (ishowTree ? ' box-blue' : '') + (disabled ? ' disabled' : '')"
>
<div v-for="item in tabData" :key="item[keys]" class="box-item">
<div class="box-item-name">{{ item.name }}</div>
<i v-if="!disabled" class="el-icon-close" @click.stop="handleDelArea(item.id)" />
</div>
<i v-if="tabData.length && !disabled" class="el-icon-error" @click.stop="handleDelAllArea()" />
<div v-if="!tabData.length" class="box-placeholder">{{ placeholder }}</div>
<div class="arrow-box">
<i :class="(ishowTree ? 'arrow-up' : 'arrow-down') + ' el-icon-arrow-down'" />
</div>
</div>
</div>
<div class="treeModule">
<el-tree
ref="tree"
key="areaCode"
:class="ishowTree ? 'ORGTree' : 'ORGTree-close'"
:style="'width:' + width + 'px'"
:data="data"
node-key="areaCode"
highlight-current
:props="defaultProps"
:default-checked-keys="checkKeys"
show-checkbox
@check="handleCheckChange"
/>
</div>
</div>
</template>
<script>
import Clickoutside from 'element-ui/src/utils/clickoutside'
export default {
directives: { Clickoutside },
props: {
value: {
type: Array,
default: () => {
return []
}
}, // 值
valueType: {
type: String,
default: 'complex'
}, // 值类型 simple: ['11','22'],complex: [{areaCode: 11, areaName:'北京市', parentAreaCode:'', ...}]
width: {
type: Number,
default: 600
}, // 宽度
keys: {
type: String,
default: 'areaCode'
}, // key值对应的属性名
label: {
type: String,
default: 'areaName'
}, // label值对应的属性名
parentKeys: {
type: String,
default: 'parentAreaCode'
}, // 每一项对应的父级的key
placeholder: {
type: String,
default: '请选择'
}, // 缺省占位文本
proIdLen: {
type: Number,
default: 2
}, // 省级 id 长度
cityIdLen: {
type: Number,
default: 4
}, // 市级 id 长度
disabled: {
type: Boolean,
default: false
}, // 是否禁用
data: {
type: Array,
default: () => {
return []
}
} // 省市区/县数据
},
data() {
return {
defaultProps: {
children: 'children',
label: 'areaName'
},
checkKeys: [], // 选中的key
areaItems: [], // 选中项的完整数据
tabData: [], // 展示的选中项
ishowTree: false
}
},
watch: {
// 深度监听 value 值的变化
value: {
handler() {
if (this.valueType === 'complex') {
const keys = []
for (const i in this.value) {
keys.push(this.value[i].areaCode)
}
if (JSON.stringify(keys) !== JSON.stringify(this.checkKeys)) {
this.init(keys)
}
} else {
if (JSON.stringify(this.value) !== JSON.stringify(this.checkKeys)) {
this.init(this.value)
}
}
},
deep: true
}
},
methods: {
// 初始化
init(keys) {
this.$refs.tree.setCheckedKeys(keys)
this.handleCheckChange()
},
// 选择/取消选择
async handleCheckChange() {
const checkKeys = this.$refs.tree.getCheckedNodes()
this.checkKeys = await this.getCurrentId(checkKeys)
const currentName = await this.getCurrentName()
const tabs = []
for (const i in this.checkKeys) {
const tabItem = {
id: this.checkKeys[i],
name: currentName[i]
}
tabs.push(tabItem)
}
this.tabData = tabs
this.echoData()
},
inputFocus() {
if (this.disabled) {
this.ishowTree = false
return
}
if (this.ishowTree) {
this.ishowTree = false
} else {
this.ishowTree = true
}
},
// 关闭树形控件
closeTree() {
this.ishowTree = false
},
// 提取区域id
getCurrentId(ids) {
const proIds = [] // 省
let citIds = [] // 市
let areIds = [] // 区
for (const i in ids) {
if (ids[i][this.keys].length === this.proIdLen) {
proIds.push(ids[i])
} else if (ids[i][this.keys].length === this.cityIdLen) {
citIds.push(ids[i])
} else {
areIds.push(ids[i])
}
}
// 过滤 有省级的过滤掉下面的市县/区,无省级有市级过滤掉下面的县、区
for (var i = 0; i < citIds.length; i++) {
const newIds = areIds.filter(item => item[this.parentKeys] !== citIds[i][this.keys])
areIds = newIds
}
for (var j = 0; j < proIds.length; j++) {
const newIds = citIds.filter(item => item[this.parentKeys] !== proIds[j][this.keys])
citIds = newIds
}
const allIds = []
for (const i in proIds) {
allIds.push(proIds[i].areaCode)
}
for (const i in citIds) {
allIds.push(citIds[i].areaCode)
}
for (const i in areIds) {
allIds.push(areIds[i].areaCode)
}
return allIds
},
// 根据id获取名称
getCurrentName() {
this.areaItems = []
const currentName = []
for (const i in this.checkKeys) {
// 省
if (this.checkKeys[i].length === this.proIdLen) {
for (const j in this.data) {
if (this.data[j][this.keys] === this.checkKeys[i]) {
currentName.push(this.data[j][this.label])
this.areaItems.push(this.data[j])
break
}
}
} else if (this.checkKeys[i].length === this.cityIdLen) { // 市
for (const j in this.data) {
let cityName = ''
for (const k in this.data[j].children) {
if (this.data[j].children[k][this.keys] === this.checkKeys[i]) {
cityName = this.data[j][this.label] + this.data[j].children[k][this.label]
this.areaItems.push(this.data[j].children[k])
currentName.push(cityName)
}
}
if (cityName) break
}
} else { // 县
for (const j in this.data) {
let cityName = ''
for (const k in this.data[j].children) {
for (const l in this.data[j].children[k].children) {
if (this.data[j].children[k].children[l][this.keys] === this.checkKeys[i]) {
cityName = this.data[j][this.label] +
this.data[j].children[k][this.label] +
this.data[j].children[k].children[l][this.label]
this.areaItems.push(this.data[j].children[k].children[l])
currentName.push(cityName)
}
if (cityName) break
}
if (cityName) break
}
if (cityName) break
}
}
}
return currentName
},
// 删除某个选中项
handleDelArea(id) {
for (const i in this.tabData) {
if (this.tabData[i].id === id) {
this.tabData.splice(i, 1)
this.checkKeys.splice(i, 1)
}
}
for (const i in this.areaItems) {
if (this.areaItems[i][this.keys] === id) {
this.areaItems.splice(i, 1)
}
}
this.$refs.tree.setCheckedKeys(this.checkKeys)
this.echoData()
},
// 删除全部选中项
handleDelAllArea() {
this.tabData = []
this.checkKeys = []
this.areaItems = []
this.$refs.tree.setCheckedKeys(this.checkKeys)
this.echoData()
},
// 返回选中的area
echoData() {
if (this.valueType === 'complex') {
const list = JSON.parse(JSON.stringify(this.areaItems))
for (const i in list) {
list[i].children = []
delete list[i].children
}
this.$emit('input', list)
} else {
this.$emit('input', this.checkKeys)
}
}
}
}
</script>
<style lang="scss" scoped>
.treeModule{
position: absolute;
z-index: 999999;
}
.ORGTree{
width: 298px;
height: 300px;
overflow: auto;
border:1px solid #e4e0e0;
border-top: 0px;
box-shadow: rgb(214, 212, 212) 0px 0px 5px;
transition: all 0.1s;
}
.ORGTree-close{
height: 0px;
overflow: hidden;
transition: all 0.1s;
}
>>> .el-input__inner{
background-color: #fff !important;
cursor: pointer !important;
}
.box{
min-height: 41px;
width: 600px;
border: 1px solid #dfe4ed;
padding: 0 20px;
display: flex;
flex-wrap: wrap;
padding-top: 5px;
padding-left: 10px;
position: relative;
border-radius: 4px;
transition: border 0.2s;
cursor: pointer;
.box-item{
display: flex;
min-height: 28px;
border-radius: 14px;
background: #f5f5f5;
line-height: 28px;
align-items: center;
padding: 0 10px;
margin-right: 15px;
margin-bottom: 5px;
cursor: auto;
.box-item-name{
cursor: text;
}
.el-icon-close{
margin-left: 6px;
cursor: pointer;
transition: all 0.2s;
}
.el-icon-close:hover{
color: #1890ff;
transition: all 0.2s;
}
}
.el-icon-error{
position: absolute;
top: 50%;
transform: translateY(-50%);
right: 10px;
font-size: 16px;
opacity: 0;
cursor: pointer;
z-index: 10;
}
.box-placeholder{
padding-left: 5px;
line-height: 32px;
color: rgb(194, 191, 191);
z-index: 0;
}
.arrow-box{
top: 50%;
transform: translateY(-50%);
right: 10px;
position: absolute;
.el-icon-arrow-down{
font-size: 16px;
color: rgb(192, 190, 190);
opacity: 1;
}
.arrow-up{
transform: rotate(-180deg);
transition: all 0.3s;
}
.arrow-down{
transform: rotate(0deg);
transition: all 0.3s;
}
}
}
.disabled{
background: #F5F7FA;
cursor: not-allowed;
.box-item{
border: 1px solid #d1d1d3;
.box-item-name{
color: #777;
}
}
}
.box:hover{
border: 1px solid #c6c6c7;
transition: border 0.2s;
.el-icon-error{
opacity: 1;
transition: all 0.2s;
}
.el-icon-error:hover{
color: #1296db;
transition: all 0.2s;
}
}
.arrow:hover{
.el-icon-arrow-down{
opacity: 0;
transition: all 0.2s;
}
}
.disabled:hover{
border: 1px solid #dfe4ed;
.el-icon-arrow-down{
opacity: 1;
}
}
.box-blue{
border: 1px solid #1296db;
transition: border 0.2s;
border-radius: 4px 4px 0 0 ;
}
.box-blue:hover{
border: 1px solid #1296db;
}
/*滚动条样式*/
.ORGTree::-webkit-scrollbar {/*滚动条整体样式*/
width: 6px; /*高宽分别对应横竖滚动条的尺寸*/
height: 4px;
}
.ORGTree::-webkit-scrollbar-thumb {/*滚动条里面小方块*/
border-radius: 5px;
/* -webkit-box-shadow: inset 0 0 5px rgba(49, 49, 49, 0.2); */
background: rgba(0,0,0,0.2);
}
.ORGTree::-webkit-scrollbar-track {/*滚动条里面轨道*/
/* -webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2); */
border-radius: 0;
background: rgba(85, 85, 85, 0.1);
}
</style>