Files
dukang/apps/admin-web/src/lib/china-region.ts
T

161 lines
4.9 KiB
TypeScript
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 { 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 位 adcode410100 */
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),
};
}