fix: 修复 Vben 表单地区选择器只展示末级地区的问题
- 新增 antd、antdv-next、ele 三端 AreaCascader 组件 - 将表单值保持为末级 areaId,展示时回显完整省市区级联路径 - 替换会员、CRM、商城交易相关表单的地区选择字段 - 按组件库默认行为保留清空和搜索默认关闭,并在使用处显式开启pull/361/MERGE
parent
431cf8f268
commit
fb80749156
|
|
@ -0,0 +1,125 @@
|
|||
<script lang="ts" setup>
|
||||
import type { CascaderProps } from 'ant-design-vue';
|
||||
|
||||
import type { SystemAreaApi } from '#/api/system/area';
|
||||
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { Cascader } from 'ant-design-vue';
|
||||
|
||||
import { getAreaTree } from '#/api/system/area';
|
||||
|
||||
defineOptions({ name: 'AreaCascader' });
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
allowClear: false,
|
||||
changeOnSelect: false,
|
||||
modelValue: undefined,
|
||||
placeholder: '请选择省市区',
|
||||
showSearch: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value?: number];
|
||||
}>();
|
||||
|
||||
interface Props {
|
||||
allowClear?: boolean;
|
||||
changeOnSelect?: boolean;
|
||||
modelValue?: number;
|
||||
placeholder?: string;
|
||||
showSearch?: boolean;
|
||||
}
|
||||
|
||||
type AreaTreeNode = SystemAreaApi.Area & {
|
||||
children?: AreaTreeNode[];
|
||||
};
|
||||
|
||||
const areaTree = ref<AreaTreeNode[]>([]); // 地区树
|
||||
const loading = ref(false); // 加载状态
|
||||
const selectedPath = ref<number[]>(); // 选中的地区路径
|
||||
|
||||
const fieldNames = {
|
||||
children: 'children', // 子级字段
|
||||
label: 'name', // 标签字段
|
||||
value: 'id', // 值字段
|
||||
};
|
||||
|
||||
/**
|
||||
* 查找地区编号对应的级联路径
|
||||
*
|
||||
* @param tree 地区树
|
||||
* @param areaId 地区编号
|
||||
* @returns 省市区编号路径
|
||||
*/
|
||||
function findAreaPath(
|
||||
tree: AreaTreeNode[],
|
||||
areaId?: number,
|
||||
): number[] | undefined {
|
||||
if (areaId === undefined || areaId === null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for (const area of tree) {
|
||||
if (area.id === undefined) {
|
||||
continue;
|
||||
}
|
||||
if (area.id === areaId) {
|
||||
return [area.id];
|
||||
}
|
||||
|
||||
const childPath = area.children?.length
|
||||
? findAreaPath(area.children, areaId)
|
||||
: undefined;
|
||||
if (childPath) {
|
||||
return [area.id, ...childPath];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 同步级联选择器展示路径 */
|
||||
function syncSelectedPath() {
|
||||
selectedPath.value = findAreaPath(areaTree.value, props.modelValue);
|
||||
}
|
||||
|
||||
/** 选择地区后回写最后一级地区编号 */
|
||||
const handleChange: NonNullable<CascaderProps['onChange']> = (value) => {
|
||||
if (!value?.length) {
|
||||
emit('update:modelValue', undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
const path = Array.isArray(value[0]) ? value[0] : value;
|
||||
const leafValue = path.at(-1);
|
||||
const areaId =
|
||||
typeof leafValue === 'number' ? leafValue : Number(leafValue);
|
||||
emit('update:modelValue', Number.isNaN(areaId) ? undefined : areaId);
|
||||
};
|
||||
|
||||
watch(() => props.modelValue, syncSelectedPath);
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
areaTree.value = (await getAreaTree()) as AreaTreeNode[];
|
||||
syncSelectedPath();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Cascader
|
||||
v-model:value="selectedPath"
|
||||
:allow-clear="allowClear"
|
||||
:change-on-select="changeOnSelect"
|
||||
:field-names="fieldNames"
|
||||
:loading="loading"
|
||||
:options="areaTree"
|
||||
:placeholder="placeholder"
|
||||
:show-search="showSearch"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</template>
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default as AreaCascader } from './area-cascader.vue';
|
||||
|
|
@ -1,12 +1,14 @@
|
|||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { markRaw } from 'vue';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
|
||||
import { getAreaTree } from '#/api/system/area';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { AreaCascader } from '#/components/area';
|
||||
import { getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
|
|
@ -119,11 +121,12 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||
{
|
||||
fieldName: 'areaId',
|
||||
label: '地址',
|
||||
component: 'ApiTreeSelect',
|
||||
component: markRaw(AreaCascader),
|
||||
componentProps: {
|
||||
api: getAreaTree,
|
||||
fieldNames: { label: 'name', value: 'id', children: 'children' },
|
||||
allowClear: true,
|
||||
class: '!w-full',
|
||||
placeholder: '请选择地址',
|
||||
showSearch: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { markRaw } from 'vue';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
|
||||
import { getSimpleContactList } from '#/api/crm/contact';
|
||||
import { getCustomerSimpleList } from '#/api/crm/customer';
|
||||
import { getAreaTree } from '#/api/system/area';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { AreaCascader } from '#/components/area';
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
export function useFormSchema(): VbenFormSchema[] {
|
||||
|
|
@ -143,11 +145,12 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||
{
|
||||
fieldName: 'areaId',
|
||||
label: '地址',
|
||||
component: 'ApiTreeSelect',
|
||||
component: markRaw(AreaCascader),
|
||||
componentProps: {
|
||||
api: getAreaTree,
|
||||
fieldNames: { label: 'name', value: 'id', children: 'children' },
|
||||
allowClear: true,
|
||||
class: '!w-full',
|
||||
placeholder: '请选择地址',
|
||||
showSearch: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { markRaw } from 'vue';
|
||||
|
||||
import { z } from '@vben/common-ui';
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
|
||||
import { getAreaTree } from '#/api/system/area';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { AreaCascader } from '#/components/area';
|
||||
import { getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
|
|
@ -128,12 +130,12 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||
{
|
||||
fieldName: 'areaId',
|
||||
label: '地址',
|
||||
component: 'ApiTreeSelect',
|
||||
component: markRaw(AreaCascader),
|
||||
componentProps: {
|
||||
api: getAreaTree,
|
||||
fieldNames: { label: 'name', value: 'id', children: 'children' },
|
||||
placeholder: '请选择地址',
|
||||
allowClear: true,
|
||||
class: '!w-full',
|
||||
placeholder: '请选择地址',
|
||||
showSearch: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { markRaw } from 'vue';
|
||||
|
||||
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { z } from '#/adapter/form';
|
||||
import { getAreaTree } from '#/api/system/area';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { AreaCascader } from '#/components/area';
|
||||
import { getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
|
|
@ -62,12 +64,13 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||
{
|
||||
fieldName: 'areaId',
|
||||
label: '门店所在地区',
|
||||
component: 'ApiTreeSelect',
|
||||
component: markRaw(AreaCascader),
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
api: getAreaTree,
|
||||
fieldNames: { label: 'name', value: 'id', children: 'children' },
|
||||
allowClear: true,
|
||||
class: '!w-full',
|
||||
placeholder: '请选择省市区',
|
||||
showSearch: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,13 +2,15 @@ import type { VbenFormSchema } from '#/adapter/form';
|
|||
import type { VxeGridPropTypes } from '#/adapter/vxe-table';
|
||||
import type { MallDeliveryPickUpStoreApi } from '#/api/mall/trade/delivery/pickUpStore';
|
||||
|
||||
import { markRaw } from 'vue';
|
||||
|
||||
import { DeliveryTypeEnum, DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
import { convertToInteger, formatToFraction } from '@vben/utils';
|
||||
|
||||
import { getSimpleDeliveryExpressList } from '#/api/mall/trade/delivery/express';
|
||||
import { getSimpleDeliveryPickUpStoreList } from '#/api/mall/trade/delivery/pickUpStore';
|
||||
import { getAreaTree } from '#/api/system/area';
|
||||
import { AreaCascader } from '#/components/area';
|
||||
import { getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 关联数据 */
|
||||
|
|
@ -368,14 +370,13 @@ export function useAddressFormSchema(): VbenFormSchema[] {
|
|||
{
|
||||
fieldName: 'receiverAreaId',
|
||||
label: '所在地',
|
||||
component: 'ApiTreeSelect',
|
||||
component: markRaw(AreaCascader),
|
||||
componentProps: {
|
||||
api: getAreaTree,
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
childrenField: 'children',
|
||||
allowClear: true,
|
||||
changeOnSelect: true,
|
||||
class: '!w-full',
|
||||
placeholder: '请选择收件人所在地',
|
||||
treeDefaultExpandAll: true,
|
||||
showSearch: true,
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { h } from 'vue';
|
||||
import { h, markRaw } from 'vue';
|
||||
|
||||
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
|
@ -13,7 +13,7 @@ import { z } from '#/adapter/form';
|
|||
import { getSimpleGroupList } from '#/api/member/group';
|
||||
import { getSimpleLevelList } from '#/api/member/level';
|
||||
import { getSimpleTagList } from '#/api/member/tag';
|
||||
import { getAreaTree } from '#/api/system/area';
|
||||
import { AreaCascader } from '#/components/area';
|
||||
import { getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
|
|
@ -102,11 +102,13 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||
{
|
||||
fieldName: 'areaId',
|
||||
label: '所在地',
|
||||
component: 'ApiTreeSelect',
|
||||
component: markRaw(AreaCascader),
|
||||
componentProps: {
|
||||
api: getAreaTree,
|
||||
fieldNames: { label: 'name', value: 'id', children: 'children' },
|
||||
allowClear: true,
|
||||
changeOnSelect: true,
|
||||
class: '!w-full',
|
||||
placeholder: '请选择所在地',
|
||||
showSearch: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,125 @@
|
|||
<script lang="ts" setup>
|
||||
import type { CascaderProps } from 'ant-design-vue';
|
||||
|
||||
import type { SystemAreaApi } from '#/api/system/area';
|
||||
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { Cascader } from 'ant-design-vue';
|
||||
|
||||
import { getAreaTree } from '#/api/system/area';
|
||||
|
||||
defineOptions({ name: 'AreaCascader' });
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
allowClear: false,
|
||||
changeOnSelect: false,
|
||||
modelValue: undefined,
|
||||
placeholder: '请选择省市区',
|
||||
showSearch: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value?: number];
|
||||
}>();
|
||||
|
||||
interface Props {
|
||||
allowClear?: boolean;
|
||||
changeOnSelect?: boolean;
|
||||
modelValue?: number;
|
||||
placeholder?: string;
|
||||
showSearch?: boolean;
|
||||
}
|
||||
|
||||
type AreaTreeNode = SystemAreaApi.Area & {
|
||||
children?: AreaTreeNode[];
|
||||
};
|
||||
|
||||
const areaTree = ref<AreaTreeNode[]>([]); // 地区树
|
||||
const loading = ref(false); // 加载状态
|
||||
const selectedPath = ref<number[]>(); // 选中的地区路径
|
||||
|
||||
const fieldNames = {
|
||||
children: 'children', // 子级字段
|
||||
label: 'name', // 标签字段
|
||||
value: 'id', // 值字段
|
||||
};
|
||||
|
||||
/**
|
||||
* 查找地区编号对应的级联路径
|
||||
*
|
||||
* @param tree 地区树
|
||||
* @param areaId 地区编号
|
||||
* @returns 省市区编号路径
|
||||
*/
|
||||
function findAreaPath(
|
||||
tree: AreaTreeNode[],
|
||||
areaId?: number,
|
||||
): number[] | undefined {
|
||||
if (areaId === undefined || areaId === null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for (const area of tree) {
|
||||
if (area.id === undefined) {
|
||||
continue;
|
||||
}
|
||||
if (area.id === areaId) {
|
||||
return [area.id];
|
||||
}
|
||||
|
||||
const childPath = area.children?.length
|
||||
? findAreaPath(area.children, areaId)
|
||||
: undefined;
|
||||
if (childPath) {
|
||||
return [area.id, ...childPath];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 同步级联选择器展示路径 */
|
||||
function syncSelectedPath() {
|
||||
selectedPath.value = findAreaPath(areaTree.value, props.modelValue);
|
||||
}
|
||||
|
||||
/** 选择地区后回写最后一级地区编号 */
|
||||
const handleChange: NonNullable<CascaderProps['onChange']> = (value) => {
|
||||
if (!value?.length) {
|
||||
emit('update:modelValue', undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
const path = Array.isArray(value[0]) ? value[0] : value;
|
||||
const leafValue = path.at(-1);
|
||||
const areaId =
|
||||
typeof leafValue === 'number' ? leafValue : Number(leafValue);
|
||||
emit('update:modelValue', Number.isNaN(areaId) ? undefined : areaId);
|
||||
};
|
||||
|
||||
watch(() => props.modelValue, syncSelectedPath);
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
areaTree.value = (await getAreaTree()) as AreaTreeNode[];
|
||||
syncSelectedPath();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Cascader
|
||||
v-model:value="selectedPath"
|
||||
:allow-clear="allowClear"
|
||||
:change-on-select="changeOnSelect"
|
||||
:field-names="fieldNames"
|
||||
:loading="loading"
|
||||
:options="areaTree"
|
||||
:placeholder="placeholder"
|
||||
:show-search="showSearch"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</template>
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default as AreaCascader } from './area-cascader.vue';
|
||||
|
|
@ -1,12 +1,14 @@
|
|||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { markRaw } from 'vue';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
|
||||
import { getAreaTree } from '#/api/system/area';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { AreaCascader } from '#/components/area';
|
||||
import { getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
|
|
@ -119,11 +121,12 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||
{
|
||||
fieldName: 'areaId',
|
||||
label: '地址',
|
||||
component: 'ApiTreeSelect',
|
||||
component: markRaw(AreaCascader),
|
||||
componentProps: {
|
||||
api: getAreaTree,
|
||||
fieldNames: { label: 'name', value: 'id', children: 'children' },
|
||||
allowClear: true,
|
||||
class: '!w-full',
|
||||
placeholder: '请选择地址',
|
||||
showSearch: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { markRaw } from 'vue';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
|
||||
import { getSimpleContactList } from '#/api/crm/contact';
|
||||
import { getCustomerSimpleList } from '#/api/crm/customer';
|
||||
import { getAreaTree } from '#/api/system/area';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { AreaCascader } from '#/components/area';
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
export function useFormSchema(): VbenFormSchema[] {
|
||||
|
|
@ -143,11 +145,12 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||
{
|
||||
fieldName: 'areaId',
|
||||
label: '地址',
|
||||
component: 'ApiTreeSelect',
|
||||
component: markRaw(AreaCascader),
|
||||
componentProps: {
|
||||
api: getAreaTree,
|
||||
fieldNames: { label: 'name', value: 'id', children: 'children' },
|
||||
allowClear: true,
|
||||
class: '!w-full',
|
||||
placeholder: '请选择地址',
|
||||
showSearch: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { markRaw } from 'vue';
|
||||
|
||||
import { z } from '@vben/common-ui';
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
|
||||
import { getAreaTree } from '#/api/system/area';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { AreaCascader } from '#/components/area';
|
||||
import { getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
|
|
@ -128,12 +130,12 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||
{
|
||||
fieldName: 'areaId',
|
||||
label: '地址',
|
||||
component: 'ApiTreeSelect',
|
||||
component: markRaw(AreaCascader),
|
||||
componentProps: {
|
||||
api: getAreaTree,
|
||||
fieldNames: { label: 'name', value: 'id', children: 'children' },
|
||||
placeholder: '请选择地址',
|
||||
allowClear: true,
|
||||
class: '!w-full',
|
||||
placeholder: '请选择地址',
|
||||
showSearch: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { markRaw } from 'vue';
|
||||
|
||||
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { z } from '#/adapter/form';
|
||||
import { getAreaTree } from '#/api/system/area';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { AreaCascader } from '#/components/area';
|
||||
import { getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
|
|
@ -62,12 +64,13 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||
{
|
||||
fieldName: 'areaId',
|
||||
label: '门店所在地区',
|
||||
component: 'ApiTreeSelect',
|
||||
component: markRaw(AreaCascader),
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
api: getAreaTree,
|
||||
fieldNames: { label: 'name', value: 'id', children: 'children' },
|
||||
allowClear: true,
|
||||
class: '!w-full',
|
||||
placeholder: '请选择省市区',
|
||||
showSearch: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,13 +2,15 @@ import type { VbenFormSchema } from '#/adapter/form';
|
|||
import type { VxeGridPropTypes } from '#/adapter/vxe-table';
|
||||
import type { MallDeliveryPickUpStoreApi } from '#/api/mall/trade/delivery/pickUpStore';
|
||||
|
||||
import { markRaw } from 'vue';
|
||||
|
||||
import { DeliveryTypeEnum, DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
import { convertToInteger, formatToFraction } from '@vben/utils';
|
||||
|
||||
import { getSimpleDeliveryExpressList } from '#/api/mall/trade/delivery/express';
|
||||
import { getSimpleDeliveryPickUpStoreList } from '#/api/mall/trade/delivery/pickUpStore';
|
||||
import { getAreaTree } from '#/api/system/area';
|
||||
import { AreaCascader } from '#/components/area';
|
||||
import { getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 关联数据 */
|
||||
|
|
@ -368,14 +370,13 @@ export function useAddressFormSchema(): VbenFormSchema[] {
|
|||
{
|
||||
fieldName: 'receiverAreaId',
|
||||
label: '所在地',
|
||||
component: 'ApiTreeSelect',
|
||||
component: markRaw(AreaCascader),
|
||||
componentProps: {
|
||||
api: getAreaTree,
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
childrenField: 'children',
|
||||
allowClear: true,
|
||||
changeOnSelect: true,
|
||||
class: '!w-full',
|
||||
placeholder: '请选择收件人所在地',
|
||||
treeDefaultExpandAll: true,
|
||||
showSearch: true,
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { h } from 'vue';
|
||||
import { h, markRaw } from 'vue';
|
||||
|
||||
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
|
@ -13,7 +13,7 @@ import { z } from '#/adapter/form';
|
|||
import { getSimpleGroupList } from '#/api/member/group';
|
||||
import { getSimpleLevelList } from '#/api/member/level';
|
||||
import { getSimpleTagList } from '#/api/member/tag';
|
||||
import { getAreaTree } from '#/api/system/area';
|
||||
import { AreaCascader } from '#/components/area';
|
||||
import { getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
|
|
@ -102,11 +102,13 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||
{
|
||||
fieldName: 'areaId',
|
||||
label: '所在地',
|
||||
component: 'ApiTreeSelect',
|
||||
component: markRaw(AreaCascader),
|
||||
componentProps: {
|
||||
api: getAreaTree,
|
||||
fieldNames: { label: 'name', value: 'id', children: 'children' },
|
||||
allowClear: true,
|
||||
changeOnSelect: true,
|
||||
class: '!w-full',
|
||||
placeholder: '请选择所在地',
|
||||
showSearch: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
<script lang="ts" setup>
|
||||
import type { CascaderValue } from 'element-plus';
|
||||
|
||||
import type { SystemAreaApi } from '#/api/system/area';
|
||||
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { ElCascader } from 'element-plus';
|
||||
|
||||
import { getAreaTree } from '#/api/system/area';
|
||||
|
||||
defineOptions({ name: 'AreaCascader' });
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
checkStrictly: false,
|
||||
clearable: false,
|
||||
filterable: false,
|
||||
modelValue: undefined,
|
||||
placeholder: '请选择省市区',
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value?: number];
|
||||
}>();
|
||||
|
||||
interface Props {
|
||||
checkStrictly?: boolean;
|
||||
clearable?: boolean;
|
||||
filterable?: boolean;
|
||||
modelValue?: number;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
type AreaTreeNode = SystemAreaApi.Area & {
|
||||
children?: AreaTreeNode[];
|
||||
};
|
||||
|
||||
const areaTree = ref<AreaTreeNode[]>([]); // 地区树
|
||||
const loading = ref(false); // 加载状态
|
||||
const selectedPath = ref<number[]>(); // 选中的地区路径
|
||||
|
||||
/**
|
||||
* 查找地区编号对应的级联路径
|
||||
*
|
||||
* @param tree 地区树
|
||||
* @param areaId 地区编号
|
||||
* @returns 省市区编号路径
|
||||
*/
|
||||
function findAreaPath(
|
||||
tree: AreaTreeNode[],
|
||||
areaId?: number,
|
||||
): number[] | undefined {
|
||||
if (areaId === undefined || areaId === null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for (const area of tree) {
|
||||
if (area.id === undefined) {
|
||||
continue;
|
||||
}
|
||||
if (area.id === areaId) {
|
||||
return [area.id];
|
||||
}
|
||||
|
||||
const childPath = area.children?.length
|
||||
? findAreaPath(area.children, areaId)
|
||||
: undefined;
|
||||
if (childPath) {
|
||||
return [area.id, ...childPath];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 同步级联选择器展示路径 */
|
||||
function syncSelectedPath() {
|
||||
selectedPath.value = findAreaPath(areaTree.value, props.modelValue);
|
||||
}
|
||||
|
||||
/** 选择地区后回写最后一级地区编号 */
|
||||
function handleChange(value?: CascaderValue | null) {
|
||||
if (!Array.isArray(value) || value.length === 0) {
|
||||
emit('update:modelValue', undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
const path = Array.isArray(value[0]) ? value[0] : value;
|
||||
const leafValue = path.at(-1);
|
||||
const areaId =
|
||||
typeof leafValue === 'number' ? leafValue : Number(leafValue);
|
||||
emit('update:modelValue', Number.isNaN(areaId) ? undefined : areaId);
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, syncSelectedPath);
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
areaTree.value = (await getAreaTree()) as AreaTreeNode[];
|
||||
syncSelectedPath();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElCascader
|
||||
v-model="selectedPath"
|
||||
:clearable="clearable"
|
||||
:filterable="filterable"
|
||||
:loading="loading"
|
||||
:options="areaTree"
|
||||
:placeholder="placeholder"
|
||||
:props="{
|
||||
checkStrictly,
|
||||
children: 'children',
|
||||
emitPath: true,
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
}"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</template>
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default as AreaCascader } from './area-cascader.vue';
|
||||
|
|
@ -1,12 +1,14 @@
|
|||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { markRaw } from 'vue';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
|
||||
import { getAreaTree } from '#/api/system/area';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { AreaCascader } from '#/components/area';
|
||||
import { getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
|
|
@ -119,12 +121,11 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||
{
|
||||
fieldName: 'areaId',
|
||||
label: '地址',
|
||||
component: 'ApiTreeSelect',
|
||||
component: markRaw(AreaCascader),
|
||||
componentProps: {
|
||||
api: getAreaTree,
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
childrenField: 'children',
|
||||
class: '!w-full',
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
placeholder: '请选择地址',
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { markRaw } from 'vue';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
|
||||
import { getSimpleContactList } from '#/api/crm/contact';
|
||||
import { getCustomerSimpleList } from '#/api/crm/customer';
|
||||
import { getAreaTree } from '#/api/system/area';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { AreaCascader } from '#/components/area';
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
export function useFormSchema(): VbenFormSchema[] {
|
||||
|
|
@ -141,12 +143,11 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||
{
|
||||
fieldName: 'areaId',
|
||||
label: '地址',
|
||||
component: 'ApiTreeSelect',
|
||||
component: markRaw(AreaCascader),
|
||||
componentProps: {
|
||||
api: getAreaTree,
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
childrenField: 'children',
|
||||
class: '!w-full',
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
placeholder: '请选择地址',
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { markRaw } from 'vue';
|
||||
|
||||
import { z } from '@vben/common-ui';
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
|
||||
import { getAreaTree } from '#/api/system/area';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { AreaCascader } from '#/components/area';
|
||||
import { getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
|
|
@ -128,14 +130,12 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||
{
|
||||
fieldName: 'areaId',
|
||||
label: '地址',
|
||||
component: 'ApiTreeSelect',
|
||||
component: markRaw(AreaCascader),
|
||||
componentProps: {
|
||||
api: getAreaTree,
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
childrenField: 'children',
|
||||
class: '!w-full',
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
placeholder: '请选择地址',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { markRaw } from 'vue';
|
||||
|
||||
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { z } from '#/adapter/form';
|
||||
import { getAreaTree } from '#/api/system/area';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { AreaCascader } from '#/components/area';
|
||||
import { getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
|
|
@ -62,13 +64,12 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||
{
|
||||
fieldName: 'areaId',
|
||||
label: '门店所在地区',
|
||||
component: 'ApiTreeSelect',
|
||||
component: markRaw(AreaCascader),
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
api: getAreaTree,
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
childrenField: 'children',
|
||||
class: '!w-full',
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
placeholder: '请选择省市区',
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,13 +2,15 @@ import type { VbenFormSchema } from '#/adapter/form';
|
|||
import type { VxeGridPropTypes } from '#/adapter/vxe-table';
|
||||
import type { MallDeliveryPickUpStoreApi } from '#/api/mall/trade/delivery/pickUpStore';
|
||||
|
||||
import { markRaw } from 'vue';
|
||||
|
||||
import { DeliveryTypeEnum, DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
import { convertToInteger, formatToFraction } from '@vben/utils';
|
||||
|
||||
import { getSimpleDeliveryExpressList } from '#/api/mall/trade/delivery/express';
|
||||
import { getSimpleDeliveryPickUpStoreList } from '#/api/mall/trade/delivery/pickUpStore';
|
||||
import { getAreaTree } from '#/api/system/area';
|
||||
import { AreaCascader } from '#/components/area';
|
||||
import { getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 关联数据 */
|
||||
|
|
@ -369,14 +371,13 @@ export function useAddressFormSchema(): VbenFormSchema[] {
|
|||
{
|
||||
fieldName: 'receiverAreaId',
|
||||
label: '所在地',
|
||||
component: 'ApiTreeSelect',
|
||||
component: markRaw(AreaCascader),
|
||||
componentProps: {
|
||||
api: getAreaTree,
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
childrenField: 'children',
|
||||
checkStrictly: true,
|
||||
class: '!w-full',
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
placeholder: '请选择收件人所在地',
|
||||
defaultExpandAll: true,
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { h } from 'vue';
|
||||
import { h, markRaw } from 'vue';
|
||||
|
||||
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
|
@ -13,7 +13,7 @@ import { z } from '#/adapter/form';
|
|||
import { getSimpleGroupList } from '#/api/member/group';
|
||||
import { getSimpleLevelList } from '#/api/member/level';
|
||||
import { getSimpleTagList } from '#/api/member/tag';
|
||||
import { getAreaTree } from '#/api/system/area';
|
||||
import { AreaCascader } from '#/components/area';
|
||||
import { getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
|
|
@ -99,12 +99,12 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||
{
|
||||
fieldName: 'areaId',
|
||||
label: '所在地',
|
||||
component: 'ApiTreeSelect',
|
||||
component: markRaw(AreaCascader),
|
||||
componentProps: {
|
||||
api: getAreaTree,
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
childrenField: 'children',
|
||||
checkStrictly: true,
|
||||
class: '!w-full',
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
placeholder: '请选择所在地',
|
||||
},
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue