369 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			369 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <template>
 | |
|   <section class="ai-area">
 | |
|     <div v-if="inputClicker" @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 nowarp-text fill" v-text="fullName"/>
 | |
|         <i class="iconfont iconChange pad-r10"/>
 | |
|       </el-row>
 | |
|     </div>
 | |
|     <el-button v-else-if="!customClicker" 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" :areatype="selectedAreaType"/>
 | |
|     </a>
 | |
|     <ai-dialog :visible.sync="dialog" title="选择地区" width="60%" @onConfirm="confirmArea" @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}
 | |
|   },
 | |
|   data() {
 | |
|     return {
 | |
|       selected: null,
 | |
|       areaOps: [],
 | |
|       fullName: '',
 | |
|       dialog: false,
 | |
|       ProvinceCityCounty: [],
 | |
|     }
 | |
|   },
 | |
|   computed: {
 | |
|     currentArea: v => v.selected || v.value,
 | |
|     selectedArea: v => new Area(v.currentArea, v.hashMap),
 | |
|     startLevel: v => Number(v.hideLevel) || 0,//地区最高可选行政地区等级
 | |
|     endLevel: v => Number(v.areaLevel) || 0,//地区最低可选行政地区等级
 | |
|     selectedName: v => v.selectedArea.name || "无",
 | |
|     validateState: v => ['', 'success'].includes(v.elFormItem?.validateState),
 | |
|     selectedMap: v => v.selectedArea.areaMap,
 | |
|     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], list
 | |
|       })).slice(Math.max(0, this.startLevel), this.endLevel)
 | |
|       if (this.startLevel > 0 && ops.length > 0) {
 | |
|         ops[0].list = ops[0].list.filter(e => e.id == this.selectedMap[this.startLevel])
 | |
|       }
 | |
|       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 = []
 | |
|       let map = {};
 | |
|       return Promise.all([null, ...this.selectedMap].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>
 |