mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 18:39:05 +08:00
Mapping 修复 boolean 值的映射问题 (#1837)
* 优化 pc 样式 * 样式微调 * Nav 支持 lazyreload * 修复 mapping boolean值映射问题 * 多提交了文件 * 还原 example
This commit is contained in:
parent
c6f951976e
commit
cede563628
@ -119,6 +119,50 @@ List 的内容、Card 卡片的内容配置同上
|
||||
}
|
||||
```
|
||||
|
||||
### 布尔值映射
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"data": {
|
||||
"type": true
|
||||
},
|
||||
"controls": [
|
||||
{
|
||||
"type": "static-mapping",
|
||||
"name": "type",
|
||||
"label": "映射",
|
||||
"map": {
|
||||
"1": "<span class='label label-info'>开</span>",
|
||||
"0": "<span class='label label-default'>关</span>"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
或者
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"data": {
|
||||
"type": true
|
||||
},
|
||||
"controls": [
|
||||
{
|
||||
"type": "static-mapping",
|
||||
"name": "type",
|
||||
"label": "映射",
|
||||
"map": {
|
||||
"true": "<span class='label label-info'>开</span>",
|
||||
"false": "<span class='label label-default'>关</span>"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 远程拉取字典
|
||||
|
||||
> since 1.1.6
|
||||
|
@ -239,8 +239,8 @@ export class App extends React.PureComponent<{
|
||||
|
||||
<div
|
||||
className={`${theme.ns}Layout-headerBar ${
|
||||
docPage ? 'DocLayout-headerBar' : ''
|
||||
} pc:flex items-center`}
|
||||
docPage ? 'DocLayout-headerBar pc:inline-flex' : 'pc:flex'
|
||||
} items-center`}
|
||||
>
|
||||
{docPage ? null : (
|
||||
<Button
|
||||
|
@ -5,12 +5,14 @@ export default {
|
||||
type: 'form',
|
||||
wrapWithPanel: false,
|
||||
target: 'window', // 直接修改location,当然也可以直接指向某个组件。
|
||||
submitOnInit: true,
|
||||
controls: [
|
||||
{
|
||||
type: 'tree',
|
||||
name: 'cat',
|
||||
inputClassName: 'no-border',
|
||||
submitOnChange: true,
|
||||
selectFirst: true,
|
||||
options: [
|
||||
{
|
||||
label: '分类1',
|
||||
@ -106,7 +108,10 @@ export default {
|
||||
body: {
|
||||
type: 'crud',
|
||||
draggable: true,
|
||||
api: '/api/sample',
|
||||
api: {
|
||||
url: '/api/sample',
|
||||
sendOn: 'this.cat'
|
||||
},
|
||||
filter: {
|
||||
title: '条件搜索',
|
||||
submitText: '',
|
||||
|
306
examples/components/CRUD/Aside2.jsx
Normal file
306
examples/components/CRUD/Aside2.jsx
Normal file
@ -0,0 +1,306 @@
|
||||
export default {
|
||||
$schema: 'https://houtai.baidu.com/v2/schemas/page.json#',
|
||||
title: '带边栏联动',
|
||||
aside: {
|
||||
type: 'nav',
|
||||
stacked: true,
|
||||
source: '/api/mock2/options/nav?parentId=${value}&waitSeconds=2'
|
||||
},
|
||||
toolbar: [
|
||||
{
|
||||
type: 'button',
|
||||
actionType: 'dialog',
|
||||
label: '新增',
|
||||
primary: true,
|
||||
dialog: {
|
||||
title: '新增',
|
||||
body: {
|
||||
type: 'form',
|
||||
name: 'sample-edit-form',
|
||||
api: 'post:/api/sample',
|
||||
controls: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'engine',
|
||||
label: 'Engine',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'browser',
|
||||
label: 'Browser',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'platform',
|
||||
label: 'Platform(s)',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'version',
|
||||
label: 'Engine version'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'grade',
|
||||
label: 'CSS grade'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
body: {
|
||||
type: 'crud',
|
||||
draggable: true,
|
||||
api: {
|
||||
url: '/api/sample',
|
||||
sendOn: 'this.cat'
|
||||
},
|
||||
filter: {
|
||||
title: '条件搜索',
|
||||
submitText: '',
|
||||
controls: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'keywords',
|
||||
placeholder: '通过关键字搜索',
|
||||
addOn: {
|
||||
label: '搜索',
|
||||
type: 'submit'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'plain',
|
||||
text: '这里的表单项可以配置多个'
|
||||
}
|
||||
]
|
||||
},
|
||||
bulkActions: [
|
||||
{
|
||||
label: '批量删除',
|
||||
actionType: 'ajax',
|
||||
api: 'delete:/api/sample/$ids',
|
||||
confirmText: '确定要批量删除?'
|
||||
},
|
||||
{
|
||||
label: '批量修改',
|
||||
actionType: 'dialog',
|
||||
dialog: {
|
||||
title: '批量编辑',
|
||||
name: 'sample-bulk-edit',
|
||||
body: {
|
||||
type: 'form',
|
||||
api: '/api/sample/bulkUpdate2',
|
||||
controls: [
|
||||
{
|
||||
type: 'hidden',
|
||||
name: 'ids'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'engine',
|
||||
label: 'Engine'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
quickSaveApi: '/api/sample/bulkUpdate',
|
||||
quickSaveItemApi: '/api/sample/$id',
|
||||
columns: [
|
||||
{
|
||||
name: 'id',
|
||||
label: 'ID',
|
||||
width: 20,
|
||||
sortable: true,
|
||||
type: 'text',
|
||||
toggled: true
|
||||
},
|
||||
{
|
||||
name: 'engine',
|
||||
label: 'Rendering engine',
|
||||
sortable: true,
|
||||
searchable: true,
|
||||
type: 'text',
|
||||
toggled: true
|
||||
},
|
||||
{
|
||||
name: 'browser',
|
||||
label: 'Browser',
|
||||
sortable: true,
|
||||
type: 'text',
|
||||
toggled: true
|
||||
},
|
||||
{
|
||||
name: 'platform',
|
||||
label: 'Platform(s)',
|
||||
sortable: true,
|
||||
type: 'text',
|
||||
toggled: true
|
||||
},
|
||||
{
|
||||
name: 'version',
|
||||
label: 'Engine version',
|
||||
quickEdit: true,
|
||||
type: 'text',
|
||||
toggled: true
|
||||
},
|
||||
{
|
||||
name: 'grade',
|
||||
label: 'CSS grade',
|
||||
quickEdit: {
|
||||
mode: 'inline',
|
||||
type: 'select',
|
||||
options: ['A', 'B', 'C', 'D', 'X'],
|
||||
saveImmediately: true
|
||||
},
|
||||
type: 'text',
|
||||
toggled: true
|
||||
},
|
||||
{
|
||||
type: 'operation',
|
||||
label: '操作',
|
||||
width: 130,
|
||||
buttons: [
|
||||
{
|
||||
type: 'button',
|
||||
icon: 'fa fa-eye',
|
||||
actionType: 'dialog',
|
||||
dialog: {
|
||||
title: '查看',
|
||||
body: {
|
||||
type: 'form',
|
||||
controls: [
|
||||
{
|
||||
type: 'static',
|
||||
name: 'engine',
|
||||
label: 'Engine'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'static',
|
||||
name: 'browser',
|
||||
label: 'Browser'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'static',
|
||||
name: 'platform',
|
||||
label: 'Platform(s)'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'static',
|
||||
name: 'version',
|
||||
label: 'Engine version'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'static',
|
||||
name: 'grade',
|
||||
label: 'CSS grade'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'html',
|
||||
html:
|
||||
'<p>添加其他 <span>Html 片段</span> 需要支持变量替换(todo).</p>'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
icon: 'fa fa-pencil',
|
||||
actionType: 'dialog',
|
||||
dialog: {
|
||||
title: '编辑',
|
||||
body: {
|
||||
type: 'form',
|
||||
name: 'sample-edit-form',
|
||||
api: '/api/sample/$id',
|
||||
controls: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'engine',
|
||||
label: 'Engine',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'browser',
|
||||
label: 'Browser',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'platform',
|
||||
label: 'Platform(s)',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'version',
|
||||
label: 'Engine version'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'grade',
|
||||
label: 'CSS grade'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
icon: 'fa fa-times text-danger',
|
||||
actionType: 'ajax',
|
||||
confirmText: '您确认要删除?',
|
||||
api: 'delete:/api/sample/$id'
|
||||
}
|
||||
],
|
||||
toggled: true
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
@ -39,6 +39,7 @@ import LoadMoreSchema from './CRUD/LoadMore';
|
||||
import TestCrudSchema from './CRUD/test';
|
||||
import FixedCrudSchema from './CRUD/Fix';
|
||||
import AsideCrudSchema from './CRUD/Aside';
|
||||
import Aside2CrudSchema from './CRUD/Aside2';
|
||||
import FieldsCrudSchema from './CRUD/Fields';
|
||||
import JumpNextCrudSchema from './CRUD/JumpNext';
|
||||
import PopOverCrudSchema from './CRUD/PopOver';
|
||||
@ -335,10 +336,15 @@ export const examples = [
|
||||
component: makeSchemaRenderer(HeaderHideSchema)
|
||||
},
|
||||
{
|
||||
label: '带边栏',
|
||||
label: '带边栏(用 tree)',
|
||||
path: '/examples/crud/aside',
|
||||
component: makeSchemaRenderer(AsideCrudSchema)
|
||||
},
|
||||
{
|
||||
label: '带边栏(用 Nav)',
|
||||
path: '/examples/crud/aside2',
|
||||
component: makeSchemaRenderer(Aside2CrudSchema)
|
||||
},
|
||||
{
|
||||
label: '固定表头/列',
|
||||
path: '/examples/crud/fixed',
|
||||
|
@ -123,6 +123,7 @@ body {
|
||||
&-header {
|
||||
height: 64px !important;
|
||||
box-shadow: none !important;
|
||||
white-space: nowrap;
|
||||
|
||||
&::before {
|
||||
position: fixed;
|
||||
@ -206,9 +207,8 @@ body {
|
||||
// padding-left: 10px;
|
||||
|
||||
> a {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
display: inline-block;
|
||||
padding: 20px;
|
||||
cursor: pointer;
|
||||
color: #666;
|
||||
|
||||
@ -278,6 +278,7 @@ body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 50px;
|
||||
white-space: nowrap;
|
||||
|
||||
> a {
|
||||
display: inline-block;
|
||||
@ -305,6 +306,10 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
#Header-toolbar {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.a-Layout-offScreen-btn,
|
||||
.cxd-Layout-offScreen-btn,
|
||||
.antd-Layout-offScreen-btn,
|
||||
@ -365,7 +370,7 @@ body {
|
||||
}
|
||||
|
||||
&-nav {
|
||||
width: 200px;
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
&-toc {
|
||||
@ -383,7 +388,7 @@ body {
|
||||
}
|
||||
|
||||
&-label {
|
||||
font-size: px2rem(14px);
|
||||
font-size: px2rem(12px);
|
||||
font-weight: 700;
|
||||
margin-bottom: px2rem(10px);
|
||||
margin-top: px2rem(30px);
|
||||
@ -402,7 +407,7 @@ body {
|
||||
position: relative;
|
||||
|
||||
a {
|
||||
font-size: px2rem(14px);
|
||||
font-size: px2rem(12px);
|
||||
font-weight: 700;
|
||||
color: #666;
|
||||
padding: px2rem(3px) 0;
|
||||
@ -799,6 +804,12 @@ body {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.app-wrapper .DocLayout-brandBar,
|
||||
.app-wrapper .DocLayout-searchBar {
|
||||
float: none;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
@ -845,6 +856,9 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
&-brandBar {
|
||||
width: 200px;
|
||||
}
|
||||
&-brandBar > div {
|
||||
width: 220px;
|
||||
position: fixed;
|
||||
|
63
mock/options/nav.js
Normal file
63
mock/options/nav.js
Normal file
@ -0,0 +1,63 @@
|
||||
module.exports = function (req, res) {
|
||||
let repeat = 2 + Math.round(Math.random() * 5);
|
||||
let options = [];
|
||||
|
||||
if (req.query.parentId) {
|
||||
while (repeat--) {
|
||||
const value = Math.round(Math.random() * 1000000);
|
||||
const label = value + '';
|
||||
|
||||
options.push({
|
||||
label: label,
|
||||
to: `?cat=${value}`,
|
||||
value: `${value}`,
|
||||
defer: Math.random() > 0.7
|
||||
});
|
||||
}
|
||||
} else {
|
||||
options = [
|
||||
{
|
||||
label: 'Nav 1',
|
||||
to: '?cat=1',
|
||||
value: '1',
|
||||
icon: 'fa fa-user'
|
||||
},
|
||||
{
|
||||
label: 'Nav 2',
|
||||
unfolded: true,
|
||||
children: [
|
||||
{
|
||||
label: 'Nav 2-1',
|
||||
children: [
|
||||
{
|
||||
label: 'Nav 2-1-1',
|
||||
to: '?cat=2-1',
|
||||
value: '2-1'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Nav 2-2',
|
||||
to: '?cat=2-2',
|
||||
value: '2-2'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Nav 3',
|
||||
to: '?cat=3',
|
||||
value: '3',
|
||||
defer: true
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
res.json({
|
||||
status: 0,
|
||||
msg: '',
|
||||
data: {
|
||||
links: options,
|
||||
value: req.query.parentId ? undefined : '?cat=1'
|
||||
}
|
||||
});
|
||||
};
|
@ -941,11 +941,12 @@
|
||||
--Nav-item-color: var(--text-color);
|
||||
--Nav-item-fontSize: var(--fontSizeBase);
|
||||
--Nav-item-onActive-bg: var(--info);
|
||||
--Nav-item-onActive-borderLeft: 4px solid transparent;
|
||||
--Nav-item-onActive-borderLeft: 3px solid transparent;
|
||||
--Nav-item-onActive-color: var(--white);
|
||||
--Nav-item-onDisabled-color: var(--text--muted-color);
|
||||
--Nav-item-onHover-bg: rgba(0, 0, 0, 0.05);
|
||||
--Nav-item-onHover-color: var(--text--loud-color);
|
||||
--Nav-subNav-bg: #fafafa;
|
||||
--Nav-subItem-fontSize: var(--fontSizeBase);
|
||||
--Nav-subItem-onActiveBeforeBg: #e5eaeb;
|
||||
|
||||
|
@ -3,9 +3,10 @@
|
||||
user-select: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
|
||||
.#{$ns}Nav-itemIcon {
|
||||
margin-right: var(--gap-xs);
|
||||
margin-right: var(--gap-sm);
|
||||
}
|
||||
|
||||
img.#{$ns}Nav-itemIcon {
|
||||
@ -71,6 +72,24 @@
|
||||
cursor: pointer;
|
||||
background: var(--Nav-item-bg);
|
||||
border-radius: var(--Nav-item-borderRadius);
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&::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));
|
||||
}
|
||||
|
||||
> a:hover,
|
||||
@ -91,8 +110,12 @@
|
||||
&.is-active > a {
|
||||
color: var(--Nav-item-onActive-color);
|
||||
background: var(--Nav-item-onActive-bg);
|
||||
border-left: var(--Nav-item-onActive-borderLeft);
|
||||
padding-left: px2rem(12px);
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
transform: scaleY(1);
|
||||
}
|
||||
}
|
||||
|
||||
&.is-unfolded > {
|
||||
@ -102,19 +125,25 @@
|
||||
|
||||
.#{$ns}Nav-subItems {
|
||||
display: block;
|
||||
margin-left: var(--gap-sm);
|
||||
// margin-left: var(--gap-sm);
|
||||
}
|
||||
}
|
||||
|
||||
.#{$ns}Nav-spinner {
|
||||
position: absolute;
|
||||
right: px2rem(10px);
|
||||
top: px2rem(8px);
|
||||
}
|
||||
|
||||
.#{$ns}Nav-itemToggler {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: px2rem(3px);
|
||||
width: px2rem(30px);
|
||||
height: px2rem(30px);
|
||||
text-align: center;
|
||||
line-height: px2rem(30px);
|
||||
vertical-align: middle;
|
||||
top: px2rem(4px);
|
||||
right: px2rem(2px);
|
||||
cursor: pointer;
|
||||
transform: scale(0.8);
|
||||
transition: transform var(--animation-duration);
|
||||
@ -127,6 +156,7 @@
|
||||
}
|
||||
|
||||
.#{$ns}Nav-subItems {
|
||||
background: var(--Nav-subNav-bg);
|
||||
display: none;
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
@ -135,21 +165,21 @@
|
||||
.#{$ns}Nav-item {
|
||||
font-size: var(--Nav-subItem-fontSize);
|
||||
|
||||
a:before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: px2rem(4px);
|
||||
height: px2rem(4px);
|
||||
background: #ccc;
|
||||
border-radius: px2rem(500px);
|
||||
margin-right: px2rem(8px);
|
||||
vertical-align: middle;
|
||||
}
|
||||
// a:before {
|
||||
// content: '';
|
||||
// display: inline-block;
|
||||
// width: px2rem(4px);
|
||||
// height: px2rem(4px);
|
||||
// background: #ccc;
|
||||
// border-radius: px2rem(500px);
|
||||
// margin-right: px2rem(8px);
|
||||
// vertical-align: middle;
|
||||
// }
|
||||
|
||||
&.active > a:before,
|
||||
&.is-active > a:before {
|
||||
background: var(--Nav-subItem-onActiveBeforeBg);
|
||||
}
|
||||
// &.active > a:before,
|
||||
// &.is-active > a:before {
|
||||
// background: var(--Nav-subItem-onActiveBeforeBg);
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -253,33 +253,4 @@
|
||||
&-placeholder {
|
||||
color: var(--text--muted-color);
|
||||
}
|
||||
|
||||
&-item
|
||||
&-item
|
||||
&-item
|
||||
&-item
|
||||
&-item
|
||||
&-item
|
||||
&-item
|
||||
&-item
|
||||
&-item
|
||||
&-item
|
||||
> &-itemLabel {
|
||||
padding-left: calc(var(--Tree-indent) * 9);
|
||||
}
|
||||
|
||||
&-item
|
||||
&-item
|
||||
&-item
|
||||
&-item
|
||||
&-item
|
||||
&-item
|
||||
&-item
|
||||
&-item
|
||||
&-item
|
||||
&-item
|
||||
&-item
|
||||
> &-itemLabel {
|
||||
padding-left: calc(var(--Tree-indent) * 10);
|
||||
}
|
||||
}
|
||||
|
@ -379,7 +379,7 @@ $Wizard-steps-liAfterBorder: none !important;
|
||||
--Nav-item-borderRadius: 0;
|
||||
--Nav-item-onActive-bg: #f6f7fb;
|
||||
--Nav-item-onActive-color: var(--primary);
|
||||
--Nav-item-onActive-borderLeft: #{px2rem(4px)} solid var(--primary);
|
||||
--Nav-item-onActive-borderLeft: #{px2rem(3px)} solid var(--primary);
|
||||
--Nav-subItem-fontSize: #{px2rem(12px)};
|
||||
--Nav-subItem-onActiveBeforeBg: var(--primary);
|
||||
|
||||
|
@ -117,4 +117,6 @@ $link-color: $info;
|
||||
--Checkbox-onDisabled-bg: var(--Panel-bg);
|
||||
--Radio-onDisabled-color: #4e4e4e;
|
||||
--Radio-onDisabled-bg: var(--Panel-bg);
|
||||
|
||||
--Nav-subNav-bg: var(--Panel-bg);
|
||||
}
|
||||
|
@ -1038,7 +1038,7 @@ export const SelectWithRemoteOptions = withRemoteConfig<Array<Options>>({
|
||||
React.ComponentProps<typeof EnhancedSelect>
|
||||
> {
|
||||
render() {
|
||||
const {loading, config, ...rest} = this.props;
|
||||
const {loading, config, deferLoad, updateConfig, ...rest} = this.props;
|
||||
return (
|
||||
<EnhancedSelect
|
||||
{...rest}
|
||||
|
@ -18,9 +18,10 @@ import {
|
||||
tokenize
|
||||
} from '../utils/tpl-builtin';
|
||||
import {reaction} from 'mobx';
|
||||
import {createObject, findIndex, findTreeIndex} from '../utils/helper';
|
||||
|
||||
export const Store = types
|
||||
.model('OptionsStore')
|
||||
.model('RemoteConfigStore')
|
||||
.props({
|
||||
fetching: false,
|
||||
errorMsg: '',
|
||||
@ -28,20 +29,26 @@ export const Store = types
|
||||
data: types.frozen({})
|
||||
})
|
||||
.actions(self => {
|
||||
let component: any = undefined;
|
||||
|
||||
const load: (
|
||||
env: RendererEnv,
|
||||
api: Api,
|
||||
data: any,
|
||||
ctx: any,
|
||||
config: WithRemoteConfigSettings
|
||||
) => Promise<any> = flow(function* (env, api, data, config = {}) {
|
||||
) => Promise<any> = flow(function* (env, api, ctx, config = {}): any {
|
||||
try {
|
||||
self.fetching = true;
|
||||
const ret: Payload = yield env.fetcher(api, data);
|
||||
const ret: Payload = yield env.fetcher(api, ctx);
|
||||
|
||||
if (ret.ok) {
|
||||
const data = ret.data || {};
|
||||
let options = config.adaptor ? config.adaptor(data) : data;
|
||||
(self as any).setConfig(options, config);
|
||||
let options = config.adaptor
|
||||
? config.adaptor(data, component.props)
|
||||
: data;
|
||||
(self as any).setConfig(options, config, 'remote');
|
||||
config.afterLoad?.(ret, self.config, component.props);
|
||||
return ret;
|
||||
} else {
|
||||
throw new Error(ret.msg || 'fetch error');
|
||||
}
|
||||
@ -53,13 +60,27 @@ export const Store = types
|
||||
});
|
||||
|
||||
return {
|
||||
setComponent(c: any) {
|
||||
component = c;
|
||||
},
|
||||
|
||||
load,
|
||||
setData(data: any) {
|
||||
self.data = data || {};
|
||||
},
|
||||
setConfig(options: any, config: WithRemoteConfigSettings) {
|
||||
setConfig(
|
||||
options: any,
|
||||
config: WithRemoteConfigSettings,
|
||||
motivation?: any
|
||||
) {
|
||||
if (config.normalizeConfig) {
|
||||
options = config.normalizeConfig(options, self.config) || options;
|
||||
options =
|
||||
config.normalizeConfig(
|
||||
options,
|
||||
self.config,
|
||||
component.props,
|
||||
motivation
|
||||
) || options;
|
||||
}
|
||||
|
||||
self.config = options;
|
||||
@ -73,17 +94,65 @@ export interface OutterProps {
|
||||
env?: RendererEnv;
|
||||
data: any;
|
||||
source?: SchemaApi | SchemaTokenizeableString;
|
||||
deferApi?: SchemaApi;
|
||||
remoteConfigRef?: (
|
||||
instance:
|
||||
| {
|
||||
loadConfig: () => Promise<any> | void;
|
||||
setConfig: (value: any) => void;
|
||||
}
|
||||
| undefined
|
||||
) => void;
|
||||
}
|
||||
|
||||
export interface RemoteOptionsProps<T = any> {
|
||||
config: T;
|
||||
loading?: boolean;
|
||||
deferLoad: (item: any) => Promise<any>;
|
||||
updateConfig: (value: T, ctx?: any) => void;
|
||||
}
|
||||
|
||||
export interface WithRemoteConfigSettings {
|
||||
configField?: string;
|
||||
adaptor?: (json: any) => any;
|
||||
normalizeConfig?: (config: any, origin: any) => any;
|
||||
/**
|
||||
* 从接口返回数据适配到配置
|
||||
*/
|
||||
adaptor?: (json: any, props: any) => any;
|
||||
|
||||
/**
|
||||
* 配置格式化
|
||||
*/
|
||||
normalizeConfig?: (
|
||||
config: any,
|
||||
origin: any,
|
||||
props: any,
|
||||
motivation?: any
|
||||
) => any;
|
||||
|
||||
/**
|
||||
* 请求返回后的回调
|
||||
*/
|
||||
afterLoad?: (ret: any, config: any, props: any) => void;
|
||||
|
||||
/**
|
||||
* 懒加载选项相关,开始懒加载的回调
|
||||
*/
|
||||
beforeDeferLoad?: (
|
||||
item: any,
|
||||
indexes: Array<number>,
|
||||
config: any,
|
||||
props: any
|
||||
) => any;
|
||||
|
||||
/**
|
||||
* 懒加载选项相关,结束懒加载的回调
|
||||
*/
|
||||
afterDeferLoad?: (
|
||||
item: any,
|
||||
indexes: Array<number>,
|
||||
reponse: Payload,
|
||||
config: any,
|
||||
props: any
|
||||
) => any;
|
||||
}
|
||||
|
||||
export function withRemoteConfig<P = any>(
|
||||
@ -114,6 +183,19 @@ export function withRemoteConfig<P = any>(
|
||||
static contextType = EnvContext;
|
||||
toDispose: Array<() => void> = [];
|
||||
|
||||
constructor(
|
||||
props: FinalOutterProps & {
|
||||
store: IStore;
|
||||
}
|
||||
) {
|
||||
super(props);
|
||||
|
||||
this.setConfig = this.setConfig.bind(this);
|
||||
props.store.setComponent(this);
|
||||
this.deferLoadConfig = this.deferLoadConfig.bind(this);
|
||||
props.remoteConfigRef?.(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const env: RendererEnv = this.props.env || this.context;
|
||||
const {store, source, data} = this.props;
|
||||
@ -121,7 +203,7 @@ export function withRemoteConfig<P = any>(
|
||||
store.setData(data);
|
||||
|
||||
if (isPureVariable(source)) {
|
||||
this.syncOptions();
|
||||
this.syncConfig();
|
||||
this.toDispose.push(
|
||||
reaction(
|
||||
() =>
|
||||
@ -130,11 +212,11 @@ export function withRemoteConfig<P = any>(
|
||||
store.data,
|
||||
'| raw'
|
||||
),
|
||||
() => this.syncOptions()
|
||||
() => this.syncConfig()
|
||||
)
|
||||
);
|
||||
} else if (env && isEffectiveApi(source, data)) {
|
||||
this.loadOptions();
|
||||
this.loadConfig();
|
||||
this.toDispose.push(
|
||||
reaction(
|
||||
() => {
|
||||
@ -145,7 +227,7 @@ export function withRemoteConfig<P = any>(
|
||||
ignoreData: true
|
||||
}).url;
|
||||
},
|
||||
() => this.loadOptions()
|
||||
() => this.loadConfig()
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -162,38 +244,86 @@ export function withRemoteConfig<P = any>(
|
||||
componentWillUnmount() {
|
||||
this.toDispose.forEach(fn => fn());
|
||||
this.toDispose = [];
|
||||
|
||||
this.props.remoteConfigRef?.(undefined);
|
||||
}
|
||||
|
||||
loadOptions() {
|
||||
async loadConfig(ctx = this.props.data) {
|
||||
const env: RendererEnv = this.props.env || this.context;
|
||||
const {store, source, data} = this.props;
|
||||
const {store, source} = this.props;
|
||||
|
||||
if (env && isEffectiveApi(source, data)) {
|
||||
store.load(env, source, data, config);
|
||||
if (env && isEffectiveApi(source, ctx)) {
|
||||
await store.load(env, source, ctx, config);
|
||||
}
|
||||
}
|
||||
|
||||
syncOptions() {
|
||||
setConfig(value: any, ctx?: any) {
|
||||
const {store} = this.props;
|
||||
store.setConfig(value, config, ctx);
|
||||
}
|
||||
|
||||
syncConfig() {
|
||||
const {store, source, data} = this.props;
|
||||
|
||||
if (isPureVariable(source)) {
|
||||
store.setConfig(
|
||||
resolveVariableAndFilter(source as string, data, '| raw') || [],
|
||||
config
|
||||
config,
|
||||
'syncConfig'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async deferLoadConfig(item: any) {
|
||||
const {store, source, data, deferApi} = this.props;
|
||||
const env: RendererEnv = this.props.env || this.context;
|
||||
const indexes = findTreeIndex(store.config, a => a === item)!;
|
||||
|
||||
const ret = config.beforeDeferLoad?.(
|
||||
item,
|
||||
indexes,
|
||||
store.config,
|
||||
this.props
|
||||
);
|
||||
|
||||
ret && store.setConfig(ret, config, 'before-defer-load');
|
||||
let response: Payload;
|
||||
try {
|
||||
response = await env.fetcher(
|
||||
item.deferApi || deferApi || source,
|
||||
createObject(data, item)
|
||||
);
|
||||
} catch (e) {
|
||||
response = {
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
status: 500,
|
||||
data: undefined
|
||||
};
|
||||
}
|
||||
const ret2 = config.afterDeferLoad?.(
|
||||
item,
|
||||
indexes, // 只能假定还是那个 index 了
|
||||
response,
|
||||
store.config,
|
||||
this.props
|
||||
);
|
||||
ret2 && store.setConfig(ret2, config, 'after-defer-load');
|
||||
}
|
||||
|
||||
render() {
|
||||
const store = this.props.store;
|
||||
const injectedProps: RemoteOptionsProps<P> = {
|
||||
config: store.config,
|
||||
loading: store.fetching
|
||||
loading: store.fetching,
|
||||
deferLoad: this.deferLoadConfig,
|
||||
updateConfig: this.setConfig
|
||||
};
|
||||
const {remoteConfigRef, ...rest} = this.props;
|
||||
|
||||
return (
|
||||
<ComposedComponent
|
||||
{...(this.props as JSX.LibraryManagedAttributes<
|
||||
{...(rest as JSX.LibraryManagedAttributes<
|
||||
T,
|
||||
React.ComponentProps<T>
|
||||
>)}
|
||||
|
@ -66,7 +66,7 @@ const ConditionBuilderWithRemoteOptions = withRemoteConfig({
|
||||
RemoteOptionsProps & React.ComponentProps<typeof ConditionBuilder>
|
||||
> {
|
||||
render() {
|
||||
const {loading, config, ...rest} = this.props;
|
||||
const {loading, config, deferLoad, ...rest} = this.props;
|
||||
return (
|
||||
<ConditionBuilder
|
||||
{...rest}
|
||||
|
@ -194,20 +194,21 @@ export const MappingField = withStore(props =>
|
||||
<span className="text-muted">{placeholder}</span>
|
||||
);
|
||||
|
||||
key =
|
||||
typeof key === 'string'
|
||||
? key.trim()
|
||||
: key === true
|
||||
? '1'
|
||||
: key === false
|
||||
? '0'
|
||||
: key; // trim 一下,干掉一些空白字符。
|
||||
key = typeof key === 'string' ? key.trim() : key; // trim 一下,干掉一些空白字符。
|
||||
let value: any = undefined;
|
||||
|
||||
if (typeof key !== 'undefined' && map && (map[key] ?? map['*'])) {
|
||||
viewValue = render(
|
||||
'tpl',
|
||||
map[key] ?? map['*'] // 兼容平台旧用法:即 value 为 true 时映射 1 ,为 false 时映射 0
|
||||
);
|
||||
if (
|
||||
typeof key !== 'undefined' &&
|
||||
map &&
|
||||
(value =
|
||||
map[key] ??
|
||||
(key === true && map['1']
|
||||
? map['1']
|
||||
: key === false && map['0']
|
||||
? map['0']
|
||||
: map['*'])) !== undefined
|
||||
) {
|
||||
viewValue = render('tpl', value);
|
||||
}
|
||||
|
||||
return <span className={cx('MappingField', className)}>{viewValue}</span>;
|
||||
|
@ -1,19 +1,25 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Renderer, RendererProps} from '../factory';
|
||||
import {ServiceStore, IServiceStore} from '../store/service';
|
||||
import cx from 'classnames';
|
||||
import {Renderer, RendererEnv, RendererProps} from '../factory';
|
||||
import getExprProperties from '../utils/filter-schema';
|
||||
import {filter, evalExpression} from '../utils/tpl';
|
||||
import {createObject, mapTree, someTree} from '../utils/helper';
|
||||
import {resolveVariable, isPureVariable} from '../utils/tpl-builtin';
|
||||
import {isApiOutdated, isEffectiveApi} from '../utils/api';
|
||||
import {
|
||||
autobind,
|
||||
findTree,
|
||||
mapTree,
|
||||
someTree,
|
||||
spliceTree
|
||||
} from '../utils/helper';
|
||||
import {ScopedContext, IScopedContext} from '../Scoped';
|
||||
import {Api} from '../types';
|
||||
import {ClassNamesFn, themeable, ThemeProps} from '../theme';
|
||||
import {themeable, ThemeProps} from '../theme';
|
||||
import {Icon} from '../components/icons';
|
||||
import {BaseSchema, SchemaApi, SchemaIcon, SchemaUrlPath} from '../Schema';
|
||||
import {generateIcon} from '../utils/icon';
|
||||
import {
|
||||
RemoteOptionsProps,
|
||||
withRemoteConfig
|
||||
} from '../components/WithRemoteConfig';
|
||||
import {Payload} from '../types';
|
||||
import Spinner from '../components/Spinner';
|
||||
|
||||
export type NavItemSchema = {
|
||||
/**
|
||||
@ -46,6 +52,11 @@ export interface NavSchema extends BaseSchema {
|
||||
*/
|
||||
links?: Array<NavItemSchema>;
|
||||
|
||||
/**
|
||||
* @default 24
|
||||
*/
|
||||
indentSize: number;
|
||||
|
||||
/**
|
||||
* 可以通过 API 拉取。
|
||||
*/
|
||||
@ -66,6 +77,8 @@ export interface Link {
|
||||
activeOn?: string;
|
||||
unfolded?: boolean;
|
||||
children?: Links;
|
||||
defer?: boolean;
|
||||
loading?: boolean;
|
||||
[propName: string]: any;
|
||||
}
|
||||
export interface Links extends Array<Link> {}
|
||||
@ -76,178 +89,138 @@ export interface NavigationState {
|
||||
}
|
||||
|
||||
export interface NavigationProps
|
||||
extends RendererProps,
|
||||
Omit<ThemeProps, 'className'>,
|
||||
extends ThemeProps,
|
||||
Omit<NavSchema, 'type' | 'className'> {
|
||||
onSelect?: (item: Link) => any;
|
||||
onSelect?: (item: Link) => void | false;
|
||||
onToggle?: (item: Link) => void;
|
||||
togglerClassName?: string;
|
||||
links?: Array<Link>;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
export class Navigation extends React.Component<
|
||||
NavigationProps,
|
||||
NavigationState
|
||||
> {
|
||||
static defaultProps: Partial<NavigationProps> = {};
|
||||
static defaultProps = {
|
||||
indentSize: 24
|
||||
};
|
||||
|
||||
mounted: boolean = true;
|
||||
constructor(props: NavigationProps) {
|
||||
super(props);
|
||||
|
||||
this.renderItem = this.renderItem.bind(this);
|
||||
|
||||
this.state = {
|
||||
links: this.syncLinks(
|
||||
props,
|
||||
(props.source &&
|
||||
typeof props.source === 'string' &&
|
||||
isPureVariable(props.source) &&
|
||||
resolveVariable(props.source, props.data)) ||
|
||||
props.links
|
||||
)
|
||||
};
|
||||
@autobind
|
||||
handleClick(link: Link) {
|
||||
this.props.onSelect?.(link);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {source} = this.props;
|
||||
@autobind
|
||||
toggleLink(target: Link) {
|
||||
this.props.onToggle?.(target);
|
||||
}
|
||||
|
||||
if (source && !isPureVariable(source as string)) {
|
||||
this.reload();
|
||||
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 hasSub = link.defer || (link.children && link.children.length);
|
||||
|
||||
return (
|
||||
<li
|
||||
key={index}
|
||||
className={cx('Nav-item', link.className, {
|
||||
'is-disabled': disabled || link.disabled || link.loading,
|
||||
'is-active': isActive,
|
||||
'is-unfolded': link.unfolded,
|
||||
'has-sub': hasSub
|
||||
})}
|
||||
>
|
||||
<a
|
||||
onClick={this.handleClick.bind(this, link)}
|
||||
style={{paddingLeft: depth * (parseInt(indentSize as any, 10) ?? 24)}}
|
||||
>
|
||||
{generateIcon(cx, link.icon, 'Nav-itemIcon')}
|
||||
{link.label}
|
||||
</a>
|
||||
|
||||
{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}
|
||||
|
||||
{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}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: NavigationProps) {
|
||||
const props = this.props;
|
||||
render(): JSX.Element {
|
||||
const {className, stacked, classnames: cx, links, loading} = this.props;
|
||||
|
||||
if (nextProps.source && isPureVariable(nextProps.source as string)) {
|
||||
if (nextProps.source !== props.source) {
|
||||
this.setState({
|
||||
links: this.syncLinks(nextProps)
|
||||
});
|
||||
} else {
|
||||
const links = resolveVariable(
|
||||
nextProps.source as string,
|
||||
nextProps.data
|
||||
);
|
||||
const prevLinks = resolveVariable(props.source as string, props.data);
|
||||
return (
|
||||
<ul
|
||||
className={cx('Nav', className, stacked ? 'Nav--stacked' : 'Nav--tabs')}
|
||||
>
|
||||
{Array.isArray(links)
|
||||
? links.map((item, index) => this.renderItem(item, index))
|
||||
: null}
|
||||
|
||||
if (links !== prevLinks) {
|
||||
this.setState({
|
||||
links: this.syncLinks(nextProps, links)
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (props.links !== nextProps.links) {
|
||||
this.setState({
|
||||
links: this.syncLinks(nextProps)
|
||||
});
|
||||
} else if (nextProps.location && props.location !== nextProps.location) {
|
||||
this.setState({
|
||||
links: this.syncLinks(nextProps, this.state.links, true)
|
||||
});
|
||||
}
|
||||
<Spinner show={loading} overlay icon="reload" />
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: NavigationProps) {
|
||||
const props = this.props;
|
||||
const ThemedNavigation = themeable(Navigation);
|
||||
|
||||
if (props.source && !isPureVariable(props.source as string)) {
|
||||
isApiOutdated(
|
||||
prevProps.source,
|
||||
props.source,
|
||||
prevProps.data,
|
||||
props.data
|
||||
) && this.reload();
|
||||
}
|
||||
}
|
||||
const ConditionBuilderWithRemoteOptions = withRemoteConfig({
|
||||
adaptor: (config: any, props: any) => {
|
||||
const links = Array.isArray(config)
|
||||
? config
|
||||
: config.links || config.options || config.items || config.rows;
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
}
|
||||
|
||||
reload(target?: string, query?: any, values?: object) {
|
||||
if (query) {
|
||||
return this.receive(query);
|
||||
if (!Array.isArray(links)) {
|
||||
throw new Error('payload.data.options is not array.');
|
||||
}
|
||||
|
||||
const {data, env, source, translate: __} = this.props;
|
||||
const finalData = values ? createObject(data, values) : data;
|
||||
|
||||
if (!isEffectiveApi(source, data)) {
|
||||
return;
|
||||
return links;
|
||||
},
|
||||
afterLoad: (response: any, config: any, props: any) => {
|
||||
if (response.value && !someTree(config, item => item.active)) {
|
||||
const {env} = props;
|
||||
env.jumpTo(filter(response.value as string, props.data));
|
||||
}
|
||||
},
|
||||
normalizeConfig(
|
||||
links: Array<Link>,
|
||||
origin: Array<Link> | undefined,
|
||||
props: any,
|
||||
motivation?: string
|
||||
) {
|
||||
if (Array.isArray(links) && motivation !== 'toggle') {
|
||||
const {data, env} = props;
|
||||
|
||||
env
|
||||
.fetcher(source as Api, finalData)
|
||||
.then(payload => {
|
||||
if (!this.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!payload.ok) {
|
||||
this.setState({
|
||||
error: payload.msg || __('Nav.sourceError')
|
||||
});
|
||||
} else {
|
||||
const links = Array.isArray(payload.data)
|
||||
? payload.data
|
||||
: payload.data.links ||
|
||||
payload.data.options ||
|
||||
payload.data.items ||
|
||||
payload.data.rows;
|
||||
|
||||
if (!Array.isArray(links)) {
|
||||
throw new Error('payload.data.options is not array.');
|
||||
}
|
||||
|
||||
this.setState(
|
||||
{
|
||||
links: this.syncLinks(this.props, links)
|
||||
},
|
||||
() => {
|
||||
if (
|
||||
payload.data &&
|
||||
payload.data.value &&
|
||||
!someTree(this.state.links, (item: any) => item.active)
|
||||
) {
|
||||
env.jumpTo(filter(payload.data.value as string, data));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch(
|
||||
e =>
|
||||
this.mounted &&
|
||||
this.setState({
|
||||
error: e.message
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
receive(values: object) {
|
||||
const {store, initApi} = this.props;
|
||||
|
||||
this.reload(undefined, undefined, values);
|
||||
}
|
||||
|
||||
syncLinks(
|
||||
props: NavigationProps,
|
||||
links = props.links,
|
||||
clearActive?: boolean
|
||||
): Links {
|
||||
const {data, env} = props;
|
||||
|
||||
if (!Array.isArray(links) || !links.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return mapTree(
|
||||
links,
|
||||
(link: Link) => {
|
||||
links = mapTree(links, (link: Link) => {
|
||||
return {
|
||||
...link,
|
||||
...getExprProperties(link, data as object),
|
||||
active:
|
||||
(!clearActive && link.active) ||
|
||||
(motivation !== 'location-change' && link.active) ||
|
||||
(link.activeOn
|
||||
? evalExpression(link.activeOn as string, data)
|
||||
: !!(
|
||||
@ -259,106 +232,122 @@ export class Navigation extends React.Component<
|
||||
link.unfolded ||
|
||||
(link.children && link.children.some(link => !!link.active))
|
||||
};
|
||||
},
|
||||
1,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
handleClick(link: {
|
||||
label?: string;
|
||||
to?: string;
|
||||
icon?: string;
|
||||
children?: Links;
|
||||
}) {
|
||||
const {env, data, onSelect} = this.props;
|
||||
|
||||
if (onSelect && onSelect(link) === false) {
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
if (!link.to) {
|
||||
link.children && link.children.length && this.toggleLink(link);
|
||||
return;
|
||||
}
|
||||
return links;
|
||||
},
|
||||
|
||||
env && env.jumpTo(filter(link.to as string, data), link as any);
|
||||
}
|
||||
|
||||
toggleLink(target: Link) {
|
||||
this.setState({
|
||||
links: mapTree(this.state.links, (link: Link) =>
|
||||
target === link
|
||||
? {
|
||||
...link,
|
||||
unfolded: !link.unfolded
|
||||
}
|
||||
: link
|
||||
)
|
||||
beforeDeferLoad(item: Link, indexes: Array<number>, links: Array<Link>) {
|
||||
return spliceTree(links, indexes, 1, {
|
||||
...item,
|
||||
defer: undefined,
|
||||
loading: true
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
renderItem(link: Link, index: number) {
|
||||
if (link.hidden === true || link.visible === false) {
|
||||
return null;
|
||||
afterDeferLoad(
|
||||
item: Link,
|
||||
indexes: Array<number>,
|
||||
ret: Payload,
|
||||
links: Array<Link>
|
||||
) {
|
||||
const newItem = {
|
||||
...item,
|
||||
defer: false,
|
||||
loading: false,
|
||||
error: ret.ok ? undefined : ret.msg
|
||||
};
|
||||
|
||||
const children = Array.isArray(ret.data)
|
||||
? ret.data
|
||||
: ret.data.links || ret.data.options || ret.data.items || ret.data.rows;
|
||||
|
||||
if (Array.isArray(children)) {
|
||||
newItem.children = children.concat();
|
||||
newItem.unfolded = true;
|
||||
}
|
||||
const isActive: boolean = !!link.active;
|
||||
const {disabled, togglerClassName, classnames: cx} = this.props;
|
||||
|
||||
return (
|
||||
<li
|
||||
key={index}
|
||||
className={cx('Nav-item', link.className, {
|
||||
'is-disabled': disabled || link.disabled,
|
||||
'is-active': isActive,
|
||||
'is-unfolded': link.unfolded
|
||||
})}
|
||||
>
|
||||
<a onClick={this.handleClick.bind(this, link)}>
|
||||
{generateIcon(cx, link.icon, 'Nav-itemIcon')}
|
||||
{link.label}
|
||||
</a>
|
||||
|
||||
{link.children && link.children.length ? (
|
||||
<span
|
||||
onClick={() => this.toggleLink(link)}
|
||||
className={cx('Nav-itemToggler', togglerClassName)}
|
||||
>
|
||||
<Icon icon="caret" className="icon" />
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
{link.children && link.children.length ? (
|
||||
<ul className={cx('Nav-subItems')}>
|
||||
{link.children.map((link, index) => this.renderItem(link, index))}
|
||||
</ul>
|
||||
) : null}
|
||||
</li>
|
||||
);
|
||||
return spliceTree(links, indexes, 1, newItem);
|
||||
}
|
||||
})(
|
||||
class extends React.Component<
|
||||
RemoteOptionsProps &
|
||||
React.ComponentProps<typeof ThemedNavigation> & {
|
||||
location?: any;
|
||||
env?: RendererEnv;
|
||||
data?: any;
|
||||
}
|
||||
> {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
this.toggleLink = this.toggleLink.bind(this);
|
||||
this.handleSelect = this.handleSelect.bind(this);
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
const {className, stacked, classnames: cx} = this.props;
|
||||
componentDidUpdate(prevProps: any) {
|
||||
if (this.props.location !== prevProps.location) {
|
||||
this.props.updateConfig(this.props.config, 'location-change');
|
||||
}
|
||||
}
|
||||
|
||||
const links = this.state.links;
|
||||
toggleLink(target: Link) {
|
||||
const {config, updateConfig, deferLoad} = this.props;
|
||||
|
||||
return (
|
||||
<ul
|
||||
className={cx('Nav', className, stacked ? 'Nav--stacked' : 'Nav--tabs')}
|
||||
>
|
||||
{links.map(this.renderItem)}
|
||||
</ul>
|
||||
);
|
||||
if (target.defer) {
|
||||
deferLoad(target);
|
||||
} else {
|
||||
updateConfig(
|
||||
mapTree(config, (link: Link) =>
|
||||
target === link
|
||||
? {
|
||||
...link,
|
||||
unfolded: !link.unfolded
|
||||
}
|
||||
: link
|
||||
),
|
||||
'toggle'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
handleSelect(link: Link) {
|
||||
const {onSelect, env, data} = this.props;
|
||||
if (onSelect && onSelect(link) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!link.to && ((link.children && link.children.length) || link.defer)) {
|
||||
this.toggleLink(link);
|
||||
return;
|
||||
}
|
||||
|
||||
env?.jumpTo(filter(link.to as string, data), link as any);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {loading, config, deferLoad, updateConfig, ...rest} = this.props;
|
||||
|
||||
return (
|
||||
<ThemedNavigation
|
||||
{...rest}
|
||||
loading={loading}
|
||||
links={config || rest.links || []}
|
||||
disabled={loading}
|
||||
onSelect={this.handleSelect}
|
||||
onToggle={this.toggleLink}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default themeable(Navigation);
|
||||
);
|
||||
|
||||
export default ThemedNavigation;
|
||||
@Renderer({
|
||||
test: /(^|\/)(?:nav|navigation)$/,
|
||||
name: 'nav'
|
||||
})
|
||||
export class NavigationRenderer extends Navigation {
|
||||
export class NavigationRenderer extends React.Component<RendererProps> {
|
||||
static contextType = ScopedContext;
|
||||
|
||||
componentWillMount() {
|
||||
@ -369,6 +358,14 @@ export class NavigationRenderer extends Navigation {
|
||||
componentWillUnmount() {
|
||||
const scoped = this.context as IScopedContext;
|
||||
scoped.unRegisterComponent(this);
|
||||
super.componentWillUnmount();
|
||||
}
|
||||
|
||||
// reload() {}
|
||||
// reciever
|
||||
|
||||
render() {
|
||||
const {...rest} = this.props;
|
||||
|
||||
return <ConditionBuilderWithRemoteOptions {...rest} />;
|
||||
}
|
||||
}
|
||||
|
@ -189,7 +189,7 @@ const StepsWithRemoteConfig = withRemoteConfig({
|
||||
RemoteOptionsProps & React.ComponentProps<typeof Steps>
|
||||
> {
|
||||
render() {
|
||||
const {config, ...rest} = this.props;
|
||||
const {config, deferLoad, loading, updateConfig, ...rest} = this.props;
|
||||
return <Steps config={config} {...rest} />;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user