2026-07-24 15:57:20 +08:00
|
|
|
import areaData from 'china-area-data';
|
|
|
|
|
|
|
|
|
|
export interface CascadeOption {
|
|
|
|
|
value: string;
|
|
|
|
|
label: string;
|
|
|
|
|
children?: CascadeOption[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 将 china-area-data 原始数据转换为 antd Cascader 所需的 options 格式
|
|
|
|
|
*
|
|
|
|
|
* 原始数据格式: { '86': { '110000': '北京' }, '110000': { '110100': '市辖区' }, ... }
|
|
|
|
|
* 目标格式: [{ value: '110000', label: '北京', children: [...] }]
|
|
|
|
|
*/
|
|
|
|
|
function buildTree(parentCode: string): CascadeOption[] {
|
|
|
|
|
const nodes = areaData[parentCode];
|
|
|
|
|
if (!nodes) return [];
|
|
|
|
|
|
|
|
|
|
return Object.entries(nodes).map(([code, name]) => {
|
|
|
|
|
const node: CascadeOption = {
|
|
|
|
|
value: code,
|
|
|
|
|
label: name as string,
|
|
|
|
|
};
|
|
|
|
|
const children = buildTree(code);
|
|
|
|
|
if (children.length > 0) {
|
|
|
|
|
node.children = children;
|
|
|
|
|
}
|
|
|
|
|
return node;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 省市区三级联动数据 */
|
|
|
|
|
export const regionOptions: CascadeOption[] = buildTree('86');
|
2026-07-30 15:43:24 +08:00
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// 省市选项(精确到市,直辖市仅市名)
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
/** 直辖市代码 */
|
|
|
|
|
const MUNICIPALITY_CODES = ['110000', '120000', '310000', '500000'];
|
|
|
|
|
|
|
|
|
|
/** 省市选项 */
|
|
|
|
|
export interface ProvinceCityOption {
|
|
|
|
|
value: string;
|
|
|
|
|
label: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取"省-市"格式的选项列表
|
|
|
|
|
* - 普通省份:格式为 "省名-市名",如 "广东省-深圳市"
|
|
|
|
|
* - 直辖市:直接使用市名,如 "北京市"
|
|
|
|
|
*/
|
|
|
|
|
export function getProvinceCityOptions(): ProvinceCityOption[] {
|
|
|
|
|
const options: ProvinceCityOption[] = [];
|
|
|
|
|
const provinces = areaData['86'];
|
|
|
|
|
if (!provinces) return options;
|
|
|
|
|
|
|
|
|
|
for (const [provinceCode, provinceName] of Object.entries(provinces)) {
|
|
|
|
|
const pname = provinceName as string;
|
|
|
|
|
|
|
|
|
|
if (MUNICIPALITY_CODES.includes(provinceCode)) {
|
|
|
|
|
options.push({ value: pname, label: pname });
|
|
|
|
|
} else {
|
|
|
|
|
const cities = areaData[provinceCode];
|
|
|
|
|
if (cities) {
|
|
|
|
|
for (const [, cityName] of Object.entries(cities)) {
|
|
|
|
|
const fullName = `${pname}-${cityName}`;
|
|
|
|
|
options.push({ value: fullName, label: fullName });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return options;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Cascader 节点 */
|
|
|
|
|
export interface CascaderNode {
|
|
|
|
|
value: string;
|
|
|
|
|
label: string;
|
|
|
|
|
children?: CascaderNode[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取省市两级 Cascader 选项
|
|
|
|
|
* - 普通省份:省 → 市,选中值 ["省名", "市名"]
|
|
|
|
|
* - 直辖市:直接叶子节点,选中值 ["市名"]
|
|
|
|
|
*/
|
|
|
|
|
export function getProvinceCityCascaderOptions(): CascaderNode[] {
|
|
|
|
|
const nodes: CascaderNode[] = [];
|
|
|
|
|
const provinces = areaData['86'];
|
|
|
|
|
if (!provinces) return nodes;
|
|
|
|
|
|
|
|
|
|
for (const [provinceCode, provinceName] of Object.entries(provinces)) {
|
|
|
|
|
const pname = provinceName as string;
|
|
|
|
|
|
|
|
|
|
if (MUNICIPALITY_CODES.includes(provinceCode)) {
|
|
|
|
|
// 直辖市:直接作为叶子节点
|
|
|
|
|
nodes.push({ value: pname, label: pname });
|
|
|
|
|
} else {
|
|
|
|
|
const cities = areaData[provinceCode];
|
|
|
|
|
if (cities) {
|
|
|
|
|
const cityNodes: CascaderNode[] = Object.entries(cities).map(([, cname]) => ({
|
|
|
|
|
value: cname as string,
|
|
|
|
|
label: cname as string,
|
|
|
|
|
}));
|
|
|
|
|
nodes.push({ value: pname, label: pname, children: cityNodes });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nodes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 将 Cascader 选中的路径数组转为存储用的区域字符串
|
|
|
|
|
* ["广东省", "深圳市"] → "广东省-深圳市"
|
|
|
|
|
* ["北京市"] → "北京市"
|
|
|
|
|
*/
|
|
|
|
|
export function formatCascaderArea(path: string[]): string {
|
|
|
|
|
if (!path || path.length === 0) return '';
|
|
|
|
|
return path.join('-');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 将存储的区域字符串反向解析为 Cascader 路径数组
|
|
|
|
|
* "广东省-深圳市" → ["广东省", "深圳市"]
|
|
|
|
|
* "北京市" → ["北京市"]
|
|
|
|
|
*/
|
|
|
|
|
export function parseAreaToCascaderPath(area: string): string[] {
|
|
|
|
|
if (!area) return [];
|
|
|
|
|
return area.split('-');
|
|
|
|
|
}
|
2026-08-04 17:01:55 +08:00
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// 城市级别选项(仅市名,加"全国")
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
/** 城市 Select 选项 */
|
|
|
|
|
export interface CityOption {
|
|
|
|
|
value: string;
|
|
|
|
|
label: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取仅城市名的选项列表,第一项为"全国"
|
|
|
|
|
* 用于 Banner 弹窗中"所属区域"下拉选择
|
|
|
|
|
* - 直辖市:直接取市名(如"北京市")
|
|
|
|
|
* - 普通省份:取市名(如"深圳市")
|
|
|
|
|
*/
|
|
|
|
|
export function getCityOnlyOptions(): CityOption[] {
|
|
|
|
|
const options: CityOption[] = [{ value: '全国', label: '全国' }];
|
|
|
|
|
const provinces = areaData['86'];
|
|
|
|
|
if (!provinces) return options;
|
|
|
|
|
|
|
|
|
|
for (const [provinceCode, provinceName] of Object.entries(provinces)) {
|
|
|
|
|
if (MUNICIPALITY_CODES.includes(provinceCode)) {
|
|
|
|
|
options.push({ value: provinceName as string, label: provinceName as string });
|
|
|
|
|
} else {
|
|
|
|
|
const cities = areaData[provinceCode];
|
|
|
|
|
if (cities) {
|
|
|
|
|
for (const [, cityName] of Object.entries(cities)) {
|
|
|
|
|
options.push({ value: cityName as string, label: cityName as string });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return options;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 根据城市名反查 Cascader 路径数组
|
|
|
|
|
* "深圳市" → ["广东省", "深圳市"]
|
|
|
|
|
* "北京市" → ["北京市"]
|
|
|
|
|
* "全国" → ["全国"]
|
|
|
|
|
*/
|
|
|
|
|
export function cityNameToCascaderPath(cityName: string): string[] {
|
|
|
|
|
if (!cityName || cityName === '全国') return ['全国'];
|
|
|
|
|
|
|
|
|
|
const provinces = areaData['86'];
|
|
|
|
|
if (!provinces) return [cityName];
|
|
|
|
|
|
|
|
|
|
for (const [provinceCode, provinceName] of Object.entries(provinces)) {
|
|
|
|
|
const pname = provinceName as string;
|
|
|
|
|
|
|
|
|
|
if (MUNICIPALITY_CODES.includes(provinceCode)) {
|
|
|
|
|
if (pname === cityName) return [pname];
|
|
|
|
|
} else {
|
|
|
|
|
const cities = areaData[provinceCode];
|
|
|
|
|
if (cities) {
|
|
|
|
|
for (const [, cname] of Object.entries(cities)) {
|
|
|
|
|
if ((cname as string) === cityName) {
|
|
|
|
|
return [pname, cname as string];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 兜底:找不到省份隶属,直接用市名
|
|
|
|
|
return [cityName];
|
|
|
|
|
}
|