zhangwencui
8 天以前 4eaf74efba7ead917bf53155a9b9fa59c4743cf8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
<!-- 省市区选择器 (Ant Design Vue 版本) -->
<script lang="ts" setup>
import { onMounted, ref, watch } from 'vue';
 
import { AreaLevelEnum } from '@vben/constants';
 
import { Cascader } from 'ant-design-vue';
 
import { getAreaTree } from '#/api/system/area';
 
defineOptions({ name: 'AreaSelect' });
 
const props = withDefaults(defineProps<Props>(), {
  modelValue: undefined,
  value: undefined,
  level: AreaLevelEnum.DISTRICT,
  disabled: false,
  placeholder: '请选择省市区',
  clearable: true,
  showAllLevels: true,
  separator: '/',
});
 
const emit = defineEmits<{
  (e: 'update:modelValue', value: number[] | string[] | undefined): void;
  (e: 'update:value', value: number[] | string[] | undefined): void;
}>();
 
// 地区数据接口
interface AreaVO {
  id: number;
  name: string;
  code: string;
  parentId?: number;
  sort?: number;
  status?: number;
  children?: AreaVO[];
}
 
// 接受父组件参数
interface Props {
  modelValue?: number[] | string[];
  value?: number[] | string[];
  level?: (typeof AreaLevelEnum)[keyof typeof AreaLevelEnum];
  disabled?: boolean;
  placeholder?: string;
  clearable?: boolean;
  showAllLevels?: boolean;
  separator?: string;
  // eslint-disable-next-line vue/require-default-prop
  formCreateInject?: any;
}
 
// Ant Design Vue Cascader 的 fieldNames 配置
const fieldNames = {
  label: 'name',
  value: 'id',
  children: 'children',
};
 
// 地区树形数据
const areaTree = ref<AreaVO[]>([]);
// 当前选中值
const selectedValue = ref<number[] | undefined>();
// 加载状态
const loading = ref(false);
 
// 加载地区树形数据
async function loadAreaTree(): Promise<void> {
  try {
    loading.value = true;
    const data = await getAreaTree();
 
    // 根据 level 限制层级
    areaTree.value = filterTreeByLevel((data || []) as AreaVO[], props.level);
  } catch (error) {
    console.warn('[AreaSelect] 加载地区数据失败:', error);
    areaTree.value = [];
  } finally {
    loading.value = false;
  }
}
 
// 根据层级过滤树形数据
function filterTreeByLevel(tree: AreaVO[], maxLevel: number): AreaVO[] {
  if (maxLevel <= 0) return [];
 
  return tree.map((node) => {
    const newNode = { ...node };
 
    // 如果当前是最后一层,移除 children
    if (maxLevel === 1) {
      delete newNode.children;
    } else if (node.children && node.children.length > 0) {
      // 递归处理子节点
      newNode.children = filterTreeByLevel(node.children, maxLevel - 1);
    }
 
    return newNode;
  });
}
 
// 处理选中值变化
function handleChange(value: any): void {
  if (value === undefined || value === null) {
    emit('update:modelValue', undefined);
    emit('update:value', undefined);
    return;
  }
 
  emit('update:modelValue', value);
  emit('update:value', value);
}
 
// 同步 modelValue 或 value 到内部选中值
function syncSelectedValue(): void {
  const newValue = props.modelValue || props.value;
 
  if (newValue === undefined || newValue === null) {
    selectedValue.value = undefined;
    return;
  }
 
  // 确保是数组格式
  selectedValue.value = Array.isArray(newValue)
    ? (newValue as number[])
    : [newValue as number];
}
 
// 监听 modelValue 和 value 变化
watch(() => props.modelValue || props.value, syncSelectedValue, {
  immediate: true,
});
 
// 组件挂载时加载数据
onMounted(async () => {
  await loadAreaTree();
});
</script>
 
<template>
  <Cascader
    v-model:value="selectedValue"
    class="w-full"
    :options="areaTree"
    :field-names="fieldNames"
    :disabled="disabled"
    :placeholder="placeholder"
    :allow-clear="clearable"
    :show-search="true"
    :change-on-select="true"
    :loading="loading"
    @change="handleChange"
  />
</template>