前段时间做需求, 要求做一个京东淘宝那样客户粘贴地址之后,自动识别省县区三级的地址,本来可以在后端进行做的,但是呢(主要是存在地址省县区不能为空,且详细地址也不能为空的校验),为了减少不必要的调度,就决定全部放在前端做了,并且做起来也挺不错的。话不多说直接上代码。主要分几个模块。
因为本人是后端开发人员,所以前端代码写的有些拉胯,各位大佬拿回去之后可以去进行优化一下,相信问题不大。
<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 太长了,就不方便粘贴到这里面了, 需要的就直接点击下面的下载,或者网上其他地方都可以进行下载(网上有大量的)
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> // 需要引入这个组件的路径 // 需要定义这个组件
展示效果大概就是下面这个样子:
各位大佬有什么问题请在下方留言。大家可以一起探讨一下。