2023-04-02 10:07:50 +08:00
|
|
|
<!--
|
|
|
|
* 可搜索树组件
|
|
|
|
* @author: SonnyLeo
|
|
|
|
* @since: 2023-03-30
|
|
|
|
* index.vue
|
|
|
|
-->
|
|
|
|
<template>
|
|
|
|
<div>
|
|
|
|
<n-card title="自定义树" class="h-full shadow-sm rounded-16px">
|
|
|
|
<n-grid :x-gap="20" :y-gap="20" cols="0 600:2 1000:3">
|
|
|
|
<n-grid-item>
|
|
|
|
<n-card title="右侧自定义后缀图标" :segmented="segmented" class="shadow-sm rounded-16px">
|
|
|
|
<n-tree
|
|
|
|
block-line
|
|
|
|
:data="caseOneData"
|
|
|
|
expand-on-click
|
|
|
|
:selectable="false"
|
|
|
|
:default-expanded-keys="caseOneDefaultExpandedKeys"
|
|
|
|
/> </n-card
|
|
|
|
></n-grid-item>
|
|
|
|
<n-grid-item>
|
|
|
|
<n-card title="右键菜单" :segmented="segmented" class="shadow-sm rounded-16px">
|
|
|
|
<n-tree
|
|
|
|
block-line
|
|
|
|
:data="data"
|
|
|
|
:default-expanded-keys="caseTwoDefaultExpandedKeys"
|
|
|
|
:node-props="caseTwoNodeProps"
|
|
|
|
/>
|
|
|
|
<n-dropdown
|
|
|
|
trigger="manual"
|
|
|
|
placement="bottom-start"
|
|
|
|
:show="showDropdown"
|
|
|
|
:options="options"
|
|
|
|
:x="x"
|
|
|
|
:y="y"
|
|
|
|
@select="handleSelect"
|
|
|
|
@clickoutside="handleClickOutside"
|
|
|
|
/> </n-card
|
|
|
|
></n-grid-item>
|
|
|
|
<n-grid-item>
|
|
|
|
<n-card title="可搜索的树组件" class="shadow-sm rounded-16px">
|
|
|
|
<n-space vertical :size="12">
|
|
|
|
<n-input v-model:value="pattern" placeholder="搜索" />
|
|
|
|
<n-switch v-model:value="showIrrelevantNodes">
|
|
|
|
<template #checked> 展示搜索无关的节点 </template>
|
|
|
|
<template #unchecked> 隐藏搜索无关的节点 </template>
|
|
|
|
</n-switch>
|
|
|
|
<n-tree :show-irrelevant-nodes="showIrrelevantNodes" :pattern="pattern" :data="data" block-line />
|
|
|
|
</n-space>
|
|
|
|
</n-card>
|
|
|
|
</n-grid-item>
|
|
|
|
<n-grid-item>
|
|
|
|
<n-card title="自定义前缀和后缀" class="shadow-sm rounded-16px" :segmented="segmented">
|
|
|
|
<n-tree
|
|
|
|
block-line
|
|
|
|
:data="caseFourData"
|
|
|
|
:default-expanded-keys="caseFourDefaultExpandedKeys"
|
|
|
|
:selectable="false"
|
|
|
|
/>
|
|
|
|
</n-card>
|
|
|
|
</n-grid-item>
|
|
|
|
<n-grid-item>
|
|
|
|
<n-card title="批量渲染前缀和后缀" class="shadow-sm rounded-16px" :segmented="segmented">
|
|
|
|
<n-tree
|
|
|
|
block-line
|
|
|
|
:data="data"
|
|
|
|
:default-expanded-keys="caseFiveDefaultExpandedKeys"
|
|
|
|
:render-prefix="renderPrefix"
|
|
|
|
:render-label="renderLabel"
|
|
|
|
:render-suffix="renderSuffix"
|
|
|
|
:selectable="false"
|
|
|
|
/>
|
|
|
|
</n-card>
|
|
|
|
</n-grid-item>
|
|
|
|
<n-grid-item>
|
|
|
|
<n-card title="滚动到指定节点" class="shadow-sm rounded-16px" :segmented="segmented">
|
|
|
|
<template #header-extra>
|
|
|
|
<n-button @click="handleCaseSixClick">滚动</n-button>
|
|
|
|
</template>
|
|
|
|
<n-tree
|
|
|
|
ref="caseSixTreeRef"
|
|
|
|
block-line
|
|
|
|
:data="data"
|
|
|
|
default-expand-all
|
|
|
|
virtual-scroll
|
|
|
|
style="height: 120px"
|
|
|
|
/>
|
|
|
|
</n-card>
|
|
|
|
</n-grid-item>
|
|
|
|
</n-grid>
|
|
|
|
</n-card>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="tsx">
|
|
|
|
import { ref, h } from 'vue';
|
|
|
|
import type { TreeOption, DropdownOption, TreeInst } from 'naive-ui';
|
|
|
|
import { NSpace, NButton } from 'naive-ui';
|
|
|
|
import { repeat } from 'seemly';
|
|
|
|
import { useIconRender } from '@/composables';
|
|
|
|
|
|
|
|
const { iconRender } = useIconRender();
|
|
|
|
const segmented = ref<boolean>(true);
|
|
|
|
|
|
|
|
/** 实例一相关 */
|
|
|
|
const caseOneData = ref<TreeOption[] | undefined>(createSuffixData());
|
|
|
|
const caseOneDefaultExpandedKeys = ref<string[]>([]);
|
|
|
|
|
|
|
|
/** 实例二相关 */
|
|
|
|
const caseTwoDefaultExpandedKeys = ref<string[]>(['20']);
|
|
|
|
const showDropdown = ref<boolean>(false);
|
|
|
|
const x = ref<number>(0);
|
|
|
|
const y = ref<number>(0);
|
|
|
|
const options = ref<DropdownOption[] | undefined>([
|
|
|
|
{ label: '编辑', key: 'edit', icon: iconRender({ icon: 'mdi:pencil', fontSize: 18 }) },
|
|
|
|
{ label: '删除', key: 'delete', icon: iconRender({ icon: 'mdi:trash-can-outline', fontSize: 18 }) }
|
|
|
|
]);
|
|
|
|
/** 右键单击事件 */
|
|
|
|
function handleClickOutside() {
|
|
|
|
showDropdown.value = false;
|
|
|
|
}
|
|
|
|
/** 左键单击事件 */
|
|
|
|
function handleSelect(key: string | number, option: DropdownOption) {
|
|
|
|
console.log(key, option);
|
|
|
|
showDropdown.value = false;
|
|
|
|
}
|
|
|
|
/** 树节点绑定事件 */
|
|
|
|
function caseTwoNodeProps() {
|
|
|
|
return {
|
|
|
|
onContextmenu(e: MouseEvent): void {
|
|
|
|
showDropdown.value = true;
|
|
|
|
x.value = e.clientX;
|
|
|
|
y.value = e.clientY;
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/** 实例三相关 */
|
|
|
|
const pattern = ref<string>('');
|
|
|
|
const showIrrelevantNodes = ref(false);
|
|
|
|
|
|
|
|
/** 实例四相关 */
|
|
|
|
const caseFourData = ref<TreeOption[] | undefined>(createPrefixAndSuffixData());
|
|
|
|
const caseFourDefaultExpandedKeys = ref<string[]>(['20', '21']);
|
|
|
|
|
|
|
|
/** 实例五相关 */
|
|
|
|
const caseFiveDefaultExpandedKeys = ref<string[]>(['20', '21']);
|
|
|
|
|
|
|
|
function renderPrefix({ option }: { option: TreeOption }) {
|
|
|
|
return h(NButton, { text: true, type: 'primary' }, { default: () => `Prefix-${option.level}` });
|
|
|
|
}
|
|
|
|
|
|
|
|
function renderLabel({ option }: { option: TreeOption }) {
|
|
|
|
return `${option.label} :)`;
|
|
|
|
}
|
|
|
|
|
|
|
|
function renderSuffix({ option }: { option: TreeOption }) {
|
|
|
|
return h(NButton, { text: true, type: 'primary' }, { default: () => `Suffix-${option.level}` });
|
|
|
|
}
|
|
|
|
|
|
|
|
/** 实例六相关 */
|
|
|
|
const caseSixTreeRef = ref<TreeInst | null>(null);
|
|
|
|
function handleCaseSixClick() {
|
|
|
|
caseSixTreeRef.value?.scrollTo({ key: '21' });
|
|
|
|
}
|
|
|
|
|
|
|
|
/** 模拟数据相关 */
|
|
|
|
function createPrefixAndSuffixData(level = 2, baseKey = ''): TreeOption[] | undefined {
|
|
|
|
if (!level) return undefined;
|
|
|
|
return repeat(4 - level, undefined).map((_, index) => {
|
|
|
|
const key = String(baseKey) + level + index;
|
|
|
|
return {
|
|
|
|
label: createLabel(level),
|
|
|
|
key,
|
|
|
|
children: createPrefixAndSuffixData(level - 1, key),
|
|
|
|
suffix: () => h(NButton, { text: true, type: 'primary' }, { default: () => 'Suffix' }),
|
|
|
|
prefix: () => h(NButton, { text: true, type: 'primary' }, { default: () => 'Prefix' })
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function createSuffixData(level = 2, baseKey = ''): TreeOption[] | undefined {
|
|
|
|
if (!level) return undefined;
|
|
|
|
return repeat(4 - level, undefined).map((_, index) => {
|
|
|
|
const key = String(baseKey) + level + index;
|
|
|
|
return {
|
|
|
|
label: createLabel(level),
|
|
|
|
key,
|
|
|
|
children: createSuffixData(level - 1, key),
|
|
|
|
suffix: () =>
|
2023-04-02 10:45:51 +08:00
|
|
|
h(NSpace, { justify: 'space-evenly' }, () => [
|
2023-04-02 10:07:50 +08:00
|
|
|
h(NButton, { text: true, class: 'pt-1.5' }, { default: () => <icon-mdi-plus class="text-18px" /> }),
|
|
|
|
h(
|
|
|
|
NButton,
|
|
|
|
{ text: true, class: 'pt-1.5' },
|
|
|
|
{ default: () => <icon-mdi-trash-can-outline class="text-18px" /> }
|
|
|
|
)
|
|
|
|
])
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function createData(level = 2, baseKey = ''): TreeOption[] | undefined {
|
|
|
|
if (!level) return undefined;
|
|
|
|
return repeat(4 - level, undefined).map((_, index) => {
|
|
|
|
const key = String(baseKey) + level + index;
|
|
|
|
return {
|
|
|
|
label: createLabel(level),
|
|
|
|
level,
|
|
|
|
key,
|
|
|
|
children: createData(level - 1, key)
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function createLabel(level: number): string {
|
|
|
|
if (level === 2) return '道生一';
|
|
|
|
if (level === 1) return '一生二';
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
const data = ref(createData());
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped></style>
|