diff --git a/apps/web-antd/src/adapter/component/index.ts b/apps/web-antd/src/adapter/component/index.ts index c61e9b48..4ef5e99a 100644 --- a/apps/web-antd/src/adapter/component/index.ts +++ b/apps/web-antd/src/adapter/component/index.ts @@ -44,6 +44,7 @@ import { FileUpload, ImageUpload } from '#/components/upload'; const withDefaultPlaceholder = ( component: T, type: 'input' | 'select', + componentProps: Recordable = {}, ) => { return defineComponent({ inheritAttrs: false, @@ -74,7 +75,13 @@ const withDefaultPlaceholder = ( return () => h( component, - { ...props, ...attrs, placeholder: placeholder.value, ref: innerRef }, + { + ...componentProps, + placeholder: placeholder.value, + ...props, + ...attrs, + ref: innerRef, + }, slots, ); }, @@ -118,38 +125,20 @@ async function initComponentAdapter() { // 如果你的组件体积比较大,可以使用异步加载 // Button: () => // import('xxx').then((res) => res.Button), - ApiSelect: (props, { attrs, slots }) => { - return h( - ApiComponent, - { - placeholder: $t('ui.placeholder.select'), - ...props, - ...attrs, - component: Select, - loadingSlot: 'suffixIcon', - visibleEvent: 'onDropdownVisibleChange', - modelPropName: 'value', - }, - slots, - ); - }, - ApiTreeSelect: (props, { attrs, slots }) => { - return h( - ApiComponent, - { - placeholder: $t('ui.placeholder.select'), - ...props, - ...attrs, - component: TreeSelect, - fieldNames: { label: 'label', value: 'value', children: 'children' }, - loadingSlot: 'suffixIcon', - modelPropName: 'value', - optionsPropName: 'treeData', - visibleEvent: 'onVisibleChange', - }, - slots, - ); - }, + ApiSelect: withDefaultPlaceholder(ApiComponent, 'select', { + component: Select, + loadingSlot: 'suffixIcon', + visibleEvent: 'onDropdownVisibleChange', + modelPropName: 'value', + }), + ApiTreeSelect: withDefaultPlaceholder(ApiComponent, 'select', { + component: TreeSelect, + fieldNames: { label: 'label', value: 'value', children: 'children' }, + loadingSlot: 'suffixIcon', + modelPropName: 'value', + optionsPropName: 'treeData', + visibleEvent: 'onVisibleChange', + }), AutoComplete, Checkbox, CheckboxGroup, @@ -159,19 +148,11 @@ async function initComponentAdapter() { return h(Button, { ...props, attrs, type: 'default' }, slots); }, Divider, - IconPicker: (props, { attrs, slots }) => { - return h( - IconPicker, - { - iconSlot: 'addonAfter', - inputComponent: Input, - modelValueProp: 'value', - ...props, - ...attrs, - }, - slots, - ); - }, + IconPicker: withDefaultPlaceholder(IconPicker, 'select', { + iconSlot: 'addonAfter', + inputComponent: Input, + modelValueProp: 'value', + }), Input: withDefaultPlaceholder(Input, 'input'), InputNumber: withDefaultPlaceholder(InputNumber, 'input'), InputPassword: withDefaultPlaceholder(InputPassword, 'input'), diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/tree/tree.vue b/packages/@core/ui-kit/shadcn-ui/src/ui/tree/tree.vue index 46866979..b75088bb 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/ui/tree/tree.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/ui/tree/tree.vue @@ -4,7 +4,9 @@ import type { FlattenedItem } from 'radix-vue'; import type { ClassType, Recordable } from '@vben-core/typings'; -import { onMounted, ref, watch, watchEffect } from 'vue'; +import type { TreeProps } from './types'; + +import { onMounted, ref, watchEffect } from 'vue'; import { ChevronRight, IconifyIcon } from '@vben-core/icons'; import { cn, get } from '@vben-core/shared/utils'; @@ -14,46 +16,13 @@ import { TreeItem, TreeRoot } from 'radix-vue'; import { Checkbox } from '../checkbox'; -interface TreeProps { - /** 单选时允许取消已有选项 */ - allowClear?: boolean; - /** 显示边框 */ - bordered?: boolean; - /** 取消父子关联选择 */ - checkStrictly?: boolean; - /** 子级字段名 */ - childrenField?: string; - /** 默认展开的键 */ - defaultExpandedKeys?: Array; - /** 默认展开的级别(优先级高于defaultExpandedKeys) */ - defaultExpandedLevel?: number; - /** 默认值 */ - defaultValue?: Arrayable; - /** 禁用 */ - disabled?: boolean; - /** 自定义节点类名 */ - getNodeClass?: (item: FlattenedItem>) => string; - iconField?: string; - /** label字段 */ - labelField?: string; - /** 当前值 */ - modelValue?: Arrayable; - /** 是否多选 */ - multiple?: boolean; - /** 显示由iconField指定的图标 */ - showIcon?: boolean; - /** 启用展开收缩动画 */ - transition?: boolean; - /** 树数据 */ - treeData: Recordable[]; - /** 值字段 */ - valueField?: string; -} const props = withDefaults(defineProps(), { allowClear: false, + autoCheckParent: true, bordered: false, checkStrictly: false, defaultExpandedKeys: () => [], + defaultExpandedLevel: 0, disabled: false, expanded: () => [], iconField: 'icon', @@ -61,7 +30,7 @@ const props = withDefaults(defineProps(), { modelValue: () => [], multiple: false, showIcon: true, - transition: false, + transition: true, valueField: 'value', childrenField: 'children', }); @@ -72,28 +41,36 @@ const emits = defineEmits<{ 'update:modelValue': [value: Arrayable>]; }>(); -interface InnerFlattenItem> { +interface InnerFlattenItem, P = number | string> { hasChildren: boolean; level: number; + parents: P[]; value: T; } -function flatten>( +function flatten, P = number | string>( items: T[], childrenField: string = 'children', level = 0, -): InnerFlattenItem[] { - const result: InnerFlattenItem[] = []; + parents: P[] = [], +): InnerFlattenItem[] { + const result: InnerFlattenItem[] = []; items.forEach((item) => { const children = get(item, childrenField) as Array; const val = { hasChildren: Array.isArray(children) && children.length > 0, level, + parents: [...parents], value: item, }; result.push(val); if (val.hasChildren) - result.push(...flatten(children, childrenField, level + 1)); + result.push( + ...flatten(children, childrenField, level + 1, [ + ...parents, + get(item, props.valueField), + ]), + ); }); return result; } @@ -133,14 +110,6 @@ function updateTreeValue() { : getItemByValue(val); } -watch( - modelValue, - () => { - updateTreeValue(); - }, - { deep: true, immediate: true }, -); - function updateModelValue(val: Arrayable>) { modelValue.value = Array.isArray(val) ? val.map((v) => get(v, props.valueField)) @@ -186,7 +155,33 @@ function collapseAll() { function onToggle(item: FlattenedItem>) { emits('expand', item); } -function onSelect(item: FlattenedItem>) { +function onSelect(item: FlattenedItem>, isSelected: boolean) { + if ( + !props.checkStrictly && + props.multiple && + props.autoCheckParent && + isSelected + ) { + flattenData.value + .find((i) => { + return ( + get(i.value, props.valueField) === get(item.value, props.valueField) + ); + }) + ?.parents?.forEach((p) => { + if (Array.isArray(modelValue.value) && !modelValue.value.includes(p)) { + modelValue.value.push(p); + } + }); + } else { + if (Array.isArray(modelValue.value)) { + const index = modelValue.value.indexOf(get(item.value, props.valueField)); + if (index !== -1) { + modelValue.value.splice(index, 1); + } + } + } + updateTreeValue(); emits('select', item); } @@ -224,78 +219,130 @@ defineExpose({
- - -
- -
- -
- - - {{ get(item.value, labelField) }} - -
-
+ +
+ +
+ +
+ + + {{ get(item.value, labelField) }} + +
+ +
+ diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/tree/types.ts b/packages/@core/ui-kit/shadcn-ui/src/ui/tree/types.ts new file mode 100644 index 00000000..cb4e82da --- /dev/null +++ b/packages/@core/ui-kit/shadcn-ui/src/ui/tree/types.ts @@ -0,0 +1,42 @@ +import type { Arrayable } from '@vueuse/core'; +import type { FlattenedItem } from 'radix-vue'; + +import type { Recordable } from '@vben-core/typings'; + +export interface TreeProps { + /** 单选时允许取消已有选项 */ + allowClear?: boolean; + /** 非关联选择时,自动选中上级节点 */ + autoCheckParent?: boolean; + /** 显示边框 */ + bordered?: boolean; + /** 取消父子关联选择 */ + checkStrictly?: boolean; + /** 子级字段名 */ + childrenField?: string; + /** 默认展开的键 */ + defaultExpandedKeys?: Array; + /** 默认展开的级别(优先级高于defaultExpandedKeys) */ + defaultExpandedLevel?: number; + /** 默认值 */ + defaultValue?: Arrayable; + /** 禁用 */ + disabled?: boolean; + /** 自定义节点类名 */ + getNodeClass?: (item: FlattenedItem>) => string; + iconField?: string; + /** label字段 */ + labelField?: string; + /** 当前值 */ + modelValue?: Arrayable; + /** 是否多选 */ + multiple?: boolean; + /** 显示由iconField指定的图标 */ + showIcon?: boolean; + /** 启用展开收缩动画 */ + transition?: boolean; + /** 树数据 */ + treeData: Recordable[]; + /** 值字段 */ + valueField?: string; +} diff --git a/packages/effects/common-ui/src/components/api-component/api-component.vue b/packages/effects/common-ui/src/components/api-component/api-component.vue index c871fe20..f4f6acd5 100644 --- a/packages/effects/common-ui/src/components/api-component/api-component.vue +++ b/packages/effects/common-ui/src/components/api-component/api-component.vue @@ -84,7 +84,7 @@ const emit = defineEmits<{ const modelValue = defineModel({ default: '' }); const attrs = useAttrs(); - +const innerParams = ref({}); const refOptions = ref([]); const loading = ref(false); // 首次是否加载过了 @@ -175,8 +175,15 @@ async function handleFetchForVisible(visible: boolean) { } } +const params = computed(() => { + return { + ...props.params, + ...unref(innerParams), + }; +}); + watch( - () => props.params, + params, (value, oldValue) => { if (isEqual(value, oldValue)) { return; @@ -189,12 +196,22 @@ watch( function emitChange() { emit('optionsChange', unref(getOptions)); } +const componentRef = ref(); +defineExpose({ + /** 获取被包装的组件实例 */ + getComponentRef: () => componentRef.value as T, + /** 更新Api参数 */ + updateParam(newParams: Record) { + innerParams.value = newParams; + }, +});