amis-saas-5749 feat: 编辑器支持外部变量管理

Change-Id: I9f4eab11308d28f0f016d5f239484ef569e8bc7b
This commit is contained in:
lurunze1226 2022-10-09 16:33:42 +08:00
parent 2803ad0a6a
commit 51c86604a0
4 changed files with 250 additions and 3 deletions

View File

@ -1,6 +1,6 @@
{
"name": "amis-editor-core",
"version": "5.2.0-beta.74",
"version": "5.2.1-alpha.3",
"description": "amis 可视化编辑器",
"main": "lib/index.min.js",
"types": "lib/index.d.ts",

View File

@ -16,6 +16,7 @@ import {PopOverForm} from './PopOverForm';
import {ContextMenuPanel} from './Panel/ContextMenuPanel';
import {LeftPanels} from './Panel/LeftPanels';
import {RightPanels} from './Panel/RightPanels';
import type {VariableGroup, VariableOptions} from '../variable';
export interface EditorProps extends PluginEventListener {
value: SchemaObject;
@ -89,6 +90,11 @@ export interface EditorProps extends PluginEventListener {
};
};
/** 上下文变量 */
variables?: VariableGroup[];
/** 变量配置 */
variableOptions?: VariableOptions;
onUndo?: () => void; // 用于触发外部 undo 事件
onRedo?: () => void; // 用于触发外部 redo 事件
onSave?: () => void; // 用于触发外部 save 事件

View File

@ -2,7 +2,7 @@
* @file 西 compoennt/Editor.tsx
* UI 西
*/
import {getRenderers, RenderOptions} from 'amis-core';
import {getRenderers, RenderOptions, mapTree} from 'amis-core';
import {
PluginInterface,
BasicPanelItem,
@ -53,12 +53,16 @@ import {reaction} from 'mobx';
import {hackIn, makeSchemaFormRender, makeWrapper} from './component/factory';
import {env} from './env';
import debounce from 'lodash/debounce';
import sortBy from 'lodash/sortBy';
import reverse from 'lodash/reverse';
import cloneDeep from 'lodash/cloneDeep';
import {openContextMenus, toast, alert, DataScope, DataSchema} from 'amis';
import {parse, stringify} from 'json-ast-comments';
import {EditorNodeType} from './store/node';
import {EditorProps} from './component/Editor';
import findIndex from 'lodash/findIndex';
import {EditorDNDManager} from './dnd';
import {VariableManager} from './variable';
import {IScopedContext} from 'amis';
import {SchemaObject, SchemaCollection} from 'amis/lib/Schema';
import type {RendererConfig} from 'amis-core/lib/factory';
@ -139,6 +143,9 @@ export class EditorManager {
dataSchema: DataSchema;
readonly isInFrame: boolean = false;
/** 变量管理 */
readonly variableManager;
constructor(
readonly config: EditorManagerConfig,
readonly store: EditorStoreType,
@ -179,8 +186,15 @@ export class EditorManager {
this.dnd = parent?.dnd || new EditorDNDManager(this, store);
this.dataSchema =
parent?.dataSchema || new DataSchema(config.schemas || []);
this.dataSchema.current.tag = '系统变量';
/** 初始化变量管理 */
this.variableManager = new VariableManager(
this.dataSchema,
config?.variables,
config?.variableOptions
);
if (isInFrame) {
return;
}

View File

@ -0,0 +1,227 @@
/**
* @file
* @desc
*/
import sortBy from 'lodash/sortBy';
import cloneDeep from 'lodash/cloneDeep';
import reverse from 'lodash/reverse';
import pick from 'lodash/pick';
import {JSONSchema, DataSchema, mapTree, findTree} from 'amis-core';
import type {Option} from 'amis-core';
export interface VariableGroup {
/** 变量命名空间 */
name: string;
/** 标题显示名称 */
title: string;
/* 父节点scope id */
parentId: string;
/** 顺序 */
order: number;
/** 结构定义根结点必须为object */
schema: JSONSchema;
}
export interface VariableOptions {
/** 变量Schema被添加到Scope之前触发 */
beforeScopeInsert?: (
context: VariableManager,
schema: JSONSchema
) => JSONSchema;
/** 事件变量Schema被添加到Scope之后触发 */
afterScopeInsert?: (context: VariableManager) => void;
/** 获取上下文数据结构时触发,可以自定义返回的数据结构 */
onContextSchemaChange?: (
context: VariableManager,
schema: JSONSchema[]
) => JSONSchema[];
/** 获取上下文数据Options时触发可以自定义返回的数据结构 */
onContextOptionChange?: (
context: VariableManager,
option: Option[],
type: 'normal' | 'formula'
) => Option[];
}
export class VariableManager {
/* 变量列表 */
readonly variables: VariableGroup[];
/* 上下文结构 */
readonly dataSchema: DataSchema;
/* 变量管理配置 */
readonly options: VariableOptions;
constructor(
dataSchema: DataSchema | undefined,
variables: VariableGroup[] | undefined,
options: VariableOptions | undefined
) {
this.variables = Array.isArray(variables)
? sortBy(cloneDeep(variables), [item => item.order ?? 1])
: [];
this.dataSchema =
dataSchema instanceof DataSchema ? dataSchema : new DataSchema([]);
this.options = pick(options, [
'beforeScopeInsert',
'afterScopeInsert',
'onContextSchemaChange',
'onContextOptionChange'
]);
this.init();
}
/**
*
* (root)
*
*
*
* ...
*/
init() {
const variables = this.variables;
const dataSchema = this.dataSchema;
const {beforeScopeInsert, afterScopeInsert} = this.options ?? {};
variables.forEach(item => {
const {parentId, name: scopeName, title: tagName} = item;
let schema = item.schema;
if (!dataSchema.hasScope(parentId)) {
return;
}
dataSchema.switchTo(parentId);
if (dataSchema.hasScope(scopeName)) {
dataSchema.removeScope(scopeName);
}
if (beforeScopeInsert && typeof beforeScopeInsert === 'function') {
schema = beforeScopeInsert(this, schema);
}
/** 初始化变量Scope */
dataSchema.addScope(schema, scopeName);
dataSchema.switchTo(scopeName);
/** 这里的Tag指变量的命名空间中文名称 */
dataSchema.current.tag = tagName;
if (afterScopeInsert && typeof afterScopeInsert === 'function') {
afterScopeInsert(this);
}
});
dataSchema.switchToRoot();
}
/**
*
*/
getVariableContextSchema() {
let variableSchemas: JSONSchema[] = [];
const {onContextSchemaChange} = this.options ?? {};
if (this.variables && this.variables?.length > 0) {
variableSchemas = this.variables
.map(item => {
if (this.dataSchema.hasScope(item.name)) {
const varScope = this.dataSchema.getScope(item.name);
/** 变量的Scope只有一个根结点 */
return varScope.schemas.length > 0 ? varScope.schemas[0] : null;
}
return null;
})
.filter((item): item is JSONSchema => item !== null);
}
if (onContextSchemaChange && typeof onContextSchemaChange === 'function') {
variableSchemas = onContextSchemaChange(this, variableSchemas);
}
return variableSchemas;
}
/**
* Option结构
*/
getVariableFormulaOptions(reverseOrder: boolean = false) {
const {onContextOptionChange} = this.options ?? {};
let options: Option[] = [];
if (this.variables && this.variables?.length > 0) {
this.variables.forEach(item => {
if (this.dataSchema.hasScope(item.name)) {
const varScope = this.dataSchema.getScope(item.name);
const children = mapTree(varScope.getDataPropsAsOptions(), item => ({
...item,
/** tag默认会被赋值为description这里得替换回来 */
tag: item.type
}));
if (varScope.tag) {
options.push({label: varScope.tag, children});
} else {
options.push(...children);
}
}
});
}
if (onContextOptionChange && typeof onContextOptionChange === 'function') {
options = onContextOptionChange(this, options, 'formula');
}
return reverseOrder ? options : reverse(options);
}
/**
*
*/
getVariableOptions() {
const {onContextOptionChange} = this.options ?? {};
let options: Option[] =
this.getVariableFormulaOptions(false)?.[0]?.children ?? [];
options = mapTree(
options,
(item: Option, key: number, level: number, paths: Option[]) => {
return {
...item,
valueExpression:
typeof item.value === 'string' && !item.value.startsWith('${')
? `\${${item.value}}`
: item.value
};
}
);
if (onContextOptionChange && typeof onContextOptionChange === 'function') {
options = onContextOptionChange(this, options, 'normal');
}
return options;
}
/**
*
*/
getNameByPath(path: string, valueField = 'value', labelField = 'label') {
if (!path || typeof path !== 'string') {
return '';
}
const options = this.getVariableOptions();
const node = findTree(
options,
item => item[valueField ?? 'value'] === path
);
return node
? node[labelField ?? 'label'] ?? node[valueField ?? 'value'] ?? ''
: '';
}
}