Browse Source

fix: harden geographic select data handling

pull/11882/head
afc163 1 month ago
parent
commit
b04aab7b38
  1. 1
      cloudflare-worker/package.json
  2. 87
      cloudflare-worker/src/routes/geographic.ts
  3. 7
      package-lock.json
  4. 1
      package.json
  5. 7
      src/pages/account/center/Center.style.ts
  6. 10
      src/pages/account/settings/_mock.ts
  7. 130
      src/pages/account/settings/components/base.test.tsx
  8. 66
      src/pages/account/settings/components/base.tsx
  9. 6
      src/pages/account/settings/data.d.ts
  10. 1784
      src/pages/account/settings/geographic/city.json
  11. 138
      src/pages/account/settings/geographic/province.json
  12. 22
      src/pages/account/settings/service.test.ts
  13. 4
      src/pages/account/settings/service.ts
  14. 52
      src/utils/chinaDivision.test.ts
  15. 69
      src/utils/chinaDivision.ts

1
cloudflare-worker/package.json

@ -8,6 +8,7 @@
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"china-division": "^2.7.0",
"hono": "^4.6.0" "hono": "^4.6.0"
}, },
"devDependencies": { "devDependencies": {

87
cloudflare-worker/src/routes/geographic.ts

@ -1,89 +1,15 @@
import { Hono } from 'hono'; import { Hono } from 'hono';
import {
getCityOptions,
provinceOptions,
} from '../../../src/utils/chinaDivision';
const app = new Hono(); const app = new Hono();
// Province data - simplified with key/label format
const provinceData = [
{ label: '北京市', key: '110000' },
{ label: '天津市', key: '120000' },
{ label: '河北省', key: '130000' },
{ label: '山西省', key: '140000' },
{ label: '内蒙古自治区', key: '150000' },
{ label: '辽宁省', key: '210000' },
{ label: '吉林省', key: '220000' },
{ label: '黑龙江省', key: '230000' },
{ label: '上海市', key: '310000' },
{ label: '江苏省', key: '320000' },
{ label: '浙江省', key: '330000' },
{ label: '安徽省', key: '340000' },
{ label: '福建省', key: '350000' },
{ label: '江西省', key: '360000' },
{ label: '山东省', key: '370000' },
{ label: '河南省', key: '410000' },
{ label: '湖北省', key: '420000' },
{ label: '湖南省', key: '430000' },
{ label: '广东省', key: '440000' },
{ label: '广西壮族自治区', key: '450000' },
{ label: '海南省', key: '460000' },
{ label: '重庆市', key: '500000' },
{ label: '四川省', key: '510000' },
{ label: '贵州省', key: '520000' },
{ label: '云南省', key: '530000' },
{ label: '西藏自治区', key: '540000' },
{ label: '陕西省', key: '610000' },
{ label: '甘肃省', key: '620000' },
{ label: '青海省', key: '630000' },
{ label: '宁夏回族自治区', key: '640000' },
{ label: '新疆维吾尔自治区', key: '650000' },
{ label: '台湾省', key: '710000' },
{ label: '香港特别行政区', key: '810000' },
{ label: '澳门特别行政区', key: '820000' },
];
// City data - moved outside handler for performance
const cityData: Record<string, { label: string; key: string }[]> = {
'110000': [{ label: '市辖区', key: '110100' }],
'120000': [{ label: '市辖区', key: '120100' }],
'310000': [{ label: '市辖区', key: '310100' }],
'330000': [
{ label: '杭州市', key: '330100' },
{ label: '宁波市', key: '330200' },
{ label: '温州市', key: '330300' },
{ label: '嘉兴市', key: '330400' },
{ label: '湖州市', key: '330500' },
{ label: '绍兴市', key: '330600' },
{ label: '金华市', key: '330700' },
{ label: '衢州市', key: '330800' },
{ label: '舟山市', key: '330900' },
{ label: '台州市', key: '331000' },
{ label: '丽水市', key: '331100' },
],
'440000': [
{ label: '广州市', key: '440100' },
{ label: '韶关市', key: '440200' },
{ label: '深圳市', key: '440300' },
{ label: '珠海市', key: '440400' },
{ label: '汕头市', key: '440500' },
{ label: '佛山市', key: '440600' },
{ label: '江门市', key: '440700' },
{ label: '湛江市', key: '440800' },
{ label: '茂名市', key: '440900' },
{ label: '肇庆市', key: '441200' },
{ label: '惠州市', key: '441300' },
{ label: '梅州市', key: '441400' },
{ label: '汕尾市', key: '441500' },
{ label: '河源市', key: '441600' },
{ label: '阳江市', key: '441700' },
{ label: '清远市', key: '441800' },
{ label: '东莞市', key: '441900' },
{ label: '中山市', key: '442000' },
],
};
// GET /api/geographic/province // GET /api/geographic/province
app.get('/province', (c) => { app.get('/province', (c) => {
return c.json({ return c.json({
data: provinceData, data: provinceOptions,
}); });
}); });
@ -91,9 +17,8 @@ app.get('/province', (c) => {
app.get('/city/:province', (c) => { app.get('/city/:province', (c) => {
const province = c.req.param('province'); const province = c.req.param('province');
// Return data for the province, or empty array if not found
return c.json({ return c.json({
data: cityData[province] || [], data: getCityOptions(province),
}); });
}); });

7
package-lock.json

@ -18,6 +18,7 @@
"@tanstack/react-query": "^5.101.0", "@tanstack/react-query": "^5.101.0",
"antd": "^6.4.3", "antd": "^6.4.3",
"antd-style": "^4.1.0", "antd-style": "^4.1.0",
"china-division": "^2.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"d3": "^7.9.0", "d3": "^7.9.0",
"dayjs": "^1.11.20", "dayjs": "^1.11.20",
@ -32616,6 +32617,12 @@
"peerDependencies": { "peerDependencies": {
"zod": "^3.18.0" "zod": "^3.18.0"
} }
},
"node_modules/china-division": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/china-division/-/china-division-2.7.0.tgz",
"integrity": "sha512-4uUPAT+1WfqDh5jytq7omdCmHNk3j+k76zEG/2IqaGcYB90c2SwcixttcypdsZ3T/9tN1TTpBDoeZn+Yw/qBEA==",
"license": "MIT"
} }
} }
} }

