@newgudi/region-picker
v1.1.2
Published
Global region picker component for Ant Design and Vben Admin
Maintainers
Readme
GlobalRegionPicker 全球地区选择器
一个支持全球地区选择的 Vue 3 组件,特别优化了中国地区的五级选择(大洲 → 国家 → 省份 → 城市 → 区县)。
功能特性
- ✅ 支持全球地区选择(大洲 → 国家 → 行政区)
- ✅ 中国地区五级选择(大洲 → 国家 → 省份 → 城市 → 区县)
- ✅ 智能搜索功能,支持拼音和中英文搜索
- ✅ 分组显示,按首字母分组
- ✅ 响应式设计,适配各种屏幕尺寸
- ✅ 支持 v-model 双向绑定
- ✅ 主题适配(支持亮色/暗色主题)
- ✅ 弹窗可拖动,点击标题栏可拖动弹窗位置
- ✅ 直辖市智能处理,自动跳过重复的市级选择
安装
# 已内置在项目中,无需额外安装基本使用
1. 在表单中使用(推荐)
<template>
<div class="form-group">
<label>地区选择:</label>
<GlobalRegionPicker
v-model="formData.region"
placeholder="请选择地区"
@change="handleRegionChange"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { GlobalRegionPicker } from '@vben/region-picker';
const formData = ref({
region: null
});
function handleRegionChange(value: any) {
console.log('选择的地区:', value);
// value 结构:
// {
// continent: '亚洲代码',
// country: '国家代码',
// province: '省份代码',
// city: '城市代码',
// district: '区县代码',
// region: '其他国家行政区代码',
// path: ['亚洲', '中国', '广东省', '东莞市', '茶山镇']
// }
}
</script>2. 在 VbenForm 中使用
<template>
<VbenForm :schema="formSchema" @submit="handleSubmit" />
</template>
<script setup lang="ts">
import { ref, markRaw } from 'vue';
import { VbenForm } from '@vben/common-ui';
import { GlobalRegionPicker } from '@vben/region-picker';
const formSchema = ref([
{
fieldName: 'region',
label: '地区',
component: markRaw(GlobalRegionPicker), // 使用 markRaw 避免响应式警告
componentProps: {
placeholder: '请选择地区'
},
rules: [
{ required: true, message: '请选择地区' }
]
}
]);
function handleSubmit(values: any) {
console.log('表单数据:', values);
}
</script>3. 编辑模式(回显数据)
<template>
<GlobalRegionPicker
v-model="selectedRegion"
placeholder="请选择地区"
/>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { GlobalRegionPicker } from '@vben/region-picker';
const selectedRegion = ref(null);
// 从后端加载数据
onMounted(async () => {
const data = await fetchCustomerData();
// 设置地区数据
selectedRegion.value = {
continent: data.continent,
country: data.country,
province: data.province,
city: data.city,
district: data.district,
region: data.region,
path: [
data.continent_name,
data.country_name,
data.province_name,
data.city_name,
data.district_name
].filter(Boolean) // 过滤空值
};
});
</script>数据结构
输入数据(v-model)
interface RegionValue {
continent: string; // 大洲代码,如:'AS'(亚洲)
country: string; // 国家代码,如:'CN'(中国)
province?: string; // 省份代码,如:'440000'(广东省)
city?: string; // 城市代码,如:'441900'(东莞市)
district?: string; // 区县代码,如:'441932'(茶山镇)
region?: string; // 其他国家行政区代码
path: string[]; // 显示路径,如:['亚洲', '中国', '广东省', '东莞市', '茶山镇']
}输出数据(change 事件)
与输入数据结构相同。
Props
| 参数 | 说明 | 类型 | 默认值 |
|------|------|------|--------|
| modelValue | 绑定值 | RegionValue \| null | null |
| placeholder | 输入框占位文本 | string | '请选择地区' |
Events
| 事件名 | 说明 | 回调参数 |
|--------|------|----------|
| update:modelValue | 值变化时触发 | (value: RegionValue) => void |
| change | 值变化时触发 | (value: RegionValue) => void |
数据库字段设计
建议使用以下字段存储地区信息:
-- 地区代码字段
continent VARCHAR(10) COMMENT '大洲代码',
country VARCHAR(10) COMMENT '国家代码',
province VARCHAR(20) COMMENT '省份代码',
city VARCHAR(20) COMMENT '城市代码',
district VARCHAR(20) COMMENT '区县代码',
region VARCHAR(50) COMMENT '其他国家行政区代码',
-- 地区名称字段(用于显示)
continent_name VARCHAR(50) COMMENT '大洲名称',
country_name VARCHAR(100) COMMENT '国家名称',
province_name VARCHAR(100) COMMENT '省份名称',
city_name VARCHAR(100) COMMENT '城市名称',
district_name VARCHAR(100) COMMENT '区县名称',
region_name VARCHAR(100) COMMENT '其他国家行政区名称',
-- 完整地址路径(可选,用于快速显示)
full_address VARCHAR(500) COMMENT '完整地址路径'完整示例
客户管理表单示例
<template>
<VbenModal
v-model:open="visible"
:title="isEdit ? '编辑客户' : '新增客户'"
@ok="handleSubmit"
>
<VbenForm ref="formRef" :schema="formSchema" />
</VbenModal>
</template>
<script setup lang="ts">
import { ref, computed, watch, nextTick, markRaw } from 'vue';
import { VbenModal, VbenForm } from '@vben/common-ui';
import { GlobalRegionPicker } from '@vben/region-picker';
import { message } from 'ant-design-vue';
const props = defineProps<{
open: boolean;
record?: any;
}>();
const emit = defineEmits<{
(e: 'update:open', value: boolean): void;
(e: 'success'): void;
}>();
const visible = computed({
get: () => props.open,
set: (val) => emit('update:open', val)
});
const formRef = ref();
const isEdit = computed(() => !!props.record?.id);
const selectedRegion = ref(null);
// 表单配置
const formSchema = computed(() => [
{
fieldName: 'name',
label: '客户名称',
component: 'Input',
rules: [{ required: true, message: '请输入客户名称' }]
},
{
fieldName: 'contact',
label: '联系人',
component: 'Input'
},
{
fieldName: 'phone',
label: '联系电话',
component: 'Input'
},
{
fieldName: 'email',
label: '电子邮箱',
component: 'Input'
}
]);
// 监听弹窗打开,加载数据
watch(() => props.open, async (newVal) => {
if (newVal && props.record) {
await nextTick();
// 设置表单数据
formRef.value?.setValues({
name: props.record.name,
contact: props.record.contact,
phone: props.record.phone,
email: props.record.email
});
// 设置地区数据
if (props.record.country) {
selectedRegion.value = {
continent: props.record.continent || '',
country: props.record.country || '',
province: props.record.province || '',
city: props.record.city || '',
district: props.record.district || '',
region: props.record.region || '',
path: [
props.record.continent_name,
props.record.country_name,
props.record.province_name,
props.record.city_name,
props.record.district_name
].filter(Boolean)
};
}
}
});
// 提交表单
async function handleSubmit() {
try {
const values = await formRef.value?.validate();
// 合并地区数据
const submitData = {
...values,
continent: selectedRegion.value?.continent || '',
country: selectedRegion.value?.country || '',
province: selectedRegion.value?.province || '',
city: selectedRegion.value?.city || '',
district: selectedRegion.value?.district || '',
region: selectedRegion.value?.region || '',
continent_name: selectedRegion.value?.path[0] || '',
country_name: selectedRegion.value?.path[1] || '',
province_name: selectedRegion.value?.path[2] || '',
city_name: selectedRegion.value?.path[3] || '',
district_name: selectedRegion.value?.path[4] || '',
full_address: selectedRegion.value?.path.join(' / ') || ''
};
// 调用 API
if (isEdit.value) {
await updateCustomer(props.record.id, submitData);
message.success('更新成功');
} else {
await createCustomer(submitData);
message.success('创建成功');
}
visible.value = false;
emit('success');
} catch (error) {
console.error('提交失败:', error);
}
}
</script>注意事项
使用 markRaw:在 VbenForm 的 schema 中使用组件时,建议用
markRaw()包裹组件,避免 Vue 响应式警告:component: markRaw(GlobalRegionPicker)数据回显:编辑模式下,需要同时提供代码和名称数据,组件会根据
path数组显示完整路径。数据验证:建议在表单提交前验证地区数据的完整性:
if (!selectedRegion.value?.country) { message.error('请选择地区'); return; }中国地区:对于中国地区,建议至少选择到城市级别,以保证地址的准确性。
其他国家:其他国家可能只有国家级别或国家+行政区两级,根据实际数据结构处理。
主题适配
组件已内置主题适配,会自动跟随系统主题切换:
/* 亮色主题 */
body.light .global-region-picker {
/* 自动应用亮色样式 */
}
/* 暗色主题 */
body.dark .global-region-picker {
/* 自动应用暗色样式 */
}常见问题
Q1: 编辑时输入框不显示数据?
A: 确保传入的数据包含 path 数组,组件通过 path 数组生成显示文本:
selectedRegion.value = {
continent: 'AS',
country: 'CN',
province: '440000',
city: '441900',
district: '441932',
path: ['亚洲', '中国', '广东省', '东莞市', '茶山镇'] // 必须提供
};Q2: 如何获取选中的地区名称?
A: 通过 path 数组获取:
const regionNames = selectedRegion.value?.path || [];
const fullAddress = regionNames.join(' / '); // "亚洲 / 中国 / 广东省 / 东莞市 / 茶山镇"Q3: 如何只选择到省份或城市级别?
A: 组件支持任意级别的选择,用户可以在任意级别点击"确定"按钮完成选择。
Q4: 如何自定义样式?
A: 可以通过 CSS 覆盖组件样式:
.global-region-picker {
/* 自定义样式 */
}
.global-region-picker .ant-input {
/* 自定义输入框样式 */
}Q5: 弹窗如何拖动?
A: 点击弹窗的标题栏("选择地区"文字区域)并按住鼠标左键,即可拖动弹窗到任意位置。松开鼠标后弹窗会固定在新位置。关闭弹窗后,位置会自动重置。
Q6: 为什么直辖市只显示一次?
A: 组件已针对中国直辖市(北京、天津、上海、重庆)做了特殊处理,选择直辖市后会自动跳过城市选择,直接显示区县,避免出现"上海市 / 上海市"这样的重复。
更新日志
v1.2.0 (2026-02-01)
- ✨ 新增弹窗拖动功能,点击标题栏可拖动弹窗
- ✨ 优化直辖市选择逻辑,自动跳过重复的市级选择
- 🐛 修复直辖市路径重复显示问题(如"上海市 / 上海市")
- 🐛 修复文件上传字段类型错误
- 🐛 修复无障碍访问警告
v1.1.0 (2026-01-31)
- ✨ 新增数据回显功能
- ✨ 支持编辑模式
- 🐛 修复组件响应式警告
v1.0.0 (2024-01-31)
- ✨ 初始版本发布
- ✨ 支持全球地区选择
- ✨ 支持中国五级地区选择
- ✨ 支持智能搜索
- ✨ 支持主题切换
- ✨ 支持数据回显
技术支持
如有问题或建议,请联系开发团队。
