123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431 |
- <script lang="tsx">
- import type { ReplaceFields, Keys, CheckKeys, TreeActionType, TreeItem } from './types';
- import {
- defineComponent,
- reactive,
- computed,
- unref,
- ref,
- watchEffect,
- toRaw,
- watch,
- CSSProperties,
- onMounted,
- } from 'vue';
- import { Tree, Empty } from 'ant-design-vue';
- import { TreeIcon } from './TreeIcon';
- import TreeHeader from './TreeHeader.vue';
- import { ScrollContainer } from '/@/components/Container';
- import { omit, get } from 'lodash-es';
- import { isBoolean, isFunction } from '/@/utils/is';
- import { extendSlots, getSlot } from '/@/utils/helper/tsxHelper';
- import { filter } from '/@/utils/helper/treeHelper';
- import { useTree } from './useTree';
- import { useContextMenu } from '/@/hooks/web/useContextMenu';
- import { useDesign } from '/@/hooks/web/useDesign';
- import { basicProps } from './props';
- import { CreateContextOptions } from '/@/components/ContextMenu';
- import { CheckEvent } from './types';
- interface State {
- expandedKeys: Keys;
- selectedKeys: Keys;
- checkedKeys: CheckKeys;
- checkStrictly: boolean;
- }
- export default defineComponent({
- name: 'BasicTree',
- inheritAttrs: false,
- props: basicProps,
- emits: ['update:expandedKeys', 'update:selectedKeys', 'update:value', 'change', 'check'],
- setup(props, { attrs, slots, emit, expose }) {
- const state = reactive<State>({
- checkStrictly: props.checkStrictly,
- expandedKeys: props.expandedKeys || [],
- selectedKeys: props.selectedKeys || [],
- checkedKeys: props.checkedKeys || [],
- });
- const searchState = reactive({
- startSearch: false,
- searchData: [] as TreeItem[],
- });
- const treeDataRef = ref<TreeItem[]>([]);
- const [createContextMenu] = useContextMenu();
- const { prefixCls } = useDesign('basic-tree');
- const getReplaceFields = computed((): Required<ReplaceFields> => {
- const { replaceFields } = props;
- return {
- children: 'children',
- title: 'title',
- key: 'key',
- ...replaceFields,
- };
- });
- const getBindValues = computed(() => {
- let propsData = {
- blockNode: true,
- ...attrs,
- ...props,
- expandedKeys: state.expandedKeys,
- selectedKeys: state.selectedKeys,
- checkedKeys: state.checkedKeys,
- checkStrictly: state.checkStrictly,
- replaceFields: unref(getReplaceFields),
- 'onUpdate:expandedKeys': (v: Keys) => {
- state.expandedKeys = v;
- emit('update:expandedKeys', v);
- },
- 'onUpdate:selectedKeys': (v: Keys) => {
- state.selectedKeys = v;
- emit('update:selectedKeys', v);
- },
- onCheck: (v: CheckKeys, e: CheckEvent) => {
- state.checkedKeys = v;
- const rawVal = toRaw(v);
- emit('update:value', rawVal);
- emit('check', rawVal, e);
- },
- onRightClick: handleRightClick,
- };
- propsData = omit(propsData, 'treeData', 'class');
- return propsData;
- });
- const getTreeData = computed((): TreeItem[] =>
- searchState.startSearch ? searchState.searchData : unref(treeDataRef)
- );
- const getNotFound = computed((): boolean => {
- return searchState.startSearch && searchState.searchData?.length === 0;
- });
- const { deleteNodeByKey, insertNodeByKey, filterByLevel, updateNodeByKey, getAllKeys } =
- useTree(treeDataRef, getReplaceFields);
- function getIcon(params: Recordable, icon?: string) {
- if (!icon) {
- if (props.renderIcon && isFunction(props.renderIcon)) {
- return props.renderIcon(params);
- }
- }
- return icon;
- }
- async function handleRightClick({ event, node }: Recordable) {
- const { rightMenuList: menuList = [], beforeRightClick } = props;
- let contextMenuOptions: CreateContextOptions = { event, items: [] };
- if (beforeRightClick && isFunction(beforeRightClick)) {
- let result = await beforeRightClick(node, event);
- if (Array.isArray(result)) {
- contextMenuOptions.items = result;
- } else {
- Object.assign(contextMenuOptions, result);
- }
- } else {
- contextMenuOptions.items = menuList;
- }
- if (!contextMenuOptions.items?.length) return;
- createContextMenu(contextMenuOptions);
- }
- function setExpandedKeys(keys: Keys) {
- state.expandedKeys = keys;
- }
- function getExpandedKeys() {
- return state.expandedKeys;
- }
- function setSelectedKeys(keys: Keys) {
- state.selectedKeys = keys;
- }
- function getSelectedKeys() {
- return state.selectedKeys;
- }
- function setCheckedKeys(keys: CheckKeys) {
- state.checkedKeys = keys;
- }
- function getCheckedKeys() {
- return state.checkedKeys;
- }
- function checkAll(checkAll: boolean) {
- state.checkedKeys = checkAll ? getAllKeys() : ([] as Keys);
- }
- function expandAll(expandAll: boolean) {
- state.expandedKeys = expandAll ? getAllKeys() : ([] as Keys);
- }
- function onStrictlyChange(strictly: boolean) {
- state.checkStrictly = strictly;
- }
- function handleSearch(searchValue: string) {
- if (!searchValue) {
- searchState.startSearch = false;
- return;
- }
- searchState.startSearch = true;
- const { title: titleField } = unref(getReplaceFields);
- searchState.searchData = filter(
- unref(treeDataRef),
- (node) => {
- return node[titleField]?.includes(searchValue) ?? false;
- },
- unref(getReplaceFields)
- );
- }
- function handleClickNode(key: string, children: TreeItem[]) {
- if (!props.clickRowToExpand || !children || children.length === 0) return;
- if (!state.expandedKeys.includes(key)) {
- setExpandedKeys([...state.expandedKeys, key]);
- } else {
- const keys = [...state.expandedKeys];
- const index = keys.findIndex((item) => item === key);
- if (index !== -1) {
- keys.splice(index, 1);
- }
- setExpandedKeys(keys);
- }
- }
- watchEffect(() => {
- treeDataRef.value = props.treeData as TreeItem[];
- });
- onMounted(() => {
- const level = parseInt(props.defaultExpandLevel);
- if (level > 0) {
- state.expandedKeys = filterByLevel(level);
- } else if (props.defaultExpandAll) {
- expandAll(true);
- }
- });
- watchEffect(() => {
- state.expandedKeys = props.expandedKeys;
- });
- watchEffect(() => {
- state.selectedKeys = props.selectedKeys;
- });
- watchEffect(() => {
- state.checkedKeys = props.checkedKeys;
- });
- watch(
- () => props.value,
- () => {
- state.checkedKeys = toRaw(props.value || []);
- }
- );
- watch(
- () => state.checkedKeys,
- () => {
- const v = toRaw(state.checkedKeys);
- emit('update:value', v);
- emit('change', v);
- }
- );
- // watchEffect(() => {
- // console.log('======================');
- // console.log(props.value);
- // console.log('======================');
- // if (props.value) {
- // state.checkedKeys = props.value;
- // }
- // });
- watchEffect(() => {
- state.checkStrictly = props.checkStrictly;
- });
- const instance: TreeActionType = {
- setExpandedKeys,
- getExpandedKeys,
- setSelectedKeys,
- getSelectedKeys,
- setCheckedKeys,
- getCheckedKeys,
- insertNodeByKey,
- deleteNodeByKey,
- updateNodeByKey,
- checkAll,
- expandAll,
- filterByLevel: (level: number) => {
- state.expandedKeys = filterByLevel(level);
- },
- };
- expose(instance);
- function renderAction(node: TreeItem) {
- const { actionList } = props;
- if (!actionList || actionList.length === 0) return;
- return actionList.map((item, index) => {
- let nodeShow = true;
- if (isFunction(item.show)) {
- nodeShow = item.show?.(node);
- } else if (isBoolean(item.show)) {
- nodeShow = item.show;
- }
- if (!nodeShow) return null;
- return (
- <span key={index} class={`${prefixCls}__action`}>
- {item.render(node)}
- </span>
- );
- });
- }
- function renderTreeNode({ data, level }: { data: TreeItem[] | undefined; level: number }) {
- if (!data) {
- return null;
- }
- return data.map((item) => {
- const {
- title: titleField,
- key: keyField,
- children: childrenField,
- } = unref(getReplaceFields);
- const propsData = omit(item, 'title');
- const icon = getIcon({ ...item, level }, item.icon);
- const children = get(item, childrenField) || [];
- return (
- <Tree.TreeNode {...propsData} node={toRaw(item)} key={get(item, keyField)}>
- {{
- title: () => (
- <span
- class={`${prefixCls}-title pl-2`}
- onClick={handleClickNode.bind(null, item[keyField], item[childrenField])}
- >
- {slots?.title ? (
- getSlot(slots, 'title', item)
- ) : (
- <>
- {icon && <TreeIcon icon={icon} />}
- <span
- class={unref(getBindValues)?.blockNode ? `${prefixCls}__content` : ''}
- >
- {get(item, titleField)}
- </span>
- <span class={`${prefixCls}__actions`}>
- {renderAction({ ...item, level })}
- </span>
- </>
- )}
- </span>
- ),
- default: () => renderTreeNode({ data: children, level: level + 1 }),
- }}
- </Tree.TreeNode>
- );
- });
- }
- return () => {
- const { title, helpMessage, toolbar, search, checkable } = props;
- const showTitle = title || toolbar || search || slots.headerTitle;
- const scrollStyle: CSSProperties = { height: 'calc(100% - 38px)' };
- return (
- <div class={[prefixCls, 'h-full', attrs.class]}>
- {showTitle && (
- <TreeHeader
- checkable={checkable}
- checkAll={checkAll}
- expandAll={expandAll}
- title={title}
- search={search}
- toolbar={toolbar}
- helpMessage={helpMessage}
- onStrictlyChange={onStrictlyChange}
- onSearch={handleSearch}
- >
- {extendSlots(slots)}
- </TreeHeader>
- )}
- <ScrollContainer style={scrollStyle} v-show={!unref(getNotFound)}>
- <Tree {...unref(getBindValues)} showIcon={false}>
- {{
- // switcherIcon: () => <DownOutlined />,
- default: () => renderTreeNode({ data: unref(getTreeData), level: 1 }),
- ...extendSlots(slots),
- }}
- </Tree>
- </ScrollContainer>
- <Empty v-show={unref(getNotFound)} class="!mt-4" />
- </div>
- );
- };
- },
- });
- </script>
- <style lang="less">
- @prefix-cls: ~'@{namespace}-basic-tree';
- .@{prefix-cls} {
- background-color: @component-background;
- .ant-tree-node-content-wrapper {
- position: relative;
- .ant-tree-title {
- position: absolute;
- left: 0;
- width: 100%;
- }
- }
- &-title {
- position: relative;
- display: flex;
- align-items: center;
- width: 100%;
- padding-right: 10px;
- &:hover {
- .@{prefix-cls}__action {
- visibility: visible;
- }
- }
- }
- &__content {
- overflow: hidden;
- }
- &__actions {
- position: absolute;
- top: 2px;
- right: 3px;
- display: flex;
- }
- &__action {
- margin-left: 4px;
- visibility: hidden;
- }
- }
- </style>
|