import { regionData } from 'element-china-area-data'; const GENERIC_CITY_LABELS = new Set([ '市辖区', '县', '自治区直辖县级行政区划', '省直辖县级行政区划' ]); const NAME_LABEL_REG = /(收货人|收件人|联系人|姓名)/g; const PHONE_LABEL_REG = /(手机号码|手机号|联系电话|联系方式|电话|手机)/g; const ADDRESS_LABEL_REG = /(收货地址|详细地址|地址|所在地区|地区)/g; const ADDRESS_SIGNAL_REG = /(省|市|区|县|旗|乡|镇|街道|大道|路|街|巷|弄|村|社区|小区|号|栋|单元|室|楼)/; const PHONE_REG = /(? ({ label, value })); const districtRecords = regionData.flatMap((province) => (province.children || []).flatMap((city) => (city.children || []).map((district) => ({ province, city, district, provinceSearchLabel: normalizeForSearch(province.label), citySearchLabel: normalizeForSearch(getDisplayCityLabel(city.label)), districtSearchLabel: normalizeForSearch(district.label), fullSearchLabel: normalizeForSearch( `${province.label}${getDisplayCityLabel(city.label)}${district.label}` ) })) ) ); function normalizeText(value = '') { return String(value) .replace(/\r/g, '\n') .replace(/\u00a0/g, ' ') .replace(/\t/g, ' ') .trim(); } function normalizeForSearch(value = '') { return normalizeText(value).replace(/[\s,,.。;;::、\-_/]/g, ''); } function getDisplayCityLabel(label = '') { return GENERIC_CITY_LABELS.has(label) ? '' : label; } function getProvinceByCode(provinceCode) { return regionData.find((item) => item.value === provinceCode) || null; } function getCityByCode(provinceCode, cityCode) { const province = getProvinceByCode(provinceCode); return province?.children?.find((item) => item.value === cityCode) || null; } function getDistrictByCode(provinceCode, cityCode, districtCode) { const city = getCityByCode(provinceCode, cityCode); return city?.children?.find((item) => item.value === districtCode) || null; } function getCityOptions(provinceCode) { const province = getProvinceByCode(provinceCode) || regionData[0]; return (province?.children || []).map(({ label, value }) => ({ label, value })); } function getDistrictOptions(provinceCode, cityCode) { const province = getProvinceByCode(provinceCode) || regionData[0]; const city = province?.children?.find((item) => item.value === cityCode) || province?.children?.[0] || null; return (city?.children || []).map(({ label, value }) => ({ label, value })); } function getLabelParts(regionCodes = []) { const [provinceCode, cityCode, districtCode] = regionCodes; const province = getProvinceByCode(provinceCode); const city = getCityByCode(provinceCode, cityCode); const district = getDistrictByCode(provinceCode, cityCode, districtCode); if (!province || !city || !district) { return []; } const displayCityLabel = getDisplayCityLabel(city.label); return [province.label, displayCityLabel, district.label].filter(Boolean); } function cleanupAddressText(value = '') { return normalizeText(value) .replace(NAME_LABEL_REG, ' ') .replace(PHONE_LABEL_REG, ' ') .replace(ADDRESS_LABEL_REG, ' ') .replace(/[::]/g, ' ') .replace(/[,,;;]/g, ' ') .replace(/\s+/g, ' ') .trim(); } function extractPhone(rawText = '') { const text = normalizeText(rawText); const match = text.match(PHONE_REG); if (!match) { return { phone: '', textWithoutPhone: text }; } const digits = match[1].replace(/\D/g, ''); const phone = digits.length > 11 ? digits.slice(-11) : digits; return { phone, textWithoutPhone: text.replace(match[0], ' ').replace(/\s+/g, ' ').trim() }; } function pickBestMatch(records, matcher) { return records .filter(matcher) .sort((left, right) => { return ( right.fullSearchLabel.length - left.fullSearchLabel.length || right.districtSearchLabel.length - left.districtSearchLabel.length ); })[0] || null; } function findRegionMatch(rawText = '') { const searchText = normalizeForSearch(rawText); if (!searchText) { return null; } let match = pickBestMatch(districtRecords, (record) => { return record.fullSearchLabel && searchText.includes(record.fullSearchLabel); }); if (!match) { match = pickBestMatch(districtRecords, (record) => { return ( record.provinceSearchLabel && record.districtSearchLabel && searchText.includes(record.provinceSearchLabel) && searchText.includes(record.districtSearchLabel) ); }); } return match; } function getRegionStartIndex(text, match) { const cityLabel = getDisplayCityLabel(match.city.label); const candidates = [match.province.label, cityLabel, match.district.label] .filter(Boolean) .map((label) => text.indexOf(label)) .filter((index) => index >= 0); return candidates.length ? Math.min(...candidates) : -1; } function stripMatchedRegion(addressText, match) { const cityLabel = getDisplayCityLabel(match.city.label); let detailAddress = normalizeText(addressText); [match.province.label, cityLabel, match.district.label] .filter(Boolean) .forEach((label) => { detailAddress = detailAddress.replace(label, ''); }); return detailAddress.replace(/^[,,;;::\s]+/, '').trim(); } function extractNameFromText(rawText = '', textWithoutPhone = '', regionStartIndex = -1) { const sourceText = regionStartIndex > 0 ? textWithoutPhone.slice(0, regionStartIndex) : textWithoutPhone || rawText; const directNameMatch = normalizeText(rawText).match( /(?:收货人|收件人|联系人|姓名)\s*[::]?\s*([A-Za-z\u4e00-\u9fa5·]{2,20})/ ); if (directNameMatch?.[1]) { return directNameMatch[1].trim(); } const cleaned = cleanupAddressText(sourceText) .replace(PHONE_REG, ' ') .replace(/\d+/g, ' ') .trim(); const tokens = cleaned.split(/\s+/).filter(Boolean); const nameToken = tokens.find((token) => { return !ADDRESS_SIGNAL_REG.test(token) && /^[A-Za-z\u4e00-\u9fa5·]{2,20}$/.test(token); }); return nameToken || ''; } function extractAddressSource(textWithoutPhone = '', name = '', match = null) { let addressSource = normalizeText(textWithoutPhone); if (name) { addressSource = addressSource.replace(name, ' ').replace(/\s+/g, ' ').trim(); } if (match) { const regionStartIndex = getRegionStartIndex(addressSource, match); if (regionStartIndex >= 0) { return addressSource.slice(regionStartIndex).trim(); } } return cleanupAddressText(addressSource); } function formatRegionCodes(match) { return [match.province.value, match.city.value, match.district.value]; } export function buildRegionColumns(regionCodes = []) { const provinceCode = getProvinceByCode(regionCodes[0])?.value || provinceOptions[0]?.value || ''; const cityOptions = getCityOptions(provinceCode); const cityCode = cityOptions.find((item) => item.value === regionCodes[1])?.value || cityOptions[0]?.value || ''; const districtOptions = getDistrictOptions(provinceCode, cityCode); const districtCode = districtOptions.find((item) => item.value === regionCodes[2])?.value || districtOptions[0]?.value || ''; return { columns: [provinceOptions, cityOptions, districtOptions], defaultIndex: [ provinceOptions.findIndex((item) => item.value === provinceCode), cityOptions.findIndex((item) => item.value === cityCode), districtOptions.findIndex((item) => item.value === districtCode) ] }; } export function getRegionDisplayText(regionCodes = []) { return getLabelParts(regionCodes).join(' / '); } export function composeRecipientAddress(regionCodes = [], detailAddress = '') { const regionText = getLabelParts(regionCodes).join(''); return `${regionText}${normalizeText(detailAddress)}`.trim(); } export function parseSavedRecipientAddress(rawAddress = '') { const addressText = normalizeText(rawAddress); if (!addressText) { return { regionCodes: [], detailAddress: '' }; } const match = findRegionMatch(addressText); if (!match) { return { regionCodes: [], detailAddress: addressText }; } const detailAddress = stripMatchedRegion(addressText, match); return { regionCodes: formatRegionCodes(match), detailAddress: detailAddress || addressText }; } export function parseShippingText(rawText = '') { const text = normalizeText(rawText); if (!text) { return { recipient_name: '', recipient_phone: '', regionCodes: [], detailAddress: '' }; } const { phone, textWithoutPhone } = extractPhone(text); const regionMatch = findRegionMatch(textWithoutPhone); const regionStartIndex = regionMatch ? getRegionStartIndex(textWithoutPhone, regionMatch) : -1; const recipientName = extractNameFromText(text, textWithoutPhone, regionStartIndex); const addressSource = extractAddressSource(textWithoutPhone, recipientName, regionMatch); const detailAddress = regionMatch ? stripMatchedRegion(addressSource, regionMatch) : cleanupAddressText(addressSource); return { recipient_name: recipientName, recipient_phone: phone, regionCodes: regionMatch ? formatRegionCodes(regionMatch) : [], detailAddress }; }