前段时间做需求, 要求做一个京东淘宝那样客户粘贴地址之后,自动识别省县区三级的地址,本来可以在后端进行做的,但是呢(主要是存在地址省县区不能为空,且详细地址也不能为空的校验),为了减少不必要的调度,就决定全部放在前端做了,并且做起来也挺不错的。话不多说直接上代码。主要分几个模块。

因为本人是后端开发人员,所以前端代码写的有些拉胯,各位大佬拿回去之后可以去进行优化一下,相信问题不大。

<template>
  <div style="">
    <div style="display: flex; margin-bottom: 20px; width: 400px">
      <el-input
        v-model="addressDetail"
        class="address-input"
        placeholder="请输入详细地址"
        @change="handleAddressParse"
      />
    </div>
    <div style="">
      <el-select
        @change="provinceCodeChange"
        filterable
        style="margin-right: 0.5rem"
        placeholder="请选择省"
        v-decorator="[
          'provinceCode',
          {
            rules: [
              { required: fillFlag, message: '请选择省!' },
            ],
          },
        ]"
        v-model="addressSelect.province"
      >
        <el-option
          v-for="item in provinceCodeList"
          :key="item.name"
          :label="item.name"
          :value="item.name">
        </el-option>
      </el-select>
      <el-select
        @change="cityCodeChange"
        style="margin-right: 0.5rem"
        filterable
         v-decorator="[
          'cityCode',
          {
            rules: [
              { required: fillFlag, message: '请选择省!' },
            ],
          },
        ]"
        placeholder="请选择市"
        v-model="addressSelect.city"
      >
        <el-option
          v-for="item in cityCodeList"
          :key="item.name"
          :label="item.name"
          :value="item.name">
        </el-option>
      </el-select>
      <el-select
        @change="countryCodeChange"
        filterable
         v-decorator="[
          'countryCode',
          {
            rules: [
              { required: fillFlag, message: '请选择省!' },
            ],
          },
        ]"
        placeholder="请选择区"
        v-model="addressSelect.country"
      >
        <el-option
          v-for="item in countryCodeList"
          :key="item.name"
          :label="item.name"
          :value="item.name">
        </el-option>
      </el-select>
    </div>
    
  </div>
</template>

<script>

import region from '../position/core/region.json'
import { longest }  from '../position/core/longest'
import { objDeepCopy } from '@/utils'

export default {
  name: 'YhAddress',
  props: { 
    value: {            // 父组件传输过来的数据,主要是为了方便编辑的时候做地区回显
      type: Object,
      default: () => {
        return {};
      },
    },
    index: Number,
  },
  data() {
    return {
      addressDetail: '',
      autoSelectAddress: '',
      provinceCodeList: [],
      cityCodeList: [],
      countryCodeList: [],
      otherAreaMap: [],
      addressSelect: { // 需要回调给父组件的数据
        province: "",
        city: "",
        country: "",
      },
      // 这里面的selectName可以修改为selectedCode, 因为当前的需求,所以保存的是省市区的名称
      allNames: [
        { selectedName: null, field: 'province', options: [] },
        { selectedName: null, field: 'city', options: [] },
        { selectedName: null, field: 'country', options: [] }
      ],
      fillFlag: true
    }
  },
  mounted() {
    this.init(this.value);
  },
  methods: {
    init(val) {
      region.map((item) => {
        this.provinceCodeList.push({name: item.name});
      })
      this.allNames[0].options = this.provinceCodeList;

      if (Object.prototype.toString.call(val.address) == "[object Array]") {
        var address = {};
        for (let index = 0; index < val.address.length; index++) {
          // 如果是空的话,那就需要进行数据
          // 每次进行赋值的时候需要进行回调一下,不然他联动不了
          index === 0 ? (address["province"] = val.address[0], this.provinceCodeChange(val.address[0])) : "";
          index === 1 ? (address["city"] = val.address[1], this.cityCodeChange( val.address[1])) : "";
          index === 2 ? (address["country"] = val.address[2]) : "";
        }
        this.addressSelect = address;
        this.addressDetail = val.detailAddress
        this.callback(); // 回调给父组件
      }
    },
    provinceCodeChange(name) {
      if (!name) {
        return [];
      }
      this.cityCodeList = []; // 选择省的时候将市,区置空
      this.countryCodeList = [];
      this.addressSelect.city = ""; // 将选择好的市区置空
      this.addressSelect.country = "";
      this.addressSelect.province = name;
      region.map((item) =>{
        if (name == item.name) {
          for(let i = 0; i < item.children.length; i++ ) {
            this.cityCodeList.push({name: item.children[i].name })
          }
        }
      })
      this.callback();
      return this.cityCodeList
    },
    cityCodeChange(value) {
       if (!value) {
        return [];
      }
      this.countryCodeList = [];
      this.addressSelect.country = "";
      region.map((item) =>{
        if (item.name == this.addressSelect.province) {
          item.children.map((ol) => {
            if (value == ol.name) {
               for(let i = 0; i < ol.children.length; i++ ) {
                this.countryCodeList.push({name: ol.children[i].name })
              }
            }
          });
        }
      })
      this.callback();
      return this.countryCodeList;
    },
    countryCodeChange(name) {
      this.addressSelect.country = name;
      this.callback();
    },
    handleAddressParse(addr) {
      // 对输入的数据进行解析回调
      // 进行数据回调
      let result = this.parseAutoSelectAddress(addr); // 对输入的地址进行解析
      this.addressSelect.province = result.areaSelects[0].selectedName
      this.addressSelect.city = result.areaSelects[1].selectedName
      this.addressSelect.country = result.areaSelects[2].selectedName
      this.callback();
    },
    callback(){
        // 将父组件的需要的数据回调回去,注意: 要根据具体的需求,将具体需要的数据回调回去
        this.$emit("value-change", {
        index: this.index,
        value: {
          address: [this.addressSelect.province, this.addressSelect.city, this.addressSelect.country],
          detailAddress: this.addressDetail
        }
      });
    },

    parseAutoSelectAddress(addr) {
      const copyAreaSelects = objDeepCopy(this.allNames)
      if (!addr) return { areaSelects: copyAreaSelects, fullAreas: [], addressDetail: '' }
      const fullAreas = []
      let pAddr = addr
      for (let i = 0; i < copyAreaSelects.length; i++) {
        const currentOptions = copyAreaSelects[i].options
        let latestSubStr = '' // 最近的匹配的最佳子串
        let bestOptIndex = -1
        for (let j = 0; j < currentOptions.length; j++) {
          const opt = currentOptions[j]
          const optName = i === 0 ? opt.name + '省' : opt.name // 省后缀
          const longestSubStr = longest([pAddr, optName])
          if (longestSubStr.length > latestSubStr.length && longestSubStr.length >= 2) {
            latestSubStr = longestSubStr
            bestOptIndex = j
          }
        }
        if (bestOptIndex < 0) {
          return { areaSelects: copyAreaSelects, /* fullAreas ,*/ addressDetail: pAddr }
        }
        // 取出 最长子串
        const area = currentOptions[bestOptIndex]
       
        let nextOptions;
        if (i == 1) { // 因为只有三级,第二级是市,所以在1的时候进行查看区级的下拉框数据
          nextOptions = this.cityCodeChange(area.name);
        } else if(i ==0) { // 省级切换
          nextOptions =  this.provinceCodeChange(area.name);
        }
        // nextOptions
        // 不是最后一个
        if (i < copyAreaSelects.length - 1) copyAreaSelects[i + 1].options = [...nextOptions]

        if (latestSubStr) {
          const start = pAddr.indexOf(latestSubStr)
          if (start >= 0) {
            pAddr = pAddr.slice(start + latestSubStr.length)
          }
        }
        fullAreas.push(area)
        if (area) {
          copyAreaSelects[i].selectedName = area.name
        }
      }
      return { areaSelects: copyAreaSelects,  addressDetail: pAddr }

    }
  }
}
</script>

