|
|
@ -1,7 +1,13 @@ |
|
|
<script lang="tsx"> |
|
|
<script lang="tsx"> |
|
|
import type { CSSProperties } from 'vue'; |
|
|
import type { CSSProperties } from 'vue'; |
|
|
import type { FieldNames, TreeState, TreeItem, KeyType, CheckKeys, TreeActionType } from './tree'; |
|
|
import type { |
|
|
|
|
|
FieldNames, |
|
|
|
|
|
TreeState, |
|
|
|
|
|
TreeItem, |
|
|
|
|
|
KeyType, |
|
|
|
|
|
CheckKeys, |
|
|
|
|
|
TreeActionType, |
|
|
|
|
|
} from './types/tree'; |
|
|
import { |
|
|
import { |
|
|
defineComponent, |
|
|
defineComponent, |
|
|
reactive, |
|
|
reactive, |
|
|
@ -13,7 +19,7 @@ |
|
|
watch, |
|
|
watch, |
|
|
onMounted, |
|
|
onMounted, |
|
|
} from 'vue'; |
|
|
} from 'vue'; |
|
|
import TreeHeader from './TreeHeader.vue'; |
|
|
import TreeHeader from './components/TreeHeader.vue'; |
|
|
import { Tree, Spin, Empty } from 'ant-design-vue'; |
|
|
import { Tree, Spin, Empty } from 'ant-design-vue'; |
|
|
import { TreeIcon } from './TreeIcon'; |
|
|
import { TreeIcon } from './TreeIcon'; |
|
|
import { ScrollContainer } from '/@/components/Container'; |
|
|
import { ScrollContainer } from '/@/components/Container'; |
|
|
@ -21,12 +27,11 @@ |
|
|
import { isArray, isBoolean, isEmpty, isFunction } from '/@/utils/is'; |
|
|
import { isArray, isBoolean, isEmpty, isFunction } from '/@/utils/is'; |
|
|
import { extendSlots, getSlot } from '/@/utils/helper/tsxHelper'; |
|
|
import { extendSlots, getSlot } from '/@/utils/helper/tsxHelper'; |
|
|
import { filter, treeToList, eachTree } from '/@/utils/helper/treeHelper'; |
|
|
import { filter, treeToList, eachTree } from '/@/utils/helper/treeHelper'; |
|
|
import { useTree } from './useTree'; |
|
|
import { useTree } from './hooks/useTree'; |
|
|
import { useContextMenu } from '/@/hooks/web/useContextMenu'; |
|
|
import { useContextMenu } from '/@/hooks/web/useContextMenu'; |
|
|
import { CreateContextOptions } from '/@/components/ContextMenu'; |
|
|
import { CreateContextOptions } from '/@/components/ContextMenu'; |
|
|
import { treeEmits, treeProps } from './tree'; |
|
|
import { treeEmits, treeProps } from './types/tree'; |
|
|
import { createBEM } from '/@/utils/bem'; |
|
|
import { createBEM } from '/@/utils/bem'; |
|
|
|
|
|
|
|
|
export default defineComponent({ |
|
|
export default defineComponent({ |
|
|
name: 'BasicTree', |
|
|
name: 'BasicTree', |
|
|
inheritAttrs: false, |
|
|
inheritAttrs: false, |
|
|
@ -34,24 +39,19 @@ |
|
|
emits: treeEmits, |
|
|
emits: treeEmits, |
|
|
setup(props, { attrs, slots, emit, expose }) { |
|
|
setup(props, { attrs, slots, emit, expose }) { |
|
|
const [bem] = createBEM('tree'); |
|
|
const [bem] = createBEM('tree'); |
|
|
|
|
|
|
|
|
const state = reactive<TreeState>({ |
|
|
const state = reactive<TreeState>({ |
|
|
checkStrictly: props.checkStrictly, |
|
|
checkStrictly: props.checkStrictly, |
|
|
expandedKeys: props.expandedKeys || [], |
|
|
expandedKeys: props.expandedKeys || [], |
|
|
selectedKeys: props.selectedKeys || [], |
|
|
selectedKeys: props.selectedKeys || [], |
|
|
checkedKeys: props.checkedKeys || [], |
|
|
checkedKeys: props.checkedKeys || [], |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const searchState = reactive({ |
|
|
const searchState = reactive({ |
|
|
startSearch: false, |
|
|
startSearch: false, |
|
|
searchText: '', |
|
|
searchText: '', |
|
|
searchData: [] as TreeItem[], |
|
|
searchData: [] as TreeItem[], |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const treeDataRef = ref<TreeItem[]>([]); |
|
|
const treeDataRef = ref<TreeItem[]>([]); |
|
|
|
|
|
|
|
|
const [createContextMenu] = useContextMenu(); |
|
|
const [createContextMenu] = useContextMenu(); |
|
|
|
|
|
|
|
|
const getFieldNames = computed((): Required<FieldNames> => { |
|
|
const getFieldNames = computed((): Required<FieldNames> => { |
|
|
const { fieldNames } = props; |
|
|
const { fieldNames } = props; |
|
|
return { |
|
|
return { |
|
|
@ -61,7 +61,6 @@ |
|
|
...fieldNames, |
|
|
...fieldNames, |
|
|
}; |
|
|
}; |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const getBindValues = computed(() => { |
|
|
const getBindValues = computed(() => { |
|
|
let propsData = { |
|
|
let propsData = { |
|
|
blockNode: true, |
|
|
blockNode: true, |
|
|
@ -83,16 +82,15 @@ |
|
|
onCheck: (v: CheckKeys, e) => { |
|
|
onCheck: (v: CheckKeys, e) => { |
|
|
let currentValue = toRaw(state.checkedKeys) as KeyType[]; |
|
|
let currentValue = toRaw(state.checkedKeys) as KeyType[]; |
|
|
if (isArray(currentValue) && searchState.startSearch) { |
|
|
if (isArray(currentValue) && searchState.startSearch) { |
|
|
const { key } = unref(getFieldNames); |
|
|
const value = e.node.eventKey; |
|
|
currentValue = difference(currentValue, getChildrenKeys(e.node.$attrs.node[key])); |
|
|
currentValue = difference(currentValue, getChildrenKeys(value)); |
|
|
if (e.checked) { |
|
|
if (e.checked) { |
|
|
currentValue.push(e.node.$attrs.node[key]); |
|
|
currentValue.push(value); |
|
|
} |
|
|
} |
|
|
state.checkedKeys = currentValue; |
|
|
state.checkedKeys = currentValue; |
|
|
} else { |
|
|
} else { |
|
|
state.checkedKeys = v; |
|
|
state.checkedKeys = v; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const rawVal = toRaw(state.checkedKeys); |
|
|
const rawVal = toRaw(state.checkedKeys); |
|
|
emit('update:value', rawVal); |
|
|
emit('update:value', rawVal); |
|
|
emit('check', rawVal, e); |
|
|
emit('check', rawVal, e); |
|
|
@ -101,15 +99,12 @@ |
|
|
}; |
|
|
}; |
|
|
return omit(propsData, 'treeData', 'class'); |
|
|
return omit(propsData, 'treeData', 'class'); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const getTreeData = computed((): TreeItem[] => |
|
|
const getTreeData = computed((): TreeItem[] => |
|
|
searchState.startSearch ? searchState.searchData : unref(treeDataRef), |
|
|
searchState.startSearch ? searchState.searchData : unref(treeDataRef), |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
const getNotFound = computed((): boolean => { |
|
|
const getNotFound = computed((): boolean => { |
|
|
return !getTreeData.value || getTreeData.value.length === 0; |
|
|
return !getTreeData.value || getTreeData.value.length === 0; |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const { |
|
|
const { |
|
|
deleteNodeByKey, |
|
|
deleteNodeByKey, |
|
|
insertNodeByKey, |
|
|
insertNodeByKey, |
|
|
@ -121,7 +116,6 @@ |
|
|
getEnabledKeys, |
|
|
getEnabledKeys, |
|
|
getSelectedNode, |
|
|
getSelectedNode, |
|
|
} = useTree(treeDataRef, getFieldNames); |
|
|
} = useTree(treeDataRef, getFieldNames); |
|
|
|
|
|
|
|
|
function getIcon(params: Recordable, icon?: string) { |
|
|
function getIcon(params: Recordable, icon?: string) { |
|
|
if (!icon) { |
|
|
if (!icon) { |
|
|
if (props.renderIcon && isFunction(props.renderIcon)) { |
|
|
if (props.renderIcon && isFunction(props.renderIcon)) { |
|
|
@ -130,11 +124,9 @@ |
|
|
} |
|
|
} |
|
|
return icon; |
|
|
return icon; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async function handleRightClick({ event, node }: Recordable) { |
|
|
async function handleRightClick({ event, node }: Recordable) { |
|
|
const { rightMenuList: menuList = [], beforeRightClick } = props; |
|
|
const { rightMenuList: menuList = [], beforeRightClick } = props; |
|
|
let contextMenuOptions: CreateContextOptions = { event, items: [] }; |
|
|
let contextMenuOptions: CreateContextOptions = { event, items: [] }; |
|
|
|
|
|
|
|
|
if (beforeRightClick && isFunction(beforeRightClick)) { |
|
|
if (beforeRightClick && isFunction(beforeRightClick)) { |
|
|
let result = await beforeRightClick(node, event); |
|
|
let result = await beforeRightClick(node, event); |
|
|
if (Array.isArray(result)) { |
|
|
if (Array.isArray(result)) { |
|
|
@ -149,42 +141,33 @@ |
|
|
contextMenuOptions.items = contextMenuOptions.items.filter((item) => !item.hidden); |
|
|
contextMenuOptions.items = contextMenuOptions.items.filter((item) => !item.hidden); |
|
|
createContextMenu(contextMenuOptions); |
|
|
createContextMenu(contextMenuOptions); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function setExpandedKeys(keys: KeyType[]) { |
|
|
function setExpandedKeys(keys: KeyType[]) { |
|
|
state.expandedKeys = keys; |
|
|
state.expandedKeys = keys; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function getExpandedKeys() { |
|
|
function getExpandedKeys() { |
|
|
return state.expandedKeys; |
|
|
return state.expandedKeys; |
|
|
} |
|
|
} |
|
|
function setSelectedKeys(keys: KeyType[]) { |
|
|
function setSelectedKeys(keys: KeyType[]) { |
|
|
state.selectedKeys = keys; |
|
|
state.selectedKeys = keys; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function getSelectedKeys() { |
|
|
function getSelectedKeys() { |
|
|
return state.selectedKeys; |
|
|
return state.selectedKeys; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function setCheckedKeys(keys: CheckKeys) { |
|
|
function setCheckedKeys(keys: CheckKeys) { |
|
|
state.checkedKeys = keys; |
|
|
state.checkedKeys = keys; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function getCheckedKeys() { |
|
|
function getCheckedKeys() { |
|
|
return state.checkedKeys; |
|
|
return state.checkedKeys; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function checkAll(checkAll: boolean) { |
|
|
function checkAll(checkAll: boolean) { |
|
|
state.checkedKeys = checkAll ? getEnabledKeys() : ([] as KeyType[]); |
|
|
state.checkedKeys = checkAll ? getEnabledKeys() : ([] as KeyType[]); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function expandAll(expandAll: boolean) { |
|
|
function expandAll(expandAll: boolean) { |
|
|
state.expandedKeys = expandAll ? getAllKeys() : ([] as KeyType[]); |
|
|
state.expandedKeys = expandAll ? getAllKeys() : ([] as KeyType[]); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function onStrictlyChange(strictly: boolean) { |
|
|
function onStrictlyChange(strictly: boolean) { |
|
|
state.checkStrictly = strictly; |
|
|
state.checkStrictly = strictly; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
watch( |
|
|
watch( |
|
|
() => props.searchValue, |
|
|
() => props.searchValue, |
|
|
(val) => { |
|
|
(val) => { |
|
|
@ -196,7 +179,6 @@ |
|
|
immediate: true, |
|
|
immediate: true, |
|
|
}, |
|
|
}, |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
watch( |
|
|
watch( |
|
|
() => props.treeData, |
|
|
() => props.treeData, |
|
|
(val) => { |
|
|
(val) => { |
|
|
@ -205,7 +187,6 @@ |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
function handleSearch(searchValue: string) { |
|
|
function handleSearch(searchValue: string) { |
|
|
if (searchValue !== searchState.searchText) searchState.searchText = searchValue; |
|
|
if (searchValue !== searchState.searchText) searchState.searchText = searchValue; |
|
|
emit('update:searchValue', searchValue); |
|
|
emit('update:searchValue', searchValue); |
|
|
@ -217,7 +198,6 @@ |
|
|
unref(props); |
|
|
unref(props); |
|
|
searchState.startSearch = true; |
|
|
searchState.startSearch = true; |
|
|
const { title: titleField, key: keyField } = unref(getFieldNames); |
|
|
const { title: titleField, key: keyField } = unref(getFieldNames); |
|
|
|
|
|
|
|
|
const matchedKeys: string[] = []; |
|
|
const matchedKeys: string[] = []; |
|
|
searchState.searchData = filter( |
|
|
searchState.searchData = filter( |
|
|
unref(treeDataRef), |
|
|
unref(treeDataRef), |
|
|
@ -232,7 +212,6 @@ |
|
|
}, |
|
|
}, |
|
|
unref(getFieldNames), |
|
|
unref(getFieldNames), |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
if (expandOnSearch) { |
|
|
if (expandOnSearch) { |
|
|
const expandKeys = treeToList(searchState.searchData).map((val) => { |
|
|
const expandKeys = treeToList(searchState.searchData).map((val) => { |
|
|
return val[keyField]; |
|
|
return val[keyField]; |
|
|
@ -241,16 +220,13 @@ |
|
|
setExpandedKeys(expandKeys); |
|
|
setExpandedKeys(expandKeys); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (checkOnSearch && checkable && matchedKeys.length) { |
|
|
if (checkOnSearch && checkable && matchedKeys.length) { |
|
|
setCheckedKeys(matchedKeys); |
|
|
setCheckedKeys(matchedKeys); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (selectedOnSearch && matchedKeys.length) { |
|
|
if (selectedOnSearch && matchedKeys.length) { |
|
|
setSelectedKeys(matchedKeys); |
|
|
setSelectedKeys(matchedKeys); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function handleClickNode(key: string, children: TreeItem[]) { |
|
|
function handleClickNode(key: string, children: TreeItem[]) { |
|
|
if (!props.clickRowToExpand || !children || children.length === 0) return; |
|
|
if (!props.clickRowToExpand || !children || children.length === 0) return; |
|
|
if (!state.expandedKeys.includes(key)) { |
|
|
if (!state.expandedKeys.includes(key)) { |
|
|
@ -264,11 +240,9 @@ |
|
|
setExpandedKeys(keys); |
|
|
setExpandedKeys(keys); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
watchEffect(() => { |
|
|
watchEffect(() => { |
|
|
treeDataRef.value = props.treeData as TreeItem[]; |
|
|
treeDataRef.value = props.treeData as TreeItem[]; |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
onMounted(() => { |
|
|
onMounted(() => { |
|
|
const level = parseInt(props.defaultExpandLevel); |
|
|
const level = parseInt(props.defaultExpandLevel); |
|
|
if (level > 0) { |
|
|
if (level > 0) { |
|
|
@ -277,19 +251,15 @@ |
|
|
expandAll(true); |
|
|
expandAll(true); |
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
watchEffect(() => { |
|
|
watchEffect(() => { |
|
|
state.expandedKeys = props.expandedKeys; |
|
|
state.expandedKeys = props.expandedKeys; |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
watchEffect(() => { |
|
|
watchEffect(() => { |
|
|
state.selectedKeys = props.selectedKeys; |
|
|
state.selectedKeys = props.selectedKeys; |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
watchEffect(() => { |
|
|
watchEffect(() => { |
|
|
state.checkedKeys = props.checkedKeys; |
|
|
state.checkedKeys = props.checkedKeys; |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
watch( |
|
|
watch( |
|
|
() => props.value, |
|
|
() => props.value, |
|
|
() => { |
|
|
() => { |
|
|
@ -297,7 +267,6 @@ |
|
|
}, |
|
|
}, |
|
|
{ immediate: true }, |
|
|
{ immediate: true }, |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
watch( |
|
|
watch( |
|
|
() => state.checkedKeys, |
|
|
() => state.checkedKeys, |
|
|
() => { |
|
|
() => { |
|
|
@ -306,11 +275,9 @@ |
|
|
emit('change', v); |
|
|
emit('change', v); |
|
|
}, |
|
|
}, |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
watchEffect(() => { |
|
|
watchEffect(() => { |
|
|
state.checkStrictly = props.checkStrictly; |
|
|
state.checkStrictly = props.checkStrictly; |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const instance: TreeActionType = { |
|
|
const instance: TreeActionType = { |
|
|
setExpandedKeys, |
|
|
setExpandedKeys, |
|
|
getExpandedKeys, |
|
|
getExpandedKeys, |
|
|
@ -335,7 +302,6 @@ |
|
|
return searchState.searchText; |
|
|
return searchState.searchText; |
|
|
}, |
|
|
}, |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
function renderAction(node: TreeItem) { |
|
|
function renderAction(node: TreeItem) { |
|
|
const { actionList } = props; |
|
|
const { actionList } = props; |
|
|
if (!actionList || actionList.length === 0) return; |
|
|
if (!actionList || actionList.length === 0) return; |
|
|
@ -346,9 +312,7 @@ |
|
|
} else if (isBoolean(item.show)) { |
|
|
} else if (isBoolean(item.show)) { |
|
|
nodeShow = item.show; |
|
|
nodeShow = item.show; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (!nodeShow) return null; |
|
|
if (!nodeShow) return null; |
|
|
|
|
|
|
|
|
return ( |
|
|
return ( |
|
|
<span key={index} class={bem('action')}> |
|
|
<span key={index} class={bem('action')}> |
|
|
{item.render(node)} |
|
|
{item.render(node)} |
|
|
@ -356,7 +320,6 @@ |
|
|
); |
|
|
); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const treeData = computed(() => { |
|
|
const treeData = computed(() => { |
|
|
const data = cloneDeep(getTreeData.value); |
|
|
const data = cloneDeep(getTreeData.value); |
|
|
eachTree(data, (item, _parent) => { |
|
|
eachTree(data, (item, _parent) => { |
|
|
@ -367,15 +330,12 @@ |
|
|
key: keyField, |
|
|
key: keyField, |
|
|
children: childrenField, |
|
|
children: childrenField, |
|
|
} = unref(getFieldNames); |
|
|
} = unref(getFieldNames); |
|
|
|
|
|
|
|
|
const icon = getIcon(item, item.icon); |
|
|
const icon = getIcon(item, item.icon); |
|
|
const title = get(item, titleField); |
|
|
const title = get(item, titleField); |
|
|
|
|
|
|
|
|
const searchIdx = searchText ? title.indexOf(searchText) : -1; |
|
|
const searchIdx = searchText ? title.indexOf(searchText) : -1; |
|
|
const isHighlight = |
|
|
const isHighlight = |
|
|
searchState.startSearch && !isEmpty(searchText) && highlight && searchIdx !== -1; |
|
|
searchState.startSearch && !isEmpty(searchText) && highlight && searchIdx !== -1; |
|
|
const highlightStyle = `color: ${isBoolean(highlight) ? '#f50' : highlight}`; |
|
|
const highlightStyle = `color: ${isBoolean(highlight) ? '#f50' : highlight}`; |
|
|
|
|
|
|
|
|
const titleDom = isHighlight ? ( |
|
|
const titleDom = isHighlight ? ( |
|
|
<span class={unref(getBindValues)?.blockNode ? `${bem('content')}` : ''}> |
|
|
<span class={unref(getBindValues)?.blockNode ? `${bem('content')}` : ''}> |
|
|
<span>{title.substr(0, searchIdx)}</span> |
|
|
<span>{title.substr(0, searchIdx)}</span> |
|
|
@ -405,9 +365,7 @@ |
|
|
}); |
|
|
}); |
|
|
return data; |
|
|
return data; |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
expose(instance); |
|
|
expose(instance); |
|
|
|
|
|
|
|
|
return () => { |
|
|
return () => { |
|
|
const { title, helpMessage, toolbar, search, checkable } = props; |
|
|
const { title, helpMessage, toolbar, search, checkable } = props; |
|
|
const showTitle = title || toolbar || search || slots.headerTitle; |
|
|
const showTitle = title || toolbar || search || slots.headerTitle; |
|
|
@ -430,7 +388,11 @@ |
|
|
{extendSlots(slots)} |
|
|
{extendSlots(slots)} |
|
|
</TreeHeader> |
|
|
</TreeHeader> |
|
|
)} |
|
|
)} |
|
|
<Spin spinning={unref(props.loading)} tip="加载中..."> |
|
|
<Spin |
|
|
|
|
|
wrapperClassName={unref(props.treeWrapperClassName)} |
|
|
|
|
|
spinning={unref(props.loading)} |
|
|
|
|
|
tip="加载中..." |
|
|
|
|
|
> |
|
|
<ScrollContainer style={scrollStyle} v-show={!unref(getNotFound)}> |
|
|
<ScrollContainer style={scrollStyle} v-show={!unref(getNotFound)}> |
|
|
<Tree {...unref(getBindValues)} showIcon={false} treeData={treeData.value} /> |
|
|
<Tree {...unref(getBindValues)} showIcon={false} treeData={treeData.value} /> |
|
|
</ScrollContainer> |
|
|
</ScrollContainer> |