mirror of
https://gitee.com/baidu/amis.git
synced 2024-12-03 04:18:29 +08:00
merge feat-optimize-4
Change-Id: I8974063a944df31cfcd401f416b69859fa53c04d
This commit is contained in:
parent
4756363b60
commit
f541968956
@ -84,7 +84,8 @@
|
||||
}
|
||||
|
||||
@mixin panel-sm-content {
|
||||
--ColorPicker-fontSize: var(--fontSizeBase);
|
||||
--Form-fontSize: #{$Editor-right-panel-font-size};
|
||||
--ColorPicker-fontSize: var($Editor-right-panel-font-size);
|
||||
--fontSizeBase: #{$Editor-right-panel-font-size};
|
||||
--Form-item-fontSize: #{$Editor-right-panel-font-size};
|
||||
--Button--md-fontSize: #{$Editor-right-panel-font-size};
|
||||
|
@ -365,7 +365,7 @@ $category-2-height: px2rem(32px);
|
||||
&-cont {
|
||||
flex: 1 1 auto;
|
||||
padding: 0;
|
||||
overflow-y: overlay;
|
||||
overflow-y: overlay !important;
|
||||
@include minScrollBar();
|
||||
}
|
||||
|
||||
|
@ -19,11 +19,64 @@
|
||||
@include flexBox();
|
||||
|
||||
.ae-ApiControl-input {
|
||||
background: var(--Form-input-bg);
|
||||
border: var(--Form-input-borderWidth) solid var(--Form-input-borderColor);
|
||||
border-radius: var(--Form-input-borderRadius);
|
||||
line-height: var(--Form-input-lineHeight);
|
||||
padding: var(--Form-input-paddingY) var(--Form-input-paddingX);
|
||||
font-size: var(--Form-input-fontSize);
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
margin-right: #{px2rem(10px)};
|
||||
max-width: calc(100% - 52px);
|
||||
height: var(--Button--sm-height);
|
||||
|
||||
& > input {
|
||||
flex-basis: 5rem;
|
||||
flex-grow: 1;
|
||||
outline: 0;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: var(--Form-input-color);
|
||||
width: 100%;
|
||||
height: calc(var(--Form-input-lineHeight) * var(--Form-input-fontSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-highlight {
|
||||
width: 100%;
|
||||
max-width: calc(100% - var(--fontSizeLg));
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
&-tag {
|
||||
display: inline-block;
|
||||
background: #007bff;
|
||||
padding: 3px 5px;
|
||||
margin: 0 1px;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
height: 20px;
|
||||
border-radius: #{px2rem(4px)};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
&-icon {
|
||||
width: var(--fontSizeLg) !important;
|
||||
height: var(--fontSizeLg) !important;
|
||||
}
|
||||
|
||||
&-dialog {
|
||||
&-body {
|
||||
padding: 0 !important;
|
||||
@ -59,3 +112,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ae-ApiControl-PickerBtn {
|
||||
padding: 0;
|
||||
|
||||
&:hover > svg path {
|
||||
stroke: var(--primary);
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,85 @@
|
||||
.ae-DataBindingList {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
height: px2rem(350px);
|
||||
border: 1px solid rgba(232, 233, 235, 1);
|
||||
border-radius: px2rem(4px);
|
||||
|
||||
&-searchBox {
|
||||
width: auto;
|
||||
padding: #{px2rem(12px)};
|
||||
|
||||
& > div {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
&-body {
|
||||
@include minScrollBar();
|
||||
flex: 1;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
&-collapse {
|
||||
border: none;
|
||||
background: #f7f7f9;
|
||||
|
||||
&-title {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: flex-start;
|
||||
align-items: unset;
|
||||
padding: #{px2rem(5px)} #{px2rem(12px)};
|
||||
background: transparent;
|
||||
font-size: var(--fontSizeSm);
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
|
||||
.#{$ns}DataSourceList-expandIcon {
|
||||
font-size: var(--fontSizeSm);
|
||||
line-height: var(--fontSizeXl);
|
||||
transform-origin: #{px2rem(7px)} #{px2rem(9px)};
|
||||
transition: transform 0.2s;
|
||||
position: absolute;
|
||||
right: #{px2rem(6px)};
|
||||
}
|
||||
}
|
||||
|
||||
&-body {
|
||||
background: #fff;
|
||||
color: #303540;
|
||||
|
||||
> div {
|
||||
padding: 5px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-item {
|
||||
cursor: pointer;
|
||||
padding: 0 var(--gap-xl); // 和标题对齐不好看,加个缩进
|
||||
height: px2rem(32px);
|
||||
line-height: px2rem(32px);
|
||||
color: #303540;
|
||||
font-weight: 400;
|
||||
|
||||
&:hover {
|
||||
background: var(--Tree-item-onHover-bg);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
color: var(--primary);
|
||||
background: var(--Tree-item-onHover-bg);
|
||||
}
|
||||
}
|
||||
|
||||
&-empty {
|
||||
color: #b4b6ba;
|
||||
padding-top: px2rem(10px);
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
64
packages/amis-editor-core/scss/control/_feature-control.scss
Normal file
64
packages/amis-editor-core/scss/control/_feature-control.scss
Normal file
@ -0,0 +1,64 @@
|
||||
.ae-FeatureControl {
|
||||
&-features {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&Item {
|
||||
display: flex;
|
||||
height: 30px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
:not(:last-child) {
|
||||
margin-right: px2rem(8px);
|
||||
}
|
||||
|
||||
&-go {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
&-label {
|
||||
flex-grow: 1;
|
||||
height: px2rem(32px);
|
||||
display: block;
|
||||
line-height: px2rem(32px);
|
||||
padding: 0 px2rem(8px);
|
||||
border: var(--Form-input-borderWidth) solid var(--Form-input-borderColor);
|
||||
border-radius: var(--Form-input-borderRadius);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&-action {
|
||||
padding: 0 6px;
|
||||
svg {
|
||||
width: px2rem(16px);
|
||||
height: px2rem(16px);
|
||||
fill: #000;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
fill: $Editor-theme;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-action {
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
||||
&--btn {
|
||||
width: 100%;
|
||||
border-color: $Editor-theme;
|
||||
color: $Editor-theme;
|
||||
}
|
||||
|
||||
&--menus {
|
||||
width: calc(100% - 12px);
|
||||
margin-left: 6px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -120,4 +120,9 @@
|
||||
width: 100%;
|
||||
height: px2rem(32px);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.form-item-gap {
|
||||
margin-bottom: var(--Form-item-gap);
|
||||
}
|
||||
|
29
packages/amis-editor-core/scss/control/_go-config.scss
Normal file
29
packages/amis-editor-core/scss/control/_go-config.scss
Normal file
@ -0,0 +1,29 @@
|
||||
.ae-GoConfig {
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
position: relative;
|
||||
background-color: #fff;
|
||||
text-align: center;
|
||||
font-size: $Editor-right-panel-font-size;
|
||||
border: 1px solid #e6e6e8;
|
||||
border-radius: $Editor-borderRadius;
|
||||
|
||||
&-trigger {
|
||||
display: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: rgba($color: #000000, $alpha: .4);
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.ae-GoConfig-trigger {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -26,6 +26,9 @@
|
||||
@import './control/formula-control';
|
||||
@import './control/dateshortcut-control';
|
||||
@import './control/badge-control';
|
||||
@import './control/go-config';
|
||||
@import './control/feature-control';
|
||||
@import './control/databinding-control';
|
||||
@import './control/event-action';
|
||||
|
||||
/* 样式控件 */
|
||||
@ -1051,55 +1054,26 @@
|
||||
|
||||
.ae-Region-placeholder {
|
||||
display: none;
|
||||
text-align: center;
|
||||
color: var(--text--muted-color);
|
||||
user-select: none;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
border: 1px dashed rgb(206, 208, 211);
|
||||
background: rgba(10, 19, 37, 0.05);
|
||||
|
||||
// &:first-child {
|
||||
// position: relative;
|
||||
// display: flex;
|
||||
// flex: 1;
|
||||
// flex-direction: column;
|
||||
// justify-content: center;
|
||||
// min-width: 60px;
|
||||
// padding: 0 5px;
|
||||
// -webkit-user-select: none;
|
||||
// user-select: none;
|
||||
// text-align: center;
|
||||
// text-transform: uppercase;
|
||||
// color: var(--text--muted-color);
|
||||
// // border: 1px dashed rgb(206, 208, 211);
|
||||
// background: rgba(10, 19, 37, 0.05);
|
||||
// }
|
||||
&:first-child {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
[data-region] {
|
||||
position: relative;
|
||||
min-height: 34px;
|
||||
|
||||
&:empty {
|
||||
min-width: 20px;
|
||||
|
||||
&:before {
|
||||
height: 100%;
|
||||
content: attr(data-region-placeholder);
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
padding: 0 5px;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
color: rgb(108, 113, 124);
|
||||
border: 1px dashed rgb(206, 208, 211);
|
||||
background: rgba(10, 19, 37, 0.05);
|
||||
}
|
||||
}
|
||||
// &.is-region-active {
|
||||
// min-height: 34px;
|
||||
// }
|
||||
|
||||
&.is-dragenter {
|
||||
background-color: #fff;
|
||||
}
|
||||
@ -1503,10 +1477,27 @@ div.ae-DragImage {
|
||||
|
||||
.ae-ApiSample {
|
||||
min-width: 200px;
|
||||
max-height: 300px;
|
||||
|
||||
&-desc {
|
||||
font-size: var(--fontSizeSm);
|
||||
display: inline-block;
|
||||
margin-top: #{px2rem(5px)};
|
||||
color: #84868c;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
--Remark-onHover-bg: #{$Editor-theme-color};
|
||||
|
||||
& > i {
|
||||
border: none;
|
||||
padding: #{px2rem(10px)};
|
||||
border-radius: #{px2rem(3px)};
|
||||
}
|
||||
}
|
||||
|
||||
> pre {
|
||||
overflow: auto;
|
||||
border: 1px solid #999;
|
||||
page-break-inside: avoid;
|
||||
display: block;
|
||||
padding: 9.5px;
|
||||
@ -1516,12 +1507,13 @@ div.ae-DragImage {
|
||||
color: #333;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
background-color: #f7f7f9;
|
||||
border-radius: #{$Editor-borderRadius};
|
||||
border: none;
|
||||
|
||||
> code {
|
||||
white-space: pre;
|
||||
color: #151a26;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1558,8 +1550,66 @@ div.ae-DragImage {
|
||||
}
|
||||
}
|
||||
|
||||
.ae-InputVariable {
|
||||
width: 100%;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
> span {
|
||||
margin-left: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.ae-collapse-checkbox{
|
||||
label{
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ae-Scaffold-Modal {
|
||||
width: px2rem(700px);
|
||||
@include panel-sm-content();
|
||||
|
||||
.ae-Steps {
|
||||
margin: auto;
|
||||
max-width: px2rem(350px);
|
||||
--Steps-title-fontsize: #{px2rem(14px)};
|
||||
|
||||
&-Icon {
|
||||
width: px2rem(22px) !important;
|
||||
height: px2rem(22px) !important;
|
||||
font-size: px2rem(12px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&-Tabs {
|
||||
--Tabs-linkFontSize: #{px2rem(12px)};
|
||||
}
|
||||
}
|
||||
|
||||
.ae-Button--link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0 !important;
|
||||
|
||||
svg {
|
||||
width: 12px;
|
||||
margin-right: 4px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ae-Fields-Setting {
|
||||
&-Item {
|
||||
display: flex;
|
||||
height: px2rem(32px);
|
||||
margin-bottom: 12px;
|
||||
padding: 0 px2rem(8px);
|
||||
border: var(--Form-input-borderWidth) solid var(--Form-input-borderColor);
|
||||
border-radius: var(--Form-input-borderRadius);
|
||||
|
||||
&-label {
|
||||
flex-grow: 1;
|
||||
line-height: px2rem(30px);
|
||||
}
|
||||
}
|
||||
}
|
584
packages/amis-editor-core/src/builder/ApiBuilder.ts
Normal file
584
packages/amis-editor-core/src/builder/ApiBuilder.ts
Normal file
@ -0,0 +1,584 @@
|
||||
/**
|
||||
* API数据源处理器
|
||||
*/
|
||||
|
||||
import {Schema, toast} from 'amis';
|
||||
import {
|
||||
DSBuilder,
|
||||
DSFeature,
|
||||
DSFeatureType,
|
||||
DSGrain,
|
||||
registerDSBuilder
|
||||
} from './DSBuilder';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import {getEnv} from 'mobx-state-tree';
|
||||
import {ButtonSchema} from 'amis/lib/renderers/Action';
|
||||
import {FormSchema, SchemaObject} from 'amis/lib/Schema';
|
||||
|
||||
import type {DSSourceSettingFormConfig} from './DSBuilder';
|
||||
import {getSchemaTpl, tipedLabel} from '../tpl';
|
||||
|
||||
class APIBuilder extends DSBuilder {
|
||||
public static type = 'api';
|
||||
|
||||
name = '接口';
|
||||
|
||||
order = 0;
|
||||
|
||||
public match = (value: any, schema?: SchemaObject) => {
|
||||
// https://aisuda.bce.baidu.com/amis/zh-CN/docs/types/api
|
||||
if (
|
||||
(typeof value === 'string' &&
|
||||
/^(get|post|put|delete|option):/.test(value)) ||
|
||||
(typeof value === 'object' && value.url)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
public static accessable = (controlType: string, propKey: string) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
public features: Array<DSFeatureType> = [
|
||||
'List',
|
||||
'Insert',
|
||||
'View',
|
||||
'Edit',
|
||||
'Delete',
|
||||
'BulkEdit',
|
||||
'BulkDelete',
|
||||
'Import',
|
||||
'Export',
|
||||
'SimpleQuery',
|
||||
'FuzzyQuery'
|
||||
];
|
||||
|
||||
public makeSourceSettingForm(
|
||||
config: DSSourceSettingFormConfig
|
||||
): SchemaObject[] {
|
||||
let {name, label, feat, inCrud, inScaffold} = config;
|
||||
|
||||
if (['Import', 'Export', 'SimpleQuery', 'FuzzyQuery'].includes(feat)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
label =
|
||||
label ??
|
||||
(inCrud && feat !== 'List' ? DSFeature[feat].label + '接口' : '接口');
|
||||
name = name ?? (inScaffold ? DSFeature[feat].value + 'Api' : 'api');
|
||||
|
||||
let sampleBuilder = null;
|
||||
let apiDesc = null;
|
||||
switch (feat) {
|
||||
case 'Insert':
|
||||
(label as any) = tipedLabel(
|
||||
label,
|
||||
`用来保存数据, 表单提交后将数据传入此接口。 <br/>
|
||||
接口响应体要求(如果data中有数据,该数据将被合并到表单上下文中):<br/>
|
||||
${JSON.stringify({status: 0, msg: '', data: {}}, null, '<br/>')}`
|
||||
);
|
||||
break;
|
||||
|
||||
case 'List':
|
||||
(label as any) = tipedLabel(
|
||||
label,
|
||||
`接口响应体要求:<br/>
|
||||
${JSON.stringify(
|
||||
{status: 0, msg: '', items: {}, page: 0, total: 0},
|
||||
null,
|
||||
'<br/>'
|
||||
)}`
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return [
|
||||
getSchemaTpl('apiControl', {
|
||||
label,
|
||||
name,
|
||||
sampleBuilder,
|
||||
apiDesc
|
||||
})
|
||||
]
|
||||
.concat(
|
||||
feat === 'Edit' && !inCrud
|
||||
? getSchemaTpl('apiControl', {
|
||||
label: tipedLabel(
|
||||
'初始化接口',
|
||||
`接口响应体要求:<br/>
|
||||
${JSON.stringify({status: 0, msg: '', data: {}}, null, '<br/>')}`
|
||||
),
|
||||
name: 'initApi'
|
||||
})
|
||||
: null
|
||||
)
|
||||
.concat(
|
||||
feat === 'List' && inCrud && inScaffold
|
||||
? this.makeFieldsSettingForm({
|
||||
feat,
|
||||
setting: true
|
||||
})
|
||||
: null
|
||||
)
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
public async getContextFileds(config: {
|
||||
schema: any;
|
||||
sourceKey: string;
|
||||
feat: DSFeatureType;
|
||||
}) {
|
||||
return config.schema.__fields;
|
||||
}
|
||||
|
||||
public async getAvailableContextFileds(config: {
|
||||
schema: any;
|
||||
sourceKey: string;
|
||||
feat: DSFeatureType;
|
||||
}) {
|
||||
if (!config.schema.__fields) {
|
||||
return;
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
label: '字段',
|
||||
value: 'fields',
|
||||
children: config.schema.__fields
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
public makeFieldsSettingForm(config: {
|
||||
sourceKey?: string;
|
||||
feat: DSFeatureType;
|
||||
inCrud?: boolean;
|
||||
setting?: boolean;
|
||||
inScaffold?: boolean;
|
||||
}) {
|
||||
let {sourceKey, feat, inCrud, setting, inScaffold} = config;
|
||||
if (
|
||||
inScaffold === false ||
|
||||
['Import', 'Export', 'FuzzyQuery'].includes(feat)
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
sourceKey = sourceKey ?? `${DSFeature[feat].value}Api`;
|
||||
const key = setting ? '__fields' : `${DSFeature[feat].value}Fields`;
|
||||
const hasInputType =
|
||||
['Edit', 'Insert'].includes(feat) || (inCrud && feat === 'List');
|
||||
const hasType = ['View', 'List'].includes(feat);
|
||||
|
||||
return ([] as any)
|
||||
.concat(
|
||||
inCrud && feat !== 'List'
|
||||
? this.makeSourceSettingForm({
|
||||
feat,
|
||||
inScaffold,
|
||||
inCrud
|
||||
})
|
||||
: null
|
||||
)
|
||||
.concat([
|
||||
{
|
||||
type: 'combo',
|
||||
className: 'mb-0 ae-Fields-Setting',
|
||||
joinValues: false,
|
||||
name: key,
|
||||
label: inCrud ? `${DSFeature[feat].label}字段` : '字段',
|
||||
multiple: true,
|
||||
draggable: true,
|
||||
addable: false,
|
||||
removable: false,
|
||||
itemClassName: 'ae-Fields-Setting-Item',
|
||||
hidden: setting || !inCrud || ['Delete', 'BulkDelete'].includes(feat),
|
||||
items: {
|
||||
type: 'container',
|
||||
body: [
|
||||
{
|
||||
name: 'checked',
|
||||
label: false,
|
||||
mode: 'inline',
|
||||
className: 'm-0 ml-1',
|
||||
type: 'checkbox'
|
||||
},
|
||||
{
|
||||
type: 'tpl',
|
||||
className: 'ae-Fields-Setting-Item-label',
|
||||
tpl: '${label}'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'input-table',
|
||||
label: '字段',
|
||||
className: 'mb-0',
|
||||
name: key,
|
||||
// 非crud,都是定义字段的模式,只有crud,有统一定义字段,因此是选择字段
|
||||
visible: setting ?? !inCrud,
|
||||
removable: true,
|
||||
columnsTogglable: false,
|
||||
needConfirm: false,
|
||||
onChange: (value: any, oldValue: any, model: any, form: any) => {
|
||||
this.features.forEach(feat => {
|
||||
const key = `${DSFeature[feat].value}Fields`;
|
||||
const currentData = form.getValueByName(key);
|
||||
|
||||
const result = cloneDeep(value || []).map((field: any) => {
|
||||
const exist = currentData?.find(
|
||||
(f: any) => f.name === field.name
|
||||
);
|
||||
|
||||
return {
|
||||
...field,
|
||||
checked: exist ? exist.checked : true
|
||||
};
|
||||
});
|
||||
form.setValueByName(key, result);
|
||||
});
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
type: 'switch',
|
||||
name: 'checked',
|
||||
value: true,
|
||||
label: '隐藏,默认选中',
|
||||
visible: false
|
||||
},
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'label',
|
||||
label: '标题'
|
||||
},
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'name',
|
||||
label: '绑定字段'
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'type',
|
||||
label: '类型',
|
||||
visible: hasType,
|
||||
value: 'tpl',
|
||||
options: [
|
||||
{
|
||||
value: 'tpl',
|
||||
label: '文本',
|
||||
typeKey: 'tpl'
|
||||
},
|
||||
{
|
||||
value: 'image',
|
||||
label: '图片',
|
||||
typeKey: 'src'
|
||||
},
|
||||
{
|
||||
value: 'date',
|
||||
label: '日期',
|
||||
typeKey: 'value'
|
||||
},
|
||||
{
|
||||
value: 'progress',
|
||||
label: '进度',
|
||||
typeKey: 'value'
|
||||
},
|
||||
{
|
||||
value: 'status',
|
||||
label: '状态',
|
||||
typeKey: 'value'
|
||||
},
|
||||
{
|
||||
value: 'mapping',
|
||||
label: '映射',
|
||||
typeKey: 'value'
|
||||
}
|
||||
],
|
||||
autoFill: {
|
||||
typeKey: '${typeKey}'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'inputType',
|
||||
label: '输入类型',
|
||||
visible: hasInputType,
|
||||
value: 'input-text',
|
||||
options: [
|
||||
{
|
||||
label: '输入框',
|
||||
value: 'input-text'
|
||||
},
|
||||
{
|
||||
label: '多行文本',
|
||||
value: 'textarea'
|
||||
},
|
||||
{
|
||||
label: '数字输入',
|
||||
value: 'input-number'
|
||||
},
|
||||
{
|
||||
label: '单选框',
|
||||
value: 'radios'
|
||||
},
|
||||
{
|
||||
label: '勾选框',
|
||||
value: 'checkbox'
|
||||
},
|
||||
{
|
||||
label: '复选框',
|
||||
value: 'checkboxes'
|
||||
},
|
||||
{
|
||||
label: '下拉框',
|
||||
value: 'select'
|
||||
},
|
||||
{
|
||||
label: '开关',
|
||||
value: 'switch'
|
||||
},
|
||||
{
|
||||
label: '日期',
|
||||
value: 'input-date'
|
||||
},
|
||||
{
|
||||
label: '表格',
|
||||
value: 'input-table'
|
||||
},
|
||||
{
|
||||
label: '文件上传',
|
||||
value: 'input-file'
|
||||
},
|
||||
{
|
||||
label: '图片上传',
|
||||
value: 'input-image'
|
||||
},
|
||||
{
|
||||
label: '富文本编辑器',
|
||||
value: 'input-rich-text'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
visible: setting ?? !inCrud,
|
||||
label: '',
|
||||
body: [
|
||||
{
|
||||
type: 'grid',
|
||||
columns: [
|
||||
{
|
||||
body: [
|
||||
{
|
||||
type: 'button',
|
||||
label: '添加字段',
|
||||
target: key,
|
||||
className: 'ae-Button--link',
|
||||
level: 'link',
|
||||
icon: 'plus',
|
||||
actionType: 'add'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
columnClassName: 'text-right',
|
||||
body: [
|
||||
{
|
||||
type: 'button',
|
||||
label: '基于接口自动生成字段',
|
||||
visible: feat === 'Edit' || feat === 'List',
|
||||
className: 'ae-Button--link',
|
||||
level: 'link',
|
||||
// className: 'm-t-xs m-b-xs',
|
||||
// 列表 或者 不在CRUD中的查看接口等
|
||||
onClick: async (e: Event, props: any) => {
|
||||
const data = props.data;
|
||||
const schemaFilter = getEnv(
|
||||
(window as any).editorStore
|
||||
).schemaFilter;
|
||||
const apiKey =
|
||||
feat === 'Edit' && !inCrud ? 'initApi' : sourceKey;
|
||||
let api: any = data[apiKey!];
|
||||
// 主要是给爱速搭中替换 url
|
||||
if (schemaFilter) {
|
||||
api = schemaFilter({
|
||||
api
|
||||
}).api;
|
||||
}
|
||||
if (!api) {
|
||||
toast.warning('请先填写接口');
|
||||
}
|
||||
|
||||
const result = await props.env.fetcher(api, data);
|
||||
|
||||
let autoFillKeyValues: Array<any> = [];
|
||||
let itemExample;
|
||||
if (feat === 'List') {
|
||||
const items = result.data?.rows || result.data?.items;
|
||||
itemExample = items?.[0];
|
||||
} else {
|
||||
itemExample = result.data;
|
||||
}
|
||||
|
||||
if (itemExample) {
|
||||
Object.entries(itemExample).forEach(
|
||||
([key, value]) => {
|
||||
autoFillKeyValues.push({
|
||||
label: key,
|
||||
type: 'tpl',
|
||||
inputType:
|
||||
typeof value === 'number'
|
||||
? 'input-number'
|
||||
: 'input-text',
|
||||
name: key
|
||||
});
|
||||
}
|
||||
);
|
||||
props.formStore.setValues({
|
||||
[key]: autoFillKeyValues
|
||||
});
|
||||
} else {
|
||||
toast.warning(
|
||||
'API返回格式不正确,请查看接口响应格式要求'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]) as SchemaObject[];
|
||||
}
|
||||
|
||||
public makeFieldFilterSetting(): SchemaObject[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
public resolveSourceSchema(config: {
|
||||
schema: SchemaObject;
|
||||
setting: any;
|
||||
name?: string;
|
||||
feat?: DSFeatureType;
|
||||
inCrud?: boolean;
|
||||
}): void {
|
||||
let {name, setting, schema, feat} = config;
|
||||
name = name ?? 'api';
|
||||
// @ts-ignore
|
||||
schema[name] = setting[feat ? `${DSFeature[feat].value}Api` : 'api'];
|
||||
|
||||
// form中需要初始化接口和编辑接口
|
||||
if (feat === 'Edit') {
|
||||
(schema as FormSchema).initApi = setting.initApi;
|
||||
}
|
||||
}
|
||||
|
||||
public resolveViewSchema(config: {
|
||||
setting: any;
|
||||
feat?: DSFeatureType;
|
||||
}): SchemaObject[] {
|
||||
let {setting, feat = 'Edit'} = config;
|
||||
const fields = setting[`${DSFeature[feat].value}Fields`] || [];
|
||||
return fields
|
||||
.filter((i: any) => i.checked)
|
||||
.map((field: any) => ({
|
||||
type: field.type,
|
||||
[field.typeKey || 'value']: '${' + field.name + '}'
|
||||
}));
|
||||
}
|
||||
|
||||
public resolveTableSchema(config: {schema: any; setting: any}): void {
|
||||
let {schema, setting} = config;
|
||||
const fields = setting.listFields.filter((i: any) => i.checked) || [];
|
||||
schema.columns = this.makeTableColumnsByFields(fields);
|
||||
}
|
||||
|
||||
public makeTableColumnsByFields(fields: any[]) {
|
||||
return fields.map((field: any) => ({
|
||||
type: field.type,
|
||||
title: field.label,
|
||||
key: field.name,
|
||||
[field.typeKey || 'value']: '${' + field.name + '}'
|
||||
}));
|
||||
}
|
||||
|
||||
public resolveCreateSchema(config: {
|
||||
schema: FormSchema;
|
||||
setting: any;
|
||||
feat: 'Insert' | 'Edit' | 'BulkEdit';
|
||||
name?: string;
|
||||
inCrud?: boolean;
|
||||
inScaffold?: boolean;
|
||||
}): void {
|
||||
let {schema, setting, feat, name} = config;
|
||||
const fields = setting[`${DSFeature[feat].value}Fields`] || [];
|
||||
// @ts-ignore
|
||||
schema[name ?? 'api'] = setting[DSFeature[feat].value + 'Api'];
|
||||
schema.initApi = setting['initApi'];
|
||||
schema.body = fields
|
||||
.filter((i: any) => i.checked)
|
||||
.map((field: any) => ({
|
||||
type: field.inputType,
|
||||
name: field.name,
|
||||
label: field.label
|
||||
}));
|
||||
}
|
||||
|
||||
public resolveDeleteSchema(config: {
|
||||
schema: ButtonSchema;
|
||||
setting: any;
|
||||
feat: 'BulkDelete' | 'Delete';
|
||||
name?: string | undefined;
|
||||
}) {
|
||||
const {schema, setting, feat} = config;
|
||||
schema.onEvent = Object.assign(schema.onEvent ?? {}, {
|
||||
click: {
|
||||
actions: []
|
||||
}
|
||||
});
|
||||
|
||||
const api = {
|
||||
...(setting[`${DSFeature[feat].value}Api`] || {})
|
||||
};
|
||||
if (feat === 'Delete') {
|
||||
api.data = {
|
||||
id: '${item.id}'
|
||||
};
|
||||
} else {
|
||||
api.data = {
|
||||
ids: '${ARRAYMAP(selectedItems, item=> item.id)}'
|
||||
};
|
||||
}
|
||||
|
||||
schema.onEvent.click.actions.push({
|
||||
actionType: 'ajax',
|
||||
args: {api}
|
||||
});
|
||||
}
|
||||
|
||||
public resolveSimpleFilterSchema(config: {setting: any}) {
|
||||
const {setting} = config;
|
||||
const fields = setting.simpleQueryFields || [];
|
||||
return fields
|
||||
.filter((i: any) => i.checked)
|
||||
.map((field: any) => ({
|
||||
type: field.inputType,
|
||||
name: field.name,
|
||||
label: field.label
|
||||
}));
|
||||
}
|
||||
|
||||
public resolveAdvancedFilterSchema(config: {setting: any}) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
registerDSBuilder(APIBuilder);
|
368
packages/amis-editor-core/src/builder/DSBuilder.ts
Normal file
368
packages/amis-editor-core/src/builder/DSBuilder.ts
Normal file
@ -0,0 +1,368 @@
|
||||
/**
|
||||
* 数据源构造器,可用于对接当前amis中的扩展数据源
|
||||
*/
|
||||
|
||||
import {ButtonSchema} from 'amis/lib/renderers/Action';
|
||||
import {CRUD2Schema} from 'amis/lib/renderers/CRUD2';
|
||||
import {FormSchema, SchemaObject} from 'amis/lib/Schema';
|
||||
|
||||
/**
|
||||
* 数据源所需操作,目前是因为schema从后端来
|
||||
*/
|
||||
export enum DSBehavior {
|
||||
create = 'create',
|
||||
view = 'view',
|
||||
update = 'update',
|
||||
table = 'table',
|
||||
filter = 'filter'
|
||||
}
|
||||
|
||||
export interface DSField {
|
||||
value: string;
|
||||
label: string;
|
||||
[propKey: string]: any;
|
||||
}
|
||||
|
||||
export interface DSFieldGroup {
|
||||
value: string;
|
||||
label: string;
|
||||
children: DSField[];
|
||||
[propKey: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持数据源配置的一些属性名
|
||||
*/
|
||||
export enum DSGrain {
|
||||
entity = 'entity',
|
||||
list = 'list',
|
||||
piece = 'piece'
|
||||
}
|
||||
|
||||
export const DSFeature = {
|
||||
List: {
|
||||
value: 'list',
|
||||
label: '列表'
|
||||
},
|
||||
Insert: {
|
||||
value: 'insert',
|
||||
label: '新增'
|
||||
},
|
||||
View: {
|
||||
value: 'view',
|
||||
label: '详情'
|
||||
},
|
||||
Edit: {
|
||||
value: 'edit',
|
||||
label: '编辑'
|
||||
},
|
||||
Delete: {
|
||||
value: 'delete',
|
||||
label: '删除'
|
||||
},
|
||||
BulkEdit: {
|
||||
value: 'bulkEdit',
|
||||
label: '批量编辑'
|
||||
},
|
||||
BulkDelete: {
|
||||
value: 'bulkDelete',
|
||||
label: '批量删除'
|
||||
},
|
||||
Import: {
|
||||
value: 'import',
|
||||
label: '导入'
|
||||
},
|
||||
Export: {
|
||||
value: 'export',
|
||||
label: '导出'
|
||||
},
|
||||
SimpleQuery: {
|
||||
value: 'simpleQuery',
|
||||
label: '简单查询'
|
||||
},
|
||||
FuzzyQuery: {
|
||||
value: 'fuzzyQuery',
|
||||
label: '模糊查询'
|
||||
},
|
||||
AdvancedQuery: {
|
||||
value: 'advancedQuery',
|
||||
label: '高级查询'
|
||||
}
|
||||
};
|
||||
|
||||
export type DSFeatureType = keyof typeof DSFeature;
|
||||
|
||||
export interface DSSourceSettingFormConfig {
|
||||
/** 数据源字段名 */
|
||||
name?: string;
|
||||
/** 数据源字段标题 */
|
||||
label?: string;
|
||||
/** 所需要配置的数据粒度 */
|
||||
grain?: DSGrain;
|
||||
/** 数据源所被使用的功能场景 */
|
||||
feat: DSFeatureType;
|
||||
/** 是否是在CRUD场景下,有的数据源在CRUD中可以统一设置 */
|
||||
inCrud?: boolean;
|
||||
/** 是否在脚手架中 */
|
||||
inScaffold?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据源选择构造器
|
||||
*/
|
||||
export abstract class DSBuilder {
|
||||
/**
|
||||
* 数据源名字,中文,可以覆盖同名
|
||||
*/
|
||||
public static type: string;
|
||||
|
||||
public name: string;
|
||||
|
||||
// 数字越小排序越靠前
|
||||
public order: number;
|
||||
|
||||
/**
|
||||
* 数据源schema运行前转换
|
||||
*/
|
||||
public static schemaFilter?: (schema: any) => any;
|
||||
|
||||
/**
|
||||
* 根据组件、属性名判断是否可以使用这个数据源
|
||||
*/
|
||||
public static accessable: (controlType: string, propKey: string) => boolean;
|
||||
|
||||
public features: Array<keyof typeof DSFeature>;
|
||||
|
||||
/**
|
||||
* 根据值内容和schema配置状态,看是否是当前数据源
|
||||
*/
|
||||
public abstract match(value: any, schema?: SchemaObject): boolean;
|
||||
|
||||
/**
|
||||
* 生成数据源的配置表单
|
||||
*/
|
||||
public abstract makeSourceSettingForm(
|
||||
config: DSSourceSettingFormConfig
|
||||
): SchemaObject[];
|
||||
|
||||
public abstract makeFieldsSettingForm(config: {
|
||||
/** 数据源字段名 */
|
||||
sourceKey?: string;
|
||||
feat: DSFeatureType;
|
||||
inCrud?: boolean;
|
||||
inScaffold?: boolean;
|
||||
/** 初次设置字段还是选择字段 */
|
||||
setting?: boolean;
|
||||
}): SchemaObject[];
|
||||
|
||||
/**
|
||||
* 生成字段的筛选配置表单
|
||||
*/
|
||||
public abstract makeFieldFilterSetting(config: {
|
||||
/** 数据源字段名 */
|
||||
sourceKey?: string;
|
||||
inCrud?: boolean;
|
||||
inScaffold?: boolean;
|
||||
schema?: any;
|
||||
}): SchemaObject[];
|
||||
|
||||
/**
|
||||
* 数据源schema生成
|
||||
*/
|
||||
abstract resolveSourceSchema(config: {
|
||||
/** schema */
|
||||
schema: SchemaObject;
|
||||
/** 数据源配置结果 */
|
||||
setting: any;
|
||||
/** 数据源字段名 */
|
||||
name?: string;
|
||||
feat?: DSFeatureType;
|
||||
/** 是否是在CRUD场景下,有的数据源在CRUD中可以统一设置 */
|
||||
inCrud?: boolean;
|
||||
inScaffold?: boolean;
|
||||
}): void;
|
||||
|
||||
/**
|
||||
* 数据删除schema生成
|
||||
*/
|
||||
abstract resolveDeleteSchema(config: {
|
||||
schema: ButtonSchema;
|
||||
setting: any;
|
||||
feat: 'BulkDelete' | 'Delete';
|
||||
name?: string;
|
||||
}): any;
|
||||
|
||||
/**
|
||||
* 生成数据创建表单schema
|
||||
*/
|
||||
abstract resolveCreateSchema(config: {
|
||||
/** schema */
|
||||
schema: FormSchema;
|
||||
/** 脚手架配置数据 */
|
||||
setting: any;
|
||||
feat: 'Insert' | 'Edit' | 'BulkEdit';
|
||||
/** 数据源字段名 */
|
||||
name?: string;
|
||||
/** 是否是在CRUD场景下,有的数据源在CRUD中可以统一设置 */
|
||||
inCrud?: boolean;
|
||||
}): void;
|
||||
|
||||
/**
|
||||
* 生成数据表格列
|
||||
*/
|
||||
abstract resolveTableSchema(config: {
|
||||
/** schema */
|
||||
schema: CRUD2Schema;
|
||||
/** 脚手架配置数据 */
|
||||
setting: any;
|
||||
/** 数据源字段名 */
|
||||
name?: string;
|
||||
/** 是否是在CRUD场景下,有的数据源在CRUD中可以统一设置 */
|
||||
inCrud?: boolean;
|
||||
}): void;
|
||||
|
||||
/**
|
||||
* 生成数据表格列
|
||||
*/
|
||||
abstract resolveViewSchema(config: {
|
||||
/** 脚手架配置数据 */
|
||||
setting: any;
|
||||
feat?: DSFeatureType;
|
||||
}): SchemaObject[];
|
||||
|
||||
abstract resolveSimpleFilterSchema(config: {
|
||||
setting: any
|
||||
}): SchemaObject[];
|
||||
|
||||
abstract resolveAdvancedFilterSchema(config: {
|
||||
setting: any;
|
||||
}): SchemaObject | void;
|
||||
|
||||
abstract makeTableColumnsByFields(fields: any[]): SchemaObject[];
|
||||
|
||||
/**
|
||||
* 当前上下文中使用的字段
|
||||
*/
|
||||
abstract getContextFileds(config: {
|
||||
schema: any,
|
||||
sourceKey: string,
|
||||
feat: DSFeatureType
|
||||
}): Promise<DSFieldGroup[] | void>;
|
||||
|
||||
/**
|
||||
* 上下文可以使用的字段
|
||||
*/
|
||||
abstract getAvailableContextFileds(config: {
|
||||
schema: any,
|
||||
sourceKey: string,
|
||||
feat: DSFeatureType
|
||||
}): Promise<DSFieldGroup[] | void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 所有的数据源构造器
|
||||
*/
|
||||
const __builders: {
|
||||
[key: string]: any;
|
||||
} = {};
|
||||
|
||||
export const registerDSBuilder = (builderKClass: any) => {
|
||||
__builders[builderKClass.type] = builderKClass;
|
||||
};
|
||||
|
||||
/**
|
||||
* 构造器管理工具,便于更好的缓存
|
||||
*/
|
||||
export class DSBuilderManager {
|
||||
/** 所有可用的数据源构造器实例 */
|
||||
builders: {
|
||||
[key: string]: DSBuilder;
|
||||
} = {};
|
||||
|
||||
get builderNum() {
|
||||
return Object.keys(this.builders).length;
|
||||
}
|
||||
|
||||
constructor(type: string, propKey: string) {
|
||||
Object.values(__builders)
|
||||
.filter(builder => builder.accessable?.(type, propKey) ?? true)
|
||||
.forEach(Builder => {
|
||||
this.builders[Builder.type] = new Builder();
|
||||
});
|
||||
}
|
||||
|
||||
resolveBuilderBySetting(setting: any) {
|
||||
return this.builders[setting.dsType] || Object.values(this.builders)[0];
|
||||
}
|
||||
|
||||
resolveBuilderBySchema(schema: any, propKey: string) {
|
||||
const builders = Object.values(this.builders);
|
||||
return builders.find(builder => builder.match(schema[propKey])) || builders[0];
|
||||
}
|
||||
|
||||
getDefaultBuilderName() {
|
||||
// 先返回第一个,之后可以加一些order之类的
|
||||
const builderOptions = Object.entries(this.builders)
|
||||
.map(([key, builder]) => {
|
||||
return {
|
||||
value: key,
|
||||
order: builder.order
|
||||
};
|
||||
}).sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
||||
return builderOptions[0].value;
|
||||
}
|
||||
|
||||
getDSSwitch(setting: any = {}) {
|
||||
const multiSource = this.builderNum > 1;
|
||||
const builderOptions = Object.entries(this.builders).map(
|
||||
([key, builder]) => ({
|
||||
label: builder.name,
|
||||
value: key,
|
||||
order: builder.order
|
||||
})
|
||||
);
|
||||
builderOptions.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
||||
|
||||
return {
|
||||
type: 'radios',
|
||||
label: '数据来源',
|
||||
name: 'dsType',
|
||||
visible: multiSource,
|
||||
selectFirst: true,
|
||||
options: builderOptions,
|
||||
...setting
|
||||
};
|
||||
}
|
||||
|
||||
// getDSSwitchFormForPanel(
|
||||
// propKey: string,
|
||||
// label: string
|
||||
// ) {
|
||||
// return Object.keys(this.builders).length > 1 ? {
|
||||
// type: Object.keys(this.builders).length > 3 ? 'select' : 'button-group-select',
|
||||
// options: Object.keys(this.builders).map(name => ({
|
||||
// label: name,
|
||||
// value: name
|
||||
// })),
|
||||
// name: propKey,
|
||||
// label: label,
|
||||
// pipeIn: (value: string) => {
|
||||
// const builders = Object.entries(this.builders);
|
||||
// return (builders.find(([, builder]) => {
|
||||
// return builder.match(value);
|
||||
// }) || builders[0])[0];
|
||||
// },
|
||||
// pipeOut: (value: string) => {
|
||||
// return this.builders[value].defaultSchema || {};
|
||||
// }
|
||||
// } : null;
|
||||
// }
|
||||
|
||||
collectFromBuilders(
|
||||
callee: (builder: DSBuilder, builderName: string) => any
|
||||
) {
|
||||
return Object.entries(this.builders).map(([name, builder]) => {
|
||||
return callee(builder, name);
|
||||
});
|
||||
}
|
||||
}
|
@ -97,10 +97,6 @@ export class RegionWrapper extends React.Component<RegionWrapperProps> {
|
||||
|
||||
wrapper.setAttribute('data-region', region);
|
||||
wrapper.setAttribute('data-region-host', id);
|
||||
wrapper.setAttribute(
|
||||
'data-region-placeholder',
|
||||
this.props.placeholder || this.props.label
|
||||
);
|
||||
rendererName && wrapper.setAttribute('data-renderer', rendererName);
|
||||
}
|
||||
|
||||
@ -108,7 +104,9 @@ export class RegionWrapper extends React.Component<RegionWrapperProps> {
|
||||
return (
|
||||
<EditorNodeContext.Provider value={this.editorNode}>
|
||||
{this.props.children}
|
||||
<span className="ae-Region-placeholder" />
|
||||
<span className="ae-Region-placeholder">
|
||||
{this.props.placeholder || this.props.label}
|
||||
</span>
|
||||
</EditorNodeContext.Provider>
|
||||
);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import {EditorManager} from '../manager';
|
||||
import {EditorStoreType} from '../store/editor';
|
||||
import {render, Modal, getTheme, Icon, Spinner, Button} from 'amis';
|
||||
import {observer} from 'mobx-react';
|
||||
import {autobind} from '../util';
|
||||
import {autobind, isObject} from '../util';
|
||||
import {createObject} from 'amis-core';
|
||||
|
||||
export interface SubEditorProps {
|
||||
@ -12,8 +12,20 @@ export interface SubEditorProps {
|
||||
theme?: string;
|
||||
}
|
||||
|
||||
interface ScaffoldState {
|
||||
step: number
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ScaffoldModal extends React.Component<SubEditorProps> {
|
||||
export class ScaffoldModal extends React.Component<SubEditorProps, ScaffoldState> {
|
||||
constructor(props: SubEditorProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
step: 0
|
||||
};
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleConfirm([values]: any) {
|
||||
const store = this.props.store;
|
||||
@ -32,21 +44,54 @@ export class ScaffoldModal extends React.Component<SubEditorProps> {
|
||||
|
||||
store.scaffoldForm?.callback(values);
|
||||
store.closeScaffoldForm();
|
||||
this.setState({step: 0});
|
||||
}
|
||||
|
||||
buildSchema() {
|
||||
const {store} = this.props;
|
||||
const scaffoldFormContext = store.scaffoldForm!;
|
||||
|
||||
let body = scaffoldFormContext.controls ?? scaffoldFormContext.body;
|
||||
if (scaffoldFormContext.stepsBody) {
|
||||
body = [
|
||||
{
|
||||
type: 'steps',
|
||||
name: '__steps',
|
||||
className: 'ae-Steps',
|
||||
steps: body.map((step, index) => ({
|
||||
title: step.title,
|
||||
value: index,
|
||||
iconClassName: 'ae-Steps-Icon'
|
||||
}))
|
||||
},
|
||||
...body.map((step, index) => ({
|
||||
type: 'container',
|
||||
visibleOn: `__step === ${index}`,
|
||||
body: step.body
|
||||
}))
|
||||
]
|
||||
}
|
||||
|
||||
let layout: object;
|
||||
if (isObject(scaffoldFormContext.mode)) {
|
||||
layout = scaffoldFormContext.mode as object;
|
||||
} else {
|
||||
layout = {
|
||||
mode: scaffoldFormContext.mode || 'normal'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'form',
|
||||
wrapWithPanel: false,
|
||||
initApi: scaffoldFormContext.initApi,
|
||||
api: scaffoldFormContext.api,
|
||||
mode: scaffoldFormContext.mode || 'normal',
|
||||
...layout,
|
||||
wrapperComponent: 'div',
|
||||
[scaffoldFormContext.controls ? 'controls' : 'body']:
|
||||
scaffoldFormContext.controls ?? scaffoldFormContext.body
|
||||
data: {
|
||||
__step: 0
|
||||
},
|
||||
[scaffoldFormContext.controls ? 'controls' : 'body']: body,
|
||||
};
|
||||
// const {store} = this.props;
|
||||
// const scaffoldFormContext = store.scaffoldForm;
|
||||
@ -100,6 +145,32 @@ export class ScaffoldModal extends React.Component<SubEditorProps> {
|
||||
this.amisScope = scoped;
|
||||
}
|
||||
|
||||
@autobind
|
||||
goToNextStep() {
|
||||
// 不能更新props的data,控制amis不重新渲染,否则数据会重新初始化
|
||||
const form = this.amisScope?.getComponents()[0].props.store;
|
||||
const step = this.state.step + 1;
|
||||
form.setValueByName('__step', step);
|
||||
|
||||
// 控制按钮
|
||||
this.setState({
|
||||
step
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
goToPrevStep() {
|
||||
// 不能更新props的data,控制amis不重新渲染,否则数据会重新初始化
|
||||
const form = this.amisScope?.getComponents()[0].props.store;
|
||||
const step = this.state.step - 1;
|
||||
form.setValueByName('__step', step);
|
||||
|
||||
// 控制按钮
|
||||
this.setState({
|
||||
step
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
async handleConfirmClick() {
|
||||
const form = this.amisScope?.getComponents()[0];
|
||||
@ -129,14 +200,25 @@ export class ScaffoldModal extends React.Component<SubEditorProps> {
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleCancelClick() {
|
||||
this.props.store.closeScaffoldForm();
|
||||
this.setState({step: 0});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {store, theme, manager} = this.props;
|
||||
const scaffoldFormContext = store.scaffoldForm;
|
||||
const cx = getTheme(theme || 'cxd').classnames;
|
||||
|
||||
const isStepBody = !! scaffoldFormContext?.stepsBody;
|
||||
const isLastStep = isStepBody && this.state.step === scaffoldFormContext!.body.length - 1;
|
||||
const isFirstStep = isStepBody && this.state.step === 0;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
size={scaffoldFormContext?.size || 'md'}
|
||||
contentClassName={scaffoldFormContext?.className}
|
||||
show={!!scaffoldFormContext}
|
||||
onHide={store.closeScaffoldForm}
|
||||
closeOnEsc={!store.scaffoldFormBuzy}
|
||||
@ -158,7 +240,10 @@ export class ScaffoldModal extends React.Component<SubEditorProps> {
|
||||
render(
|
||||
this.buildSchema(),
|
||||
{
|
||||
data: createObject(store.ctx, scaffoldFormContext?.value),
|
||||
data: createObject(store.ctx, {
|
||||
...(scaffoldFormContext?.value || {}),
|
||||
__step: 0
|
||||
}),
|
||||
onValidate: scaffoldFormContext.validate,
|
||||
scopeRef: this.scopeRef
|
||||
},
|
||||
@ -184,14 +269,38 @@ export class ScaffoldModal extends React.Component<SubEditorProps> {
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
<Button
|
||||
level="primary"
|
||||
onClick={this.handleConfirmClick}
|
||||
disabled={store.scaffoldFormBuzy}
|
||||
>
|
||||
确认
|
||||
</Button>
|
||||
<Button onClick={store.closeScaffoldForm}>取消</Button>
|
||||
{
|
||||
isStepBody && !isFirstStep && (
|
||||
<Button
|
||||
level="primary"
|
||||
onClick={this.goToPrevStep}
|
||||
>
|
||||
上一步
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
{
|
||||
isStepBody && !isLastStep && (
|
||||
<Button
|
||||
level="primary"
|
||||
onClick={this.goToNextStep}
|
||||
>
|
||||
下一步
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
{
|
||||
(!isStepBody || isLastStep) && (
|
||||
<Button
|
||||
level="primary"
|
||||
onClick={this.handleConfirmClick}
|
||||
disabled={store.scaffoldFormBuzy}
|
||||
>
|
||||
确认
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
<Button onClick={this.handleCancelClick}>取消</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
|
@ -19,6 +19,7 @@ import {CommonConfigWrapper} from './CommonConfigWrapper';
|
||||
import {Schema} from 'amis/lib/types';
|
||||
import type {DataScope} from 'amis-core';
|
||||
import type {RendererConfig} from 'amis-core/lib/factory';
|
||||
import {SchemaCollection} from 'amis/lib/Schema';
|
||||
|
||||
// 创建 Node Store 并构建成树
|
||||
export function makeWrapper(
|
||||
@ -69,6 +70,7 @@ export function makeWrapper(
|
||||
});
|
||||
this.editorNode!.setRendererConfig(rendererConfig);
|
||||
|
||||
// 查找父数据域,将当前组件数据域追加上去,使其形成父子关系
|
||||
if (
|
||||
rendererConfig.storeType &&
|
||||
!manager.dataSchema.hasScope(`${info.id}-${info.type}`)
|
||||
@ -303,7 +305,7 @@ function SchemaFrom({
|
||||
export function makeSchemaFormRender(
|
||||
manager: EditorManager,
|
||||
schema: {
|
||||
body?: Array<any>;
|
||||
body?: SchemaCollection;
|
||||
controls?: Array<any>;
|
||||
definitions?: any;
|
||||
api?: any;
|
||||
@ -311,7 +313,7 @@ export function makeSchemaFormRender(
|
||||
justify?: boolean;
|
||||
panelById?: string;
|
||||
formKey?: string;
|
||||
},
|
||||
}
|
||||
) {
|
||||
const env = {...manager.env, session: 'schema-form'};
|
||||
|
||||
@ -331,10 +333,11 @@ export function makeSchemaFormRender(
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 每一层的面板数据不要共用
|
||||
const curFormKey = `${id}-${node?.type}${schema.formKey ? '-': ''}${schema.formKey ? schema.formKey: ''}`;
|
||||
const curFormKey = `${id}-${node?.type}${schema.formKey ? '-' : ''}${
|
||||
schema.formKey ? schema.formKey : ''
|
||||
}`;
|
||||
|
||||
return (
|
||||
<SchemaFrom
|
||||
@ -676,6 +679,7 @@ export function renderThumbToGhost(
|
||||
schema: any,
|
||||
manager: EditorManager
|
||||
) {
|
||||
// bca-disable-line
|
||||
ghost.innerHTML = '';
|
||||
let path = '';
|
||||
const host = region.host!;
|
||||
@ -712,8 +716,10 @@ export function renderThumbToGhost(
|
||||
const html =
|
||||
thumbHost.innerHTML ||
|
||||
'<div class="wrapper-sm b-a b-light m-b-sm">拖入占位</div>';
|
||||
// bca-disable-line
|
||||
ghost.innerHTML = html;
|
||||
|
||||
unmountComponentAtNode(thumbHost);
|
||||
// bca-disable-line
|
||||
thumbHost.innerHTML = '';
|
||||
}
|
||||
|
@ -1,10 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>icon/删除</title>
|
||||
<g id="icon/删除" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g>
|
||||
<rect id="矩形" x="0" y="0" width="14" height="14"></rect>
|
||||
<path d="M9.25,1.1 C9.44329966,1.1 9.60457492,1.2371128 9.64187342,1.41938605 L9.65,1.5 L9.65,2.653 L12.8,2.653125 C12.9104569,2.653125 13,2.74266805 13,2.853125 L13,3.253125 C13,3.36358195 12.9104569,3.453125 12.8,3.453125 L11.15,3.453 L11.15,10.4974999 C11.15,11.7677548 10.163161,12.8075211 8.91431873,12.891963 L8.75,12.8974999 L5.25,12.8974999 C3.97974508,12.8974999 2.93997876,11.9106609 2.85553687,10.6618186 L2.85,10.4974999 L2.849,3.453 L1.2,3.453125 C1.08954305,3.453125 1,3.36358195 1,3.253125 L1,2.853125 C1,2.74266805 1.08954305,2.653125 1.2,2.653125 L4.349,2.653 L4.35,1.5 C4.35,1.30670034 4.4871128,1.14542508 4.66938605,1.10812658 L4.75,1.1 L9.25,1.1 Z M10.35,3.453 L3.649,3.453 L3.65,10.4974999 C3.65,11.3320635 4.28896152,12.0173897 5.10436739,12.0909612 L5.25,12.0974999 L8.75,12.0974999 C9.58456362,12.0974999 10.2698898,11.4585384 10.3434613,10.6431325 L10.35,10.4974999 L10.35,3.453 Z M4.81646861,4.99747011 L5.21643661,5.00252989 C5.32688472,5.00392711 5.41528793,5.09459567 5.41389071,5.20504378 L5.34937289,10.3050758 C5.34797567,10.4155239 5.25730711,10.5039271 5.146859,10.5025299 L4.746891,10.4974701 C4.63644289,10.4960729 4.54803967,10.4054043 4.5494369,10.2949562 L4.61395472,5.19492421 C4.61535194,5.0844761 4.7060205,4.99607289 4.81646861,4.99747011 Z M6.81646861,4.99747011 L7.21643661,5.00252989 C7.32688472,5.00392711 7.41528793,5.09459567 7.41389071,5.20504378 L7.34937289,10.3050758 C7.34797567,10.4155239 7.25730711,10.5039271 7.146859,10.5025299 L6.746891,10.4974701 C6.63644289,10.4960729 6.54803967,10.4054043 6.5494369,10.2949562 L6.61395472,5.19492421 C6.61535194,5.0844761 6.7060205,4.99607289 6.81646861,4.99747011 Z M8.81646861,4.99747011 L9.21643661,5.00252989 C9.32688472,5.00392711 9.41528793,5.09459567 9.41389071,5.20504378 L9.34937289,10.3050758 C9.34797567,10.4155239 9.25730711,10.5039271 9.146859,10.5025299 L8.746891,10.4974701 C8.63644289,10.4960729 8.54803967,10.4054043 8.5494369,10.2949562 L8.61395472,5.19492421 C8.61535194,5.0844761 8.7060205,4.99607289 8.81646861,4.99747011 Z M8.85,1.9 L5.15,1.9 L5.15,2.653 L8.85,2.653 L8.85,1.9 Z" id="形状结合" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</g>
|
||||
<svg viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M9.25,1.1 C9.44329966,1.1 9.60457492,1.2371128 9.64187342,1.41938605 L9.65,1.5 L9.65,2.653 L12.8,2.653125 C12.9104569,2.653125 13,2.74266805 13,2.853125 L13,3.253125 C13,3.36358195 12.9104569,3.453125 12.8,3.453125 L11.15,3.453 L11.15,10.4974999 C11.15,11.7677548 10.163161,12.8075211 8.91431873,12.891963 L8.75,12.8974999 L5.25,12.8974999 C3.97974508,12.8974999 2.93997876,11.9106609 2.85553687,10.6618186 L2.85,10.4974999 L2.849,3.453 L1.2,3.453125 C1.08954305,3.453125 1,3.36358195 1,3.253125 L1,2.853125 C1,2.74266805 1.08954305,2.653125 1.2,2.653125 L4.349,2.653 L4.35,1.5 C4.35,1.30670034 4.4871128,1.14542508 4.66938605,1.10812658 L4.75,1.1 L9.25,1.1 Z M10.35,3.453 L3.649,3.453 L3.65,10.4974999 C3.65,11.3320635 4.28896152,12.0173897 5.10436739,12.0909612 L5.25,12.0974999 L8.75,12.0974999 C9.58456362,12.0974999 10.2698898,11.4585384 10.3434613,10.6431325 L10.35,10.4974999 L10.35,3.453 Z M4.81646861,4.99747011 L5.21643661,5.00252989 C5.32688472,5.00392711 5.41528793,5.09459567 5.41389071,5.20504378 L5.34937289,10.3050758 C5.34797567,10.4155239 5.25730711,10.5039271 5.146859,10.5025299 L4.746891,10.4974701 C4.63644289,10.4960729 4.54803967,10.4054043 4.5494369,10.2949562 L4.61395472,5.19492421 C4.61535194,5.0844761 4.7060205,4.99607289 4.81646861,4.99747011 Z M6.81646861,4.99747011 L7.21643661,5.00252989 C7.32688472,5.00392711 7.41528793,5.09459567 7.41389071,5.20504378 L7.34937289,10.3050758 C7.34797567,10.4155239 7.25730711,10.5039271 7.146859,10.5025299 L6.746891,10.4974701 C6.63644289,10.4960729 6.54803967,10.4054043 6.5494369,10.2949562 L6.61395472,5.19492421 C6.61535194,5.0844761 6.7060205,4.99607289 6.81646861,4.99747011 Z M8.81646861,4.99747011 L9.21643661,5.00252989 C9.32688472,5.00392711 9.41528793,5.09459567 9.41389071,5.20504378 L9.34937289,10.3050758 C9.34797567,10.4155239 9.25730711,10.5039271 9.146859,10.5025299 L8.746891,10.4974701 C8.63644289,10.4960729 8.54803967,10.4054043 8.5494369,10.2949562 L8.61395472,5.19492421 C8.61535194,5.0844761 8.7060205,4.99607289 8.81646861,4.99747011 Z M8.85,1.9 L5.15,1.9 L5.15,2.653 L8.85,2.653 L8.85,1.9 Z" fill-rule="nonzero"></path>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.2 KiB |
@ -10,7 +10,8 @@ import DisplayInlineBlock from './display-inline-block.svg';
|
||||
import DisplayFlex from './display-flex.svg';
|
||||
import Harmmer from './hammer.svg';
|
||||
import Dialog from './dialog.svg';
|
||||
import API from './api.svg';
|
||||
import Setting from './setting.svg';
|
||||
import PickerIcon from './picker-icon.svg';
|
||||
|
||||
registerIcon('arrow-to-right', ArrowToRight);
|
||||
registerIcon('left-arrow-to-left', LeftArrowToleft);
|
||||
@ -19,7 +20,8 @@ registerIcon('arrow-to-bottom', ArrowToBottom);
|
||||
registerIcon('collapse-open', CollapseOpen);
|
||||
registerIcon('harmmer', Harmmer);
|
||||
registerIcon('dialog', Dialog);
|
||||
registerIcon('api', API);
|
||||
registerIcon('setting', Setting);
|
||||
registerIcon('picker-icon', PickerIcon);
|
||||
|
||||
// 「页面设计器改版」设计侧提供的icon(组件头部工具栏icon)
|
||||
import CopyBtn from './copy-btn.svg';
|
||||
|
9
packages/amis-editor-core/src/icons/picker-icon.svg
Normal file
9
packages/amis-editor-core/src/icons/picker-icon.svg
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M14.5 14.5h-13v-13h13v1.998"></path>
|
||||
<g stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12.775 4.943l1.768 1.768-1.766 1.765M13.85 6.71H8M9.694 9.235l-1.768 1.768 1.766 1.766M8.618 11.002l5.882-.001"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 471 B |
11
packages/amis-editor-core/src/icons/setting.svg
Normal file
11
packages/amis-editor-core/src/icons/setting.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="1实体和表单选择-接口选择1" transform="translate(-834.000000, -334.000000)">
|
||||
<g id="设置" transform="translate(834.000000, 334.000000)">
|
||||
<rect id="矩形" x="0" y="0" width="16" height="16"></rect>
|
||||
<path d="M11.4991,1.9996 L14.9991,8.0626 L11.4991,14.1246 L4.5001,14.1246 L1.0001,8.0626 L4.5001,1.9996 L11.4991,1.9996 Z M10.9221,3.0006 L5.0771,3.0006 L2.1551,8.0626 L5.0771,13.1236 L10.9221,13.1236 L13.8461,8.0626 L10.9221,3.0006 Z M7.9998,6.0625 C9.1048,6.0625 9.9998,6.9585 9.9998,8.0625 C9.9998,9.1665 9.1048,10.0625 7.9998,10.0625 C6.8958,10.0625 5.9998,9.1665 5.9998,8.0625 C5.9998,6.9585 6.8958,6.0625 7.9998,6.0625 Z M7.9998,7.0625 C7.4488,7.0625 6.9998,7.5105 6.9998,8.0625 C6.9998,8.6135 7.4488,9.0625 7.9998,9.0625 C8.5518,9.0625 8.9998,8.6135 8.9998,8.0625 C8.9998,7.5105 8.5518,7.0625 7.9998,7.0625 Z" id="图标-填色" fill="#84868C" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -20,6 +20,8 @@ export * from './manager';
|
||||
export * from './plugin';
|
||||
export * from './icons/index';
|
||||
export * from './mocker';
|
||||
export * from './builder/DSBuilder';
|
||||
import './builder/ApiBuilder';
|
||||
import {BasicEditor, RendererEditor} from './compat';
|
||||
import MiniEditor from './component/MiniEditor';
|
||||
import CodeEditor from './component/Panel/AMisCodeEditor';
|
||||
|
@ -44,7 +44,8 @@ import {
|
||||
reactionWithOldValue,
|
||||
reGenerateID,
|
||||
isString,
|
||||
isObject
|
||||
isObject,
|
||||
generateNodeId
|
||||
} from './util';
|
||||
import {reaction} from 'mobx';
|
||||
import {hackIn, makeSchemaFormRender, makeWrapper} from './component/factory';
|
||||
@ -63,7 +64,7 @@ import {EditorProps} from './component/Editor';
|
||||
import findIndex from 'lodash/findIndex';
|
||||
import {EditorDNDManager} from './dnd';
|
||||
import {IScopedContext} from 'amis';
|
||||
import {SchemaObject} from 'amis/lib/Schema';
|
||||
import {SchemaObject, SchemaCollection} from 'amis/lib/Schema';
|
||||
import type {RendererConfig} from 'amis-core/lib/factory';
|
||||
|
||||
export interface EditorManagerConfig
|
||||
@ -440,6 +441,7 @@ export class EditorManager {
|
||||
const node = this.store.getNodeById(id);
|
||||
panels = node ? this.collectPanels(node, true) : panels;
|
||||
}
|
||||
|
||||
this.store.setPanels(
|
||||
panels.map(item => ({
|
||||
...item,
|
||||
@ -1271,7 +1273,7 @@ export class EditorManager {
|
||||
const commonContext = this.buildEventContext(id);
|
||||
|
||||
if (!('id' in json)) {
|
||||
json = {...json, id: 'u:' + guid()};
|
||||
json = {...json, id: generateNodeId()};
|
||||
}
|
||||
|
||||
if (beforeId) {
|
||||
@ -1552,7 +1554,7 @@ export class EditorManager {
|
||||
* @param schema
|
||||
*/
|
||||
makeSchemaFormRender(schema: {
|
||||
body?: Array<any>;
|
||||
body?: SchemaCollection;
|
||||
controls?: Array<any>;
|
||||
definitions?: any;
|
||||
api?: any;
|
||||
@ -1665,6 +1667,8 @@ export class EditorManager {
|
||||
let scope: DataScope | void;
|
||||
let from = node;
|
||||
let region = node;
|
||||
|
||||
// 查找最近一层的数据域
|
||||
while (!scope && from) {
|
||||
scope = this.dataSchema.hasScope(`${from.id}-${from.type}`)
|
||||
? this.dataSchema.getScope(`${from.id}-${from.type}`)
|
||||
@ -1675,11 +1679,18 @@ export class EditorManager {
|
||||
}
|
||||
}
|
||||
|
||||
const nearestScope = scope;
|
||||
let nearestScope;
|
||||
|
||||
// 更新组件树中的所有上下文数据声明为最新数据
|
||||
while (scope) {
|
||||
const [id, type] = scope.id.split('-');
|
||||
const node = this.store.getNodeById(id, type);
|
||||
|
||||
// 拿非重复组件id的父组件作为主数据域展示,如CRUD,不展示表格,只展示增删改查信息,避免变量面板出现两份数据
|
||||
if (!nearestScope && node && !node.isSecondFactor) {
|
||||
nearestScope = scope;
|
||||
}
|
||||
|
||||
const jsonschema = await node?.info?.plugin?.buildDataSchemas?.(
|
||||
node,
|
||||
region
|
||||
@ -1701,6 +1712,44 @@ export class EditorManager {
|
||||
: this.dataSchema.getSchemas();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用上下文待绑定字段
|
||||
*/
|
||||
async getAvailableContextFields(node: EditorNodeType) {
|
||||
if (!node) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let scope: DataScope | void;
|
||||
let from = node;
|
||||
let region = node;
|
||||
|
||||
// 查找最近一层的数据域
|
||||
while (!scope && from) {
|
||||
scope = this.dataSchema.hasScope(`${from.id}-${from.type}`)
|
||||
? this.dataSchema.getScope(`${from.id}-${from.type}`)
|
||||
: undefined;
|
||||
from = from.parent;
|
||||
if (from?.isRegion) {
|
||||
region = from;
|
||||
}
|
||||
}
|
||||
|
||||
while (scope) {
|
||||
const [id, type] = scope.id.split('-');
|
||||
const scopeNode = this.store.getNodeById(id, type);
|
||||
|
||||
if (scopeNode) {
|
||||
return scopeNode?.info.plugin.getAvailableContextFields?.(scopeNode, node) || [];
|
||||
}
|
||||
|
||||
scope = scope.parent;
|
||||
}
|
||||
|
||||
return [];
|
||||
|
||||
}
|
||||
|
||||
beforeDispatchEvent(
|
||||
originHook: any,
|
||||
e: any,
|
||||
|
@ -1,7 +1,6 @@
|
||||
/**
|
||||
* @file 定义插件的 interface,以及提供一个 BasePlugin 基类,把一些通用的方法放在这。
|
||||
*/
|
||||
|
||||
import {RegionWrapperProps} from './component/RegionWrapper';
|
||||
import {EditorManager} from './manager';
|
||||
import {EditorStoreType} from './store/editor';
|
||||
@ -13,7 +12,8 @@ import {DiffChange} from './util';
|
||||
import find from 'lodash/find';
|
||||
import type {RendererConfig} from 'amis-core/lib/factory';
|
||||
import type {MenuDivider, MenuItem} from 'amis-ui/lib/components/ContextMenu';
|
||||
import type {BaseSchema} from 'amis/lib/Schema';
|
||||
import type {BaseSchema, SchemaCollection} from 'amis/lib/Schema';
|
||||
import {DSFieldGroup} from './builder/DSBuilder';
|
||||
|
||||
/**
|
||||
* 区域的定义,容器渲染器都需要定义区域信息。
|
||||
@ -243,7 +243,7 @@ export interface RendererInfo extends RendererScaffoldInfo {
|
||||
wrapperProps?: any;
|
||||
|
||||
/**
|
||||
* 修改一些属性,一般用来干掉 $$id
|
||||
* 修改一些属性,一般用来干掉 $$id,或者渲染假数据
|
||||
* 这样它的孩子节点就不能直接点选编辑了,比如 Combo。
|
||||
*/
|
||||
filterProps?: (props: any, node: EditorNodeType) => any;
|
||||
@ -311,12 +311,22 @@ export interface PopOverForm {
|
||||
}
|
||||
|
||||
export interface ScaffoldForm extends PopOverForm {
|
||||
mode?: 'normal' | 'horizontal' | 'inline';
|
||||
// 内容是否是分步骤的,如果是,body必须是?: Array<{title: string,body: any[]}>
|
||||
stepsBody?: boolean;
|
||||
mode?:
|
||||
| 'normal'
|
||||
| 'horizontal'
|
||||
| 'inline'
|
||||
| {
|
||||
mode: string;
|
||||
horizontal: any;
|
||||
};
|
||||
|
||||
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
||||
className?: string;
|
||||
initApi?: any;
|
||||
api?: any;
|
||||
|
||||
actions?: any[];
|
||||
/**
|
||||
* 整体验证脚手架配置,如果有错误返回错误对象。
|
||||
* key 是配置的字段名。
|
||||
@ -555,6 +565,12 @@ export interface ResizeMoveEventContext extends EventContext {
|
||||
node: EditorNodeType;
|
||||
}
|
||||
|
||||
export interface AfterBuildPanelBody extends EventContext {
|
||||
data: SchemaCollection;
|
||||
plugin: BasePlugin;
|
||||
context: BaseEventContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将事件上下文转成事件对象。
|
||||
*/
|
||||
@ -720,6 +736,9 @@ export interface PluginInterface
|
||||
|
||||
order?: number;
|
||||
|
||||
// 是否可绑定数据,一般容器类型就没有
|
||||
withDataSource?: boolean;
|
||||
|
||||
/**
|
||||
* 渲染器的名字,关联后不用自己实现 getRendererInfo 了。
|
||||
*/
|
||||
@ -768,11 +787,28 @@ export interface PluginInterface
|
||||
*/
|
||||
panelJustify?: boolean;
|
||||
|
||||
/**
|
||||
* 有数据域的容器,可以为子组件提供读取的字段列表
|
||||
*/
|
||||
getAvailableContextFields?: (
|
||||
scopeNode: EditorNodeType,
|
||||
node: EditorNodeType,
|
||||
region?: EditorNodeType
|
||||
) => Promise<DSFieldGroup[] | void>;
|
||||
|
||||
/**
|
||||
* @deprecated 用 panelBodyCreator
|
||||
*/
|
||||
panelControlsCreator?: (context: BaseEventContext) => Array<any>;
|
||||
panelBodyCreator?: (context: BaseEventContext) => Array<any>;
|
||||
panelBodyCreator?: (context: BaseEventContext) => SchemaCollection;
|
||||
|
||||
/**
|
||||
* panel还需要合并目标插件提供的配置,冲突时以当前plugin为准
|
||||
*/
|
||||
panelBodyMergeable?: (
|
||||
context: BaseEventContext,
|
||||
plugin: PluginInterface
|
||||
) => boolean;
|
||||
|
||||
popOverBody?: Array<any>;
|
||||
popOverBodyCreator?: (context: BaseEventContext) => Array<any>;
|
||||
@ -878,7 +914,11 @@ export interface RendererPluginAction {
|
||||
}
|
||||
|
||||
// 分支动作
|
||||
export interface SubRendererPluginAction extends Pick<RendererPluginAction, 'actionType' | 'innerArgs' | 'descDetail'>{}
|
||||
export interface SubRendererPluginAction
|
||||
extends Pick<
|
||||
RendererPluginAction,
|
||||
'actionType' | 'innerArgs' | 'descDetail'
|
||||
> {}
|
||||
|
||||
export interface PluginEvents {
|
||||
[propName: string]: RendererPluginEvent[];
|
||||
@ -956,6 +996,16 @@ export abstract class BasePlugin implements PluginInterface {
|
||||
plugin.panelBodyCreator) &&
|
||||
context.info.plugin === this
|
||||
) {
|
||||
const body = plugin.panelBodyCreator
|
||||
? plugin.panelBodyCreator(context)
|
||||
: plugin.panelBody!;
|
||||
|
||||
this.manager.trigger('after-build-panel-body', {
|
||||
context,
|
||||
data: body,
|
||||
plugin
|
||||
});
|
||||
|
||||
panels.push({
|
||||
key: 'config',
|
||||
icon: plugin.panelIcon || plugin.icon || 'fa fa-cog',
|
||||
@ -965,9 +1015,7 @@ export abstract class BasePlugin implements PluginInterface {
|
||||
definitions: plugin.panelDefinitions,
|
||||
submitOnChange: plugin.panelSubmitOnChange,
|
||||
api: plugin.panelApi,
|
||||
body: plugin.panelBodyCreator
|
||||
? plugin.panelBodyCreator(context)
|
||||
: plugin.panelBody!,
|
||||
body: body,
|
||||
controls: plugin.panelControlsCreator
|
||||
? plugin.panelControlsCreator(context)
|
||||
: plugin.panelControls!,
|
||||
|
@ -548,9 +548,9 @@ export class BasicToolbarPlugin extends BasePlugin {
|
||||
menus: menus,
|
||||
render: this.manager.makeSchemaFormRender({
|
||||
body: [
|
||||
// @ts-ignore amis中有问题,可选参数搞成了必选,改完了可以去掉这行
|
||||
{
|
||||
type: 'button-group',
|
||||
block: true,
|
||||
buttons: menus
|
||||
.filter(item => item !== '|')
|
||||
.map(menu => ({
|
||||
|
@ -535,7 +535,7 @@ export const EditorStore = types
|
||||
);
|
||||
}
|
||||
|
||||
return bcn;
|
||||
return bcn.filter(item => !item.isSecondFactor);
|
||||
},
|
||||
|
||||
get activePath(): Array<EditorNodeType> {
|
||||
|
@ -523,6 +523,18 @@ export const EditorNode = types
|
||||
self.h = height;
|
||||
}
|
||||
|
||||
function getClosestParentByType(type: string): EditorNodeType | void {
|
||||
let node = self;
|
||||
while(node = node.parent) {
|
||||
if (node.schema.type === type) {
|
||||
return node as EditorNodeType;
|
||||
}
|
||||
if (node.id === 'root') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 放到props会变成 frozen 的。
|
||||
let component: any;
|
||||
|
||||
@ -531,6 +543,7 @@ export const EditorNode = types
|
||||
}
|
||||
|
||||
return {
|
||||
getClosestParentByType,
|
||||
updateIsCommonConfig,
|
||||
addChild(props: {
|
||||
id: string;
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { SchemaObject } from "amis/lib/Schema";
|
||||
|
||||
/**
|
||||
* @file amis schema 配置模板,主要很多地方都要全部配置的化,
|
||||
* 会有很多份,而且改起来很麻烦,复用率高的放在这管理。
|
||||
@ -66,3 +68,25 @@ export function defaultValue(defaultValue: any, strictMode: boolean = true) {
|
||||
? (value: any) => (typeof value === 'undefined' ? defaultValue : value)
|
||||
: (value: any) => value || defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置面板带提示信息的label
|
||||
*/
|
||||
export function tipedLabel(
|
||||
body: string | Array<SchemaObject>,
|
||||
tip: string,
|
||||
style?: React.CSSProperties
|
||||
) {
|
||||
return {
|
||||
type: 'tooltip-wrapper',
|
||||
tooltip: tip,
|
||||
tooltipTheme: 'dark',
|
||||
placement: 'top',
|
||||
tooltipStyle: {
|
||||
fontSize: '12px',
|
||||
...(style || {})
|
||||
},
|
||||
className: 'ae-formItemControl-label-tip',
|
||||
body
|
||||
};
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import DeepDiff, {Diff} from 'deep-diff';
|
||||
import isPlainObject from 'lodash/isPlainObject';
|
||||
import isNumber from 'lodash/isNumber';
|
||||
import type {Schema} from 'amis/lib/types';
|
||||
import {SchemaObject} from 'amis/lib/Schema';
|
||||
|
||||
const {
|
||||
guid,
|
||||
@ -541,6 +542,7 @@ export function reGenerateID(json: any) {
|
||||
|
||||
export function createElementFromHTML(htmlString: string): HTMLElement {
|
||||
var div = document.createElement('div');
|
||||
// bca-disable-line
|
||||
div.innerHTML = htmlString.trim();
|
||||
|
||||
// Change this to div.childNodes to support multiple top-level nodes
|
||||
@ -591,9 +593,10 @@ export function filterSchemaForConfig(schema: any, valueWithConfig?: any): any {
|
||||
} else if (key === '$$commonSchema' && valueWithConfig) {
|
||||
let config: any = deepFind(valueWithConfig, value);
|
||||
config[value] &&
|
||||
(schema = mapped = {
|
||||
...config[value]
|
||||
});
|
||||
(schema = mapped =
|
||||
{
|
||||
...config[value]
|
||||
});
|
||||
}
|
||||
});
|
||||
return modified ? mapped : schema;
|
||||
@ -871,6 +874,13 @@ export function jsonToJsonSchema(json: any = {}) {
|
||||
return jsonschema;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成节点id
|
||||
*/
|
||||
export function generateNodeId() {
|
||||
return 'u:' + guid();
|
||||
}
|
||||
|
||||
// 是否使用 plugin 自带的 svg 版 icon
|
||||
export function isHasPluginIcon(plugin: any) {
|
||||
return plugin.pluginIcon && hasIcon(plugin.pluginIcon);
|
||||
|
Loading…
Reference in New Issue
Block a user