From 1f4940fb8544cee6008e797132d91212172ef388 Mon Sep 17 00:00:00 2001 From: liuzaijiang <530604689@qq.com> Date: Sun, 8 May 2022 18:10:38 +0800 Subject: [PATCH] feat(pro: tree): add pro tree component --- .../components/tree-select/docs/Index.zh.md | 4 +- .../components/tree/__tests__/tree.spec.ts | 21 +++ packages/components/tree/docs/Index.zh.md | 8 +- packages/components/tree/index.ts | 1 + packages/components/tree/src/Tree.tsx | 8 +- .../tree/src/composables/useExpandable.ts | 22 ++- packages/components/tree/src/types.ts | 3 +- packages/components/tree/style/index.less | 4 +- .../tree/style/themes/default.variable.less | 3 +- packages/pro/config/src/defaultConfig.ts | 10 +- packages/pro/config/src/types.ts | 12 ++ packages/pro/default.less | 3 +- packages/pro/index.ts | 4 +- packages/pro/seer.less | 3 +- packages/pro/style/variable/prefix.less | 3 +- .../__snapshots__/proTree.spec.ts.snap | 19 ++ packages/pro/tree/__tests__/proTree.spec.ts | 25 +++ packages/pro/tree/demo/Basic.md | 14 ++ packages/pro/tree/demo/Basic.vue | 153 ++++++++++++++++ packages/pro/tree/docs/Design.en.md | 3 + packages/pro/tree/docs/Design.zh.md | 3 + packages/pro/tree/docs/Index.en.md | 29 +++ packages/pro/tree/docs/Index.zh.md | 100 ++++++++++ packages/pro/tree/index.ts | 16 ++ packages/pro/tree/src/ProTree.tsx | 171 ++++++++++++++++++ packages/pro/tree/src/types.ts | 88 +++++++++ packages/pro/tree/style/index.less | 75 ++++++++ packages/pro/tree/style/themes/default.less | 4 + packages/pro/tree/style/themes/default.ts | 4 + .../tree/style/themes/default.variable.less | 10 + packages/pro/tree/style/themes/seer.less | 4 + packages/pro/tree/style/themes/seer.ts | 4 + .../pro/tree/style/themes/seer.variable.less | 1 + packages/pro/types.d.ts | 2 + scripts/gen/style-variable/update.ts | 4 +- 35 files changed, 820 insertions(+), 18 deletions(-) create mode 100644 packages/pro/tree/__tests__/__snapshots__/proTree.spec.ts.snap create mode 100644 packages/pro/tree/__tests__/proTree.spec.ts create mode 100644 packages/pro/tree/demo/Basic.md create mode 100644 packages/pro/tree/demo/Basic.vue create mode 100644 packages/pro/tree/docs/Design.en.md create mode 100644 packages/pro/tree/docs/Design.zh.md create mode 100644 packages/pro/tree/docs/Index.en.md create mode 100644 packages/pro/tree/docs/Index.zh.md create mode 100644 packages/pro/tree/index.ts create mode 100644 packages/pro/tree/src/ProTree.tsx create mode 100644 packages/pro/tree/src/types.ts create mode 100644 packages/pro/tree/style/index.less create mode 100644 packages/pro/tree/style/themes/default.less create mode 100644 packages/pro/tree/style/themes/default.ts create mode 100644 packages/pro/tree/style/themes/default.variable.less create mode 100644 packages/pro/tree/style/themes/seer.less create mode 100644 packages/pro/tree/style/themes/seer.ts create mode 100644 packages/pro/tree/style/themes/seer.variable.less diff --git a/packages/components/tree-select/docs/Index.zh.md b/packages/components/tree-select/docs/Index.zh.md index 1ca30d9df..f83e853f2 100644 --- a/packages/components/tree-select/docs/Index.zh.md +++ b/packages/components/tree-select/docs/Index.zh.md @@ -126,9 +126,11 @@ order: 0 | `@tree-select-option-selected-background-color` | `tint(@color-primary, 90%)` | - | - | | `@tree-select-option-selected-font-weight` | `@font-weight-xl` | - | - | | `@tree-select-option-container-zindex` | `@zindex-l4-3` | - | - | -| `@tree-select-option-container-padding` | `@spacing-sm` | `8px 12px` | - | +| `@tree-select-option-container-padding` | `@spacing-sm` | `8px 0` | - | | `@tree-select-option-container-background-color` | `@background-color-component` | - | - | | `@tree-select-option-container-border-radius` | `@border-radius-sm` | - | - | | `@tree-select-option-container-box-shadow` | `@shadow-bottom-md` | - | - | +| `@tree-select-option-container-search-wrapper-padding` | `0 @spacing-md @spacing-md @spacing-md` | - | - | +| `@tree-select-option-container-tree-node-padding` | `0 0 0 @spacing-xs` | - | - | | `@tree-select-border-radius` | - | `@border-radius-sm` | - | diff --git a/packages/components/tree/__tests__/tree.spec.ts b/packages/components/tree/__tests__/tree.spec.ts index bbce19ac2..26f489076 100644 --- a/packages/components/tree/__tests__/tree.spec.ts +++ b/packages/components/tree/__tests__/tree.spec.ts @@ -346,6 +346,27 @@ describe('Tree', () => { expect(allNodes[1].classes()).toContain('ix-tree-node-expanded') }) + test('expandAll and collapseAll work', async () => { + const wrapper = TreeMount({ + props: { + dataSource: simpleDataSource, + expandedKeys: [], + }, + }) + + await wrapper.vm.expandAll() + + let allNodes = wrapper.findAll('.ix-tree-node') + + expect(allNodes[0].find('.ix-tree-node-expand').exists()).toBe(true) + + await wrapper.vm.collapseAll() + + allNodes = wrapper.findAll('.ix-tree-node') + + expect(allNodes[0].find('.ix-tree-node-expand').exists()).toBe(false) + }) + test('v-model:selectedKeys work', async () => { const onUpdateSelectedKeys = vi.fn() const wrapper = TreeMount({ diff --git a/packages/components/tree/docs/Index.zh.md b/packages/components/tree/docs/Index.zh.md index 0bcfe5523..0f79bafa3 100644 --- a/packages/components/tree/docs/Index.zh.md +++ b/packages/components/tree/docs/Index.zh.md @@ -106,6 +106,8 @@ export type TreeDropType = 'before' | 'inside' | 'after' | 名称 | 说明 | 参数类型 | 备注 | | --- | --- | --- | --- | | `blur` | 失去焦点 | - | - | +| `collapseAll` | 收起所有节点 | - | - | +| `expandAll` | 展开所有节点 | - | - | | `focus` | 获取焦点 | - | - | | `scrollTo` | 滚动到指定位置 | `(option?: number \| VirtualScrollToOptions) => void` | 仅 `virtual` 模式下可用 | @@ -126,12 +128,12 @@ export type TreeDropType = 'before' | 'inside' | 'after' | `@tree-node-hover-background-color` | `@color-graphite-l50` | - | - | | `@tree-node-selected-color` | `@color-graphite-d40` | `@color-primary` | - | | `@tree-node-selected-background-color` | `@color-graphite-l40` | `@color-white` | - | -| `@tree-node-line-width` | `1px` | - | - | -| `@tree-node-line-border` | `1px dashed @color-graphite` | - | - | +| `@tree-node-line-size` | `1px` | - | - | +| `@tree-node-line-color` | `@color-graphite` | - | - | | `@tree-node-padding-vertical` | `(@spacing-sm / 2)` | `0` | - | -| `@tree-node-checkbox-margin` | `0 @spacing-xs 0 @spacing-xs` | - | - | | `@tree-node-content-height` | `@height-sm` | `@height-md` | - | | `@tree-node-content-label-padding` | `0 @spacing-xs` | - | - | | `@tree-node-content-label-highlight-color` | `@color-primary` | - | - | +| `@tree-node-checkbox-margin` | `0 @spacing-xs 0 (@tree-node-content-height / 2 - @tree-icon-font-size / 2)` | - | - | | `@tree-expand-icon-color` | `@color-graphite` | - | - | \ No newline at end of file diff --git a/packages/components/tree/index.ts b/packages/components/tree/index.ts index da8fae74f..641d7ef75 100644 --- a/packages/components/tree/index.ts +++ b/packages/components/tree/index.ts @@ -18,6 +18,7 @@ export type { TreeComponent, TreePublicProps as TreeProps, TreeNode, + TreeNodeDisabled, TreeCustomAdditional, TreeDroppable, TreeDroppableOptions, diff --git a/packages/components/tree/src/Tree.tsx b/packages/components/tree/src/Tree.tsx index aebc417bb..6a9ff19e1 100644 --- a/packages/components/tree/src/Tree.tsx +++ b/packages/components/tree/src/Tree.tsx @@ -132,7 +132,13 @@ export default defineComponent({ virtualScrollRef?.value?.scrollTo(option) } - expose({ focus, blur, scrollTo }) + expose({ + focus, + blur, + expandAll: expandableContext.expandAll, + collapseAll: expandableContext.collapseAll, + scrollTo, + }) const handleScrolledChange = (startIndex: number, endIndex: number, visibleNodes: MergedNode[]) => { callEmit( diff --git a/packages/components/tree/src/composables/useExpandable.ts b/packages/components/tree/src/composables/useExpandable.ts index b367fac12..c1ad66df0 100644 --- a/packages/components/tree/src/composables/useExpandable.ts +++ b/packages/components/tree/src/composables/useExpandable.ts @@ -21,6 +21,8 @@ import { convertMergeNodes, convertMergedNodeMap } from './useDataSource' export interface ExpandableContext { expandIcon: ComputedRef expandedKeys: WritableComputedRef + expandAll: () => void + collapseAll: () => void handleExpand: (key: VKey, rawNode: TreeNode) => void loadingKeys: Ref } @@ -110,9 +112,27 @@ export function useExpandable( callChange(mergedNodeMap, newKeys, onExpandedChange) } + const expandAll = () => { + const expandAllKeys: VKey[] = [] + const expendedAllNodes: TreeNode[] = [] + mergedNodeMap.value.forEach(node => { + if (!node.isLeaf) { + expandAllKeys.push(node.key) + expendedAllNodes.push(node.rawNode) + } + }) + callEmit(props.onExpandedChange, expandAllKeys, expendedAllNodes) + setExpandedKeys(expandAllKeys) + } + + const collapseAll = () => { + callEmit(props.onExpandedChange, [], []) + setExpandedKeys([]) + } + if (searchedKeys.value.length) { setExpandWithSearch(searchedKeys.value) } - return { expandIcon, expandedKeys, handleExpand, loadingKeys } + return { expandIcon, expandedKeys, expandAll, collapseAll, handleExpand, loadingKeys } } diff --git a/packages/components/tree/src/types.ts b/packages/components/tree/src/types.ts index c3f4cbb8e..b7d4bcf6f 100644 --- a/packages/components/tree/src/types.ts +++ b/packages/components/tree/src/types.ts @@ -85,8 +85,9 @@ export type TreePublicProps = Omit, 'no export interface TreeBindings { focus: (options?: FocusOptions) => void blur: () => void + collapseAll: () => void + expandAll: () => void scrollTo: VirtualScrollToFn - setExpandAll: (isAll: boolean) => void } export type TreeComponent = DefineComponent & TreePublicProps, TreeBindings> export type TreeInstance = InstanceType> diff --git a/packages/components/tree/style/index.less b/packages/components/tree/style/index.less index 8d496c65a..640a8cf83 100644 --- a/packages/components/tree/style/index.less +++ b/packages/components/tree/style/index.less @@ -56,7 +56,7 @@ &-top-line { position: absolute; height: (@tree-node-content-height / 2 - 4); - top: -6px; + top: -(@tree-node-content-height / 4 - 4); right: 50%; .vertical-line(); } @@ -65,7 +65,6 @@ position: absolute; top: 50%; width: (@tree-node-content-height / 4); - margin-left: 4px; .horizontal-line(); content: ' '; @@ -137,7 +136,6 @@ line-height: @tree-node-content-height; text-align: center; cursor: pointer; - margin-right: 4px; &-noop { cursor: default; diff --git a/packages/components/tree/style/themes/default.variable.less b/packages/components/tree/style/themes/default.variable.less index 5f2fad1d7..6e606831c 100644 --- a/packages/components/tree/style/themes/default.variable.less +++ b/packages/components/tree/style/themes/default.variable.less @@ -18,9 +18,10 @@ @tree-node-line-color: @color-graphite; @tree-node-padding-vertical: (@spacing-sm / 2); -@tree-node-checkbox-margin: 0 @spacing-xs 0 0; @tree-node-content-height: @height-sm; @tree-node-content-label-padding: 0 @spacing-xs; @tree-node-content-label-highlight-color: @color-primary; +@tree-node-checkbox-margin: 0 @spacing-xs 0 (@tree-node-content-height / 2 - @tree-icon-font-size / 2); + @tree-expand-icon-color: @color-graphite; diff --git a/packages/pro/config/src/defaultConfig.ts b/packages/pro/config/src/defaultConfig.ts index 8e69ee092..f46ac14dc 100644 --- a/packages/pro/config/src/defaultConfig.ts +++ b/packages/pro/config/src/defaultConfig.ts @@ -5,10 +5,18 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ -import type { CommonConfig, GlobalConfig } from './types' +import type { CommonConfig, GlobalConfig, ProTree } from './types' const common: CommonConfig = { prefixCls: 'ix-pro' } +const proTree: ProTree = { + clearIcon: 'close-circle', + childrenKey: 'children', + expandIcon: ['minus-square', 'plus-square'], + getKey: 'key', + labelKey: 'label', +} export const defaultConfig: GlobalConfig = { common, + proTree, } diff --git a/packages/pro/config/src/types.ts b/packages/pro/config/src/types.ts index cb96a79e1..d33c8ca66 100644 --- a/packages/pro/config/src/types.ts +++ b/packages/pro/config/src/types.ts @@ -5,14 +5,26 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ +import type { VKey } from '@idux/cdk/utils' +import type { TreeNode } from '@idux/components/tree' + // Common export interface CommonConfig { prefixCls: string } +export interface ProTree { + childrenKey: string + clearIcon: string + expandIcon: string | string[] + getKey: string | ((data: TreeNode) => VKey) + labelKey: string +} + export interface GlobalConfig { // Common common: CommonConfig + proTree: ProTree } export type GlobalConfigKey = keyof GlobalConfig diff --git a/packages/pro/default.less b/packages/pro/default.less index d52245497..8505e06b9 100644 --- a/packages/pro/default.less +++ b/packages/pro/default.less @@ -2,4 +2,5 @@ @import './layout/style/themes/default.less'; -@import './transfer/style/themes/default.less'; \ No newline at end of file +@import './transfer/style/themes/default.less'; +@import './tree/style/themes/default.less'; diff --git a/packages/pro/index.ts b/packages/pro/index.ts index ef90e6bd1..ac18df904 100644 --- a/packages/pro/index.ts +++ b/packages/pro/index.ts @@ -9,11 +9,12 @@ import type { App, Directive } from 'vue' import { IxProLayout, IxProLayoutSiderTrigger } from '@idux/pro/layout' import { IxProTransfer } from '@idux/pro/transfer' +import { IxProTree } from '@idux/pro/tree' import { version } from '@idux/pro/version' const directives: Record = {} -const components = [IxProLayout, IxProLayoutSiderTrigger, IxProTransfer] +const components = [IxProLayout, IxProLayoutSiderTrigger, IxProTransfer, IxProTree] const install = (app: App): void => { components.forEach(component => { @@ -32,4 +33,5 @@ export { install, version } export * from '@idux/pro/layout' export * from '@idux/pro/transfer' +export * from '@idux/pro/tree' export * from '@idux/pro/version' diff --git a/packages/pro/seer.less b/packages/pro/seer.less index 59e47549e..b7066639e 100644 --- a/packages/pro/seer.less +++ b/packages/pro/seer.less @@ -2,4 +2,5 @@ @import './layout/style/themes/seer.less'; -@import './transfer/style/themes/seer.less'; \ No newline at end of file +@import './transfer/style/themes/seer.less'; +@import './tree/style/themes/seer.less'; diff --git a/packages/pro/style/variable/prefix.less b/packages/pro/style/variable/prefix.less index ace3ef238..5fc0e72b7 100644 --- a/packages/pro/style/variable/prefix.less +++ b/packages/pro/style/variable/prefix.less @@ -4,4 +4,5 @@ @pro-layout-prefix: ~'@{idux-pro-prefix}-layout'; // Transfer -@pro-transfer-prefix: ~'@{idux-pro-prefix}-transfer'; \ No newline at end of file +@pro-transfer-prefix: ~'@{idux-pro-prefix}-transfer'; +@pro-tree-prefix: ~'@{idux-pro-prefix}-tree'; \ No newline at end of file diff --git a/packages/pro/tree/__tests__/__snapshots__/proTree.spec.ts.snap b/packages/pro/tree/__tests__/__snapshots__/proTree.spec.ts.snap new file mode 100644 index 000000000..f9cef9516 --- /dev/null +++ b/packages/pro/tree/__tests__/__snapshots__/proTree.spec.ts.snap @@ -0,0 +1,19 @@ +// Vitest Snapshot v1 + +exports[`ProTree > render work 1`] = ` +"
+
+ + +
+
+
+ +
+
+
暂无数据
+ +
+
+
" +`; diff --git a/packages/pro/tree/__tests__/proTree.spec.ts b/packages/pro/tree/__tests__/proTree.spec.ts new file mode 100644 index 000000000..bc5c998fb --- /dev/null +++ b/packages/pro/tree/__tests__/proTree.spec.ts @@ -0,0 +1,25 @@ +import { MountingOptions, mount } from '@vue/test-utils' + +import { renderWork } from '@tests' + +import ProTree from '../src/ProTree' +import { ProTreeProps } from '../src/types' + +describe.skip('ProTree', () => { + const ProTreeMount = (options?: MountingOptions>) => + mount(ProTree, { ...(options as MountingOptions) }) + + renderWork(ProTree, { + props: {}, + }) + + test('xxx work', async () => { + const wrapper = ProTreeMount({ props: { xxx: 'Xxx' } }) + + expect(wrapper.classes()).toContain('ix-Xxx') + + await wrapper.setProps({ xxx: 'Yyy' }) + + expect(wrapper.classes()).toContain('ix-Yyy') + }) +}) diff --git a/packages/pro/tree/demo/Basic.md b/packages/pro/tree/demo/Basic.md new file mode 100644 index 000000000..0ddf241aa --- /dev/null +++ b/packages/pro/tree/demo/Basic.md @@ -0,0 +1,14 @@ +--- +order: 0 +title: + zh: 基本使用 + en: Basic usage +--- + +## zh + +最简单的用法。 + +## en + +The simplest usage. diff --git a/packages/pro/tree/demo/Basic.vue b/packages/pro/tree/demo/Basic.vue new file mode 100644 index 000000000..dc5b319ec --- /dev/null +++ b/packages/pro/tree/demo/Basic.vue @@ -0,0 +1,153 @@ + + + + + diff --git a/packages/pro/tree/docs/Design.en.md b/packages/pro/tree/docs/Design.en.md new file mode 100644 index 000000000..d1e713d5e --- /dev/null +++ b/packages/pro/tree/docs/Design.en.md @@ -0,0 +1,3 @@ +## Description + +## Usage scenarios diff --git a/packages/pro/tree/docs/Design.zh.md b/packages/pro/tree/docs/Design.zh.md new file mode 100644 index 000000000..933801767 --- /dev/null +++ b/packages/pro/tree/docs/Design.zh.md @@ -0,0 +1,3 @@ +## 组件定义 + +## 使用场景 diff --git a/packages/pro/tree/docs/Index.en.md b/packages/pro/tree/docs/Index.en.md new file mode 100644 index 000000000..2fcd8f404 --- /dev/null +++ b/packages/pro/tree/docs/Index.en.md @@ -0,0 +1,29 @@ +--- +category: pro +type: Data Entry +order: 0 +title: ProTree +subtitle: +--- + +## API + +### IxProTree + +#### ProTreeProps + +| Name | Description | Type | Default | Global Config | Remark | +| --- | --- | --- | --- | --- | --- | +| - | - | - | - | ✅ | - | + +#### ProTreeSlots + +| Name | Description | Parameter Type | Remark | +| --- | --- | --- | --- | +| - | - | - | - | + +#### ProTreeMethods + +| Name | Description | Parameter Type | Remark | +| --- | --- | --- | --- | +| - | - | - | - | diff --git a/packages/pro/tree/docs/Index.zh.md b/packages/pro/tree/docs/Index.zh.md new file mode 100644 index 000000000..742e26fae --- /dev/null +++ b/packages/pro/tree/docs/Index.zh.md @@ -0,0 +1,100 @@ +--- +category: pro +type: 数据录入 +order: 0 +title: ProTree +subtitle: 高级树型控件 +--- + +## API + +### IxProTree + +#### ProTreeProps + +| 名称 | 说明 | 类型 | 默认值 | 全局配置 | 备注 | +| --- | --- | --- | --- | --- | --- | +| `v-model:checkedKeys` | 选中选择框节点的 `key` 数组 | `VKey[]` | - | - | - | +| `v-model:expandedKeys` | 展开节点的 `key` 数组 | `VKey[]` | - | - | - | +| `v-model:loadedKeys` | 已经加载完毕的节点的 `key` | `VKey[]` | - | - | - | +| `v-model:selectedKeys` | 选中节点的 `key` 数组 | `VKey[]` | - | - | - | +| `v-model:collapsed` | 树是否收缩 | `boolean` | - | - | 不设置时没有收缩展开功能 | +| `checkable` | 是否显示选择框 | `boolean` | `false` | - | - | +| `childrenKey` | 替代[TreeNode](#TreeNode)中的`children`字段 | `string` | `children` | ✅ | - | +| `checkStrategy` | 勾选策略 | `'all' \| 'parent' \| 'child'` | `'all'` | - | 设置勾选策略来指定显示的勾选节点,`all` 表示显示全部选中节点;`parent` 表示只显示父节点(当父节点下所有子节点都选中时);`child` 表示只显示子节点,仅当`cascade`为`true`时,`parent`和`child`才生效 | +| `collapsedWidth` | 树收缩时的宽度 | `number` | `44` | - | - | +| `customAdditional` | 自定义节点的额外属性 | `TreeCustomAdditional` | - | - | 例如 `class`, 或者原生事件 | +| `dataSource` | 树型数据数组,参见[TreeNode](#TreeNode) | `TreeNode[]` | `[]` | - | - | +| `disabled` | 禁用节点的函数 | `(node: TreeNode) => boolean \| TreeNodeDisabled` | - | - | - | +| `draggable` | 是否允许拖拽节点 | `boolean` | `false` | - | - | +| `droppable` | 是否允许放置节点,参见[TreeDroppable](#TreeDroppable) | `TreeDroppable` | - | - | - | +| `empty` | 空数据时的内容 | `string \| EmptyProps \| #empty` | - | - | - | +| `expandIcon` | 展开图标 | `string \| string[] \| #expandIcon="{key: VKey, expanded: boolean, node: TreeNode}"` | `right` | ✅ | 当为数组时表示[`展开时图标`,`未展开时图标`] | +| `header` | 树的头部 | `string \| HeaderProps \| #header="{expanded, onClick}"` | - | - | - | +| `getKey` | 获取数据的唯一标识 | `string \| (record: any) => VKey` | `key` | ✅ | - | +| `labelKey` | 替代[TreeNode](#TreeNode)中的`label`字段 | `string` | `label` | ✅ | - +| `leafLineIcon` | 叶子节点的图标,用于替换默认的连接线 | `string \| #leafLineIcon` | - | - | 仅在 `showLine` 时生效 | +| `loadChildren` | 加载子节点数据 | `(node: TreeNode) => Promise` | - | - | - | +| `searchable` | 是否拥有搜索功能 | `boolean` | `true` | - | - | +| `searchFn` | 搜索函数 | `(node: TreeNode, searchValue?: string) => boolean` | - | - | - | +| `searchValue` | 用于搜索的值 | `string` | - | - | - +| `selectable` | 是否允许选择 | `boolean \| 'multiple'` | `true` | - | 为 `multiple` 时表示允许多选 | +| `showLine` | 是否显示连接线 | `boolean` | `true` | - | - | +| `placeholder` | 搜索框的`placeholder` | `string` | - | - | - | +| `onCheck` | 选择框勾选状态发生变化时触发 | `(checked: boolean, node: TreeNode) => void` | - | - | - | +| `onCheckedChange` | 选择框勾选状态发生变化时触发 | `(checkedKeys: VKey[], checkedNodes: TreeNode[]) => void` | - | - | - | +| `onDragStart` | `dragstart` 触发时调用 | `(options: TreeDragDropOptions) => void` | - | - | - | +| `onDragEnd` | `dragend` 触发时调用 | `(options: TreeDragDropOptions) => void` | - | - | - | +| `onDragEnter` | `dragenter` 触发时调用 | `(options: TreeDragDropOptions) => void` | - | - | - | +| `onDragLeave` | `dragleave` 触发时调用 | `(options: TreeDragDropOptions) => void` | - | - | - | +| `onDragOver` | `dragover` 触发时调用 | `(options: TreeDragDropOptions) => void` | - | - | - | +| `onDrop` | `drop` 触发时调用 | `(options: TreeDragDropOptions) => void` | - | - | - | +| `onExpand` | 点击展开图标时触发 | `(expanded: boolean, node: TreeNode) => void` | - | - | - | +| `onExpandedChange` | 展开状态发生变化时触发 | `(expendedKeys: VKey[], expendedNodes: TreeNode[]) => void` | - | - | - | +| `onLoaded` | 子节点加载完毕时触发 | `(loadedKeys: VKey[], node: TreeNode) => void` | - | - | - | +| `onSelect` | 选中状态发生变化时触发 | `(selected: boolean, node: TreeNode) => void` | - | - | - | +| `onSelectedChange` | 选中状态发生变化时触发 | `(selectedKeys: VKey[], selectedNodes: TreeNode[]) => void` | - | - | - | +| `onNodeClick` | 节点点击事件 | `(evt: Event, node: TreeNode) => void` | - | - | - | +| `onSearch` | 开启搜索功能后,输入后的回调 | `(searchValue: string) => void` | - | - | 通常用于服务端搜索 | +| `onNodeContextmenu` | 节点右击事件 | `(evt: Event, node: TreeNode) => void` | - | - | - | + + +#### ProTreeSlots + +| 名称 | 说明 | 参数类型 | 备注 | +| --- | --- | --- | --- | +| `label` | 自定义节点的文本 | `{node: TreeNode}` | - | +| `header` | 自定义头部 | - | - | +| `empty` | 自定义空数据状态 | - | - | +| `expandIcon` | 自定义展开节点 | `{key: VKey, expanded: boolean, node: TreeNode}` | - | +| `prefix` | 自定义节点的前缀图标 | `{key: VKey, selected: boolean, node: TreeNode}` | - | +| `suffix` | 自定义节点的后缀图标 | `{key: VKey, selected: boolean, node: TreeNode}` | - | + + + + +## 主题变量 + +| 名称 | default | seer | 备注 | +| --- | --- | --- | --- | +| `@pro-tree-border` | `1px solid @color-graphite-l30` | - | - | +| `@pro-tree-padding` | `4px 0 8px 0` | - | - | +| `@pro-tree-header-search-wrapper-gap` | `4px` | - | - | +| `@pro-tree-header-search-wrapper-padding` | `0 12px` | - | - | +| `@pro-tree-search-wrapper-margin-bottom` | `8px` | - | - | +| `@pro-tree-search-input-suffix-color` | `@color-graphite-l10` | - | - | +| `@pro-tree-header-wrapper-icon-font-size` | `@font-size-lg` | - | - | +| `@pro-tree-header-wrapper-icon-hover-color` | `@color-primary` | - | - | +| `@pro-tree-header-wrapper-height` | `32px` | - | - | +| `@pro-tree-node-padding` | `0 0 0 8px` | - | - | + \ No newline at end of file diff --git a/packages/pro/tree/index.ts b/packages/pro/tree/index.ts new file mode 100644 index 000000000..d967f44f2 --- /dev/null +++ b/packages/pro/tree/index.ts @@ -0,0 +1,16 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { ProTreeComponent } from './src/types' + +import ProTree from './src/ProTree' + +const IxProTree = ProTree as unknown as ProTreeComponent + +export { IxProTree } + +export type { ProTreeInstance, ProTreeComponent, ProTreePublicProps as ProTreeProps } from './src/types' diff --git a/packages/pro/tree/src/ProTree.tsx b/packages/pro/tree/src/ProTree.tsx new file mode 100644 index 000000000..b9d27706c --- /dev/null +++ b/packages/pro/tree/src/ProTree.tsx @@ -0,0 +1,171 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import { computed, defineComponent, normalizeClass, normalizeStyle, ref } from 'vue' + +import { isNil } from 'lodash-es' + +import { callEmit, useControlledProp } from '@idux/cdk/utils' +import { ɵHeader } from '@idux/components/_private/header' +import { ɵInput } from '@idux/components/_private/input' +import { IxButton } from '@idux/components/button' +import { IxIcon } from '@idux/components/icon' +import { IxTree, type TreeInstance } from '@idux/components/tree' +import { useGetKey } from '@idux/components/utils' +import { useGlobalConfig } from '@idux/pro/config' + +import { proTreeProps } from './types' + +export default defineComponent({ + name: 'IxProTree', + props: proTreeProps, + setup(props, { slots }) { + const common = useGlobalConfig('common') + const config = useGlobalConfig('proTree') + + const mergedPrefixCls = computed(() => `${common.prefixCls}-tree`) + const expandIcon = computed(() => props.expandIcon ?? config.expandIcon) + const mergedChildrenKey = computed(() => props.childrenKey ?? config.childrenKey) + const mergedGetKey = useGetKey(props, config, 'proTree') + const mergedLabelKey = computed(() => props.labelKey ?? config.labelKey) + const mergedClearIcon = computed(() => props.clearIcon ?? config.clearIcon) + + const expandAllBtnStatus = ref(false) + const treeRef = ref() + + const [searchValue, setSearchValue] = useControlledProp(props, 'searchValue', '') + const [collapsed, setCollapsed] = useControlledProp(props, 'collapsed') + + const classes = computed(() => { + const prefixCls = mergedPrefixCls.value + + return normalizeClass({ + [prefixCls]: true, + [`${prefixCls}-collapsed`]: collapsed.value, + }) + }) + + const style = computed(() => { + return normalizeStyle({ + width: collapsed.value ? `${props.collapsedWidth}px` : null, + }) + }) + + const handleExpandAll = () => { + const currStatus = expandAllBtnStatus.value + expandAllBtnStatus.value = !currStatus + expandAllBtnStatus.value ? treeRef.value?.collapseAll() : treeRef.value?.expandAll() + } + + const handleCollapsed = () => { + setCollapsed(!collapsed.value) + callEmit(props.onCollapsed, !collapsed.value) + } + + const handleInput = (evt: Event) => { + setSearchValue((evt.target as HTMLInputElement).value) + callEmit(props.onSearch, searchValue.value) + } + + const handleClear = (evt: Event) => { + setSearchValue('') + callEmit(props.onClear, evt) + } + + return () => { + const prefixCls = mergedPrefixCls.value + const treeProps = { + checkedKeys: props.checkedKeys, + expandedKeys: props.expandedKeys, + selectedKeys: props.selectedKeys, + loadedKeys: props.loadedKeys, + + blocked: true, + cascade: true, + childrenKey: mergedChildrenKey.value, + checkable: props.checkable, + checkStrategy: props.checkStrategy, + customAdditional: props.customAdditional, + dataSource: props.dataSource, + disabled: props.disabled, + expandIcon: expandIcon.value, + empty: props.empty, + getKey: mergedGetKey.value, + labelKey: mergedLabelKey.value, + leafLineIcon: props.leafLineIcon, + showLine: props.showLine, + searchValue: searchValue.value, + selectable: props.selectable, + searchFn: props.searchFn, + virtual: props.virtual, + + loadChildren: props.loadChildren, + onCheck: props.onCheck, + onDragstart: props.onDragstart, + onDragend: props.onDragend, + onDragenter: props.onDragenter, + onDragleave: props.onDragleave, + onDragover: props.onDragover, + onDrop: props.onDrop, + onNodeClick: props.onNodeClick, + onNodeContextmenu: props.onNodeContextmenu, + onLoaded: props.onLoaded, + onExpand: props.onExpand, + onSelect: props.onSelect, + onCheckedChange: props.onCheckedChange, + onSelectedChange: props.onSelectedChange, + onExpandedChange: props.onExpandedChange, + onScroll: props.onScroll, + onScrolledBottom: props.onScrolledBottom, + onScrolledChange: props.onScrolledChange, + 'onUpdate:checkedKeys': props['onUpdate:checkedKeys'], + 'onUpdate:expandedKeys': props['onUpdate:expandedKeys'], + 'onUpdate:selectedKeys': props['onUpdate:selectedKeys'], + 'onUpdate:loadedKeys': props['onUpdate:loadedKeys'], + } + + const treeSlots = { + label: slots.label, + prefix: slots.prefix, + suffix: slots.suffix, + leafLineIcon: slots.leafLineIcon, + empty: slots.empty, + expandIcon: slots.expandIcon, + } + return ( +
+
+ <ɵHeader v-slots={slots} header={props.header} /> + {!isNil(collapsed.value) && } +
+ {props.searchable && ( +
+ + <ɵInput + placeholder={props.placeholder} + clearable + clearIcon={mergedClearIcon.value} + size="sm" + suffix="search" + value={searchValue.value} + clearVisible={!!searchValue.value} + onInput={handleInput} + onClear={handleClear} + /> +
+ )} + +
+ ) + } + }, +}) diff --git a/packages/pro/tree/src/types.ts b/packages/pro/tree/src/types.ts new file mode 100644 index 000000000..2ae72f119 --- /dev/null +++ b/packages/pro/tree/src/types.ts @@ -0,0 +1,88 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { ExtractInnerPropTypes, ExtractPublicPropTypes, MaybeArray, VKey } from '@idux/cdk/utils' +import type { EmptyProps } from '@idux/components/empty' +import type { HeaderProps } from '@idux/components/header' +import type { + TreeCheckStrategy, + TreeCustomAdditional, + TreeDragDropOptions, + TreeNode, + TreeNodeDisabled, +} from '@idux/components/tree' +import type { DefineComponent, HTMLAttributes, PropType } from 'vue' + +export const proTreeProps = { + checkedKeys: { type: Array as PropType, default: undefined }, + expandedKeys: { type: Array as PropType, default: undefined }, + loadedKeys: { type: Array as PropType, default: undefined }, + selectedKeys: { type: Array as PropType, default: undefined }, + + checkable: { type: Boolean, default: false }, + childrenKey: { type: String, default: undefined }, + checkStrategy: { type: String as PropType, default: 'all' }, + clearIcon: { type: String, default: undefined }, + customAdditional: { type: Object as PropType, default: undefined }, + collapsed: { type: Boolean, default: undefined }, + collapsedWidth: { type: Number, default: 44 }, + dataSource: { type: Array as PropType, default: undefined }, + disabled: { type: Function as PropType<(node: TreeNode) => boolean | TreeNodeDisabled>, default: undefined }, + empty: { type: [String, Object] as PropType, default: undefined }, + expandIcon: { type: [String, Array] as PropType, default: undefined }, + getKey: { type: [String, Function] as PropType VKey)>, default: undefined }, + header: { type: [String, Object] as PropType, default: undefined }, + labelKey: { type: String, default: undefined }, + leafLineIcon: { type: String, default: undefined }, + loadChildren: { type: Function as PropType<(node: TreeNode) => Promise>, default: undefined }, + showLine: { type: Boolean, default: true }, + searchValue: { type: String, default: undefined }, + searchable: { type: Boolean, default: true }, + searchFn: { type: Function as PropType<(node: TreeNode, searchValue?: string) => boolean>, default: undefined }, + selectable: { type: [Boolean, String] as PropType, default: true }, + placeholder: { type: String, default: undefined }, + virtual: { type: Boolean, default: false }, + + // events + onCheck: [Function, Array] as PropType void>>, + onClear: [Function, Array] as PropType void>>, + onCollapsed: [Function, Array] as PropType void>>, + onDragstart: [Function, Array] as PropType void>>, + onDragend: [Function, Array] as PropType void>>, + onDragenter: [Function, Array] as PropType void>>, + onDragleave: [Function, Array] as PropType void>>, + onDragover: [Function, Array] as PropType void>>, + onDrop: [Function, Array] as PropType void>>, + onNodeClick: [Function, Array] as PropType void>>, + onNodeContextmenu: [Function, Array] as PropType void>>, + onLoaded: [Function, Array] as PropType void>>, + onExpand: [Function, Array] as PropType void>>, + onSelect: [Function, Array] as PropType void>>, + onScroll: [Function, Array] as PropType void>>, + onScrolledChange: [Function, Array] as PropType< + MaybeArray<(startIndex: number, endIndex: number, visibleData: TreeNode[]) => void> + >, + onScrolledBottom: [Function, Array] as PropType void>>, + onSearch: [Function, Array] as PropType void>>, + onExpandedChange: [Function, Array] as PropType< + MaybeArray<(expendedKeys: VKey[], expendedNodes: TreeNode[]) => void> + >, + onCheckedChange: [Function, Array] as PropType void>>, + onSelectedChange: [Function, Array] as PropType< + MaybeArray<(selectedKeys: VKey[], selectedNodes: TreeNode[]) => void> + >, + 'onUpdate:collapsed': [Function, Array] as PropType void>>, + 'onUpdate:checkedKeys': [Function, Array] as PropType void>>, + 'onUpdate:expandedKeys': [Function, Array] as PropType void>>, + 'onUpdate:loadedKeys': [Function, Array] as PropType void>>, + 'onUpdate:selectedKeys': [Function, Array] as PropType void>>, +} as const + +export type ProTreeProps = ExtractInnerPropTypes +export type ProTreePublicProps = ExtractPublicPropTypes +export type ProTreeComponent = DefineComponent & ProTreePublicProps> +export type ProTreeInstance = InstanceType> diff --git a/packages/pro/tree/style/index.less b/packages/pro/tree/style/index.less new file mode 100644 index 000000000..4f7168b49 --- /dev/null +++ b/packages/pro/tree/style/index.less @@ -0,0 +1,75 @@ +@import '../../style/mixins/reset.less'; + +.@{pro-tree-prefix} { + .reset-component(); + + border: @pro-tree-border; + padding: @pro-tree-padding; + width: 100%; + display: flex; + flex-direction: column; + overflow: hidden; + transition: width @transition-duration-base; + + &-header-wrapper, + &-search-wrapper { + display: flex; + align-items: center; + gap: @pro-tree-header-search-wrapper-gap; + padding: @pro-tree-header-search-wrapper-padding; + } + + &-search-wrapper { + margin-bottom: @pro-tree-search-wrapper-margin-bottom; + .@{input-prefix}-suffix { + color: @pro-tree-search-input-suffix-color; + } + } + + &-header-wrapper { + .@{icon-prefix} { + font-size: @pro-tree-header-wrapper-icon-font-size; + flex: auto; + cursor: pointer; + display: inline-block; + position: relative; + top: 2px; + &:hover { + color: @pro-tree-header-wrapper-icon-hover-color; + } + } + + .@{icon-prefix}, + .@{header-prefix} { + height: @pro-tree-header-wrapper-height; + line-height: @pro-tree-header-wrapper-height; + } + } + + .@{tree-prefix} { + flex: 1; + overflow: auto; + + &-node { + padding: @pro-tree-node-padding; + } + } + + &-collapsed { + .@{pro-tree-prefix} { + &-header-wrapper { + .@{header-prefix} { + display: none; + } + } + + &-search-wrapper { + opacity: 0; + } + } + + .@{tree-prefix} { + opacity: 0; + } + } +} diff --git a/packages/pro/tree/style/themes/default.less b/packages/pro/tree/style/themes/default.less new file mode 100644 index 000000000..a66443560 --- /dev/null +++ b/packages/pro/tree/style/themes/default.less @@ -0,0 +1,4 @@ +@import '../../../style/themes/default.less'; +@import './default.variable.less'; + +@import '../index.less'; diff --git a/packages/pro/tree/style/themes/default.ts b/packages/pro/tree/style/themes/default.ts new file mode 100644 index 000000000..b00e94352 --- /dev/null +++ b/packages/pro/tree/style/themes/default.ts @@ -0,0 +1,4 @@ +// style dependencies +import '@idux/pro/style/core/default' + +import './default.less' diff --git a/packages/pro/tree/style/themes/default.variable.less b/packages/pro/tree/style/themes/default.variable.less new file mode 100644 index 000000000..d1a5ceeb9 --- /dev/null +++ b/packages/pro/tree/style/themes/default.variable.less @@ -0,0 +1,10 @@ +@pro-tree-border: 1px solid @color-graphite-l30; +@pro-tree-padding: 4px 0 8px 0; +@pro-tree-header-search-wrapper-gap: 4px; +@pro-tree-header-search-wrapper-padding: 0 12px; +@pro-tree-search-wrapper-margin-bottom: 8px; +@pro-tree-search-input-suffix-color: @color-graphite-l10; +@pro-tree-header-wrapper-icon-font-size: @font-size-lg; +@pro-tree-header-wrapper-icon-hover-color: @color-primary; +@pro-tree-header-wrapper-height: 32px; +@pro-tree-node-padding: 0 0 0 8px; diff --git a/packages/pro/tree/style/themes/seer.less b/packages/pro/tree/style/themes/seer.less new file mode 100644 index 000000000..32bf707c1 --- /dev/null +++ b/packages/pro/tree/style/themes/seer.less @@ -0,0 +1,4 @@ +@import '../../../style/themes/seer.less'; +@import './seer.variable.less'; + +@import '../index.less'; diff --git a/packages/pro/tree/style/themes/seer.ts b/packages/pro/tree/style/themes/seer.ts new file mode 100644 index 000000000..ff7a8f00f --- /dev/null +++ b/packages/pro/tree/style/themes/seer.ts @@ -0,0 +1,4 @@ +// style dependencies +import '@idux/pro/style/core/seer' + +import './seer.less' diff --git a/packages/pro/tree/style/themes/seer.variable.less b/packages/pro/tree/style/themes/seer.variable.less new file mode 100644 index 000000000..498793af1 --- /dev/null +++ b/packages/pro/tree/style/themes/seer.variable.less @@ -0,0 +1 @@ +@import './default.variable.less'; diff --git a/packages/pro/types.d.ts b/packages/pro/types.d.ts index de6ba6f8e..ae9253acd 100644 --- a/packages/pro/types.d.ts +++ b/packages/pro/types.d.ts @@ -7,12 +7,14 @@ import type { ProLayoutComponent, ProLayoutSiderTriggerComponent } from '@idux/pro/layout' import type { ProTransferComponent } from '@idux/pro/transfer' +import type { ProTreeComponent } from '@idux/pro/tree' declare module 'vue' { export interface GlobalComponents { IxProLayout: ProLayoutComponent IxProLayoutSiderTrigger: ProLayoutSiderTriggerComponent IxProTransfer: ProTransferComponent + IxProTree: ProTreeComponent } } diff --git a/scripts/gen/style-variable/update.ts b/scripts/gen/style-variable/update.ts index 1a608f201..c899b0f67 100644 --- a/scripts/gen/style-variable/update.ts +++ b/scripts/gen/style-variable/update.ts @@ -1,7 +1,7 @@ import type { UpdateStyleVariableConfig } from './config' import type { Pattern } from 'fast-glob' -import { join } from 'path' +import { basename, join } from 'path' import fg from 'fast-glob' import { appendFile, lstatSync, readFile, readdir, writeFile } from 'fs-extra' @@ -46,7 +46,7 @@ class UpdateStyleVariable { } private getTheme(themeFile: string) { - return themeFile.match(/.*\/(.*)\.variable.less$/)?.[1] + return basename(themeFile, '.variable.less') } private isRequired(dir: string, component: string, curDirPath: string, config: UpdateStyleVariableConfig) {