feat: 组件MsTreeSelect支持选择当前

This commit is contained in:
teukkk 2024-11-13 17:51:13 +08:00 committed by Craftsman
parent 407639f7e2
commit 1d08e57664
4 changed files with 190 additions and 56 deletions

View File

@ -171,6 +171,7 @@ export default function useTreeSelection(selectedModuleProps: SelectedModuleProp
Object.keys(selectedModulesMaps.value).forEach((key) => { Object.keys(selectedModulesMaps.value).forEach((key) => {
delete selectedModulesMaps.value[key]; delete selectedModulesMaps.value[key];
}); });
checkedKeys.value = [];
isCheckedAll.value = false; isCheckedAll.value = false;
} }

View File

@ -322,7 +322,7 @@
filterTreeData.value = data.value; filterTreeData.value = data.value;
} }
nextTick(() => { nextTick(() => {
treeRef.value?.expandAll(false); treeRef.value?.expandAll(props.defaultExpandAll ?? false);
}); });
} else { } else {
updateDebouncedSearch(); updateDebouncedSearch();

View File

@ -1,31 +1,38 @@
<template> <template>
<a-tooltip <a-tooltip
:content="getTreeSelectTooltip()" :content="getTreeSelectTooltip()"
:disabled="!(modelValue ?? []).length" :disabled="!(checkedKeys ?? []).length"
position="top" position="top"
content-class="tree-select-content" content-class="tree-select-content"
:mouse-enter-delay="300" :mouse-enter-delay="300"
>
<a-trigger
v-model:popup-visible="viewSelectOptionVisible"
:trigger="['click']"
:click-to-close="false"
:popup-translate="[0, 4]"
content-class="arco-trigger-menu tree-select-trigger-content"
:content-style="{ width: `${triggerWidth}px` }"
> >
<a-tree-select <a-tree-select
:id="treeSelectId"
ref="treeSelectRef" ref="treeSelectRef"
v-model:model-value="selectValue" v-model:model-value="checkedKeys"
v-model:input-value="inputValue" v-model:input-value="inputValue"
:data="props.data" :data="treeData"
:disabled="props.disabled"
:multiple="props.multiple"
:field-names="props.fieldNames"
allow-search
:filter-tree-node="filterTreeNode"
:tree-props="{
virtualListProps: {
height: 200,
threshold: 200,
},
}"
v-bind="$attrs"
:max-tag-count="maxTagCount" :max-tag-count="maxTagCount"
@input-value-change="handleInputValueChange" :multiple="props.multiple"
allow-search
disable-filter
allow-clear
:tree-props="{
virtualListProps: virtualListProps,
}"
:field-names="props.fieldNames"
:placeholder="t('common.pleaseSelect')"
:trigger-props="{ contentClass: 'view-select-trigger' }"
@popup-visible-change="handlePopupVisibleChange" @popup-visible-change="handlePopupVisibleChange"
@input-value-change="handleInputValueChange"
@change="handleChange" @change="handleChange"
@keyup="handleKeyup" @keyup="handleKeyup"
@clear="handleClear" @clear="handleClear"
@ -33,48 +40,129 @@
<template #label="{ data: slotData }"> <template #label="{ data: slotData }">
<div class="one-line-text">{{ slotData.label }}</div> <div class="one-line-text">{{ slotData.label }}</div>
</template> </template>
<template #tree-slot-title="node">
<a-tooltip :content="`${node[props?.fieldNames?.title || 'title']}`" position="tr">
<div class="one-line-text max-w-[170px]">{{ node[props?.fieldNames?.title || 'title'] }}</div>
</a-tooltip>
</template>
</a-tree-select> </a-tree-select>
<template #content>
<MsTree
v-model:checked-keys="checkedKeys"
v-model:halfCheckedKeys="halfCheckedKeys"
:selectable="false"
:data="treeData"
:keyword="inputValue"
:empty-text="t('common.noData')"
:virtual-list-props="virtualListProps"
:field-names="props.fieldNames as MsTreeFieldNames"
default-expand-all
block-node
title-tooltip-position="tr"
:multiple="props.multiple"
:checkable="props.treeCheckable"
:check-strictly="props.treeCheckStrictly"
v-bind="$attrs"
@check="checkNode"
>
<template #title="nodeData">
<div
class="one-line-text w-full cursor-pointer text-[var(--color-text-1)]"
@click="checkNode(checkedKeys, { checked: !checkedKeys.includes(nodeData.id), node: nodeData })"
>
{{ nodeData.name }}
</div>
</template>
<template #extra="nodeData">
<MsButton
v-if="nodeData.children && nodeData.children.length"
@click="selectParent(nodeData, !!checkedKeys.includes(nodeData.id))"
>
{{
checkedKeys.includes(nodeData.id)
? t('ms.case.associate.cancelCurrent')
: t('ms.case.associate.selectCurrent')
}}
</MsButton>
</template>
</MsTree>
</template>
</a-trigger>
</a-tooltip> </a-tooltip>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { MsTreeNodeData } from '@/components/business/ms-tree/types'; import { isEqual } from 'lodash-es';
import MsButton from '@/components/pure/ms-button/index.vue';
import useTreeSelection from '@/components/business/ms-associate-case/useTreeSelection';
import MsTree from '@/components/business/ms-tree/index.vue';
import type { MsTreeFieldNames, MsTreeNodeData } from '@/components/business/ms-tree/types';
import { useI18n } from '@/hooks/useI18n';
import useSelect from '@/hooks/useSelect'; import useSelect from '@/hooks/useSelect';
import { filterTreeNode, findNodeByKey } from '@/utils'; import { findNodeByKey, getGenerateId } from '@/utils';
import type { TreeFieldNames, TreeNodeData } from '@arco-design/web-vue'; import type { TreeFieldNames, TreeNodeData } from '@arco-design/web-vue';
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
data: TreeNodeData[]; data: TreeNodeData[];
fieldNames?: TreeFieldNames; fieldNames?: TreeFieldNames | MsTreeFieldNames;
disabled?: boolean;
multiple?: boolean; multiple?: boolean;
shouldCalculateMaxTag?: boolean; shouldCalculateMaxTag?: boolean;
treeCheckStrictly?: boolean;
treeCheckable?: boolean;
}>(), }>(),
{ {
shouldCalculateMaxTag: true, shouldCalculateMaxTag: true,
} }
); );
const selectValue = defineModel<any>('modelValue', { required: true });
const inputValue = ref(''); const { t } = useI18n();
const tempInputValue = ref('');
const treeSelectId = ref(getGenerateId()); //
const viewSelectOptionVisible = ref(false);
const selectValue = defineModel<Array<string | number>>('modelValue', { required: true, default: [] });
const treeData = defineModel<TreeNodeData[]>('data', { required: true });
const selectedModuleProps = ref({
modulesTree: treeData.value,
moduleCount: {},
});
const { selectedModulesMaps, checkedKeys, halfCheckedKeys, selectParent, checkNode, clearSelector } =
useTreeSelection(selectedModuleProps.value);
const skipSelectValueWatch = ref(false);
watch(
() => selectValue.value,
(newValue) => {
if (!skipSelectValueWatch.value && !isEqual(checkedKeys.value, newValue)) {
clearSelector();
(newValue ?? []).forEach((id) => {
selectedModulesMaps.value[id] = {
selectAll: true,
selectIds: new Set(),
excludeIds: new Set(),
count: 0,
};
});
}
skipSelectValueWatch.value = false;
},
{
immediate: true,
}
);
const treeSelectRef = ref(); const treeSelectRef = ref();
const { maxTagCount, calculateMaxTag } = useSelect({ const { maxTagCount, calculateMaxTag } = useSelect({
selectRef: treeSelectRef, selectRef: treeSelectRef,
selectVal: selectValue, selectVal: checkedKeys,
}); });
watch( watch(
() => selectValue.value, () => checkedKeys.value,
() => { (newValue) => {
if (!isEqual(selectValue.value, newValue)) {
// selectValuewatch selectValue
skipSelectValueWatch.value = true;
selectValue.value = [...newValue];
}
if (props.shouldCalculateMaxTag !== false && props.multiple) { if (props.shouldCalculateMaxTag !== false && props.multiple) {
calculateMaxTag(); calculateMaxTag();
} }
@ -95,10 +183,10 @@
const getTreeSelectTooltip = computed(() => { const getTreeSelectTooltip = computed(() => {
return () => { return () => {
let treeSelectTooltip = ''; let treeSelectTooltip = '';
const values = Array.isArray(selectValue.value) ? selectValue.value : [selectValue.value]; const values = Array.isArray(checkedKeys.value) ? checkedKeys.value : [checkedKeys.value];
if (props.data) { if (props.data) {
treeSelectTooltip = values treeSelectTooltip = values
?.map((valueItem: string) => { ?.map((valueItem: string | number) => {
const optItem = findNodeByKey<MsTreeNodeData>( const optItem = findNodeByKey<MsTreeNodeData>(
props.data as MsTreeNodeData[], props.data as MsTreeNodeData[],
valueItem, valueItem,
@ -114,22 +202,32 @@
}; };
}); });
const inputValue = ref('');
const tempInputValue = ref('');
const isPopupVisibleChanging = ref(false);
function handlePopupVisibleChange() {
isPopupVisibleChanging.value = true;
setTimeout(() => {
isPopupVisibleChanging.value = false;
}, 0);
}
/** /**
* 处理输入框搜索值变化 * 处理输入框搜索值变化
* @param val 搜索值 * @param val 搜索值
*/ */
function handleInputValueChange(val: string) { async function handleInputValueChange(val: string) {
// treeSelectpopupVisibleChangeinputValueinputValue
if (isPopupVisibleChanging.value) {
inputValue.value = tempInputValue.value;
} else {
inputValue.value = val; inputValue.value = val;
if (val !== '') { if (val !== '') {
// arco-tree-select //
tempInputValue.value = val; tempInputValue.value = val;
} }
} }
function handlePopupVisibleChange(val: boolean) {
if (!val) {
inputValue.value = '';
tempInputValue.value = '';
}
} }
function handleChange() { function handleChange() {
if (props.multiple) { if (props.multiple) {
@ -145,7 +243,37 @@
} }
function handleClear() { function handleClear() {
tempInputValue.value = ''; tempInputValue.value = '';
clearSelector();
} }
const triggerWidth = ref<number>(280); //
function updateTriggerWidth() {
const treeSelectInput = document.getElementById(treeSelectId.value) as HTMLElement;
if (treeSelectInput) {
triggerWidth.value = treeSelectInput.offsetWidth; //
}
}
watch(
() => viewSelectOptionVisible.value,
(val: boolean) => {
if (!val) {
inputValue.value = '';
tempInputValue.value = '';
} else {
updateTriggerWidth();
}
}
);
const virtualListProps = computed(() => {
return {
threshold: 300,
height: 280,
fixedSize: true,
buffer: 15, // 10 padding
};
});
</script> </script>
<style lang="less"> <style lang="less">
@ -154,4 +282,8 @@
max-height: 150px; max-height: 150px;
.ms-scroll-bar(); .ms-scroll-bar();
} }
.tree-select-trigger-content {
max-height: 300px;
.ms-scroll-bar();
}
</style> </style>

View File

@ -31,6 +31,7 @@
:data="moduleTree" :data="moduleTree"
allow-clear allow-clear
:multiple="true" :multiple="true"
tree-check-strictly
:tree-checkable="true" :tree-checkable="true"
:placeholder="t('common.pleaseSelect')" :placeholder="t('common.pleaseSelect')"
:field-names="{ title: 'name', key: 'id', children: 'children' }" :field-names="{ title: 'name', key: 'id', children: 'children' }"