296 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			296 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <template>
 | |
|   <section class="AiAreaGet">
 | |
|     <el-cascader v-if="refresh" ref="areaCascader" :value="value" size="small" :props="props" :show-all-levels="showAll"
 | |
|                  :options="options" @visible-change="editing=true" clearable
 | |
|                  filterable :before-filter="handleFindArea" @change="handleAfterFilter"
 | |
|                  v-bind="$attrs" v-on="$listeners" popper-class="popperSelectors"/>
 | |
|   </section>
 | |
| </template>
 | |
| 
 | |
| <script>
 | |
| /**
 | |
|  * 智能地区选择器
 | |
|  * @displayName AiAreaGet
 | |
|  */
 | |
| export default {
 | |
|   name: "AiAreaGet",
 | |
|   inject: {
 | |
|     elFormItem: {default: ""},
 | |
|   },
 | |
|   model: {
 | |
|     prop: 'value',
 | |
|     event: 'change',
 | |
|   },
 | |
|   props: {
 | |
|     /**
 | |
|      * 接口方法类:必填
 | |
|      */
 | |
|     instance: {default: () => null},
 | |
|     /**
 | |
|      * 绑定地区编码
 | |
|      * @model
 | |
|      */
 | |
|     value: {default: ""},
 | |
|     /**
 | |
|      * 是否多选
 | |
|      */
 | |
|     multiple: Boolean,
 | |
|     /**
 | |
|      * 获取地区信息接口地址,默认为:/admin/area/queryAreaByParentId
 | |
|      */
 | |
|     action: {default: "/admin/area/queryAreaByParentId"},
 | |
|     /**
 | |
|      * 限制获取地区编码的范围,1:省 2:地级市 3:县/区 4:镇/街道 5:村/社区
 | |
|      * @values 1,2,3,4,5
 | |
|      */
 | |
|     valueLevel: {default: 5},
 | |
|     /**
 | |
|      * 指定根级地区范围
 | |
|      */
 | |
|     root: {default: ""},
 | |
|     /**
 | |
|      * 获取地区名称,支持.sync 双向获取绑定
 | |
|      */
 | |
|     name: {default: ""},
 | |
|     /**
 | |
|      * 显示完整地区名称
 | |
|      */
 | |
|     showAll: Boolean
 | |
|   },
 | |
|   data() {
 | |
|     return {
 | |
|       rules: [10, 8, 6, 3, 0],
 | |
|       cacheOptions: [],
 | |
|       editing: false,
 | |
|       filterData: [],
 | |
|       caches: [],
 | |
|       roots: [],
 | |
|       refresh: true,
 | |
|       rootLoad: ""
 | |
|     }
 | |
|   },
 | |
|   watch: {
 | |
|     value(v) {
 | |
|       !this.editing && !this.caches.includes(this.value) && this.getCacheOptions()
 | |
|       this.dispatch('ElFormItem', 'el.form.change', [v]);
 | |
|       setTimeout(() => this.$emit("update:name", this.$refs.areaCascader?.inputValue))
 | |
|     },
 | |
|     root() {
 | |
|       if (this.value) {
 | |
|         this.getCacheOptions()
 | |
|       } else {
 | |
|         setTimeout(() => {
 | |
|           this.refresh = false
 | |
|           this.$nextTick(() => this.refresh = true)
 | |
|         }, 200)
 | |
|       }
 | |
|     },
 | |
|     options: {
 | |
|       handler() {
 | |
|         this.$nextTick(() => this.$forceUpdate())
 | |
|       }, deep: true, immediate: true
 | |
|     }
 | |
|   },
 | |
|   computed: {
 | |
|     fullArea() {
 | |
|       const length = 12,
 | |
|           getFull = v => this.rules.map(e => {
 | |
|             let reg = new RegExp(`(\\d{${length-e}})\\d{${e}}`, 'g')
 | |
|             return v?.replace(reg, '$1' + Array(e).fill(0).join(''))
 | |
|           }).filter((e, i) => i <= this.getLevel(v))
 | |
|       return this.multiple ? [this.value].flat()?.map(e => getFull(e)) : getFull(this.value)
 | |
|     },
 | |
|     props() {
 | |
|       return {
 | |
|         label: 'name',
 | |
|         value: 'id',
 | |
|         lazy: true,
 | |
|         multiple: this.multiple,
 | |
|         checkStrictly: true,
 | |
|         emitPath: false,
 | |
|         lazyLoad: (node, resolve) => {
 | |
|           if (!(this.caches.includes(node.value) && this.fullArea.includes(node.value)) || node.loading) {
 | |
|             if (node?.level == 0) {
 | |
|               this.getRoots(resolve, "lazyLoad")
 | |
|             } else if (node?.level > 0 && node.children?.length == 0) {
 | |
|               let {id, leaf} = node.data
 | |
|               leaf ? resolve([]) : this.getAreasByParent(id, resolve)
 | |
|             } else resolve([])
 | |
|           } else resolve([])
 | |
|         }
 | |
|       }
 | |
|     },
 | |
|     options() {
 | |
|       return [...this.cacheOptions, ...this.filterData]
 | |
|     },
 | |
|     filtering() {
 | |
|       let v = this.$refs?.areaCascader?.filtering
 | |
|       if (!v) this.filterData = []
 | |
|       return v
 | |
|     }
 | |
|   },
 | |
|   methods: {
 | |
|     getLevel(code) {
 | |
|       let lv = -1
 | |
|       this.rules.some((e, index) => {
 | |
|         let reg = new RegExp(`0{${e}}$`, "g")
 | |
|         if (reg.test(code)) {
 | |
|           lv = index
 | |
|           return true
 | |
|         }
 | |
|       })
 | |
|       return lv
 | |
|     },
 | |
|     getRoots(resolve, from) {
 | |
|       let url = '/admin/area/queryProvinceList'
 | |
|       if (this.root) {
 | |
|         url = "/admin/area/queryAreaByAreaid"
 | |
|         if (this.rootLoad == this.root) {
 | |
|           let waitRoots = (count = 0) => setTimeout(() => {
 | |
|             if (this.roots.length > 0 || count == 5) {
 | |
|               resolve(this.roots)
 | |
|             } else waitRoots(++count)
 | |
|           }, 500)
 | |
|           return from == "lazyLoad" ? '' : waitRoots()
 | |
|         }
 | |
|       }
 | |
|       this.rootLoad = JSON.parse(JSON.stringify(this.root))
 | |
|       if (this.roots.some(e => e.id == this.root)) {
 | |
|         resolve(this.roots)
 | |
|       } else this.instance.post(url, null, {
 | |
|         params: {id: this.root, from}, withoutToken: true
 | |
|       }).then(res => {
 | |
|         if (res?.data) {
 | |
|           this.roots = [res.data].flat().map(e => ({...e, leaf: e.type == this.valueLevel}))
 | |
|           resolve(this.roots)
 | |
|         }
 | |
|       })
 | |
|     },
 | |
|     getAreasByParent(id, resolve) {
 | |
|       id && this.instance.post(this.action, null, {
 | |
|         params: {id}, withoutToken: true,
 | |
|       }).then(res => {
 | |
|         if (res?.data) {
 | |
|           resolve(res.data.map(e => ({...e, leaf: e.type == this.valueLevel})))
 | |
|         }
 | |
|       })
 | |
|     },
 | |
|     async getCacheOptions() {
 | |
|       let finished = 0
 | |
|       const hasChild = ids => ids?.some(e => this.fullArea?.flat()?.includes(e)),
 | |
|           appendChildren = (area, resolve) => {
 | |
|             let values = [this.value].flat()
 | |
|             if (values.includes(area.id)) {
 | |
|               finished++
 | |
|               if (finished == values.length) {
 | |
|                 this.$emit("update:name", area.name)
 | |
|                 resolve()
 | |
|               }
 | |
|             } else this.getAreasByParent(area.id, data => {
 | |
|               this.$set(area, "children", data)
 | |
|               data.map(d => {
 | |
|                 this.caches.push(d.id)
 | |
|                 hasChild([d.id]) && appendChildren(d, resolve)
 | |
|               })
 | |
|             })
 | |
|           }
 | |
|       if (!!this.value?.toString()) {
 | |
|         this.cacheOptions = []
 | |
|         this.caches = []
 | |
|         await this.getRoots(data => {
 | |
|           this.caches = data?.map(e => e.id) || []
 | |
|           new Promise(resolve => {
 | |
|             if (hasChild(data.map(e => e.id))) {
 | |
|               data.map(e => hasChild([e.id]) && appendChildren(e, resolve))
 | |
|             } else resolve()
 | |
|           }).then(() => {
 | |
|             this.cacheOptions = data
 | |
|           })
 | |
|         }, "initWithValue")
 | |
|       } else if (!!this.root) {
 | |
|         this.caches = []
 | |
|         await this.getRoots(data => {
 | |
|           this.caches = data?.map(e => e.id) || []
 | |
|           new Promise(resolve => {
 | |
|             if (hasChild(data.map(e => e.id))) {
 | |
|               data.map(e => hasChild([e.id]) && appendChildren(e, resolve))
 | |
|             } else resolve()
 | |
|           }).then(() => {
 | |
|             this.cacheOptions = data
 | |
|           })
 | |
|         }, "initWithRoot")
 | |
|       }
 | |
|     },
 | |
|     /**
 | |
|      * 表单验证
 | |
|      * @param componentName
 | |
|      * @param eventName
 | |
|      * @param params
 | |
|      */
 | |
|     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));
 | |
