From 47c1c350584020a4afdad4528f9b88a75aeb9b99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AA=88=E7=91=9E=E6=9D=B0?= <34231082+pianruijie@users.noreply.github.com> Date: Thu, 14 Apr 2022 19:18:20 +0800 Subject: [PATCH] Feat/tree cxd 4.0 (#3536) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat:tree ui级联同数据级联分离,行为对齐社区 * feat:云舍4.0样适配 * feat:先取消拖拽 * 问题修改 * chore:变量名修改 * 解决冲突 & 修改文档 * 修改文档 Co-authored-by: pianruijie --- docs/zh-CN/components/form/input-tree.md | 103 +++++++++++++++++++++-- scss/_properties.scss | 2 + scss/components/form/_tree.scss | 89 +++++++++----------- scss/themes/_cxd-variables.scss | 9 +- src/components/Tree.tsx | 68 +++++++-------- src/renderers/Form/InputTree.tsx | 13 ++- 6 files changed, 191 insertions(+), 93 deletions(-) diff --git a/docs/zh-CN/components/form/input-tree.md b/docs/zh-CN/components/form/input-tree.md index 59495a63b..2632b6de5 100755 --- a/docs/zh-CN/components/form/input-tree.md +++ b/docs/zh-CN/components/form/input-tree.md @@ -159,7 +159,7 @@ order: 59 ## 选中父节点是否自动选中子节点 -默认选中父节点会自动选中子节点,可以设置`"cascade": true`,不自动选中子节点 +`autoCheckChildren`默认为true,选中父节点会自动选中子节点,可以设置`"autoCheckChildren": false`,不自动选中子节点 ```schema: scope="body" { @@ -209,6 +209,92 @@ order: 59 "name": "tree2", "label": "不自动选中子节点", "multiple": true, + "autoCheckChildren": false, + "options": [ + { + "label": "A", + "value": "a" + }, + { + "label": "B", + "value": "b", + "children": [ + { + "label": "B-1", + "value": "b-1" + }, + { + "label": "B-2", + "value": "b-2" + }, + { + "label": "B-3", + "value": "b-3" + } + ] + }, + { + "label": "C", + "value": "c" + } + ] + } + ] +} +``` + +## 选中父节点自动选中子节点,数据是否包含父子节点的值 +`cascade`默认为false,子节点禁止反选,值不包含子节点值,配置`"cascade": true`,子节点可以反选,值包含父子节点值 + + +```schema: scope="body" +{ + "type": "form", + "debug": true, + "api": "/api/mock2/form/saveForm", + "body": [ + { + "type": "input-tree", + "name": "tree1", + "label": "默认子节点禁止反选,值不包含子节点值", + "multiple": true, + "options": [ + { + "label": "A", + "value": "a" + }, + { + "label": "B", + "value": "b", + "children": [ + { + "label": "B-1", + "value": "b-1" + }, + { + "label": "B-2", + "value": "b-2" + }, + { + "label": "B-3", + "value": "b-3" + } + ] + }, + { + "label": "C", + "value": "c" + } + ] + }, + { + "type": "divider" + }, + { + "type": "input-tree", + "name": "tree2", + "label": "子节点可以反选,值包含父子节点值", + "multiple": true, "cascade": true, "options": [ { @@ -243,9 +329,7 @@ order: 59 } ``` -## 选中父节点,值是否包含子节点 - -默认选中父节点,是不会带上子节点的值,想要自动带上子节点的值,那么配置`"withChildren": true` +`withChildren`默认为false,子节点禁止反选,值包含父子节点值,配置`withChildren": true`,子节点禁止反选,值包含父子节点值 ```schema: scope="body" { @@ -256,7 +340,7 @@ order: 59 { "type": "input-tree", "name": "tree1", - "label": "默认不自动带上子节点的值", + "label": "默认不包含子节点的值", "multiple": true, "options": [ { @@ -340,7 +424,7 @@ order: 59 { "type": "input-tree", "name": "tree1", - "label": "默认不自动带上子节点的值", + "label": "默认不包含子节点的值", "multiple": true, "options": [ { @@ -825,9 +909,10 @@ true false true [{label: 'A/B/C', value: 'a/b/c'},{label: 'A | showRadio | `boolean` | `false` | 是否显示单选按钮,`multiple` 为 `false` 是有效。 | | initiallyOpen | `boolean` | `true` | 设置是否默认展开所有层级。 | | unfoldedLevel | `number` | `0` | 设置默认展开的级数,只有`initiallyOpen`不是`true`时生效。 | -| cascade | `boolean` | `false` | 当选中父节点时不自动选择子节点。 | -| withChildren | `boolean` | `false` | 选中父节点时,值里面将包含子节点的值,否则只会保留父节点的值。 | -| onlyChildren | `boolean` | `false` | 多选时,选中父节点时,是否只将其子节点加入到值中。 | +| autoCheckChildren | `boolean` | `true` | 当选中父节点时级联选择子节点。 | +| cascade | `boolean` | `false` | autoCheckChildren为true时生效;默认行为:子节点禁用,值只包含父节点值;设置为true时,子节点可反选,值包含父子节点值。 | +| withChildren | `boolean` | `false` | cascade为false时生效,选中父节点时,值里面将包含父子节点的值,否则只会保留父节点的值。 +| onlyChildren | `boolean` | `false` | autoCheckChildren为true时生效,不受cascade影响;onlyChildren为true,ui行为级联选中子节点,子节点可反选,值只包含子节点的值。 | | rootCreatable | `boolean` | `false` | 是否可以创建顶级节点 | | rootCreateTip | `string` | `"添加一级节点"` | 创建顶级节点的悬浮提示 | | minLength | `number` | | 最少选中的节点数 | diff --git a/scss/_properties.scss b/scss/_properties.scss index c303bcd35..844457cc3 100644 --- a/scss/_properties.scss +++ b/scss/_properties.scss @@ -1495,8 +1495,10 @@ --TabsTransfer-border-color: #e8e9eb; --Tree-indent: var(--gap-md); + --Tree-icon-gap: var(--gap-xs); --Tree-inputHeight: calc(var(--Form-input-height) * 0.85); --Tree-item-onHover-bg: rgba(0, 126, 255, 0.08); + --Tree-item-onHover-bg-pure: rgba(0, 126, 255, 0.08); --Tree-itemArrowWidth: #{px2rem(12px)}; --Tree-itemHeight: #{px2rem(30px)}; --Tree-itemLabel--onChecked-color: var(--Form-selectValue-color); diff --git a/scss/components/form/_tree.scss b/scss/components/form/_tree.scss index 385331aa9..edca24dea 100644 --- a/scss/components/form/_tree.scss +++ b/scss/components/form/_tree.scss @@ -50,7 +50,7 @@ top: 2px; width: px2rem(16px); height: px2rem(16px); - margin-left: var(--gap-xs); + margin-left: var(--Tree-icon-gap); } } } @@ -87,26 +87,35 @@ display: flex; align-items: center; position: relative; - - &:hover { - background: var(--Tree-item-onHover-bg); + user-select: none; + margin-bottom: px2rem(4px); + padding-left: var(--Tree-icon-gap); + & > * { + position: relative; + z-index: 2; } + > .#{$ns}Checkbox { + display: inline-flex; + align-self: center; + } + + &:hover { + background: var(--Tree-item-onHover-bg-pure); + } + } - // & > * { - // position: relative; - // z-index: 2; - // } - - // &:hover:after { - // position: absolute; - // content: ''; - // z-index: 1; - // background: var(--Tree-item-onHover-bg); - // top: 0; - // right: 0; - // bottom: 0; - // left: -99999px; - // } + &-item { + .is-checked { + background: var(--Tree-item-onChekced-bg); + border-radius: var(--Tree-item-onChekced-bg-borderRadius); + } + .is-disabled { + color: var(--text--muted-color); + background: none; + &:hover { + background: none; + } + } } &.is-draggable &-itemLabel:hover::after { @@ -120,12 +129,12 @@ vertical-align: top; height: var(--Tree-itemHeight); line-height: var(--Tree-itemHeight); - padding-right: var(--gap-xs); + padding-right: var(--Tree-icon-gap); > a { display: inline-block; vertical-align: middle; - margin-left: var(--gap-xs); + margin-left: var(--Tree-icon-gap); cursor: pointer; > svg { @@ -146,7 +155,7 @@ > a { display: inline-block; cursor: pointer; - margin-left: var(--gap-sm); + margin-left: var(--Tree-icon-gap); color: var(--icon-color); &:hover { @@ -205,7 +214,7 @@ width: $svgSize; height: $svgSize; top: $svgSize * 0.125; - margin-right: var(--gap-xs); + margin-right: var(--Tree-icon-gap); } } @@ -213,7 +222,7 @@ cursor: pointer; width: var(--Tree-itemArrowWidth); display: inline-flex; - margin-right: var(--gap-xs); + margin-right: var(--Tree-icon-gap); // &:before { // font-style: normal; @@ -236,7 +245,7 @@ &-itemArrowPlaceholder { display: inline-block; - width: calc(var(--Tree-itemArrowWidth) + var(--gap-xs)); + width: calc(var(--Tree-itemArrowWidth) + var(--Tree-icon-gap)); } &-itemDrager { @@ -245,12 +254,12 @@ } &-spinner { - margin-right: var(--gap-xs); + margin-right: var(--Tree-icon-gap); } &-itemIcon { display: inline-flex; - margin-right: var(--gap-xs); + margin-right: var(--Tree-icon-gap); } &-rootIcon, @@ -262,30 +271,16 @@ height: px2rem(14px); } } - - &-itemLabel { - user-select: none; - - &.is-checked, - &.is-children-checked { - color: var(--Tree-itemLabel--onChecked-color); - } - - &.is-disabled { - color: var(--text--muted-color); - } - - > .#{$ns}Checkbox { - display: inline-flex; - align-self: center; - } - } + &-itemText { cursor: pointer; flex: 1 auto; + color: var(--Form-input-color); line-height: var(--lineHeightBase); - padding: px2rem(4px) 0; + } + .is-disabled &-itemText { + color: var(--text--muted-color); } &-placeholder { @@ -313,7 +308,7 @@ &--hover { border-radius: 0; - background-color: var(--Tree-item-onHover-bg); + background-color: var(--Tree-item-onHover-bg-pure); &::after { display: none; diff --git a/scss/themes/_cxd-variables.scss b/scss/themes/_cxd-variables.scss index b6e46fa95..85dcfc0c1 100644 --- a/scss/themes/_cxd-variables.scss +++ b/scss/themes/_cxd-variables.scss @@ -632,12 +632,17 @@ $L1: 0px 4px 6px 0px rgba(8, 14, 26, 0.06), --Satus-icon-width: #{px2rem(14px)}; - --Tree-itemHeight: #{px2rem(32px)}; - --Progress-borderRadius: #{$R7}; --Rating-inactive-color: #{$G9}; + // tree + --Tree-item-onHover-bg-pure: #{$G10}; + --Tree-item-onChekced-bg: var(--Form-select-menu-onHover-bg); + --Tree-item-onChekced-bg-borderRadius: #{px2rem(2px)}; + --Tree-itemHeight: #{px2rem(24px)}; + --Tree-icon-gap: var(--gap-sm); + // Formula --InputFormula-code-bgColor: #{$G10}; diff --git a/src/components/Tree.tsx b/src/components/Tree.tsx index 628ceaa4b..8d3dd4d1e 100644 --- a/src/components/Tree.tsx +++ b/src/components/Tree.tsx @@ -88,14 +88,21 @@ interface TreeSelectorProps extends ThemeProps, LocaleProps { pathSeparator?: string; // 已选择节点路径 nodePath: any[]; + // ui级联关系,true代表级联选中,false代表不级联,默认为true + autoCheckChildren: boolean; - // 这个配置名字没取好,目前的含义是,如果这个配置成true,点父级的时候,子级点不会自选中。 - // 否则点击父级,子节点选中。 + /* + * 该属性代表数据级联关系,autoCheckChildren为true时生效,默认为false,具体数据级联关系如下: + * 1.casacde为false,ui行为为级联选中子节点,子节点禁用;值只包含父节点的值 + * 2.cascade为false,withChildren为true,ui行为为级联选中子节点,子节点禁用;值包含父子节点的值 + * 3.cascade为true,ui行为级联选中子节点,子节点可反选,值包含父子节点的值,此时withChildren属性失效 + * 4.cascade不论为true还是false,onlyChildren为true,ui行为级联选中子节点,子节点可反选,值只包含子节点的值 + */ cascade?: boolean; + selfDisabledAffectChildren?: boolean; minLength?: number; maxLength?: number; - // 是否为内建 增、改、删。当有复杂表单的时候直接抛出去让外层能统一处理 bultinCUD?: boolean; rootCreatable?: boolean; @@ -157,6 +164,7 @@ export class TreeSelector extends React.Component< hideRoot: true, rootLabel: 'Tree.root', rootValue: 0, + autoCheckChildren: true, cascade: false, selfDisabledAffectChildren: true, rootCreateTip: 'Tree.addRoot', @@ -182,7 +190,6 @@ export class TreeSelector extends React.Component< constructor(props: TreeSelectorProps) { super(props); - this.state = { value: value2array( props.value, @@ -412,15 +419,12 @@ export class TreeSelector extends React.Component< const props = this.props; const value = this.state.value.concat(); const idx = value.indexOf(item); - const onlyChildren = props.onlyChildren; - + const {onlyChildren, withChildren, cascade, autoCheckChildren} = props; if (checked) { ~idx || value.push(item); - // cascade 为 true 表示父节点跟子节点没有级联关系。 - if (!props.cascade) { + if (autoCheckChildren) { const children = item.children ? item.children.concat([]) : []; - if (onlyChildren) { // 父级选中的时候,子节点也都选中,但是自己不选中 !~idx && children.length && value.pop(); @@ -445,7 +449,7 @@ export class TreeSelector extends React.Component< value.splice(index, 1); } - if (props.withChildren) { + if (withChildren || cascade) { value.push(child); } @@ -464,7 +468,7 @@ export class TreeSelector extends React.Component< if ( parent.children.every((child: any) => ~value.indexOf(child)) ) { - if (!props.withChildren) { + if (!cascade && !withChildren) { parent.children.forEach((child: any) => { const index = value.indexOf(child); if (~index) { @@ -483,19 +487,18 @@ export class TreeSelector extends React.Component< } } else { ~idx && value.splice(idx, 1); - - if (!props.cascade && (props.withChildren || onlyChildren)) { - const children = item.children ? item.children.concat([]) : []; - while (children.length) { - let child = children.shift(); - let index = value.indexOf(child); - - if (~index) { - value.splice(index, 1); - } - - if (child.children && child.children.length) { - children.push.apply(children, child.children); + if (autoCheckChildren) { + if (cascade || withChildren || onlyChildren) { + const children = item.children ? item.children.concat([]) : []; + while (children.length) { + let child = children.shift(); + let index = value.indexOf(child); + if (~index) { + value.splice(index, 1); + } + if (child.children && child.children.length) { + children.push.apply(children, child.children); + } } } } @@ -664,7 +667,6 @@ export class TreeSelector extends React.Component< @autobind getDropInfo(e: React.DragEvent, node: Option): IDropInfo { let rect = e.currentTarget.getBoundingClientRect(); - const dragNode = this.dragNode; const deltaX = Math.min(50, rect.width * 0.3); const gap = node?.children?.length ? 0 : 16; @@ -676,7 +678,6 @@ export class TreeSelector extends React.Component< let top = targetOffset.top - offset.top; let {clientX, clientY} = e; - let position: IDropInfo['position'] = clientY >= rect.top + rect.height / 2 ? 'bottom' : 'top'; let indicator; @@ -788,6 +789,7 @@ export class TreeSelector extends React.Component< valueField, iconField, disabledField, + autoCheckChildren, cascade, selfDisabledAffectChildren, onlyChildren, @@ -818,7 +820,6 @@ export class TreeSelector extends React.Component< if (!isVisible(item as any, options)) { return null; } - const checked = !!~value.indexOf(item); const selfDisabled = item[disabledField]; let selfChecked = !!uncheckable || checked; @@ -829,16 +830,15 @@ export class TreeSelector extends React.Component< childrenItems = this.renderList( item.children, value, - cascade - ? false - : uncheckable || - (selfDisabledAffectChildren ? selfDisabled : false) || - (multiple && checked) + (!autoCheckChildren || cascade) + ? false: (uncheckable + || (selfDisabledAffectChildren ? selfDisabled : false) + || (multiple && checked)) ); selfChildrenChecked = !!childrenItems.childrenChecked; if ( !selfChecked && - onlyChildren && + onlyChildren && autoCheckChildren && item.children.length === childrenItems.childrenChecked ) { selfChecked = true; @@ -864,7 +864,7 @@ export class TreeSelector extends React.Component< diff --git a/src/renderers/Form/InputTree.tsx b/src/renderers/Form/InputTree.tsx index c1b98bcff..f2a805f78 100644 --- a/src/renderers/Form/InputTree.tsx +++ b/src/renderers/Form/InputTree.tsx @@ -39,7 +39,16 @@ export interface TreeControlSchema extends FormOptionsControl { showIcon?: boolean; /** - * 父子之间是否完全独立。 + * ui级联关系,true代表级联选中,false代表不级联,默认为true + */ + autoCheckChildren?: boolean; + + /** + * 该属性代表数据级联关系,autoCheckChildren为true时生效,默认为false,具体数据级联关系如下: + * 1.casacde为false,ui行为为级联选中子节点,子节点禁用;值只包含父节点的值 + * 2.cascade为false,withChildren为true,ui行为为级联选中子节点,子节点禁用;值包含父子节点的值 + * 3.cascade为true,ui行为级联选中子节点,子节点可反选,值包含父子节点的值,此时withChildren属性失效 + * 4.cascade不论为true还是false,onlyChildren为true,ui行为级联选中子节点,子节点可反选,值只包含子节点的值 */ cascade?: boolean; @@ -166,6 +175,7 @@ export default class TreeControl extends React.Component { loading, hideRoot, rootLabel, + autoCheckChildren, cascade, rootValue, showIcon, @@ -222,6 +232,7 @@ export default class TreeControl extends React.Component { showIcon={showIcon} showRadio={showRadio} showOutline={showOutline} + autoCheckChildren={autoCheckChildren} cascade={cascade} foldedField="collapsed" value={value || ''}