161 lines
4.9 KiB
TypeScript
161 lines
4.9 KiB
TypeScript
import { codeToText, regionData } from 'element-china-area-data';
|
||
|
||
export { regionData as CHINA_REGION_OPTIONS };
|
||
|
||
export type ProvinceCityOption = {
|
||
value: string;
|
||
label: string;
|
||
children?: Array<{ value: string; label: string }>;
|
||
};
|
||
|
||
/** 省 / 市二级(不含区县) */
|
||
export const PROVINCE_CITY_OPTIONS: ProvinceCityOption[] = regionData.map((province) => ({
|
||
value: province.value,
|
||
label: province.label,
|
||
children: province.children?.map((city) => ({
|
||
value: city.value,
|
||
label: city.label,
|
||
})),
|
||
}));
|
||
|
||
export type OpenCityRef = {
|
||
id: string;
|
||
name: string;
|
||
code: string;
|
||
partnerBindings?: Array<{ partnerAccountId: string }>;
|
||
boundPartnerIds?: string[];
|
||
};
|
||
|
||
function cityBoundPartnerIds(city: OpenCityRef): string[] {
|
||
if (city.boundPartnerIds?.length) return city.boundPartnerIds.map(String);
|
||
return (city.partnerBindings ?? []).map((b) => String(b.partnerAccountId));
|
||
}
|
||
|
||
export type ParsedChinaRegion = {
|
||
province: string;
|
||
city: string;
|
||
district: string;
|
||
provinceCode: string;
|
||
cityCode: string;
|
||
districtCode: string;
|
||
};
|
||
|
||
export type ParsedProvinceCity = {
|
||
province: string;
|
||
city: string;
|
||
provinceCode: string;
|
||
cityCode: string;
|
||
};
|
||
|
||
/** element-china-area-data 市级码(如 4101)→ 国标 6 位 adcode(410100) */
|
||
export function normalizeCityAdcode(code: string): string {
|
||
const raw = code.trim();
|
||
if (/^\d{6}$/.test(raw)) return raw;
|
||
if (/^\d{4}$/.test(raw)) return `${raw}00`;
|
||
if (/^\d{2}$/.test(raw)) return `${raw}0100`;
|
||
return raw.padEnd(6, '0').slice(0, 6);
|
||
}
|
||
|
||
export function parseProvinceCityCodes(codes?: string[]): ParsedProvinceCity | null {
|
||
if (!codes || codes.length < 2) return null;
|
||
const [provinceCode, cityCode] = codes;
|
||
const province = codeToText[provinceCode];
|
||
let city = codeToText[cityCode];
|
||
if (!province || !city) return null;
|
||
if (city === '市辖区' || city === '县' || city === '省直辖县级行政区划') {
|
||
city = province.endsWith('市') ? province : city;
|
||
}
|
||
return {
|
||
province,
|
||
city,
|
||
provinceCode,
|
||
cityCode: normalizeCityAdcode(cityCode),
|
||
};
|
||
}
|
||
|
||
/** 区县 adcode → 地级市 adcode(如 410105 → 410100) */
|
||
export function districtCodeToCityCode(districtCode: string): string {
|
||
if (districtCode.length < 6) return districtCode;
|
||
return `${districtCode.slice(0, 4)}00`;
|
||
}
|
||
|
||
export type DistrictOption = { value: string; label: string };
|
||
|
||
/** 按开城城市 code(4 位或 6 位)取该市下区县列表 */
|
||
export function getDistrictOptionsByCityCode(cityCode?: string | null): DistrictOption[] {
|
||
if (!cityCode?.trim()) return [];
|
||
const normalized = normalizeCityAdcode(cityCode.trim());
|
||
const prefix4 = normalized.slice(0, 4);
|
||
|
||
for (const province of regionData) {
|
||
for (const city of province.children ?? []) {
|
||
const cityValue = String(city.value);
|
||
const cityNorm = normalizeCityAdcode(cityValue);
|
||
if (cityNorm === normalized || cityValue === prefix4 || cityValue === cityCode.trim()) {
|
||
return (city.children ?? []).map((d) => ({
|
||
value: String(d.value),
|
||
label: String(d.label),
|
||
}));
|
||
}
|
||
}
|
||
}
|
||
return [];
|
||
}
|
||
|
||
export function districtCodeLabel(code: string): string {
|
||
return codeToText[code] || code;
|
||
}
|
||
|
||
/** 列表展示:区县码 → 中文名,顿号拼接 */
|
||
export function formatDistrictLabels(codes?: string[] | null): string {
|
||
if (!codes?.length) return '—';
|
||
return codes.map((c) => districtCodeLabel(String(c))).join('、');
|
||
}
|
||
|
||
export function parseRegionCodes(codes?: string[]): ParsedChinaRegion | null {
|
||
if (!codes || codes.length < 3) return null;
|
||
const [provinceCode, cityCode, districtCode] = codes;
|
||
const province = codeToText[provinceCode];
|
||
const city = codeToText[cityCode];
|
||
const district = codeToText[districtCode];
|
||
if (!province || !city || !district) return null;
|
||
return { province, city, district, provinceCode, cityCode, districtCode };
|
||
}
|
||
|
||
export function formatRegionLabel(region: ParsedChinaRegion): string {
|
||
return `${region.province} / ${region.city} / ${region.district}`;
|
||
}
|
||
|
||
export function matchOpenCityId(
|
||
cities: OpenCityRef[],
|
||
districtCode: string,
|
||
partnerAccountId?: string,
|
||
): string | undefined {
|
||
const cityCode = districtCodeToCityCode(districtCode);
|
||
const scoped = partnerAccountId
|
||
? cities.filter((c) => cityBoundPartnerIds(c).includes(String(partnerAccountId)))
|
||
: cities;
|
||
return (
|
||
scoped.find((c) => c.code === cityCode)?.id
|
||
?? scoped.find((c) => c.code === districtCode)?.id
|
||
?? scoped.find((c) => districtCode.startsWith(c.code.slice(0, 4)))?.id
|
||
);
|
||
}
|
||
|
||
export function resolveRegionBinding(
|
||
codes: string[],
|
||
cities: OpenCityRef[],
|
||
partnerAccountId?: string,
|
||
) {
|
||
const region = parseRegionCodes(codes);
|
||
if (!region) return null;
|
||
const cityId = matchOpenCityId(cities, region.districtCode, partnerAccountId);
|
||
const matchedCity = cityId ? cities.find((c) => c.id === cityId) : undefined;
|
||
return {
|
||
region,
|
||
cityId,
|
||
matchedCity,
|
||
cityCode: districtCodeToCityCode(region.districtCode),
|
||
};
|
||
}
|