383 lines
10 KiB
Vue
383 lines
10 KiB
Vue
<template>
|
|
<section class="ai-area">
|
|
<div v-if="inputClicker&&!$scopedSlots.default" @click="chooseArea" class="input-clicker">
|
|
<el-row type="flex" justify="space-between">
|
|
<div class="prepend">
|
|
<i style="font-size: 16px" class="iconfont iconLocation"/>
|
|
切换地区
|
|
</div>
|
|
<div class="content nowrap-text fill" v-text="fullName"/>
|
|
<i class="iconfont iconChange pad-r10"/>
|
|
</el-row>
|
|
</div>
|
|
<el-button v-else-if="!customClicker&&!$scopedSlots.default" class="area-btn" type="primary" size="mini"
|
|
@click="chooseArea">
|
|
{{ btnShowArea ? selectedName : "切换地区" }}
|
|
</el-button>
|
|
<a class="custom-clicker" v-else @click="chooseArea">
|
|
<slot :areaname="selectedName" :fullname="fullName" :id="selected"/>
|
|
</a>
|
|
<ai-dialog :visible.sync="dialog" title="选择地区" width="60%" @onConfirm="confirmArea" :modal="mask"
|
|
@open="selected=(value||'')">
|
|
<ai-highlight content="您当前选择 @v" :value="selectedName" color="#333" bold/>
|
|
<div class="area_edge">
|
|
<div class="area-box" v-for="ops in showOps">
|
|
<h2 v-text="ops.header"/>
|
|
<div class="area-item" :class="{selected: selectedMap.includes(area.id)}" v-for="area in ops.list"
|
|
@click="getChildrenAreas(area)">
|
|
<ai-badge>
|
|
<span>{{ area.name }}</span>
|
|
<div slot="badge" v-if="showBadge&&area.tipName" :class="getLabelClassByLabelType(area.labelType)">
|
|
{{ area.tipName }}
|
|
</div>
|
|
</ai-badge>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</ai-dialog>
|
|
</section>
|
|
</template>
|
|
<script>
|
|
import AiHighlight from "../layout/AiHighlight";
|
|
import instance from "../../lib/js/request";
|
|
import Area from "../../lib/js/area";
|
|
|
|
export default {
|
|
name: 'AiArea',
|
|
components: {AiHighlight},
|
|
inject: {
|
|
elFormItem: {default: ""},
|
|
elForm: {default: ''},
|
|
},
|
|
model: {
|
|
prop: 'value',
|
|
event: 'change'
|
|
},
|
|
props: {
|
|
instance: {default: () => instance},
|
|
action: String,
|
|
areaLevel: {type: [Number, String], default: 5},
|
|
btnShowArea: {type: Boolean, default: false},
|
|
customClicker: {type: Boolean, default: false},
|
|
disabled: {type: Boolean, default: false},
|
|
hideLevel: {type: [Number, String], default: 0},
|
|
inputClicker: {type: Boolean, default: true},
|
|
provinceAction: String,
|
|
separator: {type: String, default: ""},
|
|
showBadge: {type: Boolean, default: true},
|
|
value: String,
|
|
valueLevel: {type: [Number, String], default: -1},
|
|
root: String,
|
|
mask: {type: Boolean, default: true}
|
|
},
|
|
data() {
|
|
return {
|
|
selected: null,
|
|
areaOps: [],
|
|
fullName: '',
|
|
dialog: false,
|
|
ProvinceCityCounty: [],
|
|
}
|
|
},
|
|
computed: {
|
|
rootArea: v => new Area(v.root),
|
|
currentArea: v => v.selected || v.value,
|
|
startLevel: v => Math.max(Number(v.hideLevel), 0, v.rootArea.level),//地区最高可选行政地区等级
|
|
endLevel: v => Number(v.areaLevel) || 0,//地区最低可选行政地区等级
|
|
selectedArea: v => new Area(v.currentArea, v.hashMap),
|
|
selectedName: v => v.selectedArea.name || "无",
|
|
selectedMap: v => v.selectedArea.areaMap,
|
|
validateState: v => ['', 'success'].includes(v.elFormItem?.validateState),
|
|
hashMap() {
|
|
//地区数据缓存器,用于快速获取数据
|
|
const hash = {}
|
|
this.areaOps.flat().map(e => hash[e.id] = e)
|
|
return hash
|
|
},
|
|
showOps() {
|
|
const levelLabels = {
|
|
0: "省/直辖市",
|
|
1: "市",
|
|
2: "县/区",
|
|
3: "乡/镇/街道",
|
|
4: "村/社区"
|
|
}
|
|
let ops = this.areaOps.map((list, i) => ({
|
|
header: levelLabels[i], i, list
|
|
})).slice(Math.max(0, this.startLevel), this.endLevel)
|
|
if (this.startLevel > 0 && ops.length > 0) {
|
|
const tmp = this.$copy(ops[0]?.list?.[0] || {})
|
|
if (this.startLevel >= ops[0].i) {
|
|
const opsMap = this.selectedMap.length > 0 ? this.selectedMap : this.rootArea.areaMap
|
|
ops[0].list = [this.hashMap[opsMap[ops[0].i]]].filter(Boolean) || []
|
|
} else {
|
|
const prev = +tmp.type - 1
|
|
const prevId = tmp.parentId
|
|
prev > -1 && ops.unshift({header: levelLabels[prev], list: [this.hashMap[prevId]]})
|
|
}
|
|
}
|
|
return ops
|
|
}
|
|
},
|
|
watch: {
|
|
value: {
|
|
immediate: true,
|
|
handler(v) {
|
|
this.dispatch('ElFormItem', 'el.form.change', [v]);
|
|
this.initAreaName()
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
dispatch(componentName, eventName, params) {
|
|
let parent = this.$parent || this.$root;
|
|
let name = parent.$options.componentName;
|
|
|
|
while (parent && (!name || name !== componentName)) {
|
|
parent = parent.$parent;
|
|
|
|
if (parent) {
|
|
name = parent.$options.componentName;
|
|
}
|
|
}
|
|
if (parent) {
|
|
parent.$emit.apply(parent, [eventName].concat(params));
|
|
}
|
|
},
|
|
chooseArea() {
|
|
if (this.disabled) return
|
|
this.selected = this.$copy(this.value)
|
|
this.initOptions().then(() => this.dialog = true)
|
|
},
|
|
confirmArea() {
|
|
if (this.valueLevel > -1) {
|
|
this.$emit("change", this.selectedMap[this.valueLevel])
|
|
} else {
|
|
this.$emit("change", this.selected)
|
|
}
|
|
this.$emit("area", this.selected, this.selectedArea);
|
|
this.dialog = false
|
|
},
|
|
getChildrenAreas(area) {
|
|
this.selected = area.id;
|
|
const level = Area.getLevelByAreaId(area.id);
|
|
if (level < 4) {
|
|
this.getAreasByParentId(area.id).then(list => {
|
|
this.areaOps.splice(level + 1, 5, list)
|
|
})
|
|
}
|
|
},
|
|
getAreasByParentId(id) {
|
|
const level = Area.getLevelByAreaId(id)
|
|
return new Promise(resolve => {
|
|
if (level < 2) {
|
|
this.getProvinceCityCounty().then(() => {
|
|
resolve(this.ProvinceCityCounty.filter(e => e.parentId == id))
|
|
})
|
|
} else {
|
|
this.instance.post(this.action || "/admin/area/queryAreaByParentId", null, {
|
|
withoutToken: true,
|
|
params: {id}
|
|
}).then(res => {
|
|
if (res?.data) {
|
|
resolve(res.data)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
},
|
|
getLabelClassByLabelType(type) {
|
|
let cls = "badge-label"
|
|
switch (type) {
|
|
case '1':
|
|
cls += ' label-town'
|
|
break;
|
|
case '3':
|
|
cls += ' label-village'
|
|
break;
|
|
default:
|
|
cls += ' label-poor'
|
|
break
|
|
}
|
|
return cls
|
|
},
|
|
getProvinceCityCounty() {
|
|
return new Promise(resolve => {
|
|
if (localStorage.getItem("ProvinceCityCounty")) {
|
|
resolve(JSON.parse(localStorage.getItem("ProvinceCityCounty")))
|
|
} else {
|
|
this.instance.post(this.provinceAction || "/admin/area/queryProvinceListContainCity", null, {
|
|
withoutToken: true
|
|
}).then(res => {
|
|
if (res && res.data) {
|
|
localStorage.setItem("ProvinceCityCounty", JSON.stringify(res.data))
|
|
resolve(res.data)
|
|
}
|
|
})
|
|
}
|
|
}).then(list => this.ProvinceCityCounty = list)
|
|
},
|
|
initOptions() {
|
|
this.areaOps = []
|
|
const opsMap = this.selectedMap.length > 0 ? this.selectedMap : this.rootArea.areaMap
|
|
let map = {};
|
|
return Promise.all([null, ...opsMap].map((id, i) => this.getAreasByParentId(id).then(list => map[i] = list))).then(() => {
|
|
this.areaOps = Object.values(map)
|
|
})
|
|
},
|
|
initAreaName() {
|
|
if (this.value) {
|
|
Area.createByAction(this.currentArea, this.instance).then(names => {
|
|
this.selectedArea.getName(names)
|
|
this.fullName = this.selectedArea.nameMap.join(this.separator)
|
|
this.$emit("update:name", this.selectedName)
|
|
this.$emit("fullname", this.fullName)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
<style lang="scss" scoped>
|
|
.ai-area {
|
|
.area-btn {
|
|
box-shadow: 0 2px 8px 0 rgba(76, 132, 255, 0.6);
|
|
}
|
|
|
|
.input-clicker {
|
|
width: 320px;
|
|
cursor: pointer;
|
|
border: 1px solid #D0D4DC;
|
|
line-height: 32px;
|
|
border-radius: 2px;
|
|
font-size: 14px;
|
|
|
|
.prepend {
|
|
background: rgba(245, 245, 245, 1);
|
|
width: auto;
|
|
border-right: 1px solid #D0D4DC;
|
|
padding: 0 8px;
|
|
white-space: nowrap;
|
|
|
|
}
|
|
|
|
.content {
|
|
text-align: left;
|
|
padding-left: 14px;
|
|
padding-right: 8px;
|
|
direction: rtl;
|
|
}
|
|
|
|
.suffix {
|
|
width: auto;
|
|
padding: 0 12px
|
|
}
|
|
|
|
&:hover {
|
|
border-color: $primaryColor;
|
|
}
|
|
}
|
|
|
|
.custom-clicker {
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
padding: 3px;
|
|
}
|
|
|
|
.area_edge {
|
|
max-height: 350px;
|
|
overflow-y: auto;
|
|
white-space: normal;
|
|
}
|
|
|
|
.area-box {
|
|
box-shadow: 0px -1px 0px 0px rgba(238, 238, 238, 1);
|
|
padding: 16px 0 8px 0;
|
|
|
|
& > section {
|
|
font-size: 0;
|
|
}
|
|
|
|
& > h2 {
|
|
color: rgba(51, 51, 51, 1);
|
|
line-height: 22px;
|
|
margin-bottom: 8px;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.area-item {
|
|
display: inline-block;
|
|
border-radius: 2px;
|
|
border: 1px solid #D0D4DC;
|
|
margin: 8px 8px 8px 0;
|
|
padding: 3px 10px;
|
|
cursor: pointer;
|
|
text-align: center;
|
|
line-height: normal;
|
|
font-size: 14px;
|
|
|
|
&:hover {
|
|
color: rgba($primaryColor, .8);
|
|
border-color: rgba($primaryColor, .8);
|
|
}
|
|
}
|
|
|
|
a {
|
|
text-decoration: none;
|
|
border-radius: 4px;
|
|
border: 1px solid #ddd;
|
|
line-height: normal;
|
|
margin: 5px;
|
|
padding: 3px;
|
|
cursor: pointer;
|
|
|
|
span {
|
|
margin: 0 10px;
|
|
}
|
|
|
|
&:hover {
|
|
color: rgba($primaryColor, .8);
|
|
border-color: rgba($primaryColor, .8);
|
|
}
|
|
}
|
|
|
|
.selected {
|
|
color: rgba($primaryColor, .8);
|
|
border-color: rgba($primaryColor, .8);
|
|
}
|
|
}
|
|
|
|
.badge-label {
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
border-radius: 15px;
|
|
color: #fff;
|
|
width: 12px;
|
|
letter-spacing: 10px;
|
|
text-align: center;
|
|
overflow: hidden;
|
|
padding: 3px 5px;
|
|
white-space: nowrap;
|
|
transition: width 1s, letter-spacing 0.05s;
|
|
|
|
&.label-town {
|
|
background: rgba($primaryColor, .8);
|
|
}
|
|
|
|
&.label-village {
|
|
background: rgba($primaryColor, .8);
|
|
}
|
|
|
|
&.label-poor {
|
|
background: #ffb14c;
|
|
}
|
|
|
|
&:hover {
|
|
width: initial;
|
|
letter-spacing: unset;
|
|
}
|
|
}
|
|
}
|
|
</style>
|