Files
device-voice-h5/utils/shippingAddress.js
sexygoat b611808316
All checks were successful
构建并部署前端到生产环境 / build-and-deploy (push) Successful in 1m48s
fix: 优化换货
2026-05-14 12:14:05 +08:00

310 lines
8.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 = /(?<!\d)(?:\+?86[-\s]?)?(1[3-9](?:[\s-]?\d){9})(?!\d)/;
const provinceOptions = regionData.map(({ label, value }) => ({ 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
};
}