mirror of
https://gitee.com/baidu/amis.git
synced 2024-12-15 17:31:18 +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",
|
||||
"version": "5.2.0-beta.74",
|
||||
"version": "5.2.1-alpha.3",
|
||||
"description": "amis 可视化编辑器",
|
||||
"main": "lib/index.min.js",
|
||||
"types": "lib/index.d.ts",
|
||||
|
@ -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 事件
|
||||
|
@ -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;
|
||||
}
|
||||
|
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