mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 18:48:45 +08:00
feat:dropDownButton新增隐藏下拉图标属性hideCaret;badge组件支持横幅类型;nav组件新增角标配置、更多操作配置、拖拽排序 (#2800)
* feat:dropDownButton新增隐藏下拉图标属性hideCaret;nav组件新增角标、更多操作配置、支持图片拽排序 * feat:dropDownButton新增隐藏下拉图标属性hideCaret;badge组件支持横幅类型;nav组件新增角标配置、更多操作配置、拖拽排序 * chore:优化nav角标相关配置 * fix:修复badge组件ribbon高度遮挡问题 Co-authored-by: Qin,Haoyan <qinhaoyan@baidu.com>
This commit is contained in:
parent
b202b4e636
commit
168a3635ef
@ -68,7 +68,18 @@ order: 30
|
||||
"badge": {
|
||||
"position": "top-left"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "divider"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"label": "按钮",
|
||||
"badge": {
|
||||
"mode": "ribbon",
|
||||
"text": "HOT"
|
||||
}
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
@ -345,7 +356,7 @@ order: 30
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ------------- | ------------------- | ---------- | ----------------------------------------------------------------|
|
||||
| mode | `string` | dot | 角标类型,可以是 dot/text |
|
||||
| mode | `string` | dot | 角标类型,可以是 dot/text/ribbon |
|
||||
| text | `text`、`number` | | 角标文案,支持字符串和数字,在mode='dot'下设置无效 |
|
||||
| size | `number` | | 角标大小 |
|
||||
| level | `string` | | 角标级别, 可以是info/success/warning/danger, 设置之后角标背景颜色不同 |
|
||||
|
@ -83,3 +83,4 @@ order: 44
|
||||
| defaultIsOpened | `boolean` | | 默认是否打开 |
|
||||
| closeOnOutside | `boolean` | | 点击外侧区域是否收起 |
|
||||
| trigger | `click` 或 `hover` | `click` | 触发方式 |
|
||||
| hideCaret | `boolean` | false | 隐藏下拉图标 |
|
||||
|
@ -26,7 +26,12 @@ order: 58
|
||||
},
|
||||
{
|
||||
"label": "Nav 2",
|
||||
"to": "/docs/api"
|
||||
"to": "/docs/api",
|
||||
"badge": {
|
||||
"mode": "ribbon",
|
||||
"text": "HOT",
|
||||
"position": "top-left"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Nav 3",
|
||||
@ -150,6 +155,43 @@ order: 58
|
||||
}
|
||||
```
|
||||
|
||||
## 更多操作
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "nav",
|
||||
"stacked": true,
|
||||
"className": "w-md",
|
||||
"draggable": true,
|
||||
"saveOrderApi": "/api/options/nav",
|
||||
"source": "/api/options/nav?parentId=${value}",
|
||||
"itemActions": [
|
||||
{
|
||||
"type": "icon",
|
||||
"icon": "cloud",
|
||||
"visibleOn": "this.to === '?cat=1'"
|
||||
},
|
||||
{
|
||||
"type": "dropdown-button",
|
||||
"level": "link",
|
||||
"icon": "fa fa-ellipsis-h",
|
||||
"hideCaret": true,
|
||||
"buttons": [
|
||||
{
|
||||
"type": "button",
|
||||
"label": "编辑",
|
||||
|
||||
},
|
||||
{
|
||||
"type": "button",
|
||||
"label": "删除"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 属性表
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
@ -159,8 +201,13 @@ order: 58
|
||||
| stacked | `boolean` | `true` | 设置成 false 可以以 tabs 的形式展示 |
|
||||
| source | `string` 或 [API](../../docs/types/api) | | 可以通过变量或 API 接口动态创建导航 |
|
||||
| deferApi | [API](../../docs/types/api) | | 用来延时加载选项详情的接口,可以不配置,不配置公用 source 接口。 |
|
||||
| itemActions | [SchemaNode](../../docs/types/schemanode) | | 更多操作相关配置 |
|
||||
| draggable | `boolean` | | 是否支持拖拽排序 |
|
||||
| saveOrderApi | `string` 或 [API](../../docs/types/api) | |保存排序的 api |
|
||||
| badge | [BadgeSchema](../../components/badge)| | 角标 |
|
||||
| links | `Array` | | 链接集合 |
|
||||
| links[x].label | `string` | | 名称 |
|
||||
| links[x].label | `string` | | 名称 |
|
||||
| links[x].badge | [BadgeSchema](../../components/badge)| | 角标,会覆盖全局角标配置 |
|
||||
| links[x].to | [模板](../../docs/concepts/template) | | 链接地址 |
|
||||
| links[x].target | `string` | 链接关系 | |
|
||||
| links[x].icon | `string` | | 图标 |
|
||||
|
@ -3,7 +3,8 @@
|
||||
position: relative;
|
||||
|
||||
&-text,
|
||||
&-dot {
|
||||
&-dot,
|
||||
&-ribbon {
|
||||
background: var(--danger);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@ -70,6 +71,56 @@
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
// 横幅
|
||||
&-ribbon-out {
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0
|
||||
}
|
||||
|
||||
&-ribbon {
|
||||
color: var(--Badge-color);
|
||||
height: var(--Badge-size);
|
||||
line-height: var(--Badge-size);
|
||||
transform: translateX(calc(50% - 5px)) rotate(45deg) scale(.7);
|
||||
transform-origin: 50% 0;
|
||||
border-radius: 0;
|
||||
text-align: center;
|
||||
width: px2rem(1000px);
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
&-ribbon-out--top-left, &-ribbon-out--bottom-left {
|
||||
left: 0;
|
||||
right: auto
|
||||
}
|
||||
|
||||
&-ribbon--top-left {
|
||||
transform: translateX(calc(-50% + 5px)) rotate(-45deg) scale(.7);
|
||||
left: 0;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
&-ribbon--bottom-left {
|
||||
transform: translateX(calc(-50% + 5px)) rotate(45deg) scale(.7);
|
||||
transform-origin: 50% 100%;
|
||||
left: 0;
|
||||
right: auto;
|
||||
bottom: 5px;
|
||||
top: auto;
|
||||
}
|
||||
|
||||
&-ribbon--bottom-right {
|
||||
transform: translateX(calc(50% - 5px)) rotate(-45deg) scale(.7);
|
||||
transform-origin: 50% 100%;
|
||||
left: auto;
|
||||
right: 0;
|
||||
bottom: 5px;
|
||||
top: auto;
|
||||
}
|
||||
|
||||
// 小红点的动画
|
||||
@keyframes badgeDotAnimation {
|
||||
0% {
|
||||
|
@ -20,7 +20,7 @@
|
||||
.#{$ns}Nav-item {
|
||||
margin-bottom: calc(var(--Tabs-borderWidth) * -1);
|
||||
display: inline-block;
|
||||
|
||||
position: relative;
|
||||
> a {
|
||||
font-size: var(--Tabs-linkFontSize);
|
||||
display: block;
|
||||
@ -31,7 +31,7 @@
|
||||
color: var(--Tabs-color);
|
||||
text-decoration: none;
|
||||
margin-right: px2rem(2px);
|
||||
padding: var(--gap-sm) var(--gap-base);
|
||||
padding: var(--gap-sm) var(--gap-xl);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@ -62,57 +62,129 @@
|
||||
&--stacked {
|
||||
min-height: px2rem(50px);
|
||||
|
||||
.#{$ns}Nav-item {
|
||||
.#{$ns}Nav-item, .#{$ns}Badge {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: stretch;
|
||||
width: 100%;
|
||||
|
||||
> a {
|
||||
display: block;
|
||||
.#{$ns}Nav-itemDrager {
|
||||
cursor: move;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: px2rem(11px);
|
||||
display: none;
|
||||
> a, > .#{$ns}Badge > a {
|
||||
color: var(--icon-color);
|
||||
|
||||
&:hover {
|
||||
color: var(--icon-onHover-color);
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
width: px2rem(16px);
|
||||
height: px2rem(16px);
|
||||
}
|
||||
}
|
||||
|
||||
> .#{$ns}Nav-item-badgeText {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: px2rem(35px);
|
||||
overflow: hidden;
|
||||
> span {
|
||||
position: absolute;
|
||||
top: px2rem(2px);
|
||||
left: px2rem(-13px);
|
||||
transform: rotate(-45deg);
|
||||
width: px2rem(50px);
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
color: var(--white);
|
||||
background: var(--success)
|
||||
}
|
||||
}
|
||||
|
||||
> .#{$ns}Nav-item-atcions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
> a,
|
||||
> .#{$ns}Badge > a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
outline: none;
|
||||
color: var(--Nav-item-color);
|
||||
text-decoration: none;
|
||||
padding: var(--gap-sm) var(--gap-base);
|
||||
padding: var(--gap-sm) var(--gap-sm);
|
||||
cursor: pointer;
|
||||
background: var(--Nav-item-bg);
|
||||
border-radius: var(--Nav-item-borderRadius);
|
||||
text-overflow: ellipsis;
|
||||
flex: 1;
|
||||
|
||||
&::after {
|
||||
border-left: var(--Nav-item-onActive-borderLeft);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
content: '';
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
transform: scaleY(0.0001);
|
||||
transition: transform 0.15s cubic-bezier(0.215, 0.61, 0.355, 1),
|
||||
opacity 0.15s cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
}
|
||||
}
|
||||
|
||||
&.has-sub > a {
|
||||
padding-right: calc(var(--gap-base) + var(--gap-sm));
|
||||
// &::after {
|
||||
// border-left: var(--Nav-item-onActive-borderLeft);
|
||||
// position: absolute;
|
||||
// left: 0;
|
||||
// top: 0;
|
||||
// content: '';
|
||||
// width: 1px;
|
||||
// height: 100%;
|
||||
// transform: scaleY(0.0001);
|
||||
// transition: transform 0.15s cubic-bezier(0.215, 0.61, 0.355, 1),
|
||||
// opacity 0.15s cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
// }
|
||||
}
|
||||
|
||||
> a:hover,
|
||||
> a:focus {
|
||||
> a:focus,
|
||||
> a:hover + .#{$ns}Nav-item-atcions,
|
||||
> a:focus + .#{$ns}Nav-item-atcions,
|
||||
> .#{$ns}Badge > a:hover,
|
||||
> .#{$ns}Badge > a:focus,
|
||||
> .#{$ns}Badge > a:hover + .#{$ns}Nav-item-atcions,
|
||||
> .#{$ns}Badge > a:focus + .#{$ns}Nav-item-atcions
|
||||
{
|
||||
border-color: var(--Nav-item-onHover-color);
|
||||
text-decoration: none;
|
||||
background: var(--Nav-item-onHover-bg);
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
> a:hover > .#{$ns}Nav-itemDrager,
|
||||
> a:focus > .#{$ns}Nav-itemDrager,
|
||||
> .#{$ns}Badge > a:hover > .#{$ns}Nav-itemDrager,
|
||||
> .#{$ns}Badge > a:focus > .#{$ns}Nav-itemDrager {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.disabled > a,
|
||||
&.is-disabled > a {
|
||||
&.is-disabled > a,
|
||||
&.disabled > .#{$ns}Badge > a,
|
||||
&.is-disabled > .#{$ns}Badge > a {
|
||||
color: var(--Nav-item-onDisabled-color);
|
||||
background: transparent;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.active,
|
||||
&.is-active {
|
||||
background: var(--Nav-item-onActive-bg) !important;
|
||||
}
|
||||
|
||||
|
||||
&.active > a,
|
||||
&.is-active > a {
|
||||
&.is-active > .#{$ns}Nav-item-atcions,
|
||||
&.is-active > a,
|
||||
&.active > .#{$ns}Badge > a,
|
||||
&.is-active > .#{$ns}Badge > .#{$ns}Nav-item-atcions,
|
||||
&.is-active > .#{$ns}Badge > a {
|
||||
color: var(--Nav-item-onActive-color);
|
||||
background: var(--Nav-item-onActive-bg);
|
||||
padding-left: px2rem(12px);
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
@ -120,8 +192,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.is-unfolded > {
|
||||
.#{$ns}Nav-itemToggler {
|
||||
&.is-unfolded > , &.is-unfolded > .#{$ns}Badge > {
|
||||
a .#{$ns}Nav-itemToggler {
|
||||
transform: rotate(180deg) scale(0.8);
|
||||
}
|
||||
|
||||
@ -138,13 +210,14 @@
|
||||
}
|
||||
|
||||
.#{$ns}Nav-itemToggler {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: px2rem(3px);
|
||||
width: px2rem(30px);
|
||||
height: px2rem(30px);
|
||||
// position: absolute;
|
||||
// left: 0;
|
||||
// top: px2rem(3px);
|
||||
float: left;
|
||||
width: px2rem(24px);
|
||||
height: px2rem(24px);
|
||||
text-align: center;
|
||||
line-height: px2rem(30px);
|
||||
line-height: px2rem(24px);
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
transform: scale(0.8);
|
||||
@ -162,6 +235,7 @@
|
||||
display: none;
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.#{$ns}Nav-item {
|
||||
|
@ -27,7 +27,7 @@ export interface BadgeSchema extends BaseSchema {
|
||||
/**
|
||||
* 角标类型
|
||||
*/
|
||||
mode?: 'text' | 'dot';
|
||||
mode?: 'text' | 'dot' | 'ribbon';
|
||||
|
||||
/**
|
||||
* 角标位置,优先级大于position
|
||||
@ -80,6 +80,62 @@ export class Badge extends React.Component<BadgeProps, object> {
|
||||
super(props);
|
||||
}
|
||||
|
||||
renderBadge(
|
||||
text: any,
|
||||
size: number,
|
||||
position: any,
|
||||
offsetStyle: any,
|
||||
sizeStyle: any,
|
||||
animationElement: any
|
||||
) {
|
||||
const {classnames: cx, badge} = this.props;
|
||||
const {
|
||||
mode = 'dot',
|
||||
level = 'danger',
|
||||
style
|
||||
} = badge as BadgeSchema;
|
||||
switch(mode) {
|
||||
case 'dot':
|
||||
return (
|
||||
<span
|
||||
className={cx('Badge-dot', `Badge--${position}`, `Badge--${level}`)}
|
||||
style={{...offsetStyle, ...sizeStyle, ...style}}
|
||||
>
|
||||
{animationElement}
|
||||
</span>
|
||||
);
|
||||
case 'text':
|
||||
return (
|
||||
<span
|
||||
className={cx('Badge-text', `Badge--${position}`, `Badge--${level}`)}
|
||||
style={{...offsetStyle, ...sizeStyle, ...style}}
|
||||
>
|
||||
{text}
|
||||
{animationElement}
|
||||
</span>
|
||||
);
|
||||
case 'ribbon':
|
||||
const outSize = size * Math.sqrt(2) + 5;
|
||||
return (
|
||||
<div
|
||||
className={cx('Badge-ribbon-out', `Badge-ribbon-out--${position}`)}
|
||||
style={{width: outSize, height: outSize}}
|
||||
>
|
||||
<span
|
||||
className={cx('Badge-ribbon', `Badge-ribbon--${position}`, `Badge--${level}`)}
|
||||
style={{...sizeStyle, ...style}}
|
||||
>
|
||||
{text}
|
||||
{animationElement}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const badge = this.props.badge;
|
||||
if (!badge) {
|
||||
@ -101,8 +157,7 @@ export class Badge extends React.Component<BadgeProps, object> {
|
||||
overflowCount = 99,
|
||||
visibleOn,
|
||||
className,
|
||||
animation,
|
||||
level = 'danger'
|
||||
animation
|
||||
} = badge;
|
||||
|
||||
if (visibleOn) {
|
||||
@ -117,6 +172,8 @@ export class Badge extends React.Component<BadgeProps, object> {
|
||||
if (typeof size === 'undefined') {
|
||||
if (mode === 'dot') {
|
||||
size = 6;
|
||||
} else if (mode === 'ribbon'){
|
||||
size = 12;
|
||||
} else {
|
||||
size = 16;
|
||||
}
|
||||
@ -145,6 +202,14 @@ export class Badge extends React.Component<BadgeProps, object> {
|
||||
sizeStyle = {width: size, height: size};
|
||||
}
|
||||
|
||||
if (mode === 'ribbon') {
|
||||
sizeStyle = {
|
||||
height: size,
|
||||
lineHeight: size + 'px',
|
||||
fontSize: size
|
||||
};
|
||||
}
|
||||
|
||||
let offsetStyle = {};
|
||||
// 如果设置了offset属性,offset在position为'top-right'的基础上进行translate定位
|
||||
if (offset && offset.length) {
|
||||
@ -180,24 +245,15 @@ export class Badge extends React.Component<BadgeProps, object> {
|
||||
return (
|
||||
<div className={cx('Badge', className)}>
|
||||
{children}
|
||||
{isDisplay ? (
|
||||
mode === 'dot' ? (
|
||||
<span
|
||||
className={cx('Badge-dot', `Badge--${position}`, `Badge--${level}`)}
|
||||
style={{...offsetStyle, ...sizeStyle, ...style}}
|
||||
>
|
||||
{animationElement}
|
||||
</span>
|
||||
) : (
|
||||
<span
|
||||
className={cx('Badge-text', `Badge--${position}`, `Badge--${level}`)}
|
||||
style={{...offsetStyle, ...sizeStyle, ...style}}
|
||||
>
|
||||
{text}
|
||||
{animationElement}
|
||||
</span>
|
||||
)
|
||||
) : null}
|
||||
{isDisplay ?
|
||||
this.renderBadge(
|
||||
text,
|
||||
size,
|
||||
position,
|
||||
offsetStyle,
|
||||
sizeStyle,
|
||||
animationElement
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -81,6 +81,10 @@ export interface DropdownButtonSchema extends BaseSchema {
|
||||
* 触发条件,默认是 click
|
||||
*/
|
||||
trigger?: 'click' | 'hover';
|
||||
/**
|
||||
* 是否显示下拉按钮
|
||||
*/
|
||||
hideCaret?: boolean;
|
||||
}
|
||||
|
||||
export interface DropDownButtonProps
|
||||
@ -272,7 +276,8 @@ export default class DropDownButton extends React.Component<
|
||||
icon,
|
||||
isActived,
|
||||
trigger,
|
||||
data
|
||||
data,
|
||||
hideCaret
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@ -325,9 +330,13 @@ export default class DropDownButton extends React.Component<
|
||||
)
|
||||
) : null}
|
||||
{typeof label === 'string' ? filter(label, data) : label}
|
||||
<span className={cx('DropDown-caret')}>
|
||||
<Icon icon="caret" className="icon" />
|
||||
</span>
|
||||
{
|
||||
!hideCaret
|
||||
? <span className={cx('DropDown-caret')} >
|
||||
<Icon icon="caret" className="icon" />
|
||||
</span>
|
||||
: null
|
||||
}
|
||||
</button>
|
||||
</TooltipWrapper>
|
||||
{this.state.isOpened ? this.renderOuter() : null}
|
||||
|
@ -1,8 +1,10 @@
|
||||
import React from 'react';
|
||||
import Sortable from 'sortablejs';
|
||||
import {Renderer, RendererEnv, RendererProps} from '../factory';
|
||||
import getExprProperties from '../utils/filter-schema';
|
||||
import {filter, evalExpression} from '../utils/tpl';
|
||||
import {
|
||||
guid,
|
||||
autobind,
|
||||
createObject,
|
||||
findTree,
|
||||
@ -22,6 +24,9 @@ import {
|
||||
} from '../components/WithRemoteConfig';
|
||||
import {Payload} from '../types';
|
||||
import Spinner from '../components/Spinner';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import {isEffectiveApi} from '../utils/api';
|
||||
import {Badge, BadgeSchema} from '../components/Badge';
|
||||
|
||||
export type NavItemSchema = {
|
||||
/**
|
||||
@ -45,6 +50,11 @@ export type NavItemSchema = {
|
||||
deferApi?: SchemaApi;
|
||||
|
||||
children?: Array<NavItemSchema>;
|
||||
|
||||
/**
|
||||
* 角标
|
||||
*/
|
||||
badge?: BadgeSchema
|
||||
} & Omit<BaseSchema, 'type'>;
|
||||
|
||||
/**
|
||||
@ -81,6 +91,26 @@ export interface NavSchema extends BaseSchema {
|
||||
* true 为垂直排列,false 为水平排列类似如 tabs。
|
||||
*/
|
||||
stacked?: boolean;
|
||||
|
||||
/**
|
||||
* 更多操作菜单列表
|
||||
*/
|
||||
itemActions?: SchemaCollection;
|
||||
|
||||
/**
|
||||
* 可拖拽
|
||||
*/
|
||||
draggable?: boolean;
|
||||
|
||||
/**
|
||||
* 保存排序的 api
|
||||
*/
|
||||
saveOrderApi?: SchemaApi;
|
||||
|
||||
/**
|
||||
* 角标
|
||||
*/
|
||||
badge?: BadgeSchema;
|
||||
}
|
||||
|
||||
export interface Link {
|
||||
@ -97,6 +127,7 @@ export interface Link {
|
||||
loading?: boolean;
|
||||
loaded?: boolean;
|
||||
[propName: string]: any;
|
||||
badge?: BadgeSchema
|
||||
}
|
||||
export interface Links extends Array<Link> {}
|
||||
|
||||
@ -113,7 +144,9 @@ export interface NavigationProps
|
||||
togglerClassName?: string;
|
||||
links?: Array<Link>;
|
||||
loading?: boolean;
|
||||
render: RendererProps['render']
|
||||
render: RendererProps['render'];
|
||||
env: RendererEnv;
|
||||
reload?: any;
|
||||
}
|
||||
|
||||
export class Navigation extends React.Component<
|
||||
@ -123,6 +156,9 @@ export class Navigation extends React.Component<
|
||||
static defaultProps = {
|
||||
indentSize: 24
|
||||
};
|
||||
sortable: Sortable[] = [];
|
||||
id: string;
|
||||
dragRef?: HTMLElement;
|
||||
|
||||
@autobind
|
||||
handleClick(link: Link) {
|
||||
@ -134,15 +170,85 @@ export class Navigation extends React.Component<
|
||||
this.props.onToggle?.(target);
|
||||
}
|
||||
|
||||
@autobind
|
||||
dragRefFn(ref: any) {
|
||||
const {draggable} = this.props;
|
||||
if (ref && draggable) {
|
||||
this.id = guid();
|
||||
this.initDragging(ref);
|
||||
}
|
||||
}
|
||||
|
||||
initDragging(ref: HTMLElement) {
|
||||
const ns = this.props.classPrefix;
|
||||
this.sortable.push(new Sortable(
|
||||
ref,
|
||||
{
|
||||
group: `nav-${this.id}`,
|
||||
animation: 150,
|
||||
handle: `.${ns}Nav-itemDrager`,
|
||||
ghostClass: `${ns}Nav-item--dragging`,
|
||||
onEnd: async (e: any) => {
|
||||
// 没有移动
|
||||
if (e.newIndex === e.oldIndex) {
|
||||
return;
|
||||
}
|
||||
const id = e.item.getAttribute('data-id');
|
||||
const parentNode = e.to
|
||||
if (
|
||||
e.newIndex < e.oldIndex &&
|
||||
e.oldIndex < parentNode.childNodes.length - 1
|
||||
) {
|
||||
parentNode.insertBefore(e.item, parentNode.childNodes[e.oldIndex + 1]);
|
||||
} else if (e.oldIndex < parentNode.childNodes.length - 1) {
|
||||
parentNode.insertBefore(e.item, parentNode.childNodes[e.oldIndex]);
|
||||
} else {
|
||||
parentNode.appendChild(e.item);
|
||||
}
|
||||
const links = cloneDeep(this.props.links) as Link[];
|
||||
let parent = links;
|
||||
someTree(links, (item: Link, key, level, paths: Link[]) => {
|
||||
if (item.id === id) {
|
||||
const len = paths.length - 1;
|
||||
parent = (~len ? paths[len].children : links) as Link[];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
parent.splice(e.newIndex, 0, parent.splice(e.oldIndex, 1)[0]);
|
||||
const {saveOrderApi, env} = this.props;
|
||||
if (saveOrderApi && isEffectiveApi(saveOrderApi)) {
|
||||
await env.fetcher(saveOrderApi as SchemaApi, {data: links}, {method: 'post'});
|
||||
this.props.reload();
|
||||
} else {
|
||||
console.warn('请配置saveOrderApi');
|
||||
}
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
renderItem(link: Link, index: number, depth = 1) {
|
||||
if (link.hidden === true || link.visible === false) {
|
||||
return null;
|
||||
}
|
||||
const isActive: boolean = !!link.active;
|
||||
const {disabled, togglerClassName, classnames: cx, indentSize} = this.props;
|
||||
const {
|
||||
disabled,
|
||||
togglerClassName,
|
||||
classnames: cx,
|
||||
indentSize,
|
||||
render,
|
||||
itemActions,
|
||||
draggable,
|
||||
links,
|
||||
badge: defaultBadge
|
||||
} = this.props;
|
||||
const hasSub =
|
||||
(link.defer && !link.loaded) || (link.children && link.children.length);
|
||||
|
||||
const id = guid();
|
||||
link.id = id;
|
||||
const badge = defaultBadge ? Object.assign(defaultBadge, link.badge) : link.badge;
|
||||
return (
|
||||
<li
|
||||
key={index}
|
||||
@ -152,42 +258,62 @@ export class Navigation extends React.Component<
|
||||
'is-unfolded': link.unfolded,
|
||||
'has-sub': hasSub
|
||||
})}
|
||||
data-id={id}
|
||||
>
|
||||
<a
|
||||
onClick={this.handleClick.bind(this, link)}
|
||||
style={{paddingLeft: depth * (parseInt(indentSize as any, 10) ?? 24)}}
|
||||
>
|
||||
{generateIcon(cx, link.icon, 'Nav-itemIcon')}
|
||||
{
|
||||
link.label && (typeof link.label === 'string'
|
||||
? link.label
|
||||
: this.props.render('inline', link.label as SchemaCollection))
|
||||
}
|
||||
</a>
|
||||
|
||||
{link.loading ? (
|
||||
<Spinner
|
||||
size="sm"
|
||||
show
|
||||
icon="reload"
|
||||
spinnerClassName={cx('Nav-spinner')}
|
||||
/>
|
||||
) : hasSub ? (
|
||||
<span
|
||||
onClick={() => this.toggleLink(link)}
|
||||
className={cx('Nav-itemToggler', togglerClassName)}
|
||||
<Badge classnames={cx} badge={badge} data={link}>
|
||||
<a
|
||||
onClick={this.handleClick.bind(this, link)}
|
||||
style={{paddingLeft: depth * (parseInt(indentSize as any, 10) ?? 24)}}
|
||||
>
|
||||
<Icon icon="caret" className="icon" />
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
{Array.isArray(link.children) && link.children.length ? (
|
||||
<ul className={cx('Nav-subItems')}>
|
||||
{link.children.map((link, index) =>
|
||||
this.renderItem(link, index, depth + 1)
|
||||
)}
|
||||
</ul>
|
||||
) : null}
|
||||
{!disabled && draggable && links && links.length > 1 ? (
|
||||
<div className={cx('Nav-itemDrager')} >
|
||||
<a
|
||||
key="drag"
|
||||
data-position="bottom"
|
||||
>
|
||||
<Icon icon="drag-bar" className="icon" />
|
||||
</a>
|
||||
</div>
|
||||
) : null}
|
||||
{link.loading ? (
|
||||
<Spinner
|
||||
size="sm"
|
||||
show
|
||||
icon="reload"
|
||||
spinnerClassName={cx('Nav-spinner')}
|
||||
/>
|
||||
) : hasSub ? (
|
||||
<span
|
||||
onClick={() => this.toggleLink(link)}
|
||||
className={cx('Nav-itemToggler', togglerClassName)}
|
||||
>
|
||||
<Icon icon="caret" className="icon" />
|
||||
</span>
|
||||
) : null}
|
||||
{generateIcon(cx, link.icon, 'Nav-itemIcon')}
|
||||
{
|
||||
link.label && (typeof link.label === 'string'
|
||||
? link.label
|
||||
: render('inline', link.label as SchemaCollection))
|
||||
}
|
||||
</a>
|
||||
{
|
||||
// 更多操作
|
||||
itemActions
|
||||
? <div className={cx('Nav-item-atcions')}>
|
||||
{
|
||||
render('inline', itemActions, {data: link})
|
||||
}
|
||||
</div> : null
|
||||
}
|
||||
{Array.isArray(link.children) && link.children.length ? (
|
||||
<ul className={cx('Nav-subItems')} ref={this.dragRefFn}>
|
||||
{link.children.map((link, index) =>
|
||||
this.renderItem(link, index, depth + 1)
|
||||
)}
|
||||
</ul>
|
||||
) : null}
|
||||
</Badge>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
@ -198,6 +324,7 @@ export class Navigation extends React.Component<
|
||||
return (
|
||||
<ul
|
||||
className={cx('Nav', className, stacked ? 'Nav--stacked' : 'Nav--tabs')}
|
||||
ref={this.dragRefFn}
|
||||
>
|
||||
{Array.isArray(links)
|
||||
? links.map((item, index) => this.renderItem(item, index))
|
||||
@ -310,6 +437,7 @@ const ConditionBuilderWithRemoteOptions = withRemoteConfig({
|
||||
data?: any;
|
||||
unfoldedField?: string;
|
||||
foldedField?: string;
|
||||
reload?: any;
|
||||
}
|
||||
> {
|
||||
constructor(props: any) {
|
||||
@ -442,6 +570,7 @@ export class NavigationRenderer extends React.Component<RendererProps> {
|
||||
return (
|
||||
<ConditionBuilderWithRemoteOptions
|
||||
{...rest}
|
||||
reload={this.reload}
|
||||
remoteConfigRef={this.remoteConfigRef}
|
||||
/>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user