1
package.json

@ -45,6 +45,7 @@
"@tanstack/react-query": "^5.101.0", "@tanstack/react-query": "^5.101.0",
"antd": "^6.4.3", "antd": "^6.4.3",
"antd-style": "^4.1.0", "antd-style": "^4.1.0",
"china-division": "^2.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"d3": "^7.9.0", "d3": "^7.9.0",
"dayjs": "^1.11.20", "dayjs": "^1.11.20",

7
src/pages/account/center/Center.style.ts

@ -5,7 +5,12 @@ const useStyles = createStyles(({ token }) => {
avatarHolder: { avatarHolder: {
marginBottom: '24px', marginBottom: '24px',
textAlign: 'center', textAlign: 'center',
'& > img': { width: '104px', height: '104px', marginBottom: '20px' }, '& > img': {
display: 'block',
width: '104px',
height: '104px',
margin: '0 auto 20px',
},
}, },
name: { name: {
marginBottom: '4px', marginBottom: '4px',

10
src/pages/account/settings/_mock.ts

@ -1,19 +1,19 @@
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import { defaultUser } from '../../../../mock/utils'; import { defaultUser } from '../../../../mock/utils';
import { getCityOptions, provinceOptions } from '../../../utils/chinaDivision';
const city = require('./geographic/city.json');
const province = require('./geographic/province.json');
function getProvince(_: Request, res: Response) { function getProvince(_: Request, res: Response) {
return res.json({ return res.json({
data: province, data: provinceOptions,
}); });
} }
function getCity(req: Request, res: Response) { function getCity(req: Request, res: Response) {
const provinceKey = req.params.province; const provinceKey = req.params.province;
return res.json({ return res.json({
data: city[provinceKey as keyof typeof city], data: getCityOptions(
Array.isArray(provinceKey) ? provinceKey[0] : provinceKey,
),
}); });
} }

130
src/pages/account/settings/components/base.test.tsx

@ -5,15 +5,31 @@ import * as service from '../service';
import BaseView from './base'; import BaseView from './base';
const mocks = vi.hoisted(() => ({ const mocks = vi.hoisted(() => ({
form: {
setFieldValue: vi.fn(),
},
initialValues: undefined as Record<string, any> | undefined, initialValues: undefined as Record<string, any> | undefined,
onValuesChange: undefined as
| ((changedValues: Record<string, unknown>) => void)
| undefined,
dependencyProvince: { label: '浙江省', value: '330000' },
requestResults: {} as Record<string, any>, requestResults: {} as Record<string, any>,
})); }));
vi.mock('@ant-design/pro-components', async () => { vi.mock('@ant-design/pro-components', async () => {
const React = await import('react'); const React = await import('react');
const ProForm = ({ children, initialValues }: any) => { const ProForm = ({
children,
formRef,
initialValues,
onValuesChange,
}: any) => {
if (formRef) {
formRef.current = mocks.form;
}
mocks.initialValues = initialValues; mocks.initialValues = initialValues;
mocks.onValuesChange = onValuesChange;
return <form>{children}</form>; return <form>{children}</form>;
}; };
@ -22,15 +38,15 @@ vi.mock('@ant-design/pro-components', async () => {
return { return {
ProForm, ProForm,
ProFormDependency: ({ children }: any) => ( ProFormDependency: ({ children }: any) => (
<div>{children({ province: { label: '浙江省', value: '330000' } })}</div> <div>{children({ province: mocks.dependencyProvince })}</div>
), ),
ProFormFieldSet: ({ children }: any) => <div>{children}</div>, ProFormFieldSet: ({ children }: any) => <div>{children}</div>,
ProFormSelect: ({ name, request }: any) => { ProFormSelect: ({ name, params, request }: any) => {
React.useEffect(() => { React.useEffect(() => {
request?.().then((result: any) => { request?.(params).then((result: any) => {
mocks.requestResults[name] = result; mocks.requestResults[name] = result;
}); });
}, [name, request]); }, [name, params, request]);
return <div />; return <div />;
}, },
ProFormText: () => <div />, ProFormText: () => <div />,
@ -82,6 +98,8 @@ describe('BaseView geographic selects', () => {
}, },
}); });
mocks.initialValues = undefined; mocks.initialValues = undefined;
mocks.onValuesChange = undefined;
mocks.dependencyProvince = { label: '浙江省', value: '330000' };
mocks.requestResults = {}; mocks.requestResults = {};
vi.clearAllMocks(); vi.clearAllMocks();
@ -108,10 +126,10 @@ describe('BaseView geographic selects', () => {
}, },
}); });
vi.mocked(service.queryProvince).mockResolvedValue([ vi.mocked(service.queryProvince).mockResolvedValue([
{ id: '330000', name: '浙江省' }, { key: '330000', label: '浙江省' },
]); ]);
vi.mocked(service.queryCity).mockResolvedValue([ vi.mocked(service.queryCity).mockResolvedValue([
{ id: '330100', name: '杭州市' }, { key: '330100', label: '杭州市' },
]); ]);
}); });
@ -134,6 +152,43 @@ describe('BaseView geographic selects', () => {
}); });
}); });
it('does not set incomplete geographic initial values', async () => {
vi.mocked(service.queryCurrent).mockResolvedValue({
data: {
address: '西湖区工专路 77 号',
avatar: '',
country: 'China',
email: 'antdesign@alipay.com',
geographic: {
province: { label: '浙江省' },
city: { label: '杭州市' },
},
group: '',
name: 'Ant Design',
notice: [],
notifyCount: 0,
phone: '0752-268888888',
signature: '',
tags: [],
title: '',
unreadCount: 0,
userid: '00000001',
} as any,
});
render(
<QueryClientProvider client={queryClient}>
<BaseView />
</QueryClientProvider>,
);
await waitFor(() => {
expect(mocks.initialValues?.name).toBe('Ant Design');
expect(mocks.initialValues?.province).toBeUndefined();
expect(mocks.initialValues?.city).toBeUndefined();
});
});
it('loads cities with the selected province value', async () => { it('loads cities with the selected province value', async () => {
render( render(
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
@ -146,6 +201,24 @@ describe('BaseView geographic selects', () => {
}); });
}); });
it('clears city when province changes', async () => {
render(
<QueryClientProvider client={queryClient}>
<BaseView />
</QueryClientProvider>,
);
await waitFor(() => {
expect(mocks.onValuesChange).toBeTypeOf('function');
});
mocks.onValuesChange?.({
province: { label: '河北省', value: '130000' },
});
expect(mocks.form.setFieldValue).toHaveBeenCalledWith('city', undefined);
});
it('returns label/value option arrays for geographic selects', async () => { it('returns label/value option arrays for geographic selects', async () => {
render( render(
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
@ -162,4 +235,47 @@ describe('BaseView geographic selects', () => {
]); ]);
}); });
}); });
it('supports local mock name/id geographic responses', async () => {
vi.mocked(service.queryProvince).mockResolvedValue([
{ id: '440000', name: '广东省' },
]);
vi.mocked(service.queryCity).mockResolvedValue([
{ id: '440300', name: '深圳市' },
]);
render(
<QueryClientProvider client={queryClient}>
<BaseView />
</QueryClientProvider>,
);
await waitFor(() => {
expect(mocks.requestResults.province).toEqual([
{ label: '广东省', value: '440000' },
]);
expect(mocks.requestResults.city).toEqual([
{ label: '深圳市', value: '440300' },
]);
});
});
it('falls back to local city options when remote city data is empty', async () => {
mocks.dependencyProvince = { label: '江苏省', value: '320000' };
vi.mocked(service.queryCity).mockResolvedValue([]);
render(
<QueryClientProvider client={queryClient}>
<BaseView />
</QueryClientProvider>,
);
await waitFor(() => {
expect(service.queryCity).toHaveBeenCalledWith('320000');
expect(mocks.requestResults.city).toContainEqual({
label: '南京市',
value: '320100',
});
});
});
}); });

