merge feat-optimize-4

Change-Id: I8974063a944df31cfcd401f416b69859fa53c04d
This commit is contained in:
yangwei9012 2022-07-05 23:06:46 +08:00
parent 4756363b60
commit f541968956
25 changed files with 1624 additions and 101 deletions

View File

@ -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};

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View 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;
}
}
}

View File

@ -120,4 +120,9 @@
width: 100%;
height: px2rem(32px);
}
}
.form-item-gap {
margin-bottom: var(--Form-item-gap);
}

View 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;
}
}
}

View File

@ -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);
}
}
}

View 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);

View 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);
});
}
}

View File

@ -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>
);
}

View File

@ -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>
);

View File

@ -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 = '';
}

View File

@ -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

View File

@ -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';

View 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

View 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

View File

@ -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';

View File

@ -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,

View File

@ -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!,

View File

@ -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 => ({

View File

@ -535,7 +535,7 @@ export const EditorStore = types
);
}
return bcn;
return bcn.filter(item => !item.isSecondFactor);
},
get activePath(): Array<EditorNodeType> {

View File

@ -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;

View File

@ -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
};
}

View File

@ -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);