region.json 太长了,就不方便粘贴到这里面了, 需要的就直接点击下面的下载,或者网上其他地方都可以进行下载(网上有大量的)

region.json-其它文档类资源-CSDN下载

longest.js, 最长子串的最佳匹配算法, 这个JS也是在网上找到的, 大家可以去找找

/**
 * 最长子串
 * 输入 ['weeweadbshow', 'jhsaasrbgddbshow', 'ccbshow'] 输出 bshow
 * @param {string[]} sourceArr
 * @return {string}
 */
 export function longest(sourceArr) {
    // 字符串长度排序,优先选择最短的字符串,尽可能的减少性能开支
    sourceArr = string_ArraySort(sourceArr)
    const wholeArr = [] // 最短字符串所能产生的所有子串
    const firstStr = sourceArr.shift() // 以最短子串为基准
    let count = 0 // 结果长度
    let result = '' // 结果
  
    // 截取子串
    for (let i = 0; i < firstStr.length; i++) {
      for (let j = i + 1; j <= firstStr.length; j++) {
        wholeArr.push(firstStr.substring(i, j))
      }
    }
  
    // 遍历所有的子串
    for (let i = 0; i < wholeArr.length; i++) {
      let AllArray = [] // 建立一个结果过渡数组
  
      // 使用正则表达式来检索其他的字符串
      const patt = new RegExp(wholeArr[i])
      for (let j = 0; j < sourceArr.length; j++) {
        const reArr = sourceArr[j].match(patt) // 使用正则表达式来检索,match 函数直接返回结果
        if (reArr) { // 如果没检索到,返回一个false值,如果匹配到就返回结果
          AllArray = AllArray.concat(reArr) // 向结果过渡函数添加值
        }
      }
  
      if (AllArray.length === sourceArr.length) { // 验证是否在其他字符串中是否都匹配到了子串
        if (AllArray[0].length > count) {
          // 过渡结果
          count = AllArray[0].length
          result = AllArray[0]
        }
      }
    }
    return result
  }
  
  // 根据字符串长度排序
  function string_ArraySort(strArr) {
    return strArr.sort(function(str1, str2) {
      return str1.length - str2.length
    })
  }
  
  

以上就是组件构造的全部内容了,其实形式还是蛮简单的,外边的组件引用就是

<yh-address @value-change="changeValue" :value="value"></yh-address>

// 需要引入这个组件的路径
// 需要定义这个组件

展示效果大概就是下面这个样子: 

 

各位大佬有什么问题请在下方留言。大家可以一起探讨一下。

GitHub 加速计划 / vu / vue
207.54 K
33.66 K
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:2 个月前 )
73486cb5 * chore: fix link broken Signed-off-by: snoppy <michaleli@foxmail.com> * Update packages/template-compiler/README.md [skip ci] --------- Signed-off-by: snoppy <michaleli@foxmail.com> Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 4 个月前
e428d891 Updated Browser Compatibility reference. The previous currently returns HTTP 404. 5 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