66
src/pages/account/settings/components/base.tsx

@ -1,4 +1,5 @@
import { UploadOutlined } from '@ant-design/icons'; import { UploadOutlined } from '@ant-design/icons';
import type { ProFormInstance } from '@ant-design/pro-components';
import { import {
ProForm, ProForm,
ProFormDependency, ProFormDependency,
@ -10,6 +11,8 @@ import {
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { Button, Input, message, Upload } from 'antd'; import { Button, Input, message, Upload } from 'antd';
import React from 'react'; import React from 'react';
import { getCityOptions, provinceOptions } from '@/utils/chinaDivision';
import type { GeographicItemType } from '../data';
import { queryCity, queryCurrent, queryProvince } from '../service'; import { queryCity, queryCurrent, queryProvince } from '../service';
import useStyles from './index.style'; import useStyles from './index.style';
@ -28,15 +31,36 @@ const validatorPhone = (
}; };
const toSelectValue = (item?: { label?: string; key?: string }) => const toSelectValue = (item?: { label?: string; key?: string }) =>
item item?.key
? { ? {
label: item.label, label: item.label,
value: item.key, value: item.key,
} }
: undefined; : undefined;
const toSelectOptions = (items: GeographicItemType[]) =>
items
.map((item) => {
const label = item.name ?? item.label;
const value = item.id ?? item.key;
return label && value ? { label, value } : undefined;
})
.filter((item): item is { label: string; value: string } => Boolean(item));
const handleFinish = async () => {
message.success('更新基本信息成功');
};
const BaseView: React.FC = () => { const BaseView: React.FC = () => {
const { styles } = useStyles(); const { styles } = useStyles();
const formRef = React.useRef<ProFormInstance>(undefined);
const handleValuesChange = (changedValues: Record<string, unknown>) => {
if ('province' in changedValues) {
formRef.current?.setFieldValue('city', undefined);
}
};
const { data: currentUser, isLoading: loading } = useQuery({ const { data: currentUser, isLoading: loading } = useQuery({
queryKey: ['current-user'], queryKey: ['current-user'],
@ -53,17 +77,16 @@ const BaseView: React.FC = () => {
} }
return ''; return '';
}; };
const handleFinish = async () => {
message.success('更新基本信息成功');
};
return ( return (
<div className={styles.baseView}> <div className={styles.baseView}>
{loading ? null : ( {loading ? null : (
<> <>
<div className={styles.left}> <div className={styles.left}>
<ProForm <ProForm
formRef={formRef}
layout="vertical" layout="vertical"
onFinish={handleFinish} onFinish={handleFinish}
onValuesChange={handleValuesChange}
submitter={{ submitter={{
searchConfig: { searchConfig: {
submitText: '更新基本信息', submitText: '更新基本信息',
@ -129,8 +152,9 @@ const BaseView: React.FC = () => {
]} ]}
/> />
<ProForm.Group title="所在省市" size={8}> <ProForm.Group size={8}>
<ProFormSelect <ProFormSelect
label="所在省市"
rules={[ rules={[
{ {
required: true, required: true,
@ -143,20 +167,17 @@ const BaseView: React.FC = () => {
}} }}
name="province" name="province"
request={async () => { request={async () => {
return queryProvince().then((data) => { const options = toSelectOptions(await queryProvince());
return data.map((item) => { return options.length
return { ? options
label: item.name, : toSelectOptions(provinceOptions);
value: item.id,
};
});
});
}} }}
/> />
<ProFormDependency name={['province']}> <ProFormDependency name={['province']}>
{({ province }) => { {({ province }) => {
return ( return (
<ProFormSelect <ProFormSelect
label=" "
params={{ params={{
key: province?.value, key: province?.value,
}} }}
@ -172,18 +193,17 @@ const BaseView: React.FC = () => {
labelInValue: true, labelInValue: true,
}} }}
disabled={!province} disabled={!province}
request={async () => { request={async (params) => {
if (!province?.value) { if (!params.key) {
return []; return [];
} }
return queryCity(province.value).then((data) => { const provinceKey = String(params.key);
return data.map((item) => { const options = toSelectOptions(
return { await queryCity(provinceKey),
label: item.name, );
value: item.id, return options.length
}; ? options
}); : toSelectOptions(getCityOptions(provinceKey));
});
}} }}
/> />
); );

6
src/pages/account/settings/data.d.ts

@ -4,8 +4,10 @@ export type TagType = {
}; };
export type GeographicItemType = { export type GeographicItemType = {
name: string; name?: string;
id: string; id?: string;
label?: string;
key?: string;
}; };
export type GeographicType = { export type GeographicType = {

1784
src/pages/account/settings/geographic/city.json

File diff suppressed because it is too large

138
src/pages/account/settings/geographic/province.json

@ -1,138 +0,0 @@
[
{
"name": "北京市",
"id": "110000"
},
{
"name": "天津市",
"id": "120000"
},
{
"name": "河北省",
"id": "130000"
},
{
"name": "山西省",
"id": "140000"
},
{
"name": "内蒙古自治区",
"id": "150000"
},
{
"name": "辽宁省",
"id": "210000"
},
{
"name": "吉林省",
"id": "220000"
},
{
"name": "黑龙江省",
"id": "230000"
},
{
"name": "上海市",
"id": "310000"
},
{
"name": "江苏省",
"id": "320000"
},
{
"name": "浙江省",
"id": "330000"
},
{
"name": "安徽省",
"id": "340000"
},
{
"name": "福建省",
"id": "350000"
},
{
"name": "江西省",
"id": "360000"
},
{
"name": "山东省",
"id": "370000"
},
{
"name": "河南省",
"id": "410000"
},
{
"name": "湖北省",
"id": "420000"
},
{
"name": "湖南省",
"id": "430000"
},
{
"name": "广东省",
"id": "440000"
},
{
"name": "广西壮族自治区",
"id": "450000"
},
{
"name": "海南省",
"id": "460000"
},
{
"name": "重庆市",
"id": "500000"
},
{
"name": "四川省",
"id": "510000"
},
{
"name": "贵州省",
"id": "520000"
},
{
"name": "云南省",
"id": "530000"
},
{
"name": "西藏自治区",
"id": "540000"
},
{
"name": "陕西省",
"id": "610000"
},
{
"name": "甘肃省",
"id": "620000"
},
{
"name": "青海省",
"id": "630000"
},
{
"name": "宁夏回族自治区",
"id": "640000"
},
{
"name": "新疆维吾尔自治区",
"id": "650000"
},
{
"name": "台湾省",
"id": "710000"
},
{
"name": "香港特别行政区",
"id": "810000"
},
{
"name": "澳门特别行政区",
"id": "820000"
}
]

22
src/pages/account/settings/service.test.ts

@ -0,0 +1,22 @@
import { request } from '@umijs/max';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { queryCity } from './service';
vi.mock('@umijs/max', () => ({
request: vi.fn(),
}));
describe('account settings service', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(request).mockResolvedValue({ data: [] });
});
it('encodes province when requesting city options', async () => {
await queryCity('33/000?x=1');
expect(request).toHaveBeenCalledWith(
'/api/geographic/city/33%2F000%3Fx%3D1',
);
});
});

4
src/pages/account/settings/service.ts

@ -12,7 +12,9 @@ export async function queryProvince(): Promise<GeographicItemType[]> {
export async function queryCity( export async function queryCity(
province: string, province: string,
): Promise<GeographicItemType[]> { ): Promise<GeographicItemType[]> {
return request(`/api/geographic/city/${province}`).then(({ data }) => data); return request(`/api/geographic/city/${encodeURIComponent(province)}`).then(
({ data }) => data,
);
} }
export async function query() { export async function query() {

52
src/utils/chinaDivision.test.ts

@ -0,0 +1,52 @@
import { describe, expect, it } from 'vitest';
import { getCityOptions, provinceOptions } from './chinaDivision';
describe('chinaDivision geographic options', () => {
it('keeps province values compatible with existing six-digit province ids', () => {
expect(provinceOptions).toContainEqual({
label: '浙江省',
key: '330000',
});
expect(provinceOptions).toContainEqual({
label: '台湾省',
key: '710000',
});
});
it('returns city options for ordinary provinces', () => {
expect(getCityOptions('130000')).toContainEqual({
label: '石家庄市',
key: '130100',
});
expect(getCityOptions('320000')).toContainEqual({
label: '南京市',
key: '320100',
});
expect(getCityOptions('32')).toContainEqual({
label: '南京市',
key: '320100',
});
});
it('returns district options for municipalities', () => {
expect(getCityOptions('110000')).toContainEqual({
label: '东城区',
key: '110101',
});
});
it('returns city options for Hong Kong, Macao and Taiwan', () => {
expect(getCityOptions('710000')).toContainEqual({
label: '台北市',
key: '710100',
});
expect(getCityOptions('810000')).toContainEqual({
label: '香港岛',
key: '810100',
});
expect(getCityOptions('820000')).toContainEqual({
label: '澳门半岛',
key: '820100',
});
});
});

69
src/utils/chinaDivision.ts

@ -0,0 +1,69 @@
import hkMoTwSource from 'china-division/dist/HK-MO-TW.json';
import pcaSource from 'china-division/dist/pca-code.json';
export type GeographicOption = {
label: string;
key: string;
};
type DivisionNode = {
code: string;
name: string;
children?: DivisionNode[];
};
const directAdminCodes = new Set(['11', '12', '31', '50']);
const specialRegions: GeographicOption[] = [
{ label: '台湾省', key: '710000' },
{ label: '香港特别行政区', key: '810000' },
{ label: '澳门特别行政区', key: '820000' },
];
const toProvinceKey = (code: string) => `${code}0000`;
const toCityKey = (code: string) => (code.length === 4 ? `${code}00` : code);
const toOption = (item: DivisionNode): GeographicOption => ({
label: item.name,
key: toCityKey(item.code),
});
export const provinceOptions: GeographicOption[] = [
...(pcaSource as DivisionNode[]).map((item) => ({
label: item.name,
key: toProvinceKey(item.code),
})),
...specialRegions,
];
export const getCityOptions = (provinceKey: string): GeographicOption[] => {
const provinceCode =
provinceKey.length === 2 ? provinceKey : provinceKey.slice(0, 2);
const province = (pcaSource as DivisionNode[]).find(
(item) => item.code === provinceCode,
);
if (province) {
const children = province.children ?? [];
const cityNodes = directAdminCodes.has(provinceCode)
? children.flatMap((item) => item.children ?? [item])
: children;
return cityNodes.map(toOption);
}
const specialRegion = specialRegions.find((item) => item.key === provinceKey);
if (!specialRegion) {
return [];
}
return Object.keys(
(hkMoTwSource as Record<string, Record<string, string[]>>)[
specialRegion.label
] ?? {},
).map((name, index) => ({
label: name,
key: `${provinceCode}${String(index + 1).padStart(2, '0')}00`,
}));
};
Loading…
Cancel
Save