国际地址表单开发:支持美国、英国、日本、香港的多国格式
国际地址表单开发:多国格式支持
在全球化产品中,地址表单是最复杂也是最容易出问题的组件之一。不同国家的地址格式差异巨大:美国有州和ZIP Code,英国有Postcode但没有州,日本地址顺序完全相反。本文将对比四个典型国家的地址格式,并提供一套统一的开发方案。
四国地址格式对比
美国地址格式
```
John Smith
123 Main St, Apt 5B
Los Angeles, CA 90001
United States
```
特征:
英国地址格式
```
John Smith
12 High Street
Manchester
M1 1AA
United Kingdom
```
特征:
日本地址格式
```
〒150-0001
東京都渋谷区神宮前1-2-3
山田太郎様
```
特征:
香港地址格式
```
Mr. John Smith
Flat 12A, 15/F, Tower 2
The Victoria Towers
188 Canton Road
Tsim Sha Tsui, Kowloon
Hong Kong
```
特征:
统一表单设计方案
核心原则
React 实现方案
```typescript
import React, { useState, useEffect } from 'react';
interface CountryConfig {
code: string;
name: string;
fields: AddressField[];
zipPattern: string;
zipPlaceholder: string;
}
interface AddressField {
name: string;
label: string;
placeholder: string;
required: boolean;
type: 'text' | 'select' | 'postal_code';
options?: string[];
}
const COUNTRY_CONFIGS: Record<string, CountryConfig> = {
US: {
code: 'US',
name: 'United States',
fields: [
{ name: 'address_line1', label: 'Street Address', placeholder: '123 Main St', required: true, type: 'text' },
{ name: 'address_line2', label: 'Apt / Suite / Unit', placeholder: 'Apt 5B', required: false, type: 'text' },
{ name: 'city', label: 'City', placeholder: 'Los Angeles', required: true, type: 'text' },
{ name: 'state', label: 'State', placeholder: 'CA', required: true, type: 'select', options: ['CA','NY','TX','FL','IL'] },
{ name: 'zip', label: 'ZIP Code', placeholder: '90210', required: true, type: 'postal_code' }
],
zipPattern: '^\d{5}(-\d{4})?$',
zipPlaceholder: '90210'
},
GB: {
code: 'GB',
name: 'United Kingdom',
fields: [
{ name: 'address_line1', label: 'Street Address', placeholder: '12 High Street', required: true, type: 'text' },
{ name: 'address_line2', label: 'Address Line 2', placeholder: 'Optional', required: false, type: 'text' },
{ name: 'city', label: 'City / Town', placeholder: 'Manchester', required: true, type: 'text' },
{ name: 'county', label: 'County (Optional)', placeholder: 'Greater Manchester', required: false, type: 'text' },
{ name: 'zip', label: 'Postcode', placeholder: 'M1 1AA', required: true, type: 'postal_code' }
],
zipPattern: '^[A-Z]{1,2}\d[A-Z\d]? \d[A-Z]{2}$',
zipPlaceholder: 'M1 1AA'
},
JP: {
code: 'JP',
name: 'Japan',
fields: [
{ name: 'zip', label: 'Postal Code', placeholder: '150-0001', required: true, type: 'postal_code' },
{ name: 'prefecture', label: 'Prefecture', placeholder: 'Tokyo', required: true, type: 'select', options: ['Tokyo','Osaka','Kyoto'] },
{ name: 'city', label: 'City / Ward', placeholder: 'Shibuya-ku', required: true, type: 'text' },
{ name: 'address_line1', label: 'Address (Chome-Banchi-Go)', placeholder: '1-2-3 Jingumae', required: true, type: 'text' },
{ name: 'address_line2', label: 'Building / Room', placeholder: 'Building A, Room 101', required: false, type: 'text' }
],
zipPattern: '^\d{3}-\d{4}$',
zipPlaceholder: '150-0001'
},
HK: {
code: 'HK',
name: 'Hong Kong',
fields: [
{ name: 'address_line1', label: 'Flat / Floor / Block', placeholder: 'Flat 12A, 15/F, Tower 2', required: true, type: 'text' },
{ name: 'address_line2', label: 'Building Name', placeholder: 'The Victoria Towers', required: false, type: 'text' },
{ name: 'address_line3', label: 'Street', placeholder: '188 Canton Road', required: true, type: 'text' },
{ name: 'district', label: 'District', placeholder: 'Tsim Sha Tsui', required: true, type: 'text' },
{ name: 'zip', label: 'Postal Code (Optional)', placeholder: '999077', required: false, type: 'postal_code' }
],
zipPattern: '^\d{6}$',
zipPlaceholder: '999077'
}
};
const InternationalAddressForm: React.FC = () => {
const [country, setCountry] = useState('US');
const [formData, setFormData] = useState<Record<string, string>>({});
const [errors, setErrors] = useState<Record<string, string>>({});
const config = COUNTRY_CONFIGS[country];
const validateField = (field: AddressField, value: string): string => {
if (field.required && !value.trim()) {
return `${field.label} is required`;
}
if (field.type === 'postal_code' && value) {
const pattern = new RegExp(config.zipPattern);
if (!pattern.test(value)) {
return `Invalid ${field.label} format`;
}
}
return '';
};
const handleChange = (fieldName: string, value: string) => {
setFormData(prev => ({ ...prev, [fieldName]: value }));
const field = config.fields.find(f => f.name === fieldName);
if (field) {
const error = validateField(field, value);
setErrors(prev => ({ ...prev, [fieldName]: error }));
}
};
return (
<form>
<select value={country} onChange={(e) => setCountry(e.target.value)}>
{Object.values(COUNTRY_CONFIGS).map(c => (
<option key={c.code} value={c.code}>{c.name}</option>
))}
</select>
{config.fields.map(field => (
<div key={field.name}>
<label>{field.label}{field.required && ' *'}</label>
{field.type === 'select' ? (
<select
value={formData[field.name] || ''}
onChange={(e) => handleChange(field.name, e.target.value)}
>
<option value="">Select...</option>
{field.options?.map(opt => <option key={opt} value={opt}>{opt}</option>)}
</select>
) : (
<input
type="text"
placeholder={field.placeholder}
value={formData[field.name] || ''}
onChange={(e) => handleChange(field.name, e.target.value)}
/>
)}
{errors[field.name] && <span className="error">{errors[field.name]}</span>}
</div>
))}
</form>
);
};
```
邮编/邮政编码校验规则
| 国家 | 格式 | 正则表达式 | 示例 |
|---|---|---|---|
| 美国 | 5位或5+4位 | `^\d{5}(-\d{4})?$` | 90210, 90210-1234 |
| 英国 | 字母+数字 | `^[A-Z]{1,2}\d[A-Z\d]? \d[A-Z]{2}$` | M1 1AA, SW1A 1AA |
| 日本 | 3位-4位数字 | `^\d{3}-\d{4}$` | 150-0001, 100-0001 |
| 香港 | 6位数字(可选) | `^\d{6}$` | 999077 |
| 加拿大 | A1A 1A1 | `^[A-Z]\d[A-Z] \d[A-Z]\d$` | K1A 0B1 |
| 澳大利亚 | 4位数字 | `^\d{4}$` | 2000 |
| 德国 | 5位数字 | `^\d{5}$` | 10115 |
后端数据存储设计
```sql
CREATE TABLE international_addresses (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL,
country_code CHAR(2) NOT NULL,
-- 通用字段(所有国家都有)
recipient_name VARCHAR(200) NOT NULL,
address_line1 VARCHAR(300) NOT NULL,
address_line2 VARCHAR(300),
address_line3 VARCHAR(300),
city VARCHAR(100) NOT NULL,
-- 国家特定字段
state_code VARCHAR(20), -- 美国州、日本都道府县
postal_code VARCHAR(20) NOT NULL,
county VARCHAR(100), -- 英国郡
district VARCHAR(100), -- 香港区
province VARCHAR(100), -- 加拿大省
-- 验证元数据
validation_status VARCHAR(20) DEFAULT 'pending',
formatted_address TEXT, -- 完整格式化地址
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- 按国家和邮编创建索引
CREATE INDEX idx_intl_addr_country ON international_addresses(country_code);
CREATE INDEX idx_intl_addr_postal ON international_addresses(postal_code);
```
常见坑点
总结
国际地址表单设计的核心是灵活性和本地化:
通过本文的配置驱动方案,你可以轻松扩展支持更多国家,只需添加新的 CountryConfig 即可。