|       }
 | |
|     },
 | |
|     handleFindArea(areaName) {
 | |
|       return new Promise(resolve => {
 | |
|         this.instance.post("/admin/area/queryAreaByAreaName", null, {
 | |
|           params: {areaName}
 | |
|         }).then(res => {
 | |
|           if (res?.data) {
 | |
|             let range = new RegExp(`^${this.root.replace(/0+$/g, '')||'\d'}`)
 | |
|             this.filterData = res.data.filter(e => !this.caches.includes(e.id) && range.test(e.id)).map(e => ({
 | |
|               ...e,
 | |
|               leaf: e.type == this.valueLevel
 | |
|             }))
 | |
|             resolve()
 | |
|           }
 | |
|         })
 | |
|       })
 | |
|     },
 | |
|     handleAfterFilter(v) {
 | |
|       this.$emit('select', this.$refs.areaCascader?.getCheckedNodes(true))
 | |
|       this.$refs.areaCascader?.toggleDropDownVisible(false)
 | |
|       if (!this.multiple) {
 | |
|         if (this.filterData?.length > 0) {
 | |
|           this.filterData = []
 | |
|         }
 | |
|         this.editing = this.caches.includes(v);
 | |
|       }
 | |
|     }
 | |
|   },
 | |
|   created() {
 | |
|     setTimeout(() => {
 | |
|       this.cacheOptions.length == 0 && this.getCacheOptions()
 | |
|     })
 | |
|   }
 | |
| }
 | |
| </script>
 | |
| 
 | |
| <style lang="scss" scoped>
 | |
| .AiAreaGet {
 | |
|   width: 100%;
 | |
| 
 | |
|   .el-cascader {
 | |
|     width: 100%;
 | |
|   }
 | |
| }
 | |
| </style>
 | |
| <style lang="scss">
 | |
| .popperSelectors {
 | |
|   .el-cascader-menu__wrap {
 | |
|     height: 300px;
 | |
|   }
 | |
| }
 | |
| </style>
 |