mirror of
https://gitee.com/baidu/amis.git
synced 2024-12-16 01:40:53 +08:00
amis-saas-5749 feat: 编辑器支持外部变量管理
Change-Id: I9f4eab11308d28f0f016d5f239484ef569e8bc7b
This commit is contained in:
parent
2803ad0a6a
commit
51c86604a0
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "amis-editor-core",
|
"name": "amis-editor-core",
|
||||||
"version": "5.2.0-beta.74",
|
"version": "5.2.1-alpha.3",
|
||||||
"description": "amis 可视化编辑器",
|
"description": "amis 可视化编辑器",
|
||||||
"main": "lib/index.min.js",
|
"main": "lib/index.min.js",
|
||||||
"types": "lib/index.d.ts",
|
"types": "lib/index.d.ts",
|
||||||
|
@ -16,6 +16,7 @@ import {PopOverForm} from './PopOverForm';
|
|||||||
import {ContextMenuPanel} from './Panel/ContextMenuPanel';
|
import {ContextMenuPanel} from './Panel/ContextMenuPanel';
|
||||||
import {LeftPanels} from './Panel/LeftPanels';
|
import {LeftPanels} from './Panel/LeftPanels';
|
||||||
import {RightPanels} from './Panel/RightPanels';
|
import {RightPanels} from './Panel/RightPanels';
|
||||||
|
import type {VariableGroup, VariableOptions} from '../variable';
|
||||||
|
|
||||||
export interface EditorProps extends PluginEventListener {
|
export interface EditorProps extends PluginEventListener {
|
||||||
value: SchemaObject;
|
value: SchemaObject;
|
||||||
@ -89,6 +90,11 @@ export interface EditorProps extends PluginEventListener {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 上下文变量 */
|
||||||
|
variables?: VariableGroup[];
|
||||||
|
/** 变量配置 */
|
||||||
|
variableOptions?: VariableOptions;
|
||||||
|
|
||||||
onUndo?: () => void; // 用于触发外部 undo 事件
|
onUndo?: () => void; // 用于触发外部 undo 事件
|
||||||
onRedo?: () => void; // 用于触发外部 redo 事件
|
onRedo?: () => void; // 用于触发外部 redo 事件
|
||||||
onSave?: () => void; // 用于触发外部 save 事件
|
onSave?: () => void; // 用于触发外部 save 事件
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
* @file 把一些功能性的东西放在了这个里面,辅助 compoennt/Editor.tsx 组件的。
|
* @file 把一些功能性的东西放在了这个里面,辅助 compoennt/Editor.tsx 组件的。
|
||||||
* 编辑器非 UI 相关的东西应该放在这。
|
* 编辑器非 UI 相关的东西应该放在这。
|
||||||
*/
|
*/
|
||||||
import {getRenderers, RenderOptions} from 'amis-core';
|
import {getRenderers, RenderOptions, mapTree} from 'amis-core';
|
||||||
import {
|
import {
|
||||||
PluginInterface,
|
PluginInterface,
|
||||||
BasicPanelItem,
|
BasicPanelItem,
|
||||||
@ -53,12 +53,16 @@ import {reaction} from 'mobx';
|
|||||||
import {hackIn, makeSchemaFormRender, makeWrapper} from './component/factory';
|
import {hackIn, makeSchemaFormRender, makeWrapper} from './component/factory';
|
||||||
import {env} from './env';
|
import {env} from './env';
|
||||||
import debounce from 'lodash/debounce';
|
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 {openContextMenus, toast, alert, DataScope, DataSchema} from 'amis';
|
||||||
import {parse, stringify} from 'json-ast-comments';
|
import {parse, stringify} from 'json-ast-comments';
|
||||||
import {EditorNodeType} from './store/node';
|
import {EditorNodeType} from './store/node';
|
||||||
import {EditorProps} from './component/Editor';
|
import {EditorProps} from './component/Editor';
|
||||||
import findIndex from 'lodash/findIndex';
|
import findIndex from 'lodash/findIndex';
|
||||||
import {EditorDNDManager} from './dnd';
|
import {EditorDNDManager} from './dnd';
|
||||||
|
import {VariableManager} from './variable';
|
||||||
import {IScopedContext} from 'amis';
|
import {IScopedContext} from 'amis';
|
||||||
import {SchemaObject, SchemaCollection} from 'amis/lib/Schema';
|
import {SchemaObject, SchemaCollection} from 'amis/lib/Schema';
|
||||||
import type {RendererConfig} from 'amis-core/lib/factory';
|
import type {RendererConfig} from 'amis-core/lib/factory';
|
||||||
@ -139,6 +143,9 @@ export class EditorManager {
|
|||||||
dataSchema: DataSchema;
|
dataSchema: DataSchema;
|
||||||
readonly isInFrame: boolean = false;
|
readonly isInFrame: boolean = false;
|
||||||
|
|
||||||
|
/** 变量管理 */
|
||||||
|
readonly variableManager;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly config: EditorManagerConfig,
|
readonly config: EditorManagerConfig,
|
||||||
readonly store: EditorStoreType,
|
readonly store: EditorStoreType,
|
||||||
@ -179,8 +186,15 @@ export class EditorManager {
|
|||||||
this.dnd = parent?.dnd || new EditorDNDManager(this, store);
|
this.dnd = parent?.dnd || new EditorDNDManager(this, store);
|
||||||
this.dataSchema =
|
this.dataSchema =
|
||||||
parent?.dataSchema || new DataSchema(config.schemas || []);
|
parent?.dataSchema || new DataSchema(config.schemas || []);
|
||||||
|
|
||||||
this.dataSchema.current.tag = '系统变量';
|
this.dataSchema.current.tag = '系统变量';
|
||||||
|
|
||||||
|
/** 初始化变量管理 */
|
||||||
|
this.variableManager = new VariableManager(
|
||||||
|
this.dataSchema,
|
||||||
|
config?.variables,
|
||||||
|
config?.variableOptions
|
||||||
|
);
|
||||||
|
|
||||||
if (isInFrame) {
|
if (isInFrame) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
227
packages/amis-editor-core/src/variable.ts
Normal file
227
packages/amis-editor-core/src/variable.ts
Normal 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'] ?? ''
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user