|
@@ -0,0 +1,358 @@
|
|
|
+<template>
|
|
|
+ <div class="simple-region-selector">
|
|
|
+ <el-select
|
|
|
+ v-model="selectedCountry"
|
|
|
+ placeholder="选择国家"
|
|
|
+ filterable
|
|
|
+ class="!w-48"
|
|
|
+ @change="onCountryChange"
|
|
|
+ :loading="loading"
|
|
|
+ >
|
|
|
+ <el-option-group v-if="props.showAll !== false" label="全部">
|
|
|
+ <el-option :label="'全部国家'" :value="ALL_COUNTRY" />
|
|
|
+ </el-option-group>
|
|
|
+ <el-option-group v-for="continent in continentOptions" :key="continent.label" :label="continent.label">
|
|
|
+ <el-option v-for="country in continent.countries" :key="country.code" :label="country.name"
|
|
|
+ :value="country.code" />
|
|
|
+ </el-option-group>
|
|
|
+ </el-select>
|
|
|
+
|
|
|
+ <el-select
|
|
|
+ v-if="selectedCountry && selectedCountry !== ALL_COUNTRY && states.length > 0"
|
|
|
+ v-model="selectedState"
|
|
|
+ placeholder="选择省份/州"
|
|
|
+ filterable
|
|
|
+ class="!w-48 ml-2"
|
|
|
+ @change="onStateChange"
|
|
|
+ >
|
|
|
+ <el-option-group v-if="props.showAll !== false" label="全部">
|
|
|
+ <el-option :label="'全部省/州'" :value="ALL_STATE" />
|
|
|
+ </el-option-group>
|
|
|
+ <el-option v-for="state in states" :key="state.code" :label="state.name" :value="state.code" />
|
|
|
+ </el-select>
|
|
|
+
|
|
|
+ <el-select
|
|
|
+ v-if="selectedState && selectedState !== ALL_STATE && cities.length > 0"
|
|
|
+ v-model="selectedCity"
|
|
|
+ placeholder="选择城市"
|
|
|
+ filterable
|
|
|
+ class="!w-48 ml-2"
|
|
|
+ >
|
|
|
+ <el-option-group v-if="props.showAll !== false" label="全部">
|
|
|
+ <el-option :label="'全部城市'" :value="ALL_CITY" />
|
|
|
+ </el-option-group>
|
|
|
+ <el-option v-for="city in cities" :key="city.code" :label="city.name" :value="city.name" />
|
|
|
+ </el-select>
|
|
|
+
|
|
|
+ <el-button
|
|
|
+ v-if="(props.showAll !== false) || selectedCountry"
|
|
|
+ type="primary"
|
|
|
+ size="small"
|
|
|
+ class="ml-2"
|
|
|
+ @click="addRegion"
|
|
|
+ >
|
|
|
+ 添加地区
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, onMounted } from 'vue';
|
|
|
+import {
|
|
|
+ loadCountriesData,
|
|
|
+ loadChinaProvinces,
|
|
|
+ loadChinaCities,
|
|
|
+ loadOtherStates,
|
|
|
+ loadOtherCities,
|
|
|
+ loadUsStateZhMap,
|
|
|
+ loadUsCityZhMap
|
|
|
+} from './region-data-loader';
|
|
|
+
|
|
|
+interface RegionData {
|
|
|
+ country: string;
|
|
|
+ state?: string;
|
|
|
+ city?: string;
|
|
|
+ code: string;
|
|
|
+}
|
|
|
+
|
|
|
+const props = defineProps<{
|
|
|
+ modelValue?: RegionData[];
|
|
|
+ showAll?: boolean;
|
|
|
+}>();
|
|
|
+
|
|
|
+const emit = defineEmits<{
|
|
|
+ 'update:modelValue': [value: RegionData[]];
|
|
|
+}>();
|
|
|
+
|
|
|
+const continentOptions = ref<any[]>([]);
|
|
|
+const selectedCountry = ref('');
|
|
|
+const selectedState = ref('');
|
|
|
+const selectedCity = ref('');
|
|
|
+const states = ref<any[]>([]);
|
|
|
+const cities = ref<any[]>([]);
|
|
|
+const countryCode = ref('');
|
|
|
+const loading = ref(false);
|
|
|
+const ALL_COUNTRY = '__ALL_COUNTRY__';
|
|
|
+const ALL_STATE = '__ALL_STATE__';
|
|
|
+const ALL_CITY = '__ALL_CITY__';
|
|
|
+
|
|
|
+// 动态加载的数据
|
|
|
+let countriesData: any[] = [];
|
|
|
+let chinaProvinces: string[] = [];
|
|
|
+let chinaCities: { [key: string]: string[] } = {};
|
|
|
+let otherStates: { [key: string]: string[] } = {};
|
|
|
+let otherCities: { [key: string]: { [key: string]: string[] } } = {};
|
|
|
+let usStateZhMap: Record<string, string> = {};
|
|
|
+let usCityZhMap: Record<string, Record<string, string>> = {};
|
|
|
+
|
|
|
+// 简化的美国州数据(避免使用 country-state-city)
|
|
|
+const usStates = [
|
|
|
+ { name: '阿拉巴马州', code: 'AL' },
|
|
|
+ { name: '阿拉斯加州', code: 'AK' },
|
|
|
+ { name: '亚利桑那州', code: 'AZ' },
|
|
|
+ { name: '阿肯色州', code: 'AR' },
|
|
|
+ { name: '加利福尼亚州', code: 'CA' },
|
|
|
+ { name: '科罗拉多州', code: 'CO' },
|
|
|
+ { name: '康涅狄格州', code: 'CT' },
|
|
|
+ { name: '特拉华州', code: 'DE' },
|
|
|
+ { name: '佛罗里达州', code: 'FL' },
|
|
|
+ { name: '佐治亚州', code: 'GA' },
|
|
|
+ { name: '夏威夷州', code: 'HI' },
|
|
|
+ { name: '爱达荷州', code: 'ID' },
|
|
|
+ { name: '伊利诺伊州', code: 'IL' },
|
|
|
+ { name: '印第安纳州', code: 'IN' },
|
|
|
+ { name: '爱荷华州', code: 'IA' },
|
|
|
+ { name: '堪萨斯州', code: 'KS' },
|
|
|
+ { name: '肯塔基州', code: 'KY' },
|
|
|
+ { name: '路易斯安那州', code: 'LA' },
|
|
|
+ { name: '缅因州', code: 'ME' },
|
|
|
+ { name: '马里兰州', code: 'MD' },
|
|
|
+ { name: '马萨诸塞州', code: 'MA' },
|
|
|
+ { name: '密歇根州', code: 'MI' },
|
|
|
+ { name: '明尼苏达州', code: 'MN' },
|
|
|
+ { name: '密西西比州', code: 'MS' },
|
|
|
+ { name: '密苏里州', code: 'MO' },
|
|
|
+ { name: '蒙大拿州', code: 'MT' },
|
|
|
+ { name: '内布拉斯加州', code: 'NE' },
|
|
|
+ { name: '内华达州', code: 'NV' },
|
|
|
+ { name: '新罕布什尔州', code: 'NH' },
|
|
|
+ { name: '新泽西州', code: 'NJ' },
|
|
|
+ { name: '新墨西哥州', code: 'NM' },
|
|
|
+ { name: '纽约州', code: 'NY' },
|
|
|
+ { name: '北卡罗来纳州', code: 'NC' },
|
|
|
+ { name: '北达科他州', code: 'ND' },
|
|
|
+ { name: '俄亥俄州', code: 'OH' },
|
|
|
+ { name: '俄克拉荷马州', code: 'OK' },
|
|
|
+ { name: '俄勒冈州', code: 'OR' },
|
|
|
+ { name: '宾夕法尼亚州', code: 'PA' },
|
|
|
+ { name: '罗得岛州', code: 'RI' },
|
|
|
+ { name: '南卡罗来纳州', code: 'SC' },
|
|
|
+ { name: '南达科他州', code: 'SD' },
|
|
|
+ { name: '田纳西州', code: 'TN' },
|
|
|
+ { name: '德克萨斯州', code: 'TX' },
|
|
|
+ { name: '犹他州', code: 'UT' },
|
|
|
+ { name: '佛蒙特州', code: 'VT' },
|
|
|
+ { name: '弗吉尼亚州', code: 'VA' },
|
|
|
+ { name: '华盛顿州', code: 'WA' },
|
|
|
+ { name: '西弗吉尼亚州', code: 'WV' },
|
|
|
+ { name: '威斯康星州', code: 'WI' },
|
|
|
+ { name: '怀俄明州', code: 'WY' }
|
|
|
+];
|
|
|
+
|
|
|
+// 初始化地区数据
|
|
|
+const initRegionData = async () => {
|
|
|
+ loading.value = true;
|
|
|
+ try {
|
|
|
+ // 动态加载国家数据
|
|
|
+ countriesData = await loadCountriesData();
|
|
|
+
|
|
|
+ // 按大洲分组
|
|
|
+ const continentMap: { [key: string]: any[] } = {
|
|
|
+ '亚洲': [],
|
|
|
+ '欧洲': [],
|
|
|
+ '北美洲': [],
|
|
|
+ '南美洲': [],
|
|
|
+ '大洋洲': [],
|
|
|
+ '非洲': []
|
|
|
+ };
|
|
|
+
|
|
|
+ countriesData.forEach((country: any) => {
|
|
|
+ if (continentMap[country.continent]) {
|
|
|
+ continentMap[country.continent].push(country);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 转换为数组格式
|
|
|
+ continentOptions.value = Object.keys(continentMap).map(continent => ({
|
|
|
+ label: continent,
|
|
|
+ countries: continentMap[continent].sort((a, b) => a.name.localeCompare(b.name))
|
|
|
+ }));
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Failed to initialize region data:', error);
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 国家变化处理
|
|
|
+const onCountryChange = async (countryCodeValue: string) => {
|
|
|
+ selectedState.value = '';
|
|
|
+ selectedCity.value = '';
|
|
|
+ cities.value = [];
|
|
|
+ countryCode.value = countryCodeValue === ALL_COUNTRY ? '' : countryCodeValue;
|
|
|
+
|
|
|
+ // 根据国家代码获取省份/州
|
|
|
+ if (countryCodeValue === ALL_COUNTRY && props.showAll !== false) {
|
|
|
+ states.value = [];
|
|
|
+ cities.value = [];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (countryCodeValue === 'CN') {
|
|
|
+ chinaProvinces = await loadChinaProvinces();
|
|
|
+ states.value = chinaProvinces.map(name => ({ name, code: name }));
|
|
|
+ } else if (countryCodeValue === 'US') {
|
|
|
+ // 使用简化的美国州数据,避免 country-state-city 依赖
|
|
|
+ states.value = usStates;
|
|
|
+ } else {
|
|
|
+ otherStates = await loadOtherStates();
|
|
|
+ if (otherStates[countryCodeValue]) {
|
|
|
+ states.value = otherStates[countryCodeValue].map((name: string) => ({ name, code: name }));
|
|
|
+ } else {
|
|
|
+ states.value = [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Failed to load states for country:', countryCodeValue, error);
|
|
|
+ states.value = [];
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 省份变化处理
|
|
|
+const onStateChange = async (stateCodeOrName: string) => {
|
|
|
+ selectedCity.value = '';
|
|
|
+
|
|
|
+ // 根据国家和省份获取城市
|
|
|
+ if (countryCode.value === 'CN') {
|
|
|
+ const stateName = stateCodeOrName === ALL_STATE ? '' : stateCodeOrName;
|
|
|
+ if (stateCodeOrName === ALL_STATE && props.showAll !== false) {
|
|
|
+ cities.value = [];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ chinaCities = await loadChinaCities();
|
|
|
+ if (chinaCities[stateName]) {
|
|
|
+ cities.value = chinaCities[stateName].map((name: string) => ({ name, code: name }));
|
|
|
+ } else {
|
|
|
+ cities.value = [];
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Failed to load China cities:', error);
|
|
|
+ cities.value = [];
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (countryCode.value === 'US') {
|
|
|
+ // 加载美国州城市
|
|
|
+ if (stateCodeOrName === ALL_STATE && props.showAll !== false) {
|
|
|
+ cities.value = [];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ // 优先使用内置中文城市表(按州中文名)
|
|
|
+ const st = states.value.find((s: any) => s.code === stateCodeOrName);
|
|
|
+ const zhStateName = st ? st.name : '';
|
|
|
+ otherCities = await loadOtherCities();
|
|
|
+
|
|
|
+ if (otherCities['US'] && otherCities['US'][zhStateName]) {
|
|
|
+ cities.value = otherCities['US'][zhStateName].map((name: string) => ({ name, code: name }));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果没有预定义的城市数据,返回空数组
|
|
|
+ cities.value = [];
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Failed to load US cities:', error);
|
|
|
+ cities.value = [];
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const stateName = stateCodeOrName;
|
|
|
+ otherCities = await loadOtherCities();
|
|
|
+ if (otherCities[countryCode.value] && otherCities[countryCode.value][stateName]) {
|
|
|
+ cities.value = otherCities[countryCode.value][stateName].map((name: string) => ({ name, code: name }));
|
|
|
+ } else {
|
|
|
+ cities.value = [];
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Failed to load other cities:', error);
|
|
|
+ cities.value = [];
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 添加地区
|
|
|
+const addRegion = () => {
|
|
|
+ if (!selectedCountry.value && props.showAll === false) return;
|
|
|
+
|
|
|
+ let countryName = '全部国家';
|
|
|
+ let countryCodeFinal = '';
|
|
|
+ if (selectedCountry.value && selectedCountry.value !== ALL_COUNTRY) {
|
|
|
+ const country = countriesData.find((c: any) => c.code === countryCode.value);
|
|
|
+ if (!country) return;
|
|
|
+ countryName = country.name;
|
|
|
+ countryCodeFinal = country.code;
|
|
|
+ }
|
|
|
+
|
|
|
+ const newRegion: RegionData = {
|
|
|
+ country: countryName,
|
|
|
+ code: countryCodeFinal
|
|
|
+ };
|
|
|
+
|
|
|
+ if (selectedState.value || (props.showAll !== false && selectedCountry.value && selectedState.value === ALL_STATE)) {
|
|
|
+ if (countryCode.value === 'US') {
|
|
|
+ const st = states.value.find((s: any) => s.code === selectedState.value);
|
|
|
+ newRegion.state = selectedState.value === ALL_STATE ? '' : (st ? st.name : selectedState.value);
|
|
|
+ } else {
|
|
|
+ newRegion.state = selectedState.value === ALL_STATE ? '' : selectedState.value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (selectedCity.value || (props.showAll !== false && selectedState.value && selectedCity.value === ALL_CITY)) {
|
|
|
+ newRegion.city = selectedCity.value === ALL_CITY ? '' : selectedCity.value;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('添加的地区数据:', newRegion);
|
|
|
+
|
|
|
+ const currentRegions = props.modelValue || [];
|
|
|
+ const filteredRegions = currentRegions.filter(item => item != 'All');
|
|
|
+ const newRegions = [...filteredRegions, newRegion];
|
|
|
+
|
|
|
+ emit('update:modelValue', newRegions);
|
|
|
+
|
|
|
+ // 重置选择
|
|
|
+ selectedCountry.value = '';
|
|
|
+ selectedState.value = '';
|
|
|
+ selectedCity.value = '';
|
|
|
+ states.value = [];
|
|
|
+ cities.value = [];
|
|
|
+ countryCode.value = '';
|
|
|
+};
|
|
|
+
|
|
|
+// 组件挂载时初始化地区数据
|
|
|
+onMounted(async () => {
|
|
|
+ await initRegionData();
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.simple-region-selector {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+</style